1 / 72

第三章 栈和队列

第三章 栈和队列. 3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现 3.4 队列. 3.1.1 栈的定义及基本运算. 3.1 栈( Stack). 栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为 栈顶( Top) , 另一端为 栈底( Bottom) 。 当栈中没有元素时称为空栈。.

flann
Download Presentation

第三章 栈和队列

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第三章 栈和队列 3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现 3.4 队列

  2. 3.1.1 栈的定义及基本运算 3.1 栈(Stack) 栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶(Top),另一端为栈底(Bottom)。当栈中没有元素时称为空栈。 假设栈S=(a1,a2,a3,…an),则a1称为栈底元素,an为栈顶元素。栈中元素按a1,a2,a3,…an的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的。因此,栈又称为后进先出 (LIFO)表。

  3. 例、一叠书或一叠盘子。 栈顶 …… 栈底

  4. 抽象数据类型——栈 ADT Stack{ 数据对象: D={ai |ai ElemSet, i=1,2,…,n (n>=0)} 数据关系:R={<ai-1,ai>| ai-1,ai D,i=2,3,…,n} 约定an端为栈顶,a1端为栈底。 基本操作: InitStack(&S) 操作结果: 建立一个空栈S DestroyStack(&S) 初始条件:栈S已存在 操作结果: 栈S被销毁

  5. ClearStack(&S) 初始条件:栈S已存在 操作结果: 将S栈清空 StackEmpty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,返回true,否则返回false StackLength(S) 初始条件:栈S已存在 操作结果: 返回S的元素个数 GetTop(S,&e) //读栈顶元素 初始条件:栈S已存在且非空 操作结果: 用e返回栈顶元素

  6. Push(&S,e) //入栈 初始条件:栈S已存在 操作结果: 将元素e插入到栈顶 Pop(&S,&e) //出栈 初始条件:栈S已存在且非空 操作结果: 删除S的栈顶元素,用e返回 StackTraverse(S,visit()) 初始条件:栈S已存在且非空 操作结果: 从栈底到栈顶依次对S的每个元素调用visit() }ADT Stack

  7. 由于栈的逻辑结构与线性表相同,因此线性表的存储结构对栈也适应。 3.1.2 栈的表示和实现 • 顺序栈 • 链栈

  8. 顺序栈 栈的顺序存储结构简称为顺序栈,由于栈底位置是固定不变的,所以可以将栈底位置设置在数组两端的任何一端;而栈顶位置是随着入栈和出栈操作而变化的.

  9. 顺序栈的类型定义如下: #define STACK_INIT_SIZE 100 #define STACKINCREMENT 10 typedef struct { SElemType *base; //栈底指针,base=NULL表明栈不存在 SElemType *top; //栈顶指针,top==base为栈空的标志 int stacksize; //栈当前可以使用的最大容量 }SqStack;

  10. F E 5 5 5 top top top top top top top D 4 4 4 C 3 3 3 B top top top top top top 2 2 2 A base base base 1 1 1 top 0 0 0 栈空 顺序栈的入栈与出栈 栈空 栈满 F E D C B A 出栈 入栈 设数组大小为stacksize top==base,栈空,此时出栈,则下溢(underflow) top== stacksize,栈满,此时入栈,则上溢(overflow) 栈顶指针top,指向栈底 位置

  11. 0 M-1 栈1底 栈1顶 栈2顶 栈2底 • 在一个程序中同时使用两个栈

  12. 1、构造一个空栈 Status InitSatck(SqStack &S) { S.base=(SElemType*)malloc(STACK_INIT_SIZE *sizeof(SElemType)); if(!S.base) exit(OVERFLOW); S.top=S.base; S.stacksize= STACK_INIT_SIZE; }//end InitSatck 基本操作的算法描述

  13. 2、返回栈顶元素 Status GetTop(SqStack S,SElemTtype &e) { if (S.top==S.base) return ERROR; e=*(S.top-1); return OK; }//end GetTop

  14. 3、入栈 Status Push(SqStack &S,SElemType e) { if(S.top-S.base>=S.stacksize){ S.base=(SElemType*)realloc(S.base,(S. stacksize +STACKINCREMENT)* sizeof(SElemType)); if(!S.base) exit(OVERFLOW); S.top=S.base+S.stacksize; S.stacksize+= STACKINCREMENT; } *S.top++=e; //end Push

  15. 4、出栈 Status Pop(SqStack &S,SElemType &e) { if(S.top==S.base) return ERROR; e=*--S.top; return OK; }//end Pop

  16. 链栈 栈顶 栈底 top …... next data ^ 其操作与线性链表类似

  17. 3.2 栈的应用举例 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 行编辑程序 3.2.5 表达式求值

  18. 十进制n和其它进制数d的转换是计算机实现计算的十进制n和其它进制数d的转换是计算机实现计算的 基本问题,其解决方法很多,其中一个简单算法基于 下列原理: n=(n div d)*d + n mod d ( 其中:div为整除运算,mod为求余运算) 例如 (1348)10=(2504)8,其运算过程如下: 3.2.1 数制转换

  19. 例如:(1348)10 = (2504)8 , 其运算过程如下: n n div 8 n mod 81348 168 4 168 21 0 21 2 5 2 0 2 计算顺序 输出顺序

  20. 算法3.1 void conversion( ) { //十进制转换为等值的八进制 Initstack(S); scanf (“%d”,N); while(N){ Push(S,N%8); N=N/8; } while(! StackEmpty(S)){ Pop(S,e); printf(“%d”,e); } } //end conversion

  21. 假设表达式中充许括号嵌套,则检验括号是否匹配的方法假设表达式中充许括号嵌套,则检验括号是否匹配的方法 可用“期待的急迫程度”这个概念来描述。例: { [ ( ) ( ) ] } 1 2 3 4 5 6 7 8 3.2.2 括号匹配的检验 分析出现的不匹配的情况: 到来的右括号不是所“期待”的;

  22. 算法的设计思想: 1)凡出现左括号,则入栈; 2)凡出现右括号,首先检查栈是否空. 若栈空,则表明该“右括号”多余, 否则和栈顶元素比较, 若相匹配,则“左括号出栈”,否则表明括号不匹配. 3)表达式检验结束时, 若栈空,则表明表达式中括号匹配, 否则表明“左括号”有余

  23. 检验括号匹配的算法: Status matching(string &exp) { int state = 1; InitStack(S); while (i<=StrLength(exp) && state) switch of exp[i] { case “(”:{ Push(S,exp[i]); i++; break;} case “)”: { if(!StackEmpty(S) && GetTop(S)=“(”) { Pop(S,e); i++;} else { state = 0;} break; } if (StackEmpty(S) && state) return OK;}

  24. 3.2.3 行编辑程序 在用户输入程序或数据时,允许用户输入出差错,当发现有误时,可以及时更正。方法是: 设立一个输入缓冲区,用于接受用户输入的一行字符,然后逐行存入用户数据区。 假设退格符“#”表示前一个字符无效; 退行符“@”表示当前行中的字符全无效。 假设从终端接受了这样两行字符 whli##ilr#e(s#*s) outcha@putchar(*s=#++); 则实际有效的是下列两行: while (*s) putchar(*s++);

  25. void LineEdit( ) { InitStack(S); //栈S设为输入缓冲区 ch=getchar( ); while(ch!=eof){ while(ch!=eof && ch!=‘\n’){ switch(ch){ case ‘#’ : Pop(S,ch); //书上为c case ‘@’ : ClearStack(S); default : Push(S,ch); }//end switch ch=getchar( ); }//end while 将从栈底到栈顶内的字符传送到用户数据区; ClearStack(s); if(ch!=eof) ch=gethar( ); } //end while DestroyStack(s); }//end LineEdit 行编辑程序算法:

  26. 3.2.5 表达式求值 例如:3*(7 – 2 ) (1)算术四则运算的规则: a.从左算到右 b.先乘除,后加减 c.先括号内,后括号外 由此,此表达式的计算顺序为: 3*(7 – 2 )= 3 * 5 = 15

  27. 3.2.5 表达式求值 (2)根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符1和2 ,都要比较优先权关系。 算符优先法所依据的算符间的优先关系见教材P53表3.1 由表可看出,右括号 )和井号 #作为2时级别最低; 由c规则得出: * ,/, + ,-为1时的优先权低于‘(’,高于‘)’ 由a规则得出:‘(’==‘)’ 表明括号内运算,已算完。 ‘ # ’==‘ # ’ 表明表达式求值完毕。 令OP={+,-,*,/,(,),#}

  28. 3.2.5 表达式求值 (3)算法思想: • 设两个栈:操作符栈 OPTR ,操作数栈 OPND • 栈初始化:设操作数栈 OPND 为空;操作符栈 OPTR 的栈底 • 元素为表达式起始符 ‘#’; • 依次读入字符:是操作数则入OPND栈,是操作符则要判断: if操作符(1)< 栈顶元素(2),则出栈、计算,结果压入OPND栈 操作符== 栈顶元素且不为‘#’,脱括号(弹出左括号); 操作符 > 栈顶元素,压入OPTR栈。

  29. OPTR OPND INPUT OPERATE # 3*(7-2)# Push(opnd,’3’) # 3 *(7-2)# Push(optr,’*’) #, * 3 (7-2)# Push(optr,’(’) #, *, ( 3 7-2)# Push(opnd,’7’) #, *, ( 3,7 -2)# Push(optr,’-’) #, *, (, - 3,7 2)# Push(opnd,’2’) )# #, *, (, - 3,7,2 Operate(7-2) #, *, ( 3,5 )# Pop(optr) #, * 3,5 # Operate(3*5) # 15 # GetTop(opnd) 例 求表达式3*(7-2)

  30. Status EvaluateExpression( OperandType &result) { InitStack(OPND); InitStack(OPTR); Push(OPTR ,’#’); c=getchar( ); while((c!=‘#’)&&(GetTop(OPTR)!=‘#’)) { if (!In(c,OP) { Push(OPND,c); c=getchar( );} else switch(compare(c,GetTop(OPTR))) { case ‘>’ : Push(OPTR , c); c=getchar( ); break; case ‘=’: Pop(OPTR,x); c=getchar( ); break; case ‘<’ : Pop(OPTR,tem); Pop(OPND,b ); a=Pop(OPND,a ); result=Operate(a,tem,b); Push(OPND,result); c=getchar( ); break; } //switch }//while result=GetTop(OPND);}//End EvaluateExpression

  31. 3.2.4 迷宫求解 入口 出口

  32. 求迷宫路径算法的基本思想是: • 若当前位置“可通”,则纳入路径,继续前进; • 若当前位置“不可通”,则后退,换方向继续探索; • 若四周“均无通路”,则将当前位置从路径中删除出去。

  33. 求迷宫中一条从入口到出口的路径的算法思想:求迷宫中一条从入口到出口的路径的算法思想: 设当前位置为入口位置 do{ if(当前位置可通){ 当前位置插入栈顶; if(该位置是出口位置) 结束 else 切换当前位置的东邻方块为新的当前位置; } else{ if(栈不空栈顶位置尚有其它方向未经探索) 设定新的当前位置为顺时针方向旋转找到的顶点 位置的下一个邻块; if(栈不空且栈顶位置的四周均不通){ 删去栈顶元素位置,直至找到一个可通的相邻块或 出栈或栈空; } } }while(栈不空);

  34. Type struct{ int ord; //通道块在路径上的序号 PosType seat; //通道块在迷宫中的“坐标位置” int di; //从此通道块走向下一通道块的方向 }SElemType; //栈元素类型 Status MazePath(MazeType maze, PosType start, PosType end) { InitStack(S); curpos=start; //当前位置为入口位置 curstep=1; //探索第一步 do{ if(Pass(curpos)){ //当前位置可以通过 footprint(curpos); //留下足印 e=(curstep,curpos,1); Push(S,e); //加入路径 if(curpos==end) return(TRUE); //到达终点 curpose=NextPos(curpos,1); //下一个位置是当前位置的东邻 curstep++;

  35. else{ //当前位置不通 if(!StackEmpty(S)){ Pop(S,e); while(e.di==4 && !StackEmpty(S)){ MarkPrint(e.seat); Pop(S,e);//留下不能通过的标记并后退 }//end while if(e.di<4){ e.di++; Push(S,e); //换下一个方向探索 curpos=NextPos(e.seat, e.di);//新位置是旧位置的相邻块 }//end if } //end if }//end else }while(!StackEmpty(S)); return(FALSE); }//end MazePath

  36. 子函数2 子函数1 子函数3 t s s 主程序 r r r s t r r s r 3.3 栈与递归的实现 一、函数的嵌套调用

  37. 3.3 栈与递归的实现 • 二、递归函数及其实现 • 递归:函数直接或间接的调用自身 • 实现:建立递归工作栈

  38. 3.3 栈与递归的实现 例1 递归的执行情况分析--n! int fact (int w) { if ( w==0) fact= 1; else fact=w* fact ( w-1); } main() { int f; f=fact(3); print(f); }

  39. 3.3 栈与递归的实现 递归过程三要素: • 1.定义出口 • 2.把一个大的问题划分成同类型的子问题 • 3.解的合成

  40. 地址 w 递归调用执行情况如下: int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } top

  41. 4 w=3 地址 w 递归调用执行情况如下: int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } w 3 3*fact(2); top

  42. 4 w=2 4 w=3 地址 w 递归调用执行情况如下: w int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } w 2 3 2*fact(1); 3*fact(2); top

  43. 4 w=1 4 w=2 4 w=3 地址 w 递归调用执行情况如下: w 1 w int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } 1*fact(0); w 2 3 2*fact(1); 3*fact(2); top

  44. w 0 fact(0)=1 4 w=1 4 w=2 4 w=3 地址 w 递归调用执行情况如下: w 1 w int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } 1*fact(0); w 2 3 2*fact(1); 3*fact(2); top

  45. w 0 fact(0)=1 4 w=2 4 w=3 地址 w 递归调用执行情况如下: w w 2 1 int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } w 1*fact(0); 3 2*fact(1); 3*fact(2); fact(1)=1 top

  46. w 0 fact(0)=1 4 w=3 地址 w 递归调用执行情况如下: w w 2 1 int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } w 2*fact(1); 1*fact(0); 3 fact(2)=2 3*fact(2); fact(1)=1 top

  47. w 0 fact(0)=1 地址 w 递归调用执行情况如下: w w 2 1 int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } w 2*fact(1); 1*fact(0); 3 fact(2)=2 3*fact(2); fact(1)=1 fact(3)=6 top

  48. 例2:Tower of Hanoi问题 问题描述:有A,B,C三个塔座,A上套有n个直径不同的圆盘,按直径从小到大叠放,形如宝塔,编号1,2,3……n。要求将n个圆盘从A移到C,叠放顺序不变,移动过程中遵循下列原则: • 每次只能移一个圆盘 • 圆盘可在三个塔座上任意移动 • 任何时刻,每个塔座上不能将大盘压到 小盘上

  49. n x y z 返回地址 • 解决方法 • n=1时,直接把圆盘从A移到C • n>1时,先把上面n-1个圆盘从A移到B,然后将n号盘从A移到C,再将n-1个盘从B移到C。即把求解n个圆盘的Hanoi问题转化为求解n-1个圆盘的Hanoi问题,依次类推,直至转化成只有一个圆盘的Hanoi问题 • 执行情况 • 递归工作栈保存内容:形参n,x,y,z和返回地址 • 返回地址用行编号表示

  50. 1 2 3 A B C A B C 3 A B C 0 1 A B C 6 2 A C B 6 3 A B C 0 2 A C B 6 3 A B C 0 3 A B C 0 2 A C B 6 main() { int m; printf("Input number of disks "); scanf("%d",&n); printf(" Steps : %3d disks ",n); hanoi(n,‘A',‘B',‘C'); (0) } void hanoi(int n,char X,char Y,char Z) (1) { (2) if(n==1) (3) move(1,X,Z); (4) else{ (5) hanoi(n-1,X,Z,Y); (6) move(n,X,Z); (7) hanoi(n-1,Y,X,Z); (8) } (9) }

More Related