51
第3第 第第第第 3.1 3.2 第第第第 3.3 第第

第 3 章 栈与队列

  • Upload
    milt

  • View
    109

  • Download
    0

Embed Size (px)

DESCRIPTION

第 3 章 栈与队列. 3.1 栈 3.2 栈的应用 3.3 队列. 第 3 章 栈与队列. 栈和队列都是线性表的特殊形式,普遍存在于日常生活和计算机领域。 在日常生活中,可以看到许多栈和队列的应用。抽屉中,要想拿到先放进去的东西,必须把后放进去的东西先拿出来,最方便使用的是抽屉顶上的东西。米桶中的米,先倒进去的最后才能用到,最后放进去的先被用到,这些是栈的应用。 学校食堂在学生中午下课后的高峰用饭时间,各个卖饭菜的窗口都会排队,否则就会拥挤,发生不必要的. 第 3 章 栈与队列. - PowerPoint PPT Presentation

Citation preview

Page 1: 第 3 章  栈与队列

第 3 章 栈与队列3.1 栈3.2 栈的应用3.3 队列

Page 2: 第 3 章  栈与队列

第 3 章 栈与队列栈和队列都是线性表的特殊形式,普遍存在于日常

生活和计算机领域。在日常生活中,可以看到许多栈和队列的应用。抽

屉中,要想拿到先放进去的东西,必须把后放进去的东西先拿出来,最方便使用的是抽屉顶上的东西。米桶中的米,先倒进去的最后才能用到,最后放进去的先被用到,这些是栈的应用。

学校食堂在学生中午下课后的高峰用饭时间,各个卖饭菜的窗口都会排队,否则就会拥挤,发生不必要的

Page 3: 第 3 章  栈与队列

第 3 章 栈与队列纠纷;大型超市等待付款的顾客;银行各柜台前等候存取钱的顾客都会形成排队等待的现象,这些就是队列。 在计算机领域,栈和队列的使用就更为广泛,如递归调用、中断、子程序调用中栈的使用;网络中在同一链路上传输的数据报文形成一个队列,共享的打印机中所有申请打印的作业形成的队列,是队列的应用。

Page 4: 第 3 章  栈与队列

3.1 栈3.1.1 栈的定义及其运算

1. 定义:在表尾一端进行删除和插入操作的线性表叫栈 (Stack) 。允许插入和删除操作的一端称为栈顶 (top) ,另一端称

为栈底 (bottom) 。处于栈顶位置的数据元素称为栈顶元素。不含任何数据元素的栈称为空栈。

2. 基本运算栈的操作不能在栈的中间进行,其插入操作和删除操作

都在栈的一端进行,进行插入和删除操作的一端叫栈顶。设栈为 S ,下面是栈的基本运算。

Page 5: 第 3 章  栈与队列

3.1.1 栈的定义及其运算( 1 )初始化 InitStack(S) :创建一个空栈 S (只有

架构,没有任何数据元素)。( 2 )入栈 push(S,x) :将元素插入到栈中,使 x 成

为栈 S 的栈顶元素。( 3 )出栈 pop(S) :取栈顶元素,并从栈中删除栈顶

元素。( 4 )取栈顶元素 GetTop(S) :取栈顶元素。( 5 )判空 Empty(S) :判断栈是否为空。

Page 6: 第 3 章  栈与队列

3.1.2 栈的顺序存储结构1. 定义顺序栈结构描述如下:定义 3.1#define MAXSIZE 100

typedef int DataType;

typedef struct stack

{ DataType data[MAXSIZE];

int top;

} sqstack;

Page 7: 第 3 章  栈与队列

3.1.2 栈的顺序存储结构

2. 栈的操作

Page 8: 第 3 章  栈与队列

3. 顺序栈的基本运算( 1 )初始化 InitStack(sqstack *S)

{ S->top=-1; }

( 2 )入栈 push(sqstack *S, DataType x)

{ if (S->top>MAXSIZE-1)

printf(“error!”);

else{

S->top++;

S->data[S->top]=x;}

}

3.1.2 栈的顺序存储结构

