50
第 3 第 第第第第 3.1 3.2 第第 3.3 第第第第第第第

第 3 章 栈和队列

  • 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

Page 1: 第  3  章    栈和队列

第 3 章 栈和队列

3.1 栈3.2 队列3.3 栈和队列的应用

Page 2: 第  3  章    栈和队列

3.1.1 栈的抽象数据类型定义

3.1.2 栈的表示和算法实现

3.1 栈

Page 3: 第  3  章    栈和队列

1 .栈的定义栈( stack )是一种只允许在一端进行插入和删除的线性表,它是一种操作受限的线性表。在表中只允许进行插入和删除的一端称为栈顶( top ),另一端称为栈底 (bottom) 。栈的插入操作通常称为入栈或进栈 (push) ,而栈的删除操作则称为出栈或退栈 (pop) 。当栈中无数据元素时,称为空栈。

a1a2

an

入栈 出栈

栈顶 top

栈底 bottom

栈的示意图

...

栈是按照后进先出( LIFO )的原则组织数据的,因此,栈也被称为“后进先出”的线性表。

3.1.1 栈的定义

Page 4: 第  3  章    栈和队列

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 .栈的抽象数据类型定义

Page 5: 第  3  章    栈和队列

StackEmpty(S) 初始条件:栈 S 已存在操作结果:若栈 S 为空栈,则返回 TRUE ;否则,返回 FALSE 。StackLength(S) 返回 S 的元素个数,即栈的长度。GetTop(S , &e) 初始条件:栈 S 已存在且非空操作结果: 用 e 返回 S 的栈顶元素Push(&S , e)初始条件:栈 S 已存在操作结果:插入元素 e 为新的栈顶元素

Page 6: 第  3  章    栈和队列

Pop(&S , &e)

初始条件:栈 S 已存在且非空操作结果: 删除 S 的栈顶元素,并用 e 返回其值 StackTraverse(S,visit())

初始条件:栈 S 已存在且非空操作结果: 从栈底到栈顶依次对 S 的每个数据元素调用函数 visit() ,一旦 visit() 失败,则操作失效。}ADT Stack

Page 7: 第  3  章    栈和队列

1. 顺序栈

2. 链栈

3.1.2 栈的表示和算法实现

Page 8: 第  3  章    栈和队列

顺序栈是用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时由于栈的操作的特殊性,还必须附设一个位置指针 top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置。通常以 top=-1 表示空栈。

1. 顺序栈

Page 9: 第  3  章    栈和队列

顺序栈的存储结构定义

#define Stack_Size 50 typedef struct Stack{ElemType elem[Stack_Size]; /* 用来存放栈中元素的一维数组 */

int top; /* 用来存放栈顶元素的下标 */

}*SqStack;SqStack S;

Page 10: 第  3  章    栈和队列

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

Page 11: 第  3  章    栈和队列

1.2 、顺序栈基本运算的算法:

( 1)初始化栈【算法 3.1 栈的初始化】Status InitStack(SqStack S){/* 创建一个空栈由指针 S指出 */ if ((S=(SqStack*)malloc(sizeof(SqStack)))= =NULL) exit(OVERFLOW); S->top= -1;return OK;}

Page 12: 第  3  章    栈和队列

