Upload
barney
View
193
Download
5
Embed Size (px)
DESCRIPTION
第 3 章 栈和队列. 3.1 栈 3.2 队列 3.3 栈和队列的应用. 3.1 栈. 3.1.1 栈的抽象数据类型定义 3.1.2 栈的表示和算法实现. 3.1.1 栈的定义. 出栈. 入栈. 栈顶 top. an. a2. 栈底 bottom. a1. 栈的示意图. 1 .栈的定义 - PowerPoint PPT Presentation
Citation preview
第 3 章 栈和队列
3.1 栈3.2 队列3.3 栈和队列的应用
3.1.1 栈的抽象数据类型定义
3.1.2 栈的表示和算法实现
3.1 栈
1 .栈的定义栈( stack )是一种只允许在一端进行插入和删除的线性表,它是一种操作受限的线性表。在表中只允许进行插入和删除的一端称为栈顶( top ),另一端称为栈底 (bottom) 。栈的插入操作通常称为入栈或进栈 (push) ,而栈的删除操作则称为出栈或退栈 (pop) 。当栈中无数据元素时,称为空栈。
a1a2
an
入栈 出栈
栈顶 top
栈底 bottom
栈的示意图
...
栈是按照后进先出( LIFO )的原则组织数据的,因此,栈也被称为“后进先出”的线性表。
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 为栈底 基本操作: InitStack(S) 操作结果:构造一个空栈 S 。 DestroyStack(&S)初始条件:栈 S 已存在操作结果:栈 S 被销毁 ClearStack(&S)初始条件:栈 S 已存在操作结果:将 S 清为空栈
2 .栈的抽象数据类型定义
StackEmpty(S) 初始条件:栈 S 已存在操作结果:若栈 S 为空栈,则返回 TRUE ;否则,返回 FALSE 。StackLength(S) 返回 S 的元素个数,即栈的长度。GetTop(S , &e) 初始条件:栈 S 已存在且非空操作结果: 用 e 返回 S 的栈顶元素Push(&S , e)初始条件:栈 S 已存在操作结果:插入元素 e 为新的栈顶元素
Pop(&S , &e)
初始条件:栈 S 已存在且非空操作结果: 删除 S 的栈顶元素,并用 e 返回其值 StackTraverse(S,visit())
初始条件:栈 S 已存在且非空操作结果: 从栈底到栈顶依次对 S 的每个数据元素调用函数 visit() ,一旦 visit() 失败,则操作失效。}ADT Stack
1. 顺序栈
2. 链栈
3.1.2 栈的表示和算法实现
顺序栈是用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针 top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。通常以 top=-1 表示空栈。
1. 顺序栈
顺序栈的存储结构定义
#define Stack_Size 50 typedef struct Stack{ElemType elem[Stack_Size]; /* 用来存放栈中元素的一维数组 */
int top; /* 用来存放栈顶元素的下标 */
}*SqStack;SqStack S;
S->top=-1S->top=0
S->top=4
CD
ES->top=2
AB
( a )空栈;( b )插入元素 A 后;( c )插入元素 B 、 C 、 D 、E 后;( d )删除元素 E 、 D 后
( a)
A
( c)
( d )
下图展示了顺序栈入栈、出栈中数据元素与栈顶指针的变化。
C
( b)
B A
1.2 、顺序栈基本运算的算法:
( 1)初始化栈【算法 3.1 栈的初始化】Status InitStack(SqStack S){/* 创建一个空栈由指针 S指出 */ if ((S=(SqStack*)malloc(sizeof(SqStack)))= =NULL) exit(OVERFLOW); S->top= -1;return OK;}
( 2 )入栈操作
Status Push(SqStack &S, Elemtype e)
【算法 3.2 栈的入栈操作】
{ /* 将元素 e 插入到栈 S 中,作为 S 的新栈顶 */
if (S->top>= Stack_Size -1) return ERROR;
else { S->top++;
S->elem[S->top]=e;
return OK;}
topelem
xx 0
1min
Push(S,’you’)
2you
( 3 )出栈操作
Status Pop(SqStack &S,ElemType &e)
{/* 若栈 s 不为空,则删除栈顶元素 */
{
if(S->top<0) return ERROR; /* 栈空 */
else
{ e=S->elem[S->top];
S->top- -;
return OK; }
}
topelem
xx
you
0
min
1
2e=‘min’
( 4)取栈顶元素操作
Status GetTop(SqStack S,ElemType &e)
{/* 若栈 s不为空,则返回栈顶元素 */
if(S->top==-1) return ERROR; /* 栈空 */
else {e= S->elem[S->top];
return OK;}
}
取栈顶元素与出栈不同之处在于出栈操作改变栈顶指针top 的位置,而取栈顶元素操作不改变栈的栈顶指针。
( 5)判栈空操作Status StackEmpty(SqStack S){/* 栈 S为空时,返回为 TRUE ;非空时,返回为 FALSE*/ if(S->top= =-1) return TRUE; else return FALSE ;}
1.3 双向栈在一维数组中的实现栈的共享中最常见的是两栈的共享。假设两个栈共享一维数组 S->elem[Stack_Size] ,则可以利用栈的“栈底位置不变,栈顶位置动态变化”的特性,两个栈底分别为 0 和 Stack_Size-1 ,而它们的栈顶都往中间方向延伸。因此,只要整个数组 S->elem[Stack_Size] 未被占满,无论哪个栈的入栈都不会发生上溢。
自由区
lefttop rightto
0Stack_Size -1
图 3-3 两个栈共享邻接空间
2 、链栈
栈也可以采用链式存储结构表示,这种结构的栈简称为链栈。在一个链栈中,栈底就是链表的最后一个结点,而栈顶总是链表的第一个结点。因此,新入栈的元素即为链表新的第一个结点,只要系统还有存储空间,就不会有栈满的情况发生。一个链栈 (不带头结点 )可由栈顶指针 top 唯一确定,当 top 为NULL 时,是一个空栈。2.1 、链栈的 C语言定义为:typedef struct StackNode {ElemType data;Struct StackNode *next;}StackNode,*LinkStack;
top
top
top
链栈的存储结构图( a)含有两个元素 A、 B的栈;( b)插入元素 C后的栈;
( c)删除元素 C、 B后的栈
( a ) ( b ) ( c )^^
^A
B
A A
B
C p
^…...…...top data next
栈底
– 入栈过程
– 出栈过程
^…...栈底
toptop
xp
top
^…...栈底
top
q
1 )入栈操作Status Push (LinkStack &top, ElemType e){ /* 将元素 e 压入链栈 top 中 */ p=(LinkStack)malloc(sizeof(StackNode)); if(!p) exit(OVERFLOW); p->data =e; p->next=top; top=p; }
2.2 、链栈入栈、出栈的算法实现
top
pe
2 )出栈操作Status Pop(LinkStack &top,ElemType &e){ /* 从链栈 top 中删除栈顶元素 */ if (top= =NULL) return ERROR; /* 空栈 */ else{ p=top; top=top->next; e=p->data; free(p); return OK;}
top
b
c
d ^
a
p
e=‘a’
练 习四个元素入栈的顺序是 A,B,C,D. 不可能的出栈顺序是:(a)ABCD(b)DCBA(c)BADC(d)ADCB(e)DACB
3.2 队 列
3.2.1 队列的抽象数据类型定义3.2.2 链队列3.2.3 顺序队列 (循环队列 )
在日常生活中队列很常见,如,我们经常排队购物或购票 . 队列在计算机系统中的应用也非常广泛。例如:操作系统中的作业排队。
3.2.1 队列的抽象数据类型定义
定义:允许在线性表的一端插入 ,另一端进行删除操作的线性表称为队列 .插入的一端为队尾 ,删除的一端为队头。※队尾 (rear)——允许插入的一端※队头 (front)——允许删除的一端
队列特点:先进先出 (FIFO)a1 a2 a3…………………….an 入队出队
front rear
队列 Q=(a1,a2,……,an)
1 、队列的定义及特点
2. 队列的抽象数据类型定义ADT Stack{ 数据对象 : D={ai|ai∈ElemSet,i=1,2,…,n,n>=0} 数据关系: R1={<ai-1,ai>|ai-1,ai ∈D,i=2,…,n} 约定 a1 为队列头, an 为队列尾 基本操作: InitQueue(&Q) 操作结果:构造一个空队列 Q 。 DestroyQueue(&Q)初始条件:队列 Q 已存在操作结果:队列 Q 被销毁 ClearQueue(&Q)初始条件:队列 Q 已存在操作结果:将 Q 清为空队列
QueueEmpty(Q) 初始条件:队列 Q 已存在操作结果:若队列 Q 为空队列,则返回 TRUE ;否则,返回
FALSE 。QueueLength(Q) 返回 S 的元素个数,即栈的长度。GetHead(Q , &e) 初始条件:队列 Q 已存在且非空操作结果: 用 e 返回队头元素EnQueue(&Q , e)初始条件:队列 Q 已存在操作结果:插入元素 e 为 Q 的新的队尾元素DeQueue(&Q , &e)初始条件:队列 Q 已存在且非空操作结果: 删除 Q 的队头元素,并用 e 返回其值
QueueTraverse(Q,visit())
初始条件:队列 Q 已存在且非空操作结果: 从队头到队尾依次对 Q 的每个数据
元素调用函数 visit() ,一旦 visit() 失败,则操作失效。
}ADT Queue
3. 双端队列
是限定插入和删除操作是在表的两端进行的线性表 .这两端分别称为端点 1和端点 2
a1 a2 a3…………………….an 插入删除
端 1 端 2
队列 Q=(a1,a2,……,an)
插入 删除
1 、存储结构定义:typedef struct QNode{ QElemType data; struct QNode * next; } QNode,*QueuePtr
typedef struct {QueuePtr front;QueuePtr rear; }*LinkQueue ;
Q->front
Q->rear
x y z
3.2.2 链队列
LinkQueue Q;
2 、链队列的算法:算法 1 :判队列是否为空:
Status QueueEmpty( LinkQueue Q)
{ if Q->front = = Q->rear
return TRUE;
else
return FALSE;
}
Q->rear
Q->front
算法 2 :入队列:
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; }
Q->front
Q->rear
p
m n
e
算法 3 :出队列
Status DeQueue(LinkQueue &Q,QElemType &e)
{ if Q->front = = Q->rear
reture ERROR;// 若队列 Q 为空队列
p=Q->front->next; e=p->data;
Q->front->next = p->next
if(Q.rear ==p) Q.rear=Q.front; // 若 Q 只有一个结点
free(p); return OK; }
Q.front
Q.rear
p
x y z
e=‘x’
g
Q.rear
Q.frontQ.rear= =Q.front= = 0 初始化队列为空
Q.rear = 4; Q.front = 0
Q.front
Q.rear
s
i
k
5 4 3 2 1 0
1. 顺序队列的存储结构定义:#define MAXSIZE 100// 最大队列长度typedef struct{QElemType *base; int front,rear ; }SqQueue;
3.2.3 顺序队列
SqQueue Q;
5 4 3 2 1 0
Q.front=0Q.rear=0
1
2
3
4
5
0队空
1
2
3
4
5
0Q.front
J1,J1,J3 入队
J1
J2
J3
Q.rear
Q.rear
1
2
3
4
5
0J4,J5,J6 入队
J4
J5
J6
Q.front
设两个指针 Q.front,Q.rear, 约定:rear 指示队尾元素下一位置;front 指示队头元素初值 Q.front=Q.rear=0
空队列条件: Q.front==Q.rear入队列: Q.data[Q.rear++]=x;出队列: x=Q.data[Q.front++];
Q.rear
Q.rear
Q.front
Q.rear
1
2
3
4
5
0J1,J2,J3 出队
J1
J2
J3
Q.front
Q.front
Q.front
队列的顺序存储结构
Q.rear
Q.rear
Q.frontQ.rear
5 4 3 2 1 0
j
l
wnQ.rear
Q.front
2. 顺序队列存储中的缺点:
Q.rear == MAXSIZE 队列满
Q.rear = = Q.front为空,但不能使用
5 4 3 2 1 0
解决缺点的方法:
1 、采用平移元素的方法:
xy
z
front
rear
2 、将整个队列作为循环队列处理:
x
y
z
Q.rear
Q.front
z
yxQ.front
Q.rear
m
4 3 2 1 0
(1)入队列:
Q.rear=(Q.rear +1)%MAXSIZE
Q.rear
Q.front
z
y
x
(2) 出队列 :
4 3 2 1 0
Q.rear
Q.frontm
Q.front=(Q.front + 1)% MAXSIZE
(3) 在 Q.rear 和 Q.front 可以循环指示时,如何判断队列的空和满:令: Q.rear = = Q.front 为空
令: (Q.rear + 1)%MAXSIZE = = Q.front 为满
Q.rear
Q.front
队列为满
c
b
vv
Q.front
Q.rear c
dw
q
Q.rear
Q.front
队列为空
3 、循环队列的重要算法:
算法 1 :构造一个空队列 Q
Status InitQueue (SqQueue &Q)
{ Q.base=(QElemType *)malloc(MAXSIZE*sizeof(QElemType));
if (!Q.base) exit(OVERFLOW);
Q.front = Q.rear=0;
return OK;
}
3 、循环队列的重要算法:
算法 2 :求队列 Q 的长度
int QueueLength (SqQueue Q)
{return ((Q.rear-Q.front+MAXSIZE)%MAXSIZE);
}
算法 3 、循环队列的入队列:
Status EnQueque(SqQueque &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;
}
Q.rear
Q.frontx
y
EnQueue(Q,’m’)
m
算法 4 、循环队列出队列:void DeQueue (SqQueque &Q, QElemtype &e)
{ if (Q.front = = Q.rear)
return ERROR;
else
{ e=Q.base[Q.front];
Q.front= (Q.front + 1 ) % MAXSIZE;
return OK;}
}
Q.rear
Q.frontx
y
m
e=‘x’
1. 栈的应用 -- 数制转换
2. 栈的应用 -- 递归函数
3. 队列的应用
3.3 栈和队列的应用举例
1. 数制转换 将一个非负的十进制整数N转换为另一个等价的基为B的 B进制数的问题,很容易通过"除B取余法 "来解决。【例】将十进制数13转化为二进制数。
13
2 6 1
2
3 0
1
2
2 1
0 1
算法实现void conversion(int N,int B){ // 假设 N 是非负的十进制整数,输出等值的B 进制数 InitStack(S); while(N){ // 从右向左产生 B 进制的各位数字,并将其进栈 Push( S,N%B); N=N/B; } while(!StackEmpty( S)){ // 栈非空时退栈输出 Pop( S,e); printf("%d",e); } }
13
2 6 1
2
3 0
1
2
2 1
0 1
S->top
e=
1 1 0 1
例题
递归过程及其实现递归:函数直接或间接的调用自身叫递归。实现:建立递归工作栈
void print(int w){if ( w!=0) { print(w-1); for(i=1;i<=w;++i) printf(“%3d,”, w); printf(“\n”); }}
调用 print(3)
运行结果:1 ,2 , 2 ,3 , 3 , 3 ,
2 、递归函数
递归调用执行情况如下:
主函数
(1)
print(w) w=3;
3
print(2);
( 1 ) w=3
top
(2) 输出: 3, 3, 3
w
2
print(1);
( 2 ) w=2 ( 1 ) w=3
top
(3) 输出: 2, 2
w1
print(0);
( 3 ) w=1 ( 2 ) w=2 ( 1 ) w=3
top
(4) 输出: 1
w0
( 4 ) w=0 ( 3 ) w=1 ( 2 ) w=2 ( 1 ) w=3
top
w
(3) 输出: 2, 2
(2) 2(1) 3
S->top
(4) 输出: 1
(3) 1(2) 2(1) 3
S->top
(2) 输出: 3, 3, 3
(1 ) 3
S->top
返回
(3) 1(2) 2(1) 3
S->top
(4) 0
结束
(1)
if ( w!=0) { print(w-1); for(i=1;i<=w;++i) printf(“%3d,”,w); printf(“\n”); }
3. 队列的应用• 银行业务模拟程序• 舞伴问题—假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。
• 图的深度优先遍历—栈的应用(见第 7章图)
• 图的广度优先遍历—队列的应用(见第 7章图)