Page 9: 第 3 章  栈与队列

3. 顺序栈的基本运算( 3 )出栈 DataType pop(sqstack *S)

{

if (S->top==-1)

printf(“Underflow!”);

else{

x=S->data[S->top];

S->top--;

}

return x;

}

3.1.2 栈的顺序存储结构

Page 10: 第 3 章  栈与队列

3. 顺序栈的基本运算( 4 )取栈顶元素 DataType GetTop(sqstack *S)

{ if (S->top==-1) printf(“Underflow!”);

else return S->data[S->top];

}

5 )判空 int Empty(sqstack *S)

{ if (S->top==-1) return 1;

else return 0;

}

3.1.2 栈的顺序存储结构

Page 11: 第 3 章  栈与队列

3.1.3 栈的链表存储结构 1. 定义定义 3.2typedef int DataType;

typedef struct stack

{ DataType data;

struct stack *next;

} lstack;

lstack *top;

上面的逻辑定义中定义了一个 top 指针,用来指示链栈的栈顶。

Page 12: 第 3 章  栈与队列

2. 链栈的操作

3.1.3 栈的链表存储结构

Page 13: 第 3 章  栈与队列

3. 链栈的基本运算( 1 )初始化 算法 3.6InitStack(lstack *top)

{ top->next=NULL; }

( 2 )入栈 算法 3.7push(lstack *top, DataType x)

{ p=(lstack *)malloc(sizeof(lstack));

p->data=x;

p->next=top->next;

top->next=p;

}

3.1.3 栈的链表存储结构

Page 14: 第 3 章  栈与队列

( 3 )出栈 算法 3.8DataType pop(lstack *top)

{ if (top->next==NULL)

printf(“Underflow!”);

else{

x=(top->next)->data;

top->next=top->next->next;}

return x;

}

3.1.3 栈的链表存储结构

Page 15: 第 3 章  栈与队列

( 4 )取栈顶元素 算法 3.9DataType GetTop(lstack *top)

{ if (top->next==NULL)

printf(“Underflow!”);

else return

(top->next)->data;

}

( 5 )判空 算法 3.10int Empty(lstack *top)

{ if (top->next==NULL) return 1;

else return 0;}

3.1.3 栈的链表存储结构

Page 16: 第 3 章  栈与队列

3.2 栈的应用3.2.1 数制转换

数制转换是一种常见的计算。在十进制转换成二进制时,根据除 2 取余法,先得到的余数是结果数的低位数,最后得到的是结果数的最高位,如果直接打印,会使结果成为倒序数字。为了得到正确的结果,借助栈,可以把每次计算的余数压入栈,最先得到的余数在栈底,最后得到的余数在栈顶,然后从栈中分别弹出各个余数并打印,可以得到正确的顺序。

Page 17: 第 3 章  栈与队列

3.2.1 数制转换十进制数 N 与其他数制 r 的转换,可以用栈来实现。算法基于下列

原理: N=int(N÷r)×r + N mod r

如,十进制数 1024 转换成八进制数的过程描述在图 3.3 中。设 s 为一个栈,进行数制转换的算法描述为:①初始化栈②输入十进制数 x 和其他进制数 y

③ 把 x 除以 y 的余数压入栈,商赋给 x ;如果 x 为 0 ,结束计算,否则重复③;

④弹出栈顶元素并打印,如果栈空,结束,否则重复④。

Page 18: 第 3 章  栈与队列

算法 3.11 十进制数与其他数制的转换Digit_conversion()

{InitStack(s);

scanf(“%d,%d”,&x,&y);

while(x)

{push(s,x%y);x=x/y; }

while(!Empty(s))

{k=pop(s); printf(“%d”,k); }

}

3.2.1 数制转换

Page 19: 第 3 章  栈与队列

3.2.2 算术表达式转换1. 算术表达式求值2. 表达式转换

Page 20: 第 3 章  栈与队列

3.2.3 子程序调用在子程序调用中,当子程序完成后,应该正确地返回主程