( 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

Page 13: 第  3  章    栈和队列

( 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’

Page 14: 第  3  章    栈和队列

( 4)取栈顶元素操作

Status GetTop(SqStack S,ElemType &e)

{/* 若栈 s不为空,则返回栈顶元素 */

if(S->top==-1) return ERROR; /* 栈空 */

else {e= S->elem[S->top];

return OK;}

}

取栈顶元素与出栈不同之处在于出栈操作改变栈顶指针top 的位置,而取栈顶元素操作不改变栈的栈顶指针。

Page 15: 第  3  章    栈和队列

( 5)判栈空操作Status StackEmpty(SqStack S){/* 栈 S为空时,返回为 TRUE ;非空时,返回为 FALSE*/ if(S->top= =-1) return TRUE; else return FALSE ;}

Page 16: 第  3  章    栈和队列

1.3 双向栈在一维数组中的实现栈的共享中最常见的是两栈的共享。假设两个栈共享一维数组 S->elem[Stack_Size] ,则可以利用栈的“栈底位置不变,栈顶位置动态变化”的特性,两个栈底分别为 0 和 Stack_Size-1 ,而它们的栈顶都往中间方向延伸。因此,只要整个数组 S->elem[Stack_Size] 未被占满,无论哪个栈的入栈都不会发生上溢。

自由区

lefttop rightto

0Stack_Size -1

图 3-3 两个栈共享邻接空间

Page 17: 第  3  章    栈和队列

2 、链栈

栈也可以采用链式存储结构表示,这种结构的栈简称为链栈。在一个链栈中,栈底就是链表的最后一个结点,而栈顶总是链表的第一个结点。因此,新入栈的元素即为链表新的第一个结点,只要系统还有存储空间,就不会有栈满的情况发生。一个链栈 (不带头结点 )可由栈顶指针 top 唯一确定,当 top 为NULL 时,是一个空栈。2.1 、链栈的 C语言定义为:typedef struct StackNode {ElemType data;Struct StackNode *next;}StackNode,*LinkStack;

Page 18: 第  3  章    栈和队列

top

top

top

链栈的存储结构图( a)含有两个元素 A、 B的栈;( b)插入元素 C后的栈;

( c)删除元素 C、 B后的栈

( a ) ( b ) ( c )^^

^A

B

A A

B

C p

Page 19: 第  3  章    栈和队列

^…...…...top data next

栈底

– 入栈过程

– 出栈过程

^…...栈底

toptop

xp

top

^…...栈底

top

q

Page 20: 第  3  章    栈和队列

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

Page 21: 第  3  章    栈和队列

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’

Page 22: 第  3  章    栈和队列

练 习四个元素入栈的顺序是 A,B,C,D. 不可能的出栈顺序是:(a)ABCD(b)DCBA(c)BADC(d)ADCB(e)DACB

Page 23: 第  3  章    栈和队列

3.2 队 列

3.2.1 队列的抽象数据类型定义3.2.2 链队列3.2.3 顺序队列 (循环队列 )

Page 24: 第  3  章    栈和队列

在日常生活中队列很常见,如,我们经常排队购物或购票 . 队列在计算机系统中的应用也非常广泛。例如:操作系统中的作业排队。

3.2.1 队列的抽象数据类型定义

Page 25: 第  3  章    栈和队列

定义:允许在线性表的一端插入 ,另一端进行删除操作的线性表称为队列 .插入的一端为队尾 ,删除的一端为队头。※队尾 (rear)——允许插入的一端※队头 (front)——允许删除的一端

队列特点:先进先出 (FIFO)a1 a2 a3…………………….an 入队出队

front rear

队列 Q=(a1,a2,……,an)

1 、队列的定义及特点

Page 26: 第  3  章    栈和队列

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 清为空队列

Page 27: 第  3  章    栈和队列

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 返回其值

Page 28: 第  3  章    栈和队列

QueueTraverse(Q,visit())

初始条件:队列 Q 已存在且非空操作结果: 从队头到队尾依次对 Q 的每个数据

元素调用函数 visit() ,一旦 visit() 失败,则操作失效。

}ADT Queue

Page 29: 第  3  章    栈和队列

3. 双端队列

是限定插入和删除操作是在表的两端进行的线性表 .这两端分别称为端点 1和端点 2

a1 a2 a3…………………….an 插入删除

端 1 端 2

队列 Q=(a1,a2,……,an)

插入 删除

Page 30: 第  3  章    栈和队列

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;

Page 31: 第  3  章    栈和队列

2 、链队列的算法:算法 1 :判队列是否为空:

Status QueueEmpty( LinkQueue Q)

{ if Q->front = = Q->rear

return TRUE;

else

return FALSE;

}

Q->rear

Q->front

Page 32: 第  3  章    栈和队列

算法 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

Page 33: 第  3  章    栈和队列

算法 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’

Page 34: 第  3  章    栈和队列

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

Page 35: 第  3  章    栈和队列

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

Page 36: 第  3  章    栈和队列

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

Page 37: 第  3  章    栈和队列

解决缺点的方法:

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

Page 38: 第  3  章    栈和队列

(2) 出队列 :

4 3 2 1 0

Q.rear

Q.frontm

Q.front=(Q.front + 1)% MAXSIZE

Page 39: 第  3  章    栈和队列

(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

队列为空

Page 40: 第  3  章    栈和队列

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;

}

Page 41: 第  3  章    栈和队列

3 、循环队列的重要算法:

算法 2 :求队列 Q 的长度

int QueueLength (SqQueue Q)

{return ((Q.rear-Q.front+MAXSIZE)%MAXSIZE);

}

Page 42: 第  3  章    栈和队列

算法 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

Page 43: 第  3  章    栈和队列

算法 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’

Page 44: 第  3  章    栈和队列

1. 栈的应用 -- 数制转换

2. 栈的应用 -- 递归函数

3. 队列的应用

3.3 栈和队列的应用举例

Page 45: 第  3  章    栈和队列

1. 数制转换 将一个非负的十进制整数N转换为另一个等价的基为B的 B进制数的问题,很容易通过"除B取余法 "来解决。【例】将十进制数13转化为二进制数。

13

2 6 1

2

3 0

1

2

2 1

0 1

Page 46: 第  3  章    栈和队列

算法实现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

Page 47: 第  3  章    栈和队列

例题

递归过程及其实现递归:函数直接或间接的调用自身叫递归。实现:建立递归工作栈

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 、递归函数

Page 48: 第  3  章    栈和队列

递归调用执行情况如下:

主函数

(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”); }

Page 49: 第  3  章    栈和队列

3. 队列的应用• 银行业务模拟程序• 舞伴问题—假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。

Page 50: 第  3  章    栈和队列

• 图的深度优先遍历—栈的应用(见第 7章图)

• 图的广度优先遍历—队列的应用(见第 7章图)