1 / 52

第三章 栈和队列

第三章 栈和队列. 3.1 栈. 3.2 栈的应用举例. 3.4 队列. 栈和队列是操作受限的线性表,他们的逻辑结构相同。通常称,栈和队列是限定 插入和删除 只能在表的“端点”进行的线性表。. 线性表 栈 队列 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. 栈和队列是两种常用的数据结构.

jerica
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.4 队列

  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 栈(stack) 3.1.1栈的类型定义 3.1.2栈的表示和实现

  4. 栈 ( Stack ) • 定义:是限定仅在表尾进行插入或删除操作的线性表。 • 允许插入和删除的一端 称为栈顶(top),另一端 称为栈底(bottom) • 特点:后进先出 (LIFO) 进栈 出栈 top an . . . . a1 bottom

  5. 栈的类型定义 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) StackLength(S) StackEmpty(s) GetTop(S, &e) ClearStack(&S) Push(&S, e) Pop(&S, &e) StackTravers(S, visit())

  7. 3.1.2 栈的表示和实现 类似于线性表的顺序映象实现,指向表尾的指针可以作为栈顶指针。 • 顺序栈 //----- 栈的顺序存储表示----- #define STACK_INIT_SIZE 100; #define STACKINCREMENT 10; typedef struct { SElemType*base; SElemType*top; int stacksize; }SqStack;

  8. 5 5 5 top top top 4 4 4 3 3 3 top top top top top top 2 2 2 top top 1 1 1 base base base 0 0 0 栈空 栈满 F 实现:一维数组s[M] E D C B B A A 出栈 进栈 空栈 设数组维数为M top=base,栈空,此时出栈,则下溢(underflow) top=M,栈满,此时入栈,则上溢(overflow) 栈底指针base,始终指向栈底位置;栈顶指针top,其初值指向栈底,始终在栈顶元素的下一个位置

  9. Status InitStack (SqStack &S) {// 构造一个空栈S S.base=(SElemType*)malloc(STACK_INIT_SIZE *sizeof(SElemType)); if (!S.base) exit (OVERFLOW); //存储分配失败 S.top = S.base; S.stacksize = STACK_INIT_SIZE; return OK; }

  10. 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; return OK; }

  11. Status Pop (SqStack &S, SElemType &e) { // 若栈不空,则删除S的栈顶元素, // 用e返回其值,并返回OK; // 否则返回ERROR if(S.top==S.base)return ERROR; e = *--S.top; return OK; }

  12. top1 栈1底 栈2底 top2 两栈共享空间 两栈共享空间 0 1 2 …… S-1 a1a2 … ai bj … … b2b1 栈1的底固定在下标为0的一端; 栈2的底固定在下标为StackSize-1的一端。 top1和top2分别为栈1和栈2的栈顶指针; Stack_Size为整个数组空间的大小(图中用S表示);

  13. top1 top1 top2 什么时候栈1为空? 两栈共享空间 两栈共享空间 0 1 2 …… S-1 a1a2 … ai bj … … b2b1 top1= -1

  14. top1 top2 top2 什么时候栈1为空? 什么时候栈2为空? 两栈共享空间 两栈共享空间 0 1 2 …… S-1 a1a2 … ai bj … … b2b1 top1= -1 top2= Stack_Size

  15. top1 top2 什么时候栈1为空? 什么时候栈2为空? 什么时候栈满? 两栈共享空间 两栈共享空间 0 1 2 …… S-1 a1a2 … … ai bj … … b2b1 top1= -1 top2= Stack_Size top2= top1+1

  16. first a1 a2 ai an ∧ 将哪一端作为栈顶? 链栈需要加头结点吗? 栈的链接存储结构及实现 链栈:栈的链接存储结构 如何改造链表实现栈的链接存储? 将链头作为栈顶,方便操作。 链栈不需要附设头结点。

  17. first a1 a2 ai an ∧ top an top an-1 an a1 ∧ an-1 a1 ∧ 栈的链接存储结构及实现 链栈:栈的链接存储结构 栈顶 栈顶 栈底 两种示意图在内存中对应同一种状态 栈底

  18. top top 栈底 p x …... ^ q top 栈底 top …... ^ • 入栈操作 p->next=top ; top=p • 出栈操作 q=top ; top=top->next

  19. 3.2 栈的应用 3.2.1 数制转换 3.2.2 括号匹配的检验 3.2.3 行编辑程序问题 3.2.4 迷宫求解 3.2.5 表达式求值

  20. 十进制N和其他d进制数的转换原理: N=( N div d )*d + N mod d 其中:div为整除运算,mod为求余运算 3.2.1 数制转换

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

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

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

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

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

  26. 3.2.3 行编辑程序问题 不恰当! 如何实现? “每接受一个字符即存入存储器” ? 合理的作法是: 设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区,并假设“#”为退格符,“@”为退行符。

  27. 假设从终端接受了这样两行字符: whli##ilr#e(s#*s) outcha@putchar(*s=#++); 则实际有效的是下列两行: while (*s) putchar(*s++);

  28. while (ch != EOF) { //EOF为全文结束符 while (ch != EOF && ch != '\n') { switch (ch) { case '#' : Pop(S, c); break; case '@': ClearStack(S); break;// 重置S为空栈 default: Push(S, ch); break; } ch = getchar(); // 从终端接收下一个字符 } 将从栈底到栈顶的字符传送至调用过程的 数据区; ClearStack(S); // 重置S为空栈 if (ch != EOF) ch = getchar();

  29. 3.2.4 迷宫求解 通常用的是“穷举求解”的方法

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

  31. 3.2.5 表达式求值 限于二元运算符的表达式定义: Exp = S1 OP S2 操作数 :变量、常量、表达式 运算符 :算术运算符、关系运算符、 逻辑运算符 界限符:括号、结束符

  32. C C C C C C C C OPTR栈 OPND栈 例:3 * ( 7 – 2 ) # - 2 7 - 2 ( 5 * 7 5 3 * 3 15 #

  33. 例:3 * ( 7 – 2 ) OPTR栈 OPND栈 输入 操作 1 # 3 * ( 7 – 2 ) # PUSH( OPND, ‘3’ ) 2 # 3 * ( 7 – 2 ) # PUSH( OPTR, ‘*’ ) 3 # * 3 ( 7 – 2 ) # PUSH( OPTR, ‘(’ ) 4 # * ( 3 7 – 2 ) # PUSH( OPND, ‘7’ ) 5 # * ( 3 7 – 2 ) # PUSH( OPTR, ‘–’ ) 6 # * (– 3 7 2 ) # PHSH( OPND, ‘2’ ) 7 # * (– 3 7 2 ) # operate( ‘7’,’-’,’2’ ) 8 # * ( 3 5 ) # POP( OPTR ) 9 # * 3 5 # operate( ‘3’, ‘*’, ‘5’ ) 10 # 15 # return GetTop( OPND )

  34. OperandType EvaluateExpression() { // 设OPTR和OPND分别为运算符栈和运算数栈,OP为运算符集合。 InitStack (OPTR); Push (OPTR, '#'); initStack (OPND); c = getchar(); while (c!= '#' || GetTop(OPTR)!= '#') { if (!In(c, OP)) { Push((OPND, c); c = getchar(); } // 不是运算符则进栈 else } // while return GetTop(OPND); } // EvaluateExpression … …

  35. switch ( precede(GetTop(OPTR), c) { case '<': // 栈顶元素优先权低 Push(OPTR, c); c = getchar(); break; case '=': // 脱括号并接收下一字符 Pop(OPTR, x); c = getchar(); break; case '> ': // 退栈并将运算结果入栈 Pop(OPTR, theta); Pop(OPND, b); Pop(OPND, a); Push(OPND, Operate(a, theta, b)); break; } // switch

  36. 3.4 队列 3.4.1队列的类型定义 3.4.2链队列 3.4.3循环队列

  37. 出队 入队 a1 a2 a3…………………….an 队列Q=(a1,a2,……,an) front rear 3.4.1队列的类型定义 • 队列是限定只能在表的一端进行插入,在表的另一端进行删除的线性表。 • 队尾(rear)——允许插入的一端 • 队头(front)——允许删除的一端 • 队列特点:先进先出(FIFO)

  38. 队列的类型定义 ADT Queue { 数据对象: D={ai | ai∈ElemSet, i=1,2,...,n, n≥0} 数据关系: R1={ <a i-1,ai > | ai-1, ai ∈D, i=2,...,n} 约定其中a1端为队列头, an端为队列尾 基本操作: }ADT Queue

  39. 队列的基本操作: InitQueue(&Q) DestroyQueue(&Q) QueueEmpty(Q) QueueLength(Q) ClearQueue(&Q) GetHead(Q, &e) EnQueue(&Q, e) DeQueue(&Q, &e) QueueTravers(Q, visit())

  40. 3.4.2 链队列-队列的链式表示和实现 typedef struct QNode{// 结点类型 QElemType data ; struct QNode *next ; }QNode, *QueuePtr; typedef struct{ //链队列类型 QueuePtr front ; //队头指针 QueuePtr rear ; //队尾指针 } LinkQueue;

  41. Q.front Q.rear a1 an ∧ Q.front Q.rear 空队列 ∧

  42. ^ ^ x出队 x y ^ rear front 空队 y出队 x入队 x ^ rear rear front front front rear y入队 x y ^ rear front Q.rear -> next=p Q.rear=p p= Q.front -> next Q.front -> next = p -> next

  43. Status InitQueue (LinkQueue &Q) { // 构造一个空队列Q Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode)); if (!Q.front) exit (OVERFLOW); //存储分配失败 Q.front->next = NULL; return OK; }

  44. Status EnQueue (LinkQueue &Q, QElemType e) { // 插入元素e为Q的新的队尾元素 p = (QueuePtr) malloc (sizeof (QNode)); if (!p) exit (OVERFLOW); //存储分配失败 p->data = e; p->next = NULL; Q.rear->next = p; Q.rear = p; return OK; }

  45. Status DeQueue (LinkQueue &Q, QElemType &e) { // 若队列不空,则删除Q的队头元素, //用 e 返回其值,并返回OK;否则返回ERROR 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; }

  46. 3.4.3循环队列-队列的顺序表示和实现 #define MAXQSIZE 100 //最大队列长度 typedef struct { QElemType *base; // 动态分配存储空间 int front; // 头指针,若队列不空, //指向队列头元素 int rear; // 尾指针,若队列不空,指向 // 队列尾元素 的下一个位置 } SqQueue;

  47. rear J6 5 J5 4 rear 5 5 5 J4 3 4 4 4 rear rear rear rear J3 2 front 3 3 3 front front front front J2 1 2 2 2 rear=0 front=0 J1 front 0 1 1 1 J1,J2,J3出队 J4,J5,J6入队 空队列 J1,J1,J3入队 0 0 0 • 实现:用一维数组实现sq[M] J3 J2 J1 • 存在问题: • 当front=0,rear=M时再有元素入队发生溢出——真溢出 • 当front≠0,rear=M时再有元素入队发生溢出——假溢出

  48. 解决方案 • 队首固定,每次出队剩余元素向下移动——浪费时间 • 循环队列 • 基本思想:把队列设想成环形,让sq[0]接在sq[M-1] 之后,若rear+1==M,则令rear=0; • 实现:利用“模”运算 • 入队: base[rear]=x; rear=(rear+1)%M; • 出队: x=base[front]; front=(front+1)%M; • 队满、队空判定条件

  49. front rear 5 4 0 3 1 2 rear J6 J5 5 4 0 J4,J5,J6出队 J4 3 1 2 front J7,J8,J9入队 J6 初始状态 J7 J5 5 4 0 3 1 J8 J4 2 J9 front rear 队空:front==rear 队满:front==rear 解决方案: 1.另外设一个标志位以区别队空、队满 2.少用一个元素空间: 队空:front==rear 队满:(rear+1)%M==front 3.使用一个计数器记录队列中元素的总数

  50. Status InitQueue (SqQueue &Q) { // 构造一个空队列Q Q.base = (QElemType *) malloc (MAXQSIZE *sizeof (QElemType)); if (!Q.base) exit (OVERFLOW); // 存储分配失败 Q.front = Q.rear = 0; return OK; }

More Related