序调用该子程序的地方。在计算机技术中使用栈来完成此工作。具体做法是在调用子程序时,用栈把主程序调用处的所有参数保存起来,当子程序调用完成时,从栈中弹出调用主程序断点处的所有参数,使主程序能够正确地从断点处继续执行后续程序。

Page 21: 第 3 章  栈与队列

3.2 栈的应用

Page 22: 第 3 章  栈与队列

3.2.4 递归调用

递归调用是通过自身调用解决问题的一种办法。递归现象是自然界存在的一种奇妙的现象,能够用递归调用解决的问题,必须满足下列条件:

① 问题要有出口,否则递归调用将成为死循环;② 问题是有限规模的。

Page 23: 第 3 章  栈与队列

3.2.4 递归调用 1. Hanoi塔Hanoi塔问题这样描述:有三个塔座 X 、 Y 、 Z ,在 X 上

有 n 个盘,从顶倒底依次编号为 1 、 2 、 3 、… n ,每个的盘的半径依次增加如图 3.6(a) 所示。现在要求把 X座上的 n 个盘逐个移动倒 Z座,依然是原来在 X座上的形态,如图 3.6(b) 所示。

盘移动的规则为:A 、每次只能移动一个盘。B 、盘可以插在 X 、 Y 、 Z 中的任何一个上。C 、任何时刻都不能将大盘压在小盘上。

Page 24: 第 3 章  栈与队列

3.2.4 递归调用

Page 25: 第 3 章  栈与队列

3.2.4 递归调用

Page 26: 第 3 章  栈与队列

算法 3.12 Hanoi塔递归调用算法Hanoi(int n,char x,char y,char z)

{ if (n==1) move (1,x,z);

else

{ Hanoi(n-1,x,z,y);

move(n,x,z);

Hanoi(n-1,y,z,x);

}

}

其中, move() 的算法可以设计为:move(int m , char u , char v)

{ printf(“disk %d from %c,to,%c”,m,u,v); }

3.2.4 递归调用

Page 27: 第 3 章  栈与队列

2. n!

算法 3.13 阶乘的递归调用fact( int n )

{

if ( n==0 ) return 1;

else return n*fact(n-1);

}

3.2.4 递归调用

Page 28: 第 3 章  栈与队列

3. Fibonacci 数列Fibonacci 数列 1202年由 Fibonacci 发现的,该数列是:0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55……

其中,每一个数都是其后续两个数的和, Fibonacci 数列有下列关系:

1 当 n=0,n=1

Fn=

Fn-1+Fn-2 当 n>1

当 n=0,1 时,可以结束 Fibonacci 数列的计算,这就是出口

3.2.4 递归调用

Page 29: 第 3 章  栈与队列

算法 3.14 Fibonacci 数列的递归调用fib(int n)

{ if (n<=1) s=n;

else s=fib(n-1)+fib(n-2);

return s;

}

设此算法的时间复杂度为 T(n) , f(n) 是算法执行的时间。可以得到:

3.2.4 递归调用

Page 30: 第 3 章  栈与队列

3.2.5 序列进出栈的排列问题对于初始输入序列 1,2,3,…,n ,利用栈得到输出序列

p1p2…pi…pn ,则在 p1p2…pi…pn 中,如果有 i<j<k ,则对于一个输入序列 pi<pj<pk ,即:

…pi ,… pj ,… pk (pi<pj<pk)

不存在这样的输出序列:…pk ,… pi ,… pj

因为 pk 后进先出,满足栈的特点,而 pi 在 pj 的前面进入,却在 pk 的前面出来,这是不可能的(因为不满足后进先出的原则)。

Page 31: 第 3 章  栈与队列

例 3.6 已知输入序列为 abcde ,请给出部分不可能的输出序列。解:根据上面的讨论,不可能的输出序列有: cabde 、 adbce 、

abecd 、 aedbc ,因为对于 cabde , c 在 ab 后进入,先出来是正确的,但 a 在 b 先进,却在 b 前先出来,这是不可能的。对于 adbce ,a 先进入就出来, d 后进先出,但 b 在 c 先进,却在 c 前先出来,这是不可能的。类似地可以分析 abecd 、 aedbc 。

