1 / 66

栈 栈的应用 队列 队列的应用

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

tola
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. 第三章 栈和队列 • 栈 • 栈的应用 • 队列 • 队列的应用

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

  3. a n 栈顶 a n-1 …… a2 栈底 a1 例:一叠书或一叠盘子。 栈的抽象数据类型的定义如下:P44

  4. 3.1.2 顺序栈 由于栈是运算受限的线性表,因此线性表的存储结构对栈也适应。 栈的顺序存储结构简称为顺序栈,它是运算受限的线性表。因此,可用数组来实现顺序栈。因为栈底位置是固定不变的,所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作而变化的,故需用一个整型变量top

  5. 来指示当前栈顶的位置,通常称top为栈顶指针。因此,顺序栈的类型定义只需将顺序表的类型定义中的长度属性改为top即可。顺序栈的类型定义如下:来指示当前栈顶的位置,通常称top为栈顶指针。因此,顺序栈的类型定义只需将顺序表的类型定义中的长度属性改为top即可。顺序栈的类型定义如下: # define StackSize 100 typedef char datatype; typedef struct { datatype data[stacksize]; int top; }seqstack;

  6. 6 5 4 3 2 1 0 -1 top

  7. 设S是SeqStack类型的指针变量。若栈底位置在向量的低端,即s–>data[0]是栈底元素,那么栈顶指针s–>top是正向增加的,即进栈时需将s–>top加1,退栈时需将s–>top 减1。因此,s–>top<0表示空栈,s–>top =stacksize-1表示栈满。当栈满时再做进栈运算必定产生空间溢出,简称“上溢”;当栈空时再做退栈运算也将产生溢出,简称“下溢”。上溢是一种出错状态,应该设法避免之;下溢则可能是正常现象,因为栈在程序中使用时,其初态或终态都是空栈,所以下溢常常用来作为程序控制转移的条件。

  8. 1、置空栈 void initstack(seqstack *s) { s–>top=-1; } 2、判断栈空 int stackempty(seqstack *s) { return(s–>top==-1); }

  9. 3、判断栈满 int stackfull(seqstack *s) { return(s–>top==stacksize-1); } 4、进栈 void push(seqstack *s,datatype x) { if (stackfull(s)) error(“stack overflow”); s–>data[++s–>top]=x; }

  10. 5、退栈 datatype pop(seqstack *s) { if(stackempty(s)) error(“stack underflow”); x=s–>data[top]; s–>top--; return(x); //return(s–>data[s–>top--]); }

  11. 6、取栈顶元素 Datatype stacktop(seqstack *s) { if(stackempty(s) error(“stack is empty”); return (s–>data[s–>top]); }

  12. 3.1.3 链栈 栈的链式存储结构称为链栈,它的运算是受限的单链表,插入和删除操作仅限制在表头位置上进行。由于只能在链表头部进行操作,故链表没有必要像单链表那样附加头结点。栈顶指针就是链表的头指针。 链栈的类型说明如下:

  13. 栈的链接存储结构: typedef int datatype; typedef struct node { datatype data; struct node *next; } linkstack;

  14. 栈的链接表示—链栈: • 链式栈无栈满问题,空间可扩充 • 插入与删除仅在栈顶处执行 • 链式栈的栈顶在链头 • 适合于多栈操作

  15. maxsize-1 0 lefttop righttop 两个堆栈共享空间:

  16. 链栈的进栈算法: • linkstack *PUSHLSTACK(linkstack*top, datatype x) • { • linkstack *p; • p=(linkstack *)malloc(sizeof(linkstack)); • p->data=x; • p->next=top; • return p; • }

  17. 链栈的出栈算法: linkstack *POPLSTACK(linkstack *top, datatype datap) { linkstack *p; if (top==NULL) {printf(“under flow\n”); return NULL;} else { *datap=top->data; p=top; top=top->next; free(p); return top; } }

  18. 3.2 栈的应用举例 由于栈结构具有的后进先出的固有特性,致使栈成为程序设计中常用的工具。以下是几个栈应用的例子。 栈的应用举例-----数制转换 十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理: N=(n div d)*d+n mod d ( 其中:div为整除运算,mod为求余运算) 例如 (1348)10=(2504)8,其运算过程如下:

  19. n n div 8 n mod 8 1348 168 4 168 21 0 21 2 5 2 0 2

  20. 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); } }

  21. 栈的应用举例--文字编辑器 seqstack s; EDIT( ) { char c; initstack(&s); c=getchar(); while (c!=‘*’) { if (c==‘#’) POP(&s); else if (c==‘@’) initstack(&s); else PUSH(&s,c); c=getchar(); } } 输入缓冲区为一堆栈 #为退格符,@为退行符 P49 输入#,执行退栈操作 输入@,执行清栈空 输入其他,进栈

  22. 栈的应用举例--表达式计算 中缀表达式:A + ( B – C / D) × E 后缀表达式:ABCD/-E×+ 后缀表达式特点 1、与相应的中缀表达式中的操作数次序相同 2、没有括号

  23. 后缀表达式的处理过程:

  24. 中缀表达式转换为后缀表达式 当前运算符 栈顶运算符

  25. 中缀表达式转换为后缀表达式的处理规则: 1、如为操作数,直接输出到队列; 2、如当前运算符高于栈顶运算符,入栈; 3、如当前运算符低于栈顶运算符,栈顶运算符退栈, 并输出到队列,当前运算符再与栈顶运算符比较; 4、如当前运算符等于栈顶运算符,且栈顶运算符为 “(”,当前运算符为“)”,则栈顶运算符退栈, 继续读下一符号; 5、如当前运算符等于栈顶运算符,且栈顶运算符为 “#”,当前运算符也为“#”,则栈顶运算符退栈, 继续读下一符号;

  26. 算法步骤: 设置一堆栈,将栈顶预置为#。顺序读入中缀表达式,当读到的单词为操作数就将其输出,并等着读下一个单词; 当读到的单词为运算符就赋予x2,比较 x2与栈顶运算符 xl的优先级,若 x2的优先级高于xl的,将x2进栈,继续读下一个单词; 若x2的优先级低于 xl的,xl退栈并作为后缀表达式的一个单词输出,然后继续比较x2与新的栈顶运算符 x1; 若 x2的优先级等于xl的,且 xl为“(”, X2为“)”,则xl退栈,然后继续读下一个单词;若 x2的优先级等于xl的,且xl为#,x2也为#,则算法结束

  27. 算法:中缀表达式变换为后缀表达式 int Postfix(qstype *s,char *a) { char x1,x2; char x; int j=0; s->Stack[0]='#', s->Top=0; x2=a[j]; if ((x1=GetTopQStack(s))==NIL) exit(0); while(1){ if (x2!='+'&&x2!='-'&&x2!='*'&& x2!='/'&&x2!='/'&&x2!='(' &&x2!=')' &&x2!='#')

  28. { printf("%c",x2); //操作数输出 • j++; • x2=a[j]; //取下一个单词 • } //此种情况下 x2读到的是操作数 • else if(Proceed(x1,x2)=='<') • { if(!PushQStack(s,x2)) exit(0); //读到的操作符入栈 • if((x1=GetTopQStack(s))==NIL) exit(0); • //更新xl的值为新入栈的操作符 • j++; • x2=a[j]; //取下一个单词 • } //栈顶操作符X1小于读到的操作符X2 • else if(Proceed(x1,x2)=='>')

  29. { if((x=PopQStack(s))==NIL) exit(0); //退栈 • printf("%c",x); //输出原栈顶操作符 • if ((x1=GetTopQStack(s))==NIL) exit(0); //更新xl的值 • } //栈顶操作符大于读到的操作符 • else if(Proceed(x1,x2)=='='&&x1=='('&&x2==')') • { if((PopQStack(s))==NIL) exit(0); • if ((x1= GetTopQStack(s)) == NIL) exit(0); • j++; • x2=a[j]; • } //去掉后缀表达式中的一对括号 • else if(Proceed(x1,x2)=='='&&x1=='#'&&x2=='#') return 1 ; • } • }

  30. 中缀表达式转换成后缀表达式的过程: A+(B-C/D)*E

  31. D C T1 B B T2 A A A top top top

  32. E T3 T1 A A T4 top top top

  33. 3.4 队列 3.4.1 抽象数据类型队列的定义 队列(Queue)是一种运算受限的线性表。 表的一端进行插入,而在另一端进行删除 队头(front) 允许删除的一端 队尾(rear) 允许插入的一端 先进先出(First In First Out)的线性表,简称FIFO表 空队列 当队列中没有元素

  34. 出队 入队 a1a2…an 队头 队尾 A B C D

  35. ADT Queue { 数据对象:D={ai | ai∈ElemSet, i=1,2,...,n, n≥0} 数据关系:R1={ <ai-1,ai > | ai-1, ai ∈D, i=2,...,n} 约定其中a1端为队列头,an-1端为队列尾 基本操作: Initiate(q) 初始化 Enter (q,x) 入队列 Delete(q) 出队列 Gethead(q) 取队头元素 Empty(q) 判队空否 } ADT Queue

  36. 3.4.2 循环队列-队列的顺序表示和实现 队列的顺序存储结构简称顺序队列 #define MAXNUM //队列允许存放的最大元素数 typedef struct {elemtype queue[MAXNUM]; int front; int rear; }qtype;

  37. D D D C C B B A A Rear Rear Rear Rear Rear Rear Rear Front Front Front Front Front Front Front E D 若删除D: 若插入E: 队尾、队头、队尾指示器、队头指示器 空队列、进队列、出队列

  38. (l) 初始化 void initiateq(qtype *q){ //用指针类型传引用调用 q->front=-1; q->rear=-1; }

  39. (2) 进队列 int enterq( qtype *q, elemtype x) { if(q->rear==MAXNUM-l) return(false); //队列已满 else { q->rear++; q->queue[q->rear]=x; return(true); } }

  40. (3)出队列 elemtype deleteq(qtype *q) { if(q->front==q->rear)//队列已空 return(ERR); //ERR为elemtype类型常量 else{ q->front++; return(q->queue[q->front]); } }

  41. 队列中亦有上溢和下溢现象。 此外,顺序队列中还存在“假上溢”现象。 因为在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。 因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作。该现象称为假上溢。

  42. q->rear=-1 q->front=-1 q->rear=2 q->front=-1 q->rear=2 q->front=1 q->rear=5 q->front=1

  43. an an q->Rear q->Rear an-1 an-1 an q->Rear . . . . . . . . . a1 a1 a2 a0 a1 q->Front q->Front q->Front 解决顺序队列假溢出问题的四种方法: ①按最大可能的进队列操作次数设置顺序队列的最大元素个数 ②修改出队列算法 时间复杂度由O(1)增大为O(n)

  44. ③修改进队列算法 即把向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的队列称为循环队列(Circular Queue)。在循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界(MAXNUM-1)时,其加1操作的结果是指向向量的下界0。 这种循环意义下的加1操作可以描述为: if (q->rear+1== MAXNUM) q->rear =0; else q->rear++; 利用模运算可简化为: q->rear =(q->rear +1)%MAXNUM

  45. q-> front q-> rear 5 4 0 3 1 2 q->rear J5 J6 J4 5 4 0 J4,J5,J6出队 3 1 2 q-> front J7,J8,J9入队 J5 初始状态 J6 J4 5 4 0 3 1 J7 J9 2 J8 q-> front q-> rear 队空:q->front== q-> rear 队满:q->front== q-> rear 如何解决?

  46. 解决方案: (1)少用一个数据元素空间,以队尾指示器加 l等于队头指示器判断队满,即队满条件为: (q-> rear+l)%MAXNUM=q->front 队空条件仍为q->rear=q->front。 队空:q->front =q-> rear 队满:q->front =(q->rear + 1) % MAXNUM 入队: q->rear = (q->rear + 1) % MAXNUM 出队: q->front = (q->front + 1) % MAXNUM 求队长:(q->rear - q->front+MAXNUM)%MAXNUM

  47. (2)另设一个标志位以区别队列是满还是空。 (3)使用一个计数器记录队列中元素的总数(实际上是队列长度)。 我们用第三种方法实现循环队列上的六种基本操作,为此先给出循环队列的类型定义。

  48. #define MAXNUM 100 typedef char elemtype; typedef struct{ int front; int rear; int count; elemtype data[MAXNUM]; }cirqueue; 循环队列的进队列和出队列算法如下:

More Related