720 likes | 930 Views
第三章 栈和队列. 3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现 3.4 队列. 3.1.1 栈的定义及基本运算. 3.1 栈( Stack). 栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为 栈顶( Top) , 另一端为 栈底( Bottom) 。 当栈中没有元素时称为空栈。.
E N D
第三章 栈和队列 3.1 栈 3.2 栈的应用举例 3.3 栈与递归的实现 3.4 队列
3.1.1 栈的定义及基本运算 3.1 栈(Stack) 栈是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶(Top),另一端为栈底(Bottom)。当栈中没有元素时称为空栈。 假设栈S=(a1,a2,a3,…an),则a1称为栈底元素,an为栈顶元素。栈中元素按a1,a2,a3,…an的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的。因此,栈又称为后进先出 (LIFO)表。
例、一叠书或一叠盘子。 栈顶 …… 栈底
抽象数据类型——栈 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被销毁
ClearStack(&S) 初始条件:栈S已存在 操作结果: 将S栈清空 StackEmpty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,返回true,否则返回false StackLength(S) 初始条件:栈S已存在 操作结果: 返回S的元素个数 GetTop(S,&e) //读栈顶元素 初始条件:栈S已存在且非空 操作结果: 用e返回栈顶元素
Push(&S,e) //入栈 初始条件:栈S已存在 操作结果: 将元素e插入到栈顶 Pop(&S,&e) //出栈 初始条件:栈S已存在且非空 操作结果: 删除S的栈顶元素,用e返回 StackTraverse(S,visit()) 初始条件:栈S已存在且非空 操作结果: 从栈底到栈顶依次对S的每个元素调用visit() }ADT Stack
顺序栈 栈的顺序存储结构简称为顺序栈,由于栈底位置是固定不变的,所以可以将栈底位置设置在数组两端的任何一端;而栈顶位置是随着入栈和出栈操作而变化的.
顺序栈的类型定义如下: #define STACK_INIT_SIZE 100 #define STACKINCREMENT 10 typedef struct { SElemType *base; //栈底指针,base=NULL表明栈不存在 SElemType *top; //栈顶指针,top==base为栈空的标志 int stacksize; //栈当前可以使用的最大容量 }SqStack;
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,指向栈底 位置
0 M-1 栈1底 栈1顶 栈2顶 栈2底 • 在一个程序中同时使用两个栈
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 基本操作的算法描述
2、返回栈顶元素 Status GetTop(SqStack S,SElemTtype &e) { if (S.top==S.base) return ERROR; e=*(S.top-1); return OK; }//end GetTop
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
4、出栈 Status Pop(SqStack &S,SElemType &e) { if(S.top==S.base) return ERROR; e=*--S.top; return OK; }//end Pop
链栈 栈顶 栈底 top …... next data ^ 其操作与线性链表类似
3.2 栈的应用举例 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 行编辑程序 3.2.5 表达式求值
十进制n和其它进制数d的转换是计算机实现计算的十进制n和其它进制数d的转换是计算机实现计算的 基本问题,其解决方法很多,其中一个简单算法基于 下列原理: n=(n div d)*d + n mod d ( 其中:div为整除运算,mod为求余运算) 例如 (1348)10=(2504)8,其运算过程如下: 3.2.1 数制转换
例如:(1348)10 = (2504)8 , 其运算过程如下: n n div 8 n mod 81348 168 4 168 21 0 21 2 5 2 0 2 计算顺序 输出顺序
算法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
假设表达式中充许括号嵌套,则检验括号是否匹配的方法假设表达式中充许括号嵌套,则检验括号是否匹配的方法 可用“期待的急迫程度”这个概念来描述。例: { [ ( ) ( ) ] } 1 2 3 4 5 6 7 8 3.2.2 括号匹配的检验 分析出现的不匹配的情况: 到来的右括号不是所“期待”的;
算法的设计思想: 1)凡出现左括号,则入栈; 2)凡出现右括号,首先检查栈是否空. 若栈空,则表明该“右括号”多余, 否则和栈顶元素比较, 若相匹配,则“左括号出栈”,否则表明括号不匹配. 3)表达式检验结束时, 若栈空,则表明表达式中括号匹配, 否则表明“左括号”有余
检验括号匹配的算法: 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;}
3.2.3 行编辑程序 在用户输入程序或数据时,允许用户输入出差错,当发现有误时,可以及时更正。方法是: 设立一个输入缓冲区,用于接受用户输入的一行字符,然后逐行存入用户数据区。 假设退格符“#”表示前一个字符无效; 退行符“@”表示当前行中的字符全无效。 假设从终端接受了这样两行字符 whli##ilr#e(s#*s) outcha@putchar(*s=#++); 则实际有效的是下列两行: while (*s) putchar(*s++);
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 行编辑程序算法:
3.2.5 表达式求值 例如:3*(7 – 2 ) (1)算术四则运算的规则: a.从左算到右 b.先乘除,后加减 c.先括号内,后括号外 由此,此表达式的计算顺序为: 3*(7 – 2 )= 3 * 5 = 15
3.2.5 表达式求值 (2)根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符1和2 ,都要比较优先权关系。 算符优先法所依据的算符间的优先关系见教材P53表3.1 由表可看出,右括号 )和井号 #作为2时级别最低; 由c规则得出: * ,/, + ,-为1时的优先权低于‘(’,高于‘)’ 由a规则得出:‘(’==‘)’ 表明括号内运算,已算完。 ‘ # ’==‘ # ’ 表明表达式求值完毕。 令OP={+,-,*,/,(,),#}
3.2.5 表达式求值 (3)算法思想: • 设两个栈:操作符栈 OPTR ,操作数栈 OPND • 栈初始化:设操作数栈 OPND 为空;操作符栈 OPTR 的栈底 • 元素为表达式起始符 ‘#’; • 依次读入字符:是操作数则入OPND栈,是操作符则要判断: if操作符(1)< 栈顶元素(2),则出栈、计算,结果压入OPND栈 操作符== 栈顶元素且不为‘#’,脱括号(弹出左括号); 操作符 > 栈顶元素,压入OPTR栈。
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)
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
3.2.4 迷宫求解 入口 出口
求迷宫路径算法的基本思想是: • 若当前位置“可通”,则纳入路径,继续前进; • 若当前位置“不可通”,则后退,换方向继续探索; • 若四周“均无通路”,则将当前位置从路径中删除出去。
求迷宫中一条从入口到出口的路径的算法思想:求迷宫中一条从入口到出口的路径的算法思想: 设当前位置为入口位置 do{ if(当前位置可通){ 当前位置插入栈顶; if(该位置是出口位置) 结束 else 切换当前位置的东邻方块为新的当前位置; } else{ if(栈不空栈顶位置尚有其它方向未经探索) 设定新的当前位置为顺时针方向旋转找到的顶点 位置的下一个邻块; if(栈不空且栈顶位置的四周均不通){ 删去栈顶元素位置,直至找到一个可通的相邻块或 出栈或栈空; } } }while(栈不空);
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++;
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
子函数2 子函数1 子函数3 t s s 主程序 r r r s t r r s r 3.3 栈与递归的实现 一、函数的嵌套调用
3.3 栈与递归的实现 • 二、递归函数及其实现 • 递归:函数直接或间接的调用自身 • 实现:建立递归工作栈
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); }
3.3 栈与递归的实现 递归过程三要素: • 1.定义出口 • 2.把一个大的问题划分成同类型的子问题 • 3.解的合成
地址 w 递归调用执行情况如下: int fact (int w) {1if ( w==0) 2 fact= 1; 3else 4fact=w* fact( w-1); } top
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
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
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
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
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
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
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
例2:Tower of Hanoi问题 问题描述:有A,B,C三个塔座,A上套有n个直径不同的圆盘,按直径从小到大叠放,形如宝塔,编号1,2,3……n。要求将n个圆盘从A移到C,叠放顺序不变,移动过程中遵循下列原则: • 每次只能移一个圆盘 • 圆盘可在三个塔座上任意移动 • 任何时刻,每个塔座上不能将大盘压到 小盘上
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和返回地址 • 返回地址用行编号表示
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) }