下 面 的 输 出 序 列 都 是 可 行 的 , 如 :abcde 、 abced 、 abdce 、 abdec 、 edcba 。

5 个元素的输入序列有 42 种排列,此处给出了部分结果。

3.2.5 序列进出栈的排列问题

Page 32: 第 3 章  栈与队列

3.3 队列 3.3.1 队列的定义及运算1. 定义定义:在表的一端进行删除操作而在另一端进行插入操

作的线性表叫队列 (Queue) 。允许删除操作的一端称为队头,允许插入操作的一端称

为队尾。新插入的元素只能被加到队尾,被删除的元素只能是队头元素。

为队头和队尾分别设置指针,队头指针指向队头元素的前一个单元,队尾指针指向队尾元素,当队头和队尾指针相等时,队列为空。

Page 33: 第 3 章  栈与队列

2. 基本运算设队列为 Q ,下面是队列的基本运算。( 1 )初始化 SetNULL(Q) :创建一个空队列 Q 。( 2 )入队列 AddQ(Q,x) :入队操作,将元素插入到队

列中,使 x 成为队列 Q 的元素。( 3 )出队列 DelQ(Q) :出队操作,取队头元素,并从

队列中删除队头元素。( 4 )取队头元素 GetFront(Q) :取队列顶元素,该元

素不出队列。( 5 )判空 Empty(Q) :判断队列是否为空。

3.3.1 队列的定义及运算

Page 34: 第 3 章  栈与队列

3.3.2 队列的顺序存储结构1. 定义 顺序存储结构:用程序语言的数组来描述队列的顺序结构,称为顺序队列。 定义 3.3 #define MAXSIZE 20

typedef int DataType;

typedef struct queue

{DataType data[MAXSIZE];

int front,rear;

} squeue;

Page 35: 第 3 章  栈与队列

2. 队列的操作

3.3.2 队列的顺序存储结构

Page 36: 第 3 章  栈与队列

3. 循环队列 在上图 3.10(e) 中,因为 rear 和 front已经达到队列尾部, rear==MAXSIZE-1 ,要再插入元素,队列已满,无法插入,因而产生了溢出,但实际上队列为空。这种现象叫假“溢出”。解决这个问题的办法是把队列首尾连接起来,构成循环队列。

3.3.2 队列的顺序存储结构

Page 37: 第 3 章  栈与队列

3.3.2 队列的顺序存储结构

Page 38: 第 3 章  栈与队列

4. 基本运算( 1 )初始化 算法 3.15SetNULL (Squeue *Q)

{Q->front=0; Q->rear=0;}

( 2 )入队列 算法 3.16AddQ(Squeue *Q,DataType x)

{ if (Q->rear+1)%MAXQIZE==Q->front) printf(“Queue is full!”);

else{Q->rear=(Q->rear+1) % MAXSIZE; /* 队尾指针加 1*/

Q->data[Q->rear]=x;} /* 元素入队 */

}

3.3.2 队列的顺序存储结构

Page 39: 第 3 章  栈与队列

( 3 )出队列 算法 3.17DataType DelQ(Squeue *Q)

{ if (Q->front==Q->rear) printf(“Queue is empty!”);

else{Q->front=(Q->front+1) % MAXSIZE; /* 队头指针加 1*/

return Q->data[Q->front];} /* 队头元素出队 */

}

3.3.2 队列的顺序存储结构

Page 40: 第 3 章  栈与队列

( 4 )取队头元素 算法 3.18DataType GetFront(Squeue *Q)

{ if (Q->front==Q->rear) printf(“Queue is empty!”);

else{Return Q->data[(Q->front+1) % MAXSIZE];} /*返回队头元素 */

}

( 5 )判空 算法 3.19int Empty(Squeue *Q)

{ if (Q->front==Q->rear) return 1;

else return 0;

}

3.3.2 队列的顺序存储结构

Page 41: 第 3 章  栈与队列

例 3.6 编写一个算法,将队列 L 的内容反序排列。解:设 Q 是一个顺序队列,要把其中的内容反转排列。

