52
Stack Stack 栈栈栈栈 栈栈栈栈 栈栈栈栈Queue Queue 栈栈栈栈栈 栈栈栈栈栈 栈栈栈 栈栈栈栈

第三章 栈和队列

  • Upload
    bryony

  • View
    28

  • Download
    4

Embed Size (px)

DESCRIPTION

第三章 栈和队列. 栈( Stack ) 栈的应用 队列( Queue ) 队列的应用. 逻辑结构. 存储结构. 运算规则. 实现方式. 3.1 栈. 定 义. 限定只能在表的 一端 进行插入和删除运算的线性表。. 与线性表相同,数据元素之间仍为一对一的关系。. 用 顺序栈 或 链栈 存储均可。. 只能在 栈顶 运算,且访问结点时依照 后进先出 ( LIFO )或 先进后出 ( FILO )的原则。. 关键是编写 入栈 、 出栈 等函数,具体实现按顺序栈或链栈的存储结构的不同而不同。. - PowerPoint PPT Presentation

Citation preview

Page 1: 第三章  栈和队列

栈(栈( StackStack ) ) 栈的应用栈的应用 队列(队列( QueueQueue )) 队列的应用队列的应用

第三章 栈和队列

Page 2: 第三章  栈和队列

定 义3.1 3.1 栈栈

与线性表相同,数据元素之间仍为一对一的关系。用顺序栈顺序栈或链栈链栈存储均可。

只能在栈顶栈顶运算,且访问结点时依照后进先出后进先出( LIFO )或先进后出先进后出( FILO )的原则。

关键是编写入栈入栈、出栈出栈等函数,具体实现按顺序栈或链栈的存储结构的不同而不同。

存储结构

运算规则

实现方式

逻辑结构

限定只能在表的一端一端进行插入和删除运算的线性表。

基本操作 建栈、判断栈满或栈空、入栈、出栈、读栈顶元素值,等等。

Page 3: 第三章  栈和队列

栈栈 是仅在表尾进行插入、删除操作的线性表。表尾 (即 an 端 )称为栈顶 (top) ; 表头 (即 a1 端 )称为栈底 (bottom) 。例如: 栈 SS= (a1 , a2 , a3 , ……….,an-1 , an )

插入元素到栈顶的操作,称为入栈栈。

从栈顶删除元素的操作,称为出栈。

an 称为栈顶元素a1 称为栈底元素

强调:强调:插入和删除都只能在表的一端(栈顶)进行!

Page 4: 第三章  栈和队列

栈的基本操作

• InitStack(S) : 构造一个空栈 S• ClearStack(S) : 清除栈 S中的所有元素• StackEmpty(S) :判断栈 S是否为空,若为空,则返回

true ;否则返回 false• GetTop(S) : 返回 S的栈顶元素,但不移动栈顶指针• Push(S, x) :插入元素 x为新的栈顶元素(入栈操作 )• Pop(S) : 删除 S的栈顶元素并返回其值(出栈操作)

Page 5: 第三章  栈和队列

顺序栈的存储结构和操作的实现 顺序栈 :是利用一组地址连续的存储单元依次存放从栈底到栈顶的数据元素。

在在 CC 语言中,预语言中,预设一个数组的最设一个数组的最大空间大空间 ,, 栈底设栈底设置在置在 00 下标端,下标端,栈顶随着插入和栈顶随着插入和删除元素而变化,删除元素而变化,用一个整型变量用一个整型变量 ttopop 来指示栈顶的来指示栈顶的位置位置。

a1

a2

……

an

顺序栈 S

ai

……

0

n

栈底

栈顶 toptop

压入 (PUSH):PUSH): S[++toptop]=an+1

弹出 ( POP)POP) : : e=Se=S[top top - -- -]

Page 6: 第三章  栈和队列

顺序栈存储结构的描述:#define Maxsize 100 #define Maxsize 100 /* 设顺序栈的最大长度为 100 ,可依实现情况而修改 */

typedef int datatype;typedef int datatype;

typedef structtypedef struct

{{

datatype stack[Maxsize];datatype stack[Maxsize];

int topint top ; ; /* 栈顶指针 */

}SeqStack}SeqStack ; ; /* 顺序栈类型定义 */

SeqStack *sSeqStack *s ; ; /*s 为顺序栈类型变量的指针 */

Page 7: 第三章  栈和队列

顺序栈的几种状态以及在这些状态下栈顶指针 top 和栈中结点的关系

栈为空的条件 : top==-1;

栈满的条件 : top==Maxsize-1

Page 8: 第三章  栈和队列

