390 likes | 583 Vues
第三章 栈和队列. 学习要点 1. 掌握栈和队列这两种抽象数据类型的特点,并能在相应的应用问题中正确选用它们。 2. 熟练掌握栈类型的两种实现方法,即两种存储结构表示时的基本操作实现算法,特别应注意栈满和栈空的条件以及它们的描述方法。 3. 熟练掌握循环队列和链队列的基本操作实现算法,特别注意队满和队空的描述方法。 4. 理解递归算法执行过程中栈的状态变化过程。. 第三章 栈和队列 3.1 栈 3.2 队列. 返回主菜单. 进栈. 出栈. 栈顶. a n. ……. 栈 s=(a1,a2,……,an).
E N D
学习要点 1. 掌握栈和队列这两种抽象数据类型的特点,并能在相应的应用问题中正确选用它们。 2. 熟练掌握栈类型的两种实现方法,即两种存储结构表示时的基本操作实现算法,特别应注意栈满和栈空的条件以及它们的描述方法。 3. 熟练掌握循环队列和链队列的基本操作实现算法,特别注意队满和队空的描述方法。 4. 理解递归算法执行过程中栈的状态变化过程。
第三章 栈和队列 3.1 栈 3.2 队列 返回主菜单
进栈 出栈 ... 栈顶 an ……... 栈s=(a1,a2,……,an) a2 栈底 a1 栈和队列是两种特殊的线性表,是操作受限的线性表,称限定性DS • 3.1 栈(stack) • 栈的定义和特点 • 定义:限定仅在表尾进行插入或删除操作的线性表,表尾—栈顶,表头—栈底,不含元素的空表称空栈 • 特点:先进后出(FILO)或后进先出(LIFO)
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 1 1 1 top=0 0 0 0 栈空 栈空 栈满 • 栈的表示和实现 • 顺序栈 • 实现:一维数组s[M] F E D C B A 出栈 进栈 设数组维数为M top=0,栈空,此时出栈,则下溢(underflow) top=M,栈满,此时入栈,则上溢(overflow) 栈顶指针top,指向实际栈顶 后的空位置,初值为0
#define INITSIZE 100; #define INCREMENT 10; Typedef struct { SElemType *base; SElemType *top; int stacksize; }SqStack; • 实现:存储表示 • 初始化算法 Status InitStack(SqStack &S) { // 构造一个空栈S S.base=(SElemType *) malloc (INITSIZE * sizeof(SElemType)); if (!S.base) exit (OVERFLOW); //存储分配失败 S.top=S.base; S.stacksize=INITSIZE; return OK; }//InitStack
取栈项元素算法 Stack GetTop (SqStack S,SElemType &e) { //若栈不空,则用e返回S的栈顶元素,并返回OK, //否则返回ERROR; if (S.top==S.base) return ERROR; e=*(S.top-1); return OK; }//GetTop
0 M-1 栈1底 栈1顶 栈2顶 栈2底 int push(int s[],int x,int top) { if(top==M) { printf("overflow"); return(-M); } s[top]=x; return(++top); } • 入栈算法 • 出栈算法 int pop(int s[],int top,int *q) { if(top==0) { printf("stack empty"); return(0); } *q=s[--top]; return(top); } • 在一个程序中同时使用两个栈
栈顶 栈底 …... top ^ data link • 链栈 • 结点定义 typedef struct node { int data; struct node *link; }JD;
top top 栈底 p x …... ^ • 入栈算法 JD *lzjz(JD *top,int x) { JD *p; p=(JD *)malloc(sizeof(JD)); p->data=x; p->link=top; top=p; return(p); }
q top 栈底 top …... ^ • 出栈算法 JD *lztz(JD *top,int *p) { JD *q; if(top!=NULL) { q=top; *p=top->data; top=top->link; free(q); } return(top); }
8 159 8 8 19 2 2 3 7 例 把十进制数159转换成八进制数 余 7 3 top top top top 余 3 7 7 余 2 0 2 (159)10=(237)8 3 7 • 多进制输出: 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); } }//conversion
括号匹配的检验 假设表达式中充许括号嵌套,则检验括号是否匹配的方法可用“期待的急迫程度”这个概念来描述。例: [([ ] [ ])] • 行编辑程序 在编辑程序中,设立一个输入缓冲区,用于接受用户输入的一行字符,然后逐行存入用户数据区。允许用户输入错误,并在发现有误时可以及时更正。 top d a d • 回文游戏:顺读与逆读字符串一样(不含空格) 1.读入字符串 2.去掉空格(原串) 3.压入栈 4.原串字符与出栈字符依次比较 若不等,非回文 若直到栈空都相等,回文 字符串:“madam im adam”
行编辑程序算法如下:void lineedit( ){ initstack(S); ch=getchar( ); while(ch!=eof){ while(ch!=eof && ch!=‘\n’){ switch(ch){ case ‘#’ : pop(S,c); case ‘@’ : clearstack(S); default : push(S,ch); } ch=getchar( ); } clearstack(S); if(ch!=eof) ch=gethar( ); } destroystack(S); }//LineEdit
6 18 3 * 4 + - - - 12 6 6 6 2 操作数 操作数 操作数 操作数 操作数 运算符 运算符 运算符 运算符 运算符 • 表达式求值 中缀表达式 后缀表达式(RPN) a*b+c ab*c+ a+b*c abc*+ a+(b*c+d)/e abc*d+e/+ 中缀表达式:操作数栈和运算符栈 例 计算 2+4-3*6
15 3 top top top top top top 4 4 5 19 4 3 7 后缀表达式求值步骤: 1、读入表达式一个字符 2、若是操作数,压入栈,转4 3、若是运算符,从栈中弹出2个数,将运算结果再压入栈 4、若表达式输入完毕,栈顶即表达式值; 若表达式未输入完,转1 后缀表达式:435*+ 例 计算 4+3*5
Y=2; Z=3; X=y+z*2; 如何对表达式求值呢? ? 栈的应用举例 • 表达式求值 • 问题的提出: 设计一个小计算器: 对键入的表达式进行求值。 高级语言中的赋值语句:变量=表达式;
如何确定运算符的运算顺序? ? • 表达式的构成 操作数+运算符+界符(如括号) • 表达式的求值: • 例 5+6 (1+2)- 4 • 按照四则运算法则,上述表达式的计算过程为: • 5+6 (1+2) - 4=5+6 3- 4 = 5+18-4= 23-4=19 3 2 1 4
c2 + - * / ( ) # c1 + > > < < < > > - > > < < < > > * > > > > < > > / > > > > < > > ( < < < < < = ) > > > > > > < < < < < = # • 算符优先关系表 • 表达式中任何相邻运算符c1、c2的优先关系有:c1<c2:c1的优先级低于c2 c1=c2:c1的优先级等于c2 c1>c2:c1的优先级高于c2 算符间的优先关系表 注: c1c2是相邻算符, c1在左, c2在右
算符优先算法 用两个栈分别保存扫描过程中遇到的操作数和运算符 5 + 4 (1 + 2) - 6 从左向右扫描表达式: 遇操作数——保存; 遇运算符号cj——与前面的刚扫描过的运算符ci比较 若ci<cj 则保存cj,( 因此已保存的运算符的优先关系为 c1<c2<c3<c4。。)若ci>cj 则说明ci是已扫描的运算符中优先级最高者,可进行运算; 若ci = cj 则ci为(,cj 为 ),说明括号内的式子已计算完,需要消去括号 后面也许有优先级更高的算符 + ( + 后保存的算符有优先级高
在算符优先算法中,建立了两个工作栈。一个是OPTR栈,用以保存运算符.一个是OPND栈,用以保存操作数或运算结果。int express ( ) {//运算数栈,OP为运算符集合。 InitStack(OPTR); Push (OPTR, ‘# ‘); InitStack(OPND); w=getchar( ); While(w!=‘ #’ || GetTop(OPTR)!=‘#’) {if(! In(w,OP)){Push(OPND,w);w=getchar();} //不是运算符则进栈 else // In(w, OP)判断c是否 是运算符的函数
续switch (Precede(GetTop(OPTR), w) { case ‘<‘:// 新输入的算符 w 优先级高,w 进栈 Push(OPTR, w ); w =getchar( ); break; case ‘=‘: // 去括号并接收下一字符 x=Pop(OPTR); w=getchar( ); break; case ‘>’://新输入的算符c优先级低,即栈顶算符优先权高 //出栈并将运算结果入栈OPND op=Pop(OPTR); b=Pop(OPND); a= Pop(OPND); Push(OPND, Operate(a, op, b)); break; } } return GetTop(OPND); }
子过程2 子过程1 子过程3 t s s 主程序 r r r s t r r s r • 栈的应用 • 过程的嵌套调用
递归过程及其实现 • 递归:函数直接或间接的调用自身叫~ • 实现:建立递归工作栈 例 递归的执行情况分析 void print(int w) { int i; if ( w!=0) { print(w-1); for(i=1;i<=w;++i) printf(“%3d,”,w); printf(“/n”); } } 运行结果: 1, 2,2, 3,3,3,
w w 1 0 w print(0); 2 返回 w (4)输出:1 (4)输出:1 主程序 print(1); 3 (3) 输出:2, 2 (3) 输出:2, 2 print(2); w=3; (2) 输出:3, 3, 3 (2) 输出:3, 3, 3 print(w) (1) (1) top (3)w=1 (1 ) 3 (4)w=0 (2)w=2 (3)w=1 (2) 2 (1)w=3 (2)w=2 (1) 3 top (1)w=3 top (4) 0 top (3) 1 (3) 1 top (2)w=2 (2) 2 (2) 2 (1)w=3 (1) 3 (1) 3 (1)w=3 top top top 结束 递归调用执行情况如下:
n x y z 返回地址 • Tower of Hanoi问题 • 问题描述:有A,B,C三个塔座,A上套有n个直径不同的圆盘,按直径从小到大叠放,形如宝塔,编号1,2,3……n。要求将n个圆盘从A移到C,叠放顺序不变,移动过程中遵循下列原则: • 每次只能移一个圆盘 • 圆盘可在三个塔座上任意移动 • 任何时刻,每个塔座上不能将大盘压到小盘上 • 解决方法: • 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",&m); printf(”Steps : %3d disks”,m); hanoi(m,'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) }
A B C A B C 3 A B C 0 2 A C B 6 1 C A B 8 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 the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'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) }
A B C A B C 3 A B C 0 2 B A C 8 3 A B C 0 2 B A C 8 1 B C A 6 3 A B C 0 2 B A C 8 3 A B C 0 main() { int m; printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'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) }
A B C A B C 3 A B C 0 2 B A C 8 3 A B C 0 2 B A C 8 1 A B C 8 2 B A C 8 3 A B C 0 3 A B C 0 Hanoi.c D:\fengyi\bkc\power\power.c 栈空 main() { int m; printf("Input the number of disks scanf("%d",&m); printf("The steps to moving %3d hanoi(m,'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) }
出队 a1 a2 a3…………………….an 入队 队列Q=(a1,a2,……,an) front rear 出队 出队 a1 a2 a3…………………….an 入队 入队 端1 端2 • 3.2 队列 • 队列的定义及特点 • 定义:队列是限定只能在表的一端进行插入,在表的另一端进行删除的线性表 • 队尾(rear)——允许插入的一端 • 队头(front)——允许删除的一端 • 队列特点:先进先出(FIFO) • 双端队列
头结点 队头 队尾 …... front ^ rear typedef struct Qnode{ QElemType data; struct Qnode *next; }Qnode,*QueuePtr; Typedef struct{ QueuePtr front; QueuePtr rear; }//LinkQueue; • 链队列 • 结点定义 设队首、队尾指针front和rear, front指向头结点,rear指向队尾
^ ^ x出队 x y ^ rear front y出队 空队 x入队 x ^ rear rear front front front rear y入队 x y ^ rear front
入队算法 Status EnQueue(LinkQueue &Q,QElemType e){ p=(QueuePtr) malloc (sizeof(QNode)); if (!p) exit (OVERFLOW); p->data=e; p->next=NULL; Q.rear->next=p; Q.rear=p; return OK; } • 出队算法 Status DeQueue(LinkQueue &Q,QElemType &e){ if (Q.front==Q.rear) return ERROR; p=Q.front->next; e=p->data; Q.front->next=p->next; if (Q.rear==p) Q.rear=Q.front; free(p); return OK; }
rear J6 J5 5 5 5 5 rear J4 4 4 4 4 rear rear rear J3 front 3 3 3 3 front front front J2 2 2 2 2 J1 1 1 1 1 front=0 rear=0 front front J4,J5,J6入队 J1,J2,J3出队 J1,J1,J3入队 0 0 0 0 队空 • 队列的顺序表示和实现 • 实现:用一维数组实现sq[M] J3 J2 J1 设两个指针front,rear,约定: rear指示队尾元素; front指示队头元素位置 初值front=rear=0 空队列条件:front==rear 入队列:sq[++rear]=x; 出队列:x=sq[++front];
M-1 rear …... 0 1 …... front • 存在问题 设数组维数为M,则: • 当front=0,rear=M-1时,再有元素入队发生溢出——真溢出 • 当front0,rear=M-1时,再有元素入队发生溢出——假溢出 • 解决方案 • 队首固定,每次出队剩余元素向下移动——浪费时间 • 循环队列 • 基本思想:把队列设想成环形,让sq[0]接在sq[M-1]之后,若rear+1==M,则令rear=0; • 实现:利用“模”运算 • 入队: rear=(rear+1)%M; sq[rear]=x; • 出队: front=(front+1)%M; x=sq[front]; • 队满、队空判定条件
front rear 5 4 0 3 1 2 rear J5 J6 J4 5 4 0 J4,J5,J6出队 3 1 2 front J7,J8,J9入队 J5 初始状态 J6 J4 5 4 0 3 1 J7 J9 2 J8 front rear 队空:front==rear 队满:front==rear 解决方案: 1.另外设一个标志以区别队空、队满 2.少用一个元素空间: 队空:front==rear 队满:(rear+1)%M==front
入队算法: Status EnQueue(SqQueue &Q,QElemType e) { if ((Q.rear+1) % MAXSIZE ==Q.front) return ERROR; Q.base[Q.rear]=e; Q.rear=(Q.rear+1) % MAXSIZE; return OK; } • 出队算法: Status DeQueue (SqQueue &Q,QElemType &e) { if (Q.front==Q.rear) return ERROR; e=Q.base[Q.front]; Q.front= (Q.front+1) % MAXSIZE; return OK; }
第三章 结 束 返回主菜单