设置一个辅助栈 S ,先把 L 中的内容压入 S ,然后弹出栈中内容的办法来倒换。

算法如下:reverse_queue(squeue *Q)

{ DataType x;

sqstack S;

InitStack(S);

3.3.2 队列的顺序存储结构

Page 42: 第 3 章  栈与队列

while(!Empty(Q))

{x=DelQ(Q);

push(S,x);

}

SetNULL(Squeue *Q)

while(!Empty(S))

{x=pop(S);

AddQ(S,x);

}

}

3.3.2 队列的顺序存储结构

Page 43: 第 3 章  栈与队列

3.3.3 队列的链表存储结构 1. 定义队列的链式存储结构简称链队列。定义 3.4typedef int DataType;

typedef struct NodeType

{ DataType data;

struct NodeType *next;

} lqnode;

typedef struct {

lqnode *front,*rear;

} Lqueue ;

Page 44: 第 3 章  栈与队列

1. 定义

3.3.3 队列的链表存储结构

Page 45: 第 3 章  栈与队列

2. 链队列的基本运算设链队列为 Q , 算法 3.20~3.24 的时间复杂度均为 O(1) 。( 1 )初始化算法 3.20 初始化算法SetNULL (Lqueue *Q)

{ lqnode *p;

p=(lqnode *)malloc(sizeof(lqnode));

p->next=NULL;

Q->front=p Q->rear=p;

}

3.3.3 队列的链表存储结构

Page 46: 第 3 章  栈与队列

( 2 )入队列 算法 3.21 入队算法AddQ(Lqueue *Q, DataType x)

{ lqnode *p;

p=(lqnode *)malloc(sizeof(lqnode));

p->data=x;

p->next=NULL;

Q->rear->next=p; /*头结点指针指向 P*/

Q->rear=p; /*Q 的尾指针指向 P*/

}

3.3.3 队列的链表存储结构

Page 47: 第 3 章  栈与队列

( 3 )出队列 算法 3.22DataType DelQ(Lqueue *Q)

{ lqnode *p;

if (Q->front==Q->rear)

printf(“Queue is empty!”);

else{

p=Q->front->next; /* 指针 p 指向第一个元素 */

x=p->data ;Q->front->next=p->next; /* 队头指针指向第二个元素 */

free(p);} /*释放第一个元素 */

return x;

}

3.3.3 队列的链表存储结构

Page 48: 第 3 章  栈与队列

( 4 )取队头元素 算法 3.23 取队头元素的算法DataType GetFront(Lqueue *Q)

{ lqnode *p;

if (Q->front==Q->rear)

printf(“Queue is empty!”);

else{

p=Q->front->next;

return p->data}

}

3.3.3 队列的链表存储结构

Page 49: 第 3 章  栈与队列

( 5 )判空 算法 3.24 判空算法int Empty(Lqueue *Q)

{ if (Q->front==Q->rear) return 1;

else return 0;

}

3.3.3 队列的链表存储结构

Page 50: 第 3 章  栈与队列

3.3.4 队列的应用 在计算机领域,队列有十分广泛的应用,网络中在同一链

路上传输的数据报文形成一个队列,在网络环境下各个客户申请打印的作业在打印服务器中形成的队列,在操作系统中,存在临界资源的共享和互斥的问题等,都使用了队列。

1. 生产者与消费者问题

Page 51: 第 3 章  栈与队列

本章小结 栈是一种特殊的线性表,采用后进先出或先进后出的结构。在栈中插入或删除元素只能在其一端进行,能够进行插入和删除操作的一端叫栈顶,另一端叫栈底。栈的存储结构与线性表类似,分为顺序结构和链表结构。栈的运算主要有初始化,入栈,出栈,取栈顶元素和判空。 队列是一种特殊的线性表,采用先进先出的结构。在队列中插入和删除元素只能在队列的不同端头进行,能够进行插入操作的一端叫队尾,能进行删除操作的一端叫队头。栈队列的存储结构与线性表类似,分为顺序结构和链表结构。队列的运算主要有初始化,入队,出队,取队头元素和判空。