520 likes | 650 Views
第三章 栈和队列. 栈( Stack ) 栈的应用 队列( Queue ) 队列的应用. 逻辑结构. 存储结构. 运算规则. 实现方式. 3.1 栈. 定 义. 限定只能在表的 一端 进行插入和删除运算的线性表。. 与线性表相同,数据元素之间仍为一对一的关系。. 用 顺序栈 或 链栈 存储均可。. 只能在 栈顶 运算,且访问结点时依照 后进先出 ( LIFO )或 先进后出 ( FILO )的原则。. 关键是编写 入栈 、 出栈 等函数,具体实现按顺序栈或链栈的存储结构的不同而不同。.
E N D
第三章 栈和队列 • 栈(Stack) • 栈的应用 • 队列(Queue) • 队列的应用
逻辑结构 存储结构 运算规则 实现方式 3.1 栈 定 义 限定只能在表的一端进行插入和删除运算的线性表。 与线性表相同,数据元素之间仍为一对一的关系。 用顺序栈或链栈存储均可。 只能在栈顶运算,且访问结点时依照后进先出(LIFO)或先进后出(FILO)的原则。 关键是编写入栈、出栈等函数,具体实现按顺序栈或链栈的存储结构的不同而不同。 基本操作建栈、判断栈满或栈空、入栈、出栈、读栈顶元素值,等等。
栈是仅在表尾进行插入、删除操作的线性表。 表尾(即 an 端)称为栈顶 (top) ; 表头(即 a1 端)称为栈底(bottom)。 例如: 栈S= (a1 , a2 , a3 , ……….,an-1 , an ) a1称为栈底元素 an称为栈顶元素 强调:插入和删除都只能在表的一端(栈顶)进行! 插入元素到栈顶的操作,称为入栈。 从栈顶删除元素的操作,称为出栈。
栈的基本操作 • InitStack(S): 构造一个空栈S • ClearStack(S): 清除栈S中的所有元素 • StackEmpty(S):判断栈S是否为空,若为空,则返回 true;否则返回false • GetTop(S) : 返回S的栈顶元素,但不移动栈顶指针 • Push(S, x) :插入元素x为新的栈顶元素(入栈操作) • Pop(S) : 删除S的栈顶元素并返回其值(出栈操作)
顺序栈S 栈顶top an …… ai …… a2 栈底 a1 顺序栈的存储结构和操作的实现 顺序栈:是利用一组地址连续的存储单元依次存放从栈底到栈顶的数据元素。 在C语言中,预设一个数组的最大空间,栈底设置在0下标端,栈顶随着插入和删除元素而变化,用一个整型变量top来指示栈顶的位置。 n 0 压入(PUSH):S[++top]=an+1 弹出( POP) : e=S[top - -]
顺序栈存储结构的描述: #define Maxsize 100 /*设顺序栈的最大长度为100,可依实现情况而修改*/ typedef int datatype; typedef struct { datatype stack[Maxsize]; int top; /*栈顶指针*/ }SeqStack; /*顺序栈类型定义*/ SeqStack *s; /*s为顺序栈类型变量的指针*/
顺序栈的几种状态以及在这些状态下栈顶指针top和栈中结点的关系顺序栈的几种状态以及在这些状态下栈顶指针top和栈中结点的关系 栈为空的条件 : top==-1; 栈满的条件 : top==Maxsize-1
顺序栈的基本操作 若入栈动作使地址向高端增长,称为“向上生成”的栈; 若入栈动作使地址向低端增长,称为“向下生成”的栈; 对于向上生成的堆栈: 入栈:栈指针top “先加后压”: S[++top]=an+1 出栈:栈指针top “先弹后减”: e=S[top--]
构造一个空顺序栈 SeqStack *InitStack() { SeqStack *S ; S=(SeqStack *)malloc(sizeof(SeqStack)); if(!S) {printf(“空间不足”); return NULL;} else {S->top=-1; return S;} }
取顺序栈栈顶元素 datatype GetTop(SeqStack *S) {if (S->top== -1) { printf("\n栈是空的!"); return FALSE;} else return S->stack[S->top]; }
判别空栈 int StackEmpty(SeqStack *S) {if(S->top== -1) return TRUE; else return FALSE; }
top top top top top 顺序栈的入栈操作——例如用堆栈存放(A,B,C,D) 高地址M A B A C B A D C B A 低地址L 顺序栈入栈函数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 (A); Push (B); Push (C); Push (D);
高地址M D C B A D C A D C B A D C B A D B top top top top 低地址L 顺序栈出栈操作——例如从栈中取出‘B’ 核心语句: 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 () );
data next top a n a n-1 a 2 a 1 链 栈 栈也可以用链式结构来表示,用链式结构来表示的栈就是链栈。 链栈的构造方式——用top指向栈顶,只能在栈顶插入或删除。 链栈中每个结点由两个域构成: data域和next域,其定义为: 栈顶 Typedef struct node {datatype data; /*数据域*/ struct node *next; /*指针域*/ }LinkStack; LinkStack *top; /* top为栈顶指针*/ 栈底
链栈的入栈操作和出栈操作 将x入栈,修改栈顶指针:top=p an出栈,修改栈顶指针:top=top->next
链栈入栈操作 LinkStack *Push((LinkStack *top,datatype x) { LinkStack *p; p=( Linkstack *)malloc(sizeof(LinkStack)); p->data=x; p->next=top; top=p; return top; }
链栈出栈操作 LinkStack *Pop( LinkStack *top) { LinkStack *q; if(!top) {printf("\n链栈是空的!");return NULL;} else {q=top; /*q指向要删除的栈顶结点*/ top=top->next; /*栈顶指针下移*/ free(q); /*释放q指向结点空间*/ return top; } }
3.2 栈的应用举例 1、数制转换(十进制数N转换为r进制数) 设计思路:用栈暂存余数(低位值) 2、括号匹配问题 设计思路:用栈暂存左括号 3、子程序的调用 设计思路:用栈暂存指令地址 4、逆置一个单链表 设计思路:用栈暂存每一结点的数据
例3.2 将十进制整数转换成二至九之间的任一进制数输出 将一个十进制数4327转换成八进制数(10347)8:
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”); } 解题思路如下: ⑴当N≠0,将N%r压入栈s中; ⑵ N/r ⇒N; ⑶ 若N>0,则重复⑴、⑵两步;若N=0,则将栈s的内容依次出栈。
例3.5 利用一个顺序栈将一个带头结点的单链表(a1,a2,…,an)(其中n>=0)逆置为(an,an-1,…,a1)。
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); } 解题思路如下: 1、建立一个带头结点的单链表head; 2、输出该单链表; 3、建立一个空栈s(顺序栈); 4、依次将单链表的数据入栈; 5、依次将单链表的数据出栈,并逐个将出栈的数据存入单链表的数据域(自前向后); 6、再输出单链表。 ;
逻辑结构 存储结构 运算规则 实现方式 尾部插入 3.3 队列 队列定义 只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表。 头部删除 与线性表相同,仍为一对一关系。 顺序存储方式:顺序队列(循环队列)和链式存储方式:链队列。 只能在队尾入队、队头出队,并遵循先进先出(FIFO)的原则。 关键是掌握入队和出队操作,具体实现按存储方式的不同(顺序队列或链队列)而不同。 基本操作入队、出队、建空队列、判队空或队满等。
队列 (Queue)是仅在表尾进行插入操作、在表头进行删除操作的线性表。它是一种先进先出(FIFO)的线性表。 例如:队列Q= (a1 , a2 , a3 , ……….,an-1 , an ) 队首 队尾 在队尾插入元素称为入队;在队首删除元素称为出队。 队列的实现方式是本节重点,关键是掌握入队和出队等操作。具体实现按存储结构的不同(顺序队列或链队列)而不同。
队列的基本操作 (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中的所有元素
链队列 结点类型定义: typedef Struct Qnode { datatype data; /*数据域*/ Struct Qnode *next; /*指针域*/ }Qnode; 链队列类型定义: typedef struct { Qnode *front ; /*队头指针*/ Qnode *rear ; /*队尾指针*/ }LinkQueue; LinkQueue *q; /*q是链队列指针,其中封装了队头指针和队尾指针*/
链队列的几种状态示意图: 修改rear指针 此时,front==rear 修改rear指针 修改头结点的指针域 ①链队列为空的特征:front==rear ② 链队列会满吗? 一般不会,因为删除时有free动作,除非内存不足!
③ 怎样实现链队列的入队操作和出队操作? 假设S所指结点为入队结点,则有 入队(尾部插入):rear->next=S; rear=S; 出队(头部删除):front->next=front->next->next;
构造一个空链队列 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; }
取链队列的队头元素 datatype GetHead(LinkQueue *Q) {if(Q->front==Q->rear) {printf(“\n链队列为空!”); return FALSE;} else return Q->front->next->data; /*返回队头元素的值*/ }
链队列的入队操作 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; }
链队列的出队操作 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; /*返回已出队元素(即原队头元素)的值*/ } }
循环(顺序)队列的类型定义: #define MAXSIZE 100 /* 最大队列长度 */ typedef struct { datatype data[MAXSIZE];/* 存储队列的数据空间 */ int front;/* 队头指针,若队列不空,则指向队头元素 */ int rear;/* 队尾指针,若队列不空,则指向队尾元素的下一个位置 */ }SeqQueue; 头、尾指针与队列中元素之间的关系示意图:
循环队列示意图 循环队列的几种状态 入队操作时的尾指针运算: rear=(rear+1)%Maxsize 出队操作时的头指针运算: front=(front+1)%Maxaize 问题:在循环队列中,由于队空时有front==rear;队满时也有front==rear;因此我们无法通过front==rear来判断队列是“空”还是“满”。
J2 J3 J1 J4 front J5 rear 解决办法:少用一个元素空间,约定以“队头指针在队尾指针的下一位置(指环状的下一位置)”作为队列“满”的标志。也就是说,若数组的大小是MAXSIZE,则该数组所表示的循环队列最多允许存储MAXSIZE-1个结点。注意: rear所指的结点始终为空。 循环队列空的条件 : front = =rear 循环队列满的条件: front == (rear+1) %MAXSIZE 循环队列长度为:L=(rear-front +MAXSIZE)% MAXSIZE 循环队列满的示意图:
经过约定后的循环队列操作示意图 (p.70,图3.13改)
循环队列的基本操作实现 以创建队列、入队和出队三种基本操作为例 1、构造(初始化)一个空队列 算法要求:生成一空队列 算法步骤: ①为队列分配存储空间; ②设置队列为空队列,其特征为: front=rear=0
构造一个空队列q SeqQueue *InitQueue() { SeqQueue *q; q=(SeqQueue *)malloc(sizeof(SeqQueue)); /* 开辟一个足够大的存储队列空间 */ q->front = q->rear = 0; /* 将队列头尾指针置为零 */ return q;/* 返回队列的首地址 */ }
2、入队操作 算法说明:向循环队列的队尾插入一个元素。 分 析: (1)入队前应当先判断队列是否已满? if((q->rear+1)% Maxsize)==q->front) return false; (2)入队动作如下: q->data[q->rear] = x; q->rear=(q->rear+1)% Maxsize;
循环队列入队操作 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; }
3、出队操作 算法说明:删除循环队列的队头元素,返回其值 x。 分 析: (1)在出队前应当判断队列是否为空? if (q->front==q->rear) return false; (2)出队动作如下: x= q->data[q->front]; q->front=(q->front+1)% Maxsize;
循环队列出队操作 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; }
3.4 队列的应用 1、打印杨辉三角形 2、迷宫问题:寻找一条从迷宫入口到出口的最短路径
例3.7 打印杨辉三角形。 此问题是一个初等数学问题。系数表中的第i行有i+1个数,除了第1个和最后一个数为1外,其余的数则为上一行中位于其左、右的两数之和。
算法分析 如果要计算并输出二项式系数表(即杨辉三角形)的前n行的值,则所设循环队列的最大空间应为n+2。假设队列中已存有第i行的值,为计算方便,在两行之间均加一个“0”作为行间的分隔符,则在计算第i+1行之前,头指针正指向第i行的“0”,而尾元素为第i+1行的“0”。由此,从左至右输出第i行的值,并将计算所得的第i+1行的值插入队列。
分析第 i 行元素与第 i+1行元素的关系如图所示 :
假设n=4,i=3,则输出第3行元素并求解第4行元素值的循环执行过程中队列的变化状态如图所示:假设n=4,i=3,则输出第3行元素并求解第4行元素值的循环执行过程中队列的变化状态如图所示:
程序如下: (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; }
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; }
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行的值入队*/