若入栈动作使地址向高端增长,称为“向上生成”的栈;

若入栈动作使地址向低端增长,称为“向下生成”的栈;

对于向上生成的堆栈 :入栈:栈指针 top “先加后压” : S[++top]=an+1

出栈:栈指针 top “先弹后减” : e=S[top--]

顺序栈的基本操作

Page 9: 第三章  栈和队列

构造一个空顺序栈

SeqStack *InitStack()

{ SeqStack *S ;

S=(SeqStack *)malloc(sizeof(SeqStack));

if(!S)

{printf(“ 空间不足” ) ;

return NULL;}

else

{S->top=-1;

return S;}

}

Page 10: 第三章  栈和队列

取顺序栈栈顶元素

datatype GetTop(SeqStack *S)

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

{ printf("\n 栈是空的 !");

return FALSE;}

else

return S->stack[S->top];

}

Page 11: 第三章  栈和队列

判别空栈

int StackEmpty(SeqStack *S)

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

return TRUE;

else

return FALSE;

}

Page 12: 第三章  栈和队列

顺序栈的入栈操作——例如用堆栈存放( A, B, C,D)

A A

CBA

BA

top

核心语句: 顺序栈入栈函数 PUSH ()SeqStack*Push(SeqStack*S , datatype x) {if(S->top==Maxsize-1)  return NULL;/* 栈满 */    else {S->top++; S->stack[S->top]=x; return s;} }

Push (B);

Push (C);Push (D);

top

toptop

top

低地址 L

Push (A);

高地址 M

BC

D

Page 13: 第三章  栈和队列

顺序栈出栈操作——例如从栈中取出‘ BB’

DCBA

top

topDC

AB

DCBA

topDCBA

top

低地址 L

高地址 M

D

核心语句:Pop ( );

顺序栈出栈函数 POP()datatype Pop( SeqStack *S)  {if(S->top==-1) /* 栈空 */ {printf("\nThe sequence stack is empty!"); return FALSE;} else {S->top- -; return S->stack[S->top+1];}

}

Pop ( );

Printf( Pop () );

Page 14: 第三章  栈和队列

链 栈

链栈的构造方式——用 top 指向栈顶,只能在栈顶插入或删除。

栈顶

栈底

栈也可以用链式结构来表示,用链式结构来表示的栈就是链栈。

toptop

a1

a2

an-1

an

nextdata 链栈中每个结点由两个域构成:data 域和 next 域,其定义为:

Typedef struct node

{datatype data; /* 数据域 */

struct node *next; /* 指针域 */

}LinkStack;

LinkStack *top; /* top 为栈顶指针*/

Page 15: 第三章  栈和队列

将 x入栈,修改栈顶指针 :top=p

an 出栈,修改栈顶指针 :top=top->next

链栈的入栈操作和出栈操作

Page 16: 第三章  栈和队列

