1 / 98

第 3 章栈和队列

第 3 章栈和队列. 栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。. 线性表 栈 队列 Insert(L, i , x) Insert(S, n+1 , x) Insert(Q, n+1 , x) 1≤i≤n+1 Delete(L, i ) Delete(S, n ) Delete(Q, 1 ) 1≤i≤n. 栈和队列是两种常用的数据类型. 3.1 栈. 3.2 栈的应用举例. 3.3 栈与递归实现. 3.4 队列. 3.1 栈.

aysel
Download Presentation

第 3 章栈和队列

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章栈和队列

  2. 栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。栈和队列是操作受限的线性表。栈:后进先出;队列:先进先出。 线性表 栈 队列 Insert(L,i, x) Insert(S, n+1, x) Insert(Q, n+1, x) 1≤i≤n+1 Delete(L, i) Delete(S, n) Delete(Q, 1) 1≤i≤n 栈和队列是两种常用的数据类型

  3. 3.1 栈 3.2 栈的应用举例 3.3 栈与递归实现 3.4 队列

  4. 3.1栈 3.1.1 栈的抽象数据类型定义 3.1.2 栈的表示与实现

  5. 3.1.1 栈的抽象数据类型定义 ADT Stack{ 数据对象: D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } 数据关系: R1={ <ai-1, ai >| ai-1, ai∈D, i=2,...,n } 约定an端为栈顶,a1 端为栈底。 基本操作: } ADT Stack

  6. InitStack(&S) DestroyStack(&S) GetTop(S, &e) StackEmpty(S) Push(&S, e) Pop(&S, &e)

  7. InitStack(&S)操作结果:构造一个空栈 S。 Return

  8. DestroyStack(&S)初始条件:栈 S 已存在。操作结果:栈 S 被销毁。 Return

  9. StackEmpty(S)初始条件:栈 S 已存在。 操作结果:若栈 S 为空栈,则返回 TRUE,否则 FALE。 Return

  10. GetTop(S, &e)初始条件:栈 S 已存在且非空。操作结果:用 e 返回 S 的栈顶元素。 … … a1 a2 an Return

  11. Push(&S, e)初始条件:栈 S 已存在。操作结果:插入元素 e 为新的栈顶元素。 … … a1 a2 an e Return

  12. Pop(&S, &e)初始条件:栈 S 已存在且非空。操作结果:删除 S 的栈顶元素,并用 e 返回其值。 … … a1 a2 an-1 an Return

  13. 3.1.2 栈的表示与实现 1.顺序栈 2.链 栈

  14. 利用顺序存储方式实现的栈称为顺序栈。 //----- 顺序栈的类型描述----- #define STACK_INIT_SIZE 100; typedef int ElemType; typedef struct { ElemType*data; int top; int stacksize; }SqStack;

  15. 通常将数组的0下标作为栈底,这样空栈时栈顶指针top=-1;进栈时,栈顶指针加1,即top++;出栈时,栈顶指针减1,即top--。栈操作的示意图如图3-1所示。通常将数组的0下标作为栈底,这样空栈时栈顶指针top=-1;进栈时,栈顶指针加1,即top++;出栈时,栈顶指针减1,即top--。栈操作的示意图如图3-1所示。

  16. int InitStack (SqStack &S){ // 构造一个最大空间为STACK_INIT_SIZE的空顺序栈 S S.data = newElemType[STACK_INIT_SIZE]; if (!S.data) exit (OVERFLOW); //存储分配失败 S.top = -1; S.stacksize = STACK_INIT_SIZE; return OK; }

  17. int Push (SqStack &S, ElemType e) { //若栈不满,则将e插入栈顶 if (S.top >= S.stacksize-1) //栈满 return OVERFLOW; S.data[++S.top]= e; return OK; }

  18. int Pop (SqStack &S, ElemType &e) { // 若栈不空,则删除S的栈顶元素, // 用 e 返回其值,并返回OK; // 否则返回ERROR if(S.top== -1)return ERROR; e = S.data[S.top--]; return OK; }

  19. Elemtype GetTop (SqStack S) { //栈已存在且不空,返回栈顶元素 if(S.top== -1)return ERROR; return S.data[S.top]; }

  20. int StackEmpty (SqStack S) { //判栈是否为空栈 if(S.top== -1)return OK; return ERROR; }

  21. 需要注意的是,对顺序栈,入栈时应首先判断栈是否满了,栈满的条件是S.top>=S.stacksize-1。栈满时,不能入栈,否则将出现空间溢出,引起错误,这种现象称为上溢。解决的办法是追加存储空间。出栈和读取栈顶元素时,应先判断栈是否为空,为空时不能操作,否则将产生错误,通常栈空作为控制转移的条件。需要注意的是,对顺序栈,入栈时应首先判断栈是否满了,栈满的条件是S.top>=S.stacksize-1。栈满时,不能入栈,否则将出现空间溢出,引起错误,这种现象称为上溢。解决的办法是追加存储空间。出栈和读取栈顶元素时,应先判断栈是否为空,为空时不能操作,否则将产生错误,通常栈空作为控制转移的条件。

  22. 链栈 用链式存储结构实现的栈称为链栈。通常链栈用单链表表示,因此其结点结构与单链表的结点结构相同。在此用LinkStack表示,即有: typedef struct node{ ElemTypedata; struct node *next; }StackNode,*LinkStack; LinkList top;

  23. 因为栈中的主要运算是在栈顶插入、删除,显然在链表的头部做栈顶是最方便的,而且没有必要象单链表那样为了运算方便附加一个头结点。如图3-2:因为栈中的主要运算是在栈顶插入、删除,显然在链表的头部做栈顶是最方便的,而且没有必要象单链表那样为了运算方便附加一个头结点。如图3-2:

  24. void InitStack (LinkStack &top){ // 构造一个空顺序栈 ,栈顶指针top top=NULL; }

  25. int Push (LinkStack &top, ElemType x) { S=new StackNode; if (!S)exit(OVERFLOW); S->data=x;S->next=top;top=s; return OK; }

  26. int Pop (LinkStack &top, ElemType &x) { if(S.top== NULL)return ERROR; else{ x = top->data; p=top; top=top->next; delete p; return OK; } }

  27. int StackEmpty (ListStack S) { //判栈是否为空栈 if(S.top== NULL)return OK; return ERROR; }

  28. 3.2栈的应用举例 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 表达式求值 3.2.4 命题公式求值

  29. 3.2.1 数制转换算法基于原理:N = (N div d)×d + N mod d

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

  31. 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

  32. 3.2.2 括号匹配的检验 假设在表达式中 ([]())或[([ ][ ])] 等为正确的格式, [( ])或([( ))或 (( )]) 均为不正确的格式。 则 检验括号是否匹配的方法可用 “期待的急迫程度”这个概念来描述。

  33. 分析可能出现的不匹配的情况: 到来的右括弧非是所“期待”的; 例如:考虑下列括号序列: [ ( [ ] [ ] ) ] 1 2 3 4 5 6 7 8 • 到来的是“不速之客”; • 直到结束,也没有到来所“期待”的括弧;

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

  35. int matching(char exp[]) { int state = 1;i=0;InitStack(S); while (i<Length(exp) && state) { switch of exp[i] { case ‘(‘:{Push(S,exp[i]); i++; break;} case”)”: {e=GetTop(S); if(!StackEmpty(S)&&e=‘(‘ {Pop(S,e); i++;} else {state = 0;} break; } }} if (StackEmpty(S)&&state) return OK; else return ERROR; }

  36. 3.2.3 表达式求值 限于二元运算符的表达式定义: 表达式 ::= (操作数) + (运算符) + (操作数) 操作数 ::= 简单变量 | 表达式 简单变量 :: = 标识符 | 无符号整数

  37. 表达式的三种标识方法: 设 Exp = S1 +OP+ S2 则称OP+ S1 + S2为前缀表示法 S1 +OP+ S2为中缀表示法 S1 + S2 +OP为后缀表示法

  38. 例如: Exp = a  b+(c  d / e)  f 前缀式: + a b c / d e f 中缀式: a  b+c  d / e  f 后缀式: a b c d e /  f + 结论: • 5)后缀式的运算规则为: • 运算符在式中出现的顺序恰为 • 表达式的运算顺序; • 每个运算符和在它之前出现且 • 紧靠它的两个操作数构成一个最小 • 表达式; • 4)前缀式的运算规则为: • 连续出现的两个操作数和在它们 • 之前且紧靠它们的运算符构成一 • 个最小表达式; • 3)中缀式丢失了括弧信息, • 致使运算的次序不确定; 1)操作数之间的相对次序不变; 2)运算符的相对次序不同;

  39. 如何从后缀式求值? 例如: a b  c d e /  f  + 先找运算符, 再找操作数 ab d/e c-d/e (c-d/e)f

  40. 如何从原表达式求得后缀式? 分析 “原表达式” 和 “后缀式”中的运算符: 原表达式: a + b c  d / e f 后缀式: a b c  + d e / f  每个运算符的运算次序要由它之后的一个运算符来定,在后缀式中,优先数高的运算符领先于优先数低的运算符。

  41. 从原表达式求得后缀式的规律为: • 1) 设立暂存运算符的栈; • 2) 设表达式的结束符为“#”, • 予设运算符栈的栈底为“#” • 3) 若当前字符是操作数, • 则直接发送给后缀式;

  42. 从原表达式求得后缀式的规律为: • 4) 若当前运算符的优先数高于栈顶运算符,则进栈; • 否则,退出栈顶运算符发送给后缀式; 5) “(” 对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符。

  43. void transform(char suffix[ ], char exp[ ] ) { InitStack(OPTR); Push(OPTR, #); while (*exp!=‘#’) { if (*exp>=‘0’&&*exp<=‘9’){…} else { … } } while(!StackEmpty(OPTR)) {Pop(OPTR,ch);suffix[i++]=ch;} } // CrtExptree

  44. do {//不是运算符,则处理连续出现的数字 Suffix[i++]=*exp; exp++; } while(*exp>=‘0’&&*exp<=‘9’); Suffix[i++]=‘$’; //操作数之间的间隔符 Return

  45. switch (*exp) { case(: Push(OPTR, *exp); break; case):Pop(OPTR, ch); while (ch!=( ) {Suffix[i++]=ch; Pop(OPTR,ch) } break; defult : while(precede(GetTop(OPTR),*exp)!=‘<‘) {Pop(OPTR, ch);Suffix[i++]=ch; } Push(OPTR, *exp); break; } // switch exp++; Return

  46. 求后缀式表示的表达式的值: int calcul_exp(char *Suffix ) {//返回后缀表达式的运算结果 InitStack(OPND); while (*Suffix! = # ){ d=0; if (*Suffix>=‘0’&&*Suffix<=‘9’) {…} else {…} } Pop(OPND,result); return result; }

  47. While(*Suffix!=‘$’) {//不是运算符,则处理连续出现的数字 d=10*d+*Suffix-’0’; Suffix++; } Push(OPND,d); //操作数进栈 Suffix++; Return

  48. Pop(OPND,b); Pop(OPND,a); switch (*Suffix) { casech== +: c=a+b; break; case ch== - : c=a-b; break; case ch== * : c=a*b; break; case ch== / : c=a/b; break; }// switch Push(OPND,c); Suffix++; Return

  49. 3.2.4 命题公式求值 利用计算机求命题公式真值的关键是:①给出命题变元的每一组赋值;②计算命题公式在每一组赋值下的真值,其计算类似于中缀表达式的求值。

  50. 设定两个全局变量:字符数组myopnd[10]和整型数组A[1024][11],分别存储命题公式中的命题变元和命题公式的真值表。全局变量n表示命题公式的变元数,m为其对应的赋值数。设定两个全局变量:字符数组myopnd[10]和整型数组A[1024][11],分别存储命题公式中的命题变元和命题公式的真值表。全局变量n表示命题公式的变元数,m为其对应的赋值数。 真值表中命题变元的取值具有如下规律:每列中0和1是交替出现的,且0和1连续出现的个数相同。n个命题变元的每组赋值的生成算法可基于这种思想。

More Related