1、06:27:01,1,第3章 栈和队列,3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现* 3.4 队列 3.5 离散事件模拟*,06:27:01,CH.3,2,3.1 栈,栈(Stack)是特殊的线性表,是操作受限的线性表。 栈是限定仅在表的一端(通常为表尾)进行插入或删除操作的线性表。 栈能进行插入或删除操作的一端称为栈顶(Top),另一端称为栈底(Bottom),不含元素的空表称为空栈。 对栈的操作所具有的特征 “先进后出”(FILO)、“后进先出” (LIFO)。 栈的典型操作 入栈(Push)和出栈(Pop)。,06:27:01,CH.3,3,3.1.l抽象数据类型栈的定义,
2、ADT Stack 数据对象:D=ai|ai(- ElemSet,i=1,2,.,n,n=0 数据关系:R1=|ai-1,ai(- D,i=2,.,n 基本操作: InitStack(&S)/ 构造一个空栈S DestroyStack(&S)/ 栈S存在则栈S被销毁 ClearStack(&S) /栈S存在则清为空栈 StackEmpty(S) /栈S存在则返回TRUE,否则FALSE StackLength(S) /栈S存在则返回S的元素个数,即栈的长度 GetTop(S,&e) /栈S存在且非空则返回S的栈顶元素 Push(&S,e) /栈S存在则插入元素e为新的栈顶元素 Pop(&S,&
3、e) /栈S存在且非空则删除S的栈顶元素并用e返回其值 StackTraverse(S,visit()/栈S存在且非空则从栈底到栈顶依次对S的每个数据元素调用函数visit()一旦visit()失败,则操作失败 ADT Stack,06:27:01,CH.3,4,3.1.2栈的表示和实现,栈的存储方式: 1、顺序栈 利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top,指示栈顶元素在顺序栈中的位置。 2、链栈 利用链表来实现,此时的栈顶在链表的表头。,06:27:01,CH.3,5,栈的典型操作:入栈和出栈,06:27:01,CH.3,6,顺序栈的类C语言定义:,typ
4、edef struct SElemType *base; /设栈底,以便于判断栈是否为空 SElemType *top; /设栈顶指针 int StackSize; /栈的当前可使用的最大容量,以便判断溢出. SqStack; 注意: 满栈时执行Push操作会产生上溢。 空栈时执行Pop操作会产生下溢。,06:27:01,CH.3,7,ADT STACK的表示和实现,#define STACK_INIT_SIZE 100 #define STACKINCREMENT 10 /用于动态分配 struct STACK SElemType *base; SElemType *top; int sta
5、cksize; ; typedef struct STACK Sqstack;,06:27:01,CH.3,8,ADT STACK的表示和实现,Status InitStack(SqStack ,06:27:01,CH.3,9,ADT STACK的表示和实现,Status InitStack(SqStack /IniStack,06:27:01,CH.3,10,ADT STACK的表示和实现,Status DestroyStack(SqStack /StackEmpty,06:27:01,CH.3,11,ADT STACK的表示和实现,int StackLength(SqStack S); i
6、nt i=0; SElemType *p; p=S.top; while(p!=S.base) p-; i+; Return i; /stackLengthStatus GetTop(SqStack S,SElemType /GetTop,06:27:01,CH.3,12,ADT STACK的表示和实现,Status Push(SqStack /Push,06:27:01,CH.3,13,ADT STACK的表示和实现,Status Pop(SqStack /Pop,06:27:01,CH.3,14,3.2 栈的应用举例,3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 行编辑程序
7、 3.2.4 迷宫求解 3.2.5 表达式求值 其他: 中断程序、现场保护、子程序调用、参数传递、递归的实现。,06:27:01,CH.3,15,3.2.1 数制转换(算法3.1*),void conversion(void) InitStack( / conversion 注意: 教材上的算法3.1(P48)运行界面不好,异常情况没有考虑周到。 本算法不符合第一章对算法特征的描述,输入输出参数处理不恰当。,06:27:01,CH.3,16,3.2.2 括号匹配的检验,思考题(选做) 假设一个算术表达式中可以包含3种括号:圆括号“(”和“)”,方括号“”和“”和花括号“”和“”,且这3种括号可
8、按任意的次序嵌套使用,如:()。编写判别给定表达式中所含括号是否正确配对出现的算法(已知表达式已存入数据元素为字符的顺序表中)。,06:27:01,CH.3,17,3.2.3行编辑程序,一个简单的行编辑程序的功能 接受用户从终端输入的程序或数据,并存入用户的数据区。允许用户输入出错时可以及时更正。 可以约定为退格符,以表示前一个字符无效,为退行符,表示当前行所有字符均无效。 例:在终端上用户输入: whli#ilr#e(s#*s) 应视为:while(*s),06:27:01,CH.3,18,3.2.3行编辑程序(算法3.2),void LineEdit(void) InitStack( ,0
9、6:27:01,CH.3,19,算法3.2(行编辑程序)存在的问题,本算法输入输出参数处理不恰当,不符合第一章对算法特征的描述。 ClearStack(S);语句依次输出一行的次序与输入相反。 一个改进的函数原型如下: void LineEdit (char *line_in, char *line_out); 其中 line_in为待处理的一行字符串; line_out为处理后的字符串数组; 思考题(选做) 试编写上述算法。,06:27:01,CH.3,20,3.2.4迷宫求解,方法 回溯或递归或用堆栈来模拟。 “靠一边走”始终沿左边(或右边)走。 存在问题 实际的迷宫用图表示更确切,而不是
10、二维表的形式。,06:27:01,CH.3,21,3.2.5表达式求值,表达式的要素是运算符、操作数、界定符、算符优先级关系。 例:1+2*3 有+,*两个运算符,*的优先级高,1,2,3是操作数。 界定符有括号和表达式结束符等。 算法基本思想: 首先置操作数栈OPND为空栈,表达式起始符为运算符栈OPTR的栈底元素; 依次输入表达式中每个字符,若是操作数则进OPND栈,若是运算符,则和OPTR栈的栈顶运算符比较优先权后作相应操作,直至整个表达式求值完毕。,06:27:01,CH.3,22,3.2.5表达式求值(算法3.4),char EvaluateExpression() InitStac
11、k( ,06:27:01,CH.3,23,算法3.4 (P51-52)表达式求值的几点说明,决定优先级的函数Precede(OP1,OP2)可用二维数组的查表方法来实现 char OP10=+,-,*,/,(,),#; int precede77= 1,1,2,2,2,1,1, 1,1,2,2,2,1,1, 1,1,1,1,2,1,1, 1,1,1,1,2,1,1, 2,2,2,2,2,3,0, 1,1,1,1,0,1,1, 2,2,2,2,2,0,3; 算法中的变量theta 为的读音,变量x仅作临时存放分界符用。,06:27:01,CH.3,24,算法3.4 (P51-52)存在的问题,本
12、算法输入输出参数处理仍不恰当,宜用一个字符串类型的输入参数和一个数值类型的输出参数。 每次运行该算法会产生一个“垃圾”,因此需要加上如下语句: DestroyStack(OPTR); DestroyStack(OPND); 如果表达式中有多位数字字符组成的数值表达式会怎样?,06:27:01,CH.3,25,3.3 栈与递归的实现,有关结论 栈的一个重要应用是实现递归。 一个递归算法总是可以转换为非递归的算法,但此时往往要显式地用到栈的数据结构。 递归算法的表达相对非递归的算法更简洁,但实际运行效率往往不如后者。 有些递归算法实际上是一些“坏”的算法,即其T(n)已不是关于n的多项式数量级。,
13、06:27:01,CH.3,26,有关Hanoi算法(算法3.5)的T(n),void hanoi(int n,char x,char y,char z) If (n=1) move(x,1,z); /次数:常数M else hanoi(n-1,x,z,y); /次数:L*(n-1) move(x,n,z); /次数:常数M hanoi(n-1,y,x,z); /次数:L*(n-1) 如忽略常数M的影响,则该算法的运行次数与规模n有如下关系:f(n)f(n-1)f(n-1) 2f(n-1) 所以有:f(n)2n ( 2n 22n-1 ) 该算法的T(n)将是T(n)=O(2n),06:27:0
14、1,CH.3,27,3.4 队列,队列(Queue)是特殊的线性表,是操作受限的线性表。 它只允许在表的一端进行插入,而在另一端删除元素。 在队列中,允许插入的的一端叫队尾,允许删除的一端则称为队头。 对队列的操作所具有的特征 “先进先出”(FIFO)、“后进后出” (LILO)。 对队列的最典型的操作 入队(EnQueue)和出队(DeQueue)。,06:27:01,CH.3,28,队列的典型操作:入队和出队。,06:27:01,CH.3,29,特殊队列双端队列,双端队列是限定插入和删除操作在表的两端进行的线性表。,06:27:01,CH.3,30,3.4.1抽象数据类型队列的定义,ADT
15、 Queue 数据对象: D=ai| ai(-ElemSet,i=1,2,.,n,n=0 数据关系: R1= | ai-1,ai(- D,i=2,.,n 基本操作: InitQueue(&Q) /构造一个空队列Q Destroyqueue(&Q)/ 队列Q存在则销毁Q ClearQueue(&Q) /队列Q存在则将Q清为空队列 QueueEmpty(Q) /队列Q存在,若Q为空队列则返回TRUE,否则返回FALSE QueueLenght(Q)/ 队列Q存在,返回Q的元素个数,即队列的长度 GetHead(Q,&e) /Q为非空队列,用e返回Q的队头元素 EnQueue(&Q,e) /队列Q存
16、在,插入元素e为Q的队尾元素 DeQueue(&Q,&e) /Q为非空队列,删除Q的队头元素,并用e返回其值 QueueTraverse(Q,vivsit()/ Q存在且非空,从队头到队尾,依次对Q的每个数据元素调用函数visit()。一旦visit()失败,则操作失败 ADT Queue,06:27:01,CH.3,31,3.4.2 链队列-队列的链式表示和实现,单链队列存储表示 typedef struct QNode QElemType data; struct QNode *next; QNode,*QueuePtr; typedef struct QueuePtr front; Qu
17、euePtr rear; LinkQueue;,06:27:01,CH.3,32,单链队列的表示,队头在链头,队尾在链尾。 对无头结点的单链队列在队空的操作需特殊考虑。 队空条件为 Q.front -next 和Q.rear- next均为NULL,06:27:01,CH.3,33,3.4.2 链队列-队列的链式表示和实现,/操作说明 Status InitQueue(LinkQueue &Q) /构造一个空队列QStatus Destroyqueue(LinkQueue &Q) /队列Q存在则销毁QStatus ClearQueue(LinkQueue &Q) /队列Q存在则将Q清为空队列S
18、tatus QueueEmpty(LinkQueue Q) / 队列Q存在,若Q为空队列则返回TRUE,否则返回FALSEStatus QueueLenght(LinkQueue Q) / 队列Q存在,返回Q的元素个数,即队列的长度,06:27:01,CH.3,34,3.4.2 链队列-队列的链式表示和实现,Status GetHead(LinkQueue Q,QElemType &e) /Q为非空队列,用e返回Q的队头元素Status EnQueue(LinkQueue &Q,QElemType e) /队列Q存在,插入元素e为Q的队尾元素Status DeQueue(LinkQueue &
19、Q,QElemType &e) /Q为非空队列,删除Q的队头元素,并用e返回其值Status QueueTraverse(LinkQueue Q,QElemType vivsit() /Q存在且非空,从队头到队尾,依次对Q的每个数据元素调用函数visit()。一旦visit()失败,则操作失败,06:27:01,CH.3,35,3.4.2 链队列-队列的链式表示和实现,/操作的实现 Status InitQueue(LinkQueue /Destroyqueue,06:27:01,CH.3,36,链队列的入队操作:分配、插入、调整。,/操作的实现 Status EnQueue(LinkQueu
20、e /EnQueue,06:27:01,CH.3,37,链队列的出队操作:读出、调整、销毁 。,/操作的实现 Status DeQueue(LinkQueue /DeQueue,06:27:01,CH.3,38,3.4.3 循环队列-队列的顺序表示和实现,队列的顺序存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表,和顺序表一样,顺序队列也是必须用一个向量空间来存放当前队列中的元素。 由于队列的队头和队尾的位置是变化的,因而要设两个指针和分别指示队头和队尾元素在队列中的位置,它们的初始值在队列初始化时可置为。 入队时将新元素插入所指的位置,然后将加。出队时,删去所指的元素,然后将加并返回被
21、删元素。由此可见,当头尾指针相等时队列为空。在非空队列里,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一位置。,06:27:01,CH.3,39,3.4.3 循环队列-队列的顺序表示和实现,06:27:01,CH.3,40,循环队列-队列的顺序表示和实现,循环队列的一个注意事项: 如何区分空队和满队? 如何解决溢出问题? 可通过增加一个标志位或单元来解决。,06:27:01,CH.3,41,循环顺序队列存储表示,#define MAXQSIZE 20 /最大队列长度 typedef struct QElemType *base; /初始化的动态分配存储空间int front; /头指针
22、,若队列不空,指向队列头元素;int rear; /尾指针,若队列不空,指向队列尾元素的下一个位置; SqQueue;,06:27:01,CH.3,42,/循环顺序队列的判空操作,Status QueueEmpty(SqQueue Q) /=异常处理if (Q.front=MAXQSIZE)|(Q.rear=MAXQSIZE)return ERROR;if (Q.front=Q.rear)return TRUE;elsereturn FALSE; /QueueEmpty,06:27:01,CH.3,43,/循环顺序队列的求长度,Status QueueLength(SqQueue Q,int
23、/QueueLenght,06:27:01,CH.3,44,/循环顺序队列的取队头元素,Status GetHead(SqQueue Q,QElemType /GetHead,06:27:01,CH.3,45,/循环顺序队列的入队操作,Status EnQueue(SqQueue /EnQueue,06:27:01,CH.3,46,/循环顺序队列的出队操作,Status DeQueue(SqQueue /DeQueue,06:27:01,CH.3,47,队列的应用,图的遍历 树的遍历 资源的调度 离散事件模拟,06:27:01,CH.3,48,3.5 离散事件模拟*(略),06:27:01,CH.3,49,第3章 知识点 *,31、栈的基本概念和典型操作 32、栈的表示和应用 (如括号匹配算法 ) 33、队列的基本概念和典型操作 34、链队列的表示和实现 35、循环队列队列的顺序表示和实现,Ch.4,本章习题,