LinkStack *Push((LinkStack *top , datatype x)

{ LinkStack *p;

p=( Linkstack *)malloc(sizeof(LinkStack));

p->data=x;

p->next=top;

top=p;

return top;

}

链栈入栈操作

Page 17: 第三章  栈和队列

LinkStack *Pop( LinkStack *top)

{ LinkStack *q;

if(!top) {printf("\n 链栈是空的 !");return NULL;}

else

{q=top; /*q 指向要删除的栈顶结点 */

top=top->next; /* 栈顶指针下移 */

free(q); /* 释放 q 指向结点空间 */

return top;

}

}

链栈出栈操作

Page 18: 第三章  栈和队列

1 、数制转换(十进制数 N 转换为 r进制数) 设计思路:用栈暂存余数(低位值)

2 、括号匹配问题 设计思路:用栈暂存左括号

3 、子程序的调用 设计思路:用栈暂存指令地址

4 、逆置一个单链表 设计思路:用栈暂存每一结点的数据

3.2 3.2 栈的应用举例栈的应用举例

Page 19: 第三章  栈和队列

例 3.2 将十进制整数转换成二至九之间的任一进制数输出

将一个十进制数 4327 转换成八进制数 (10347)8 :

Page 20: 第三章  栈和队列

⑴  当 N≠0 ,将 N%r 压入栈 s 中;

⑵  N/r ⇒ N ;

⑶ 若 N> 0 ,则重复⑴、⑵两步;若 N=0 ,则将栈 s 的内容依次出栈。

解题思路如下:

void conversion(int N, int r)

{ int x=N,y=r;

SeqStack *s;

s=InitStack(); while(N!=0)

{ Push(s, N %r );

N=N/r ;

}

printf(“\n 十进制数 %d 所对应的 %d 进制数是 :” , x,y);

while(!StackEmpty(s)) printf(“%d”,Pop(s));

printf(“\n”);

}

Page 21: 第三章  栈和队列

例 3.5 利用一个顺序栈将一个带头结点的单链表( a1,a2,…,an )(其中 n>=0 )逆置为( an,an-1,… , a1 )。

Page 22: 第三章  栈和队列

1 、建立一个带头结点的单链表 head ;

2、输出该单链表;

3 、建立一个空栈 s (顺序栈);

4 、依次将单链表的数据入栈;

5 、依次将单链表的数据出栈,并逐个将出栈的数据存入单链表的数据域(自前向后) ;

6、再输出单链表。

解题思路如下:

linklist*backlinklist(linklist *head)

{linklist *p;

p=head->next;

initstack();

while(p)

{push(&s, p->data); p=p->next ;

}

p=head->next;

while(!stackempty(s))

{p->data=pop(&s); p=p->next;

}

return (head);

}

Page 23: 第三章  栈和队列

3.3 3.3 队列 队列

与线性表相同,仍为一对一关系。

顺序存储方式:顺序队列 (循环队列 )和链式存储方式:链队列。

只能在队尾入队、队头出队,并遵循先进先出( FIFO )的原则。

关键是掌握入队和出队操作,具体实现按存储方式的不同 (顺序队列或链队列 )而不同。

存储结构存储结构

运算规则运算规则

实现方式实现方式

逻辑结构逻辑结构

只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表。

基本操作基本操作 入队、出队、建空队列、判队空或队满等。

尾部插入

头部删除

队列定义队列定义

Page 24: 第三章  栈和队列

队列 ( Queue )是仅在表尾进行插入操作、在表头进行删除操作的线性表。它是一种先进先出(FIFO)的线性表。

例如:队列 Q= (a1 , a2 , a3 , ……….,an-1 , an )

在队尾插入元素称为入队入队;在队首删除元素称为出队出队。

队首 队尾

队列的队列的实现方式实现方式是本节重点,是本节重点,关键是掌握入队和出队等等操作。 具体实现按存储结构的不同(顺序队列或链队列)而不同。

Page 25: 第三章  栈和队列

队列的基本操作队列的基本操作

( 1) InitQueue(Q) : 构造一个空队列 Q( 2) QueueEmpty(Q) : 判断队列是否为空 ( 3) QueueLength(Q) :求队列的长度 ( 4) GetHead(Q) : 返回 Q的队头元素,不改变队列状态 ( 5) EnQueue(Q,x) : 入队列:插入元素 x 为 Q的新的队尾元素 ( 6) DeQueue(Q) : 出队列:删除 Q的队头元素( 7) ClearQueue(Q) : 清除队列 Q中的所有元素

Page 26: 第三章  栈和队列

链队列类型定义: typedef struct { Qnode *front ; /* 队头指针 */ Qnode *rear ; /* 队尾指针 */ }LinkQueue; LinkQueue *q; /*q 是链队列指针 , 其中封装了队头指针和队尾指针 */

链队列链队列结点类型定义: typedef Struct Qnode { datatype data; /* 数据域 */ Struct Qnode *next; /* 指针域 */ }Qnode;

Page 27: 第三章  栈和队列

链队列的几种状态示意图:此时, front==rear

修改 rear 指针

修改头结点的指针域

①链队列为空的特征: front==rear② 链队列会满吗?一般不会,因为删除时有 free 动作,除非内存不足!

修改 rear 指针

Page 28: 第三章  栈和队列

入队(尾部插入): rear->next=S;

rear=S;

出队(头部删除): front->next=front->next->next;

③ 怎样实现链队列的入队操作和出队操作? 假设 S所指结点为入队结点,则有

Page 29: 第三章  栈和队列

LinkQueue *InitQueue()

 {LinkQueue *q; /*q 为链队列指针 ,q 中封装了队头指针和队尾指针

*/

Qnode *p; /*p 为指向链队列结点的指针 */

Q=(LinkQueue*)malloc(sizeof(LinkQueue));

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

p->next=NULL;

q->front =q->rear=p;

return q;

}

构造一个空链队列构造一个空链队列

Page 30: 第三章  栈和队列

datatype GetHead(LinkQueue *Q)

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

{printf(“\n 链队列为空 !”);

return FALSE;}

else

   return Q->front->next->data ; /* 返回队头元素的值 */

}

取链队列的队头元素取链队列的队头元素

Page 31: 第三章  栈和队列

void EnQueue(LinkQueue *Q , datatype x)

   { Qnode *p; /*p 为指向链队列结点的指针 */

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

p->data = x ;

p->next = NULL ;

Q->rear->next = p ;

Q->rear=p ;

   }

链队列的入队操作链队列的入队操作

Page 32: 第三章  栈和队列

datatype DeQueue(LinkQueue *Q)

  { Qnode *p; /*p 为指向链队列结点的指针 */ datatype x;

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

{printf(" 队列为空,无法删除! "); return FALSE;}

    else

{p = Q->front->next ; /*p 指向链队列的队头结点 */

x = p->data ; /* 将队头元素的值赋给变量 x*/

Q->front->next = p->next ; /* 出队列,修改队头指针 */

if(Q->rear == p) Q->rear=Q->front ; /* 若出队列后队列为空, 则还应当修改队尾指针,使队尾指针指向头结点 */

   free(p) ; /* 释放空间 */

  return x; /* 返回已出队元素(即原队头元素)的值 */ }

}

链队列的出队操作链队列的出队操作

Page 33: 第三章  栈和队列

循环(顺序)队列的类型定义:

#define MAXSIZE 100 /* 最大队列长度 */typedef struct{ datatype data[MAXSIZE] ; /* 存储队列的数据空间 */ int front ; /* 队头指针,若队列不空,则指向队头元素 */ int rear ; /* 队尾指针,若队列不空,则指向队尾元素的下一个位置 */}SeqQueue;

头、尾指针与队列中元素之间的关系示意图:

Page 34: 第三章  栈和队列

入队操作时的尾指针运算 : rear=(rear+1)%Maxsize出队操作时的头指针运算 : front=(front+1)%Maxaize问题:在循环队列中,由于队空时有 front==rear ;队满时也有 front==rear ;因此我们无法通过 front==rear 来判断队列是“空”还是“满”。

循环队列示意图

循环队列的几种状态

Page 35: 第三章  栈和队列

循环队列空的条件 : front = =rear循环队列满的条件: front == (rear+1) % MAXSIZE

循环队列长度为: L= ( rear- front +MAXSIZE ) % MAXS

IZE

J2 J3

J1 J4

J5front

rear

解决办法:少用一个元素空间,约定以“队头指针在队尾指针的下一位置 ( 指环状的下一位置 )” 作为队列“满”的标志。也就是说,若数组的大小是 MAXSIZE ,则该数组所表示的循环队列最多允许存储 M

AXSIZE-1 个结点。注意: rear 所指的结点始终为空。

循环队列满的示意图:

Page 36: 第三章  栈和队列

经过约定后的循环队列操作示意图 ( p.70 ,图 3.13改)

Page 37: 第三章  栈和队列

循环队列的基本操作实现

以创建队列、入队和出队三种基本操作为例

1、构造(初始化)一个空队列算法要求:生成一空队列

算法步骤:

①为队列分配存储空间;

②设置队列为空队列,其特征为:

front=rear=0

Page 38: 第三章  栈和队列

构造一个空队列 q

SeqQueue *InitQueue()

{ SeqQueue *q ;

q=(SeqQueue *)malloc(sizeof(SeqQueue)) ; /* 开辟一个足够大的存储队列空间 */

q->front = q->rear = 0 ; /* 将队列头尾指针置为零 */

return q ; /* 返回队列的首地址 */

}

Page 39: 第三章  栈和队列

算法说明:向循环队列的队尾插入一个元素。

分 析:(1) 入队前应当先判断队列是否已满? if((q->rear+1)% Maxsize)==q->front) return false;

(2) 入队动作如下 : q->data[q->rear] = x; q->rear=(q->rear+1)% Maxsize;

2、入队操作

Page 40: 第三章  栈和队列

int EnQueue(SeqQueue *q, datatype x)

{ if((q->rear+1) % MAXSIZE==q->front)

{printf(“\n 循环队列满 !”); return FALSE;}

q->data[q->rear] = x ;

q->rear = (q->rear+1)%MAXSIZE;

return TRUE;

}

循环队列入队操作

Page 41: 第三章  栈和队列

3、出队操作算法说明:删除循环队列的队头元素,返回其值 x。

分 析:

( 1)在出队前应当判断队列是否为空? if (q->front==q->rear) return false;

( 2)出队动作如下 : x= q->data[q->front]; q->front=(q->front+1)% Maxsize;

Page 42: 第三章  栈和队列

datatype DeQueue(SeqQueue *q)

{ datatype x;

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

{printf(“\n 循环队列空!不能做删除操作!” );

return FALSE;}

x = q->data[ q->front ] ;

q->front = (q->front+1) % MAXSIZE ;

  return x ;

}

循环队列出队操作

Page 43: 第三章  栈和队列

3.4 队列的应用

11 、打印杨辉三角形、打印杨辉三角形

2、迷宫问题:寻找一条从迷宫入口到出口的最短路径

Page 44: 第三章  栈和队列

例 3.7 打印杨辉三角形。 此问题是一个初等数学问题。系数表中的第 i 行有 i+1个数,除了第 1 个和最后一个数为 1外,其余的数则为上一行中位于其左、右的两数之和。

Page 45: 第三章  栈和队列

算法分析算法分析

如果要计算并输出二项式系数表(即杨辉三角形)的前 n 行的值,则所设循环队列的最大空间应为 n+2。假设队列中已存有第 i 行的值,为计算方便,在两行之间均加一个“ 0” 作为行间的分隔符,则在计算第 i+1 行之前,头指针正指向第 i 行的“ 0” ,而尾元素为第 i+1 行的“ 0”。由此,从左至右输出第 i行的值,并将计算所得的第 i+1 行的值插入队列。

Page 46: 第三章  栈和队列

分析第 i 行元素与第 i+1 行元素的关系如图所示 :

Page 47: 第三章  栈和队列

假设 n=4 , i=3 ,则输出第 3 行元素并求解第 4行元素值的循环执行过程中队列的变化状态如图所示 :

Page 48: 第三章  栈和队列

程序如下 : (n≤7)#define MAXSIZE 10 /* 定义队列的最大长度 */#include <stdio.h>#include <malloc.h>typedef int datatype;typedef struct{ int data[MAXSIZE]; int front; int rear;}SeqQueue;

SeqQueue *InitQueue() /* 队列初始化 */{ SeqQueue *q;q=(SeqQueue*)malloc(sizeof(SeqQueue));q->front = q->rear = 0;return q;}

Page 49: 第三章  栈和队列

void EnQueue(SeqQueue *q, datatype x) /* 入队列 */{ if((q->rear+1)%MAXSIZE==q->front) {printf("\n 顺序循环队列是满的 !");} else {q->data[q->rear] = x; q->rear = (q->rear+1)%MAXSIZE; }}

datatype DeQueue (SeqQueue *q) /* 出队列 */{datatype x; if(q->front==q->rear) { printf("\n 顺序队列是空的!不能做删除操作! "); return 0;} x = q->data[q->front]; q->front = (q->front+1)%MAXSIZE; return x;}

Page 50: 第三章  栈和队列

int QueueEmpty(SeqQueue *q) /* 判队空 */{ return(q->front==q->rear);}

int GetHead(SeqQueue *q) /* 取队头元素 */{int e; if(q->front==q->rear) e=0; else e=q->data[q->front]; return e;}

void YangHui( int n ) /* 打印杨辉三角形的前 n 行 , n≤7*/{ SeqQueue *q;int i, j,s,t;for(i=1;i<=n;i++)printf(" ");printf(“1\n”); /* 在中心位置输出杨辉三角最顶端的 1( 第 0 行 )*/q=InitQueue(); /* 设置容量为 n+2 的空队列 */EnQueue(q,0); /* 添加行分隔符 */EnQueue(q,1);EnQueue(q,1); /* 第 1 行的值入队 */

Page 51: 第三章  栈和队列

for(j=1;j<n;j++) /* 利用循环队列输出前 n-1 行的值 */{ for(i=1;i<n-j;i++) /* 在输出第 j 行的首元素之间输出 n-j 个空格 */ printf(" "); EnQueue(q,0); /* 行分隔符 0 入队 */ do /* 输出第 j 行并计算第 j+1 行 */ {s=DeQueue(q); /* 删除队头元素并赋给 s*/ t=GetHead(q); /* 取队头元素给 t*/ if(t) printf("%5d",t); /* 若不到行分隔符则输出 t ,再输出一个空格*/ else printf("\n"); /* 否则输出一个换行符 */ EnQueue(q,s+t); /* 将第 j+1 行的对应元素 s+t 入队 */ }while(t!=0); } DeQueue(q); /* 删除行分隔符 */ printf("%3d",DeQueue(q)); /* 输出第 n 行的第一个元素 */ while(!QueueEmpty(q)) /* 输出第 n 行的其余元素 */ {t=DeQueue(q); printf("%5d",t); }}

Page 52: 第三章  栈和队列

void main(){int n; printf("\n 请输入杨辉三角形的行数 :\n"); scanf("%d",&n); YangHui(n);}运行情况 :请输入杨辉三角形的行数 : 7↓输出结果 :