0
点赞
收藏
分享

微信扫一扫

第三章 栈和队列【数据结构与算法】


第三章 栈和队列

  • ​​配套资源下载​​
  • ​​第 3 章 栈和队列​​
  • ​​3.1 应用实例​​
  • ​​应用实例一 迷宫求解问题​​
  • ​​应用实例二“马”踏棋盘问题​​
  • ​​3.2栈​​
  • ​​3.2.1 栈的概念及运算​​
  • ​​3.2.2栈的顺序存储结构​​
  • ​​1. 顺序栈​​
  • ​​栈的基本操作如下​​
  • ​​2. 多栈共享邻接空间​​
  • ​​共享栈的基本操作如下​​
  • ​​3.2.3栈的链式存储结构​​
  • ​​1)链栈定义​​
  • ​​2)链栈操作​​
  • ​​(3)多个链栈的操作​​
  • ​​多个链栈的基本操作​​
  • ​​3.2.4栈的应用​​
  • ​​0. 括号匹配​​
  • ​​1. 算术表达式求值(上机)​​
  • ​​3.3 队列​​
  • ​​3.3.1队列的概念及其运算​​
  • ​​3.3.2循环队列​​
  • ​​循环队列的类型定义​​
  • ​​循环队列的基本运算​​
  • ​​3.3.3 链队列​​
  • ​​链队列的基本运算​​
  • ​​3.4 实例分析与实现​​
  • ​​应用实例一 迷宫求解​​
  • ​​应用实例二 马踏棋盘​​
  • ​​应用实例三 计算器​​
  • ​​3.5 算法总结 ——递归与分治 算法​​
  • ​​习题3​​
  • ​​题目​​
  • ​​1.单项选择题​​
  • ​​(1)(2010考研真题)若元素a,b,e,d,e,f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈操作,则不可能得到的出栈序列是____。​​
  • ​​(2)若某堆栈的输人序列为1,2,3,,n-l,n,输出序列的第1个元素为n,则第i个输出元素为____。​​
  • ​​(3)以数组Q[0..m-1]存放循环队列中的元素,变量rear和qulen分别指示循环队列中队尾元素的实际位置和当前队列中元素的个数,队列第一个元素的实际位置是___。​​
  • ​​(4)设输人元素为1,2,3,A,B,输人次序为123AB,元素经过栈后到达输出序列,当所有元素均到达输出序列后,序列____是不可能的。​​
  • ​​(5)(2012年考研真题)已知操作符包括“+”“一”“*”“/”“(”和“)”。将中缀表达式a+b-a*((c+d)/e-)+g转换为等价的后缀表达式ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是___。​​
  • ​​2.综合题​​
  • ​​(2) 回文是指正读反读均相同的字符序列,如“abba”和“abdba"均是回文,但“good"不是回文。试写一个算法判定给定的字符串是否为回文。​​
  • ​​(6) 假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素结点,试编写相应的置空队、判队空、入队和出队算法。​​
  • ​​(8) 在循环队列中,可以设置一个标志域tag,以区分当尾指针和头指针相等时,队列状态是“空”还是“满”(tag的值为0表示“空”,tag的值为1表示“满”),编写此结构相应的队列初始化、入队、出队算法。​​
  • ​​参考​​
  • ​​1.单项选择题​​
  • ​​(1) D​​
  • ​​(2) A​​
  • ​​(3) D​​
  • ​​(4) C​​
  • ​​(5) A​​
  • ​​2.综合题​​
  • ​​(2)​​
  • ​​(6)​​
  • ​​(8)​​

配套资源下载

数据结构资源下载导航【数据结构】

第 3 章 栈和队列

第2章介绍了线性表的概念,从数据结构上看,栈和队列也是线性表,只不过是两种特殊的 线性表。栈只允许在表的一端进行插入或删除操作,而队列只允许在表的一端进行插入操作、在另一端进行删除操作。因而,栈和队列也可以被称为操作受限的线性表。从数据类型的角度来看,栈与队列是与线性表不同的抽象数据结构。

3.1 应用实例

应用实例一 迷宫求解问题

这是实验心理学中的一个经典问题,心理学家用一个无顶盖的大盒子做成“迷宫”从然后把一只老鼠从入口处赶进迷宫。迷宫中设置了很多隔断,对老鼠的前进方向构成多处障碍,在迷宫的唯一出口处放置了一块奶酪,吸引老鼠寻找正确的通路以到达出口。

可以用一个mxn的方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计
一个程序,对任意设定的迷宫,求出一条从入口到出口的通路,或得到没有通路的结论。

计算机解迷宫时,通常是采用“穷举求解”的方法,即从入口出发,沿某方向向前探索,若能 走通则继续往前走;否则沿原路返回,换一个方向再继续探索,直至所有可能的通路都探索到为止。为了保证在任何位置上都能沿原路退回,显然要用具有后进先出特性的栈来保存从入口到当前位置的路径。

应用实例二“马”踏棋盘问题

将棋子“马”随机地放在国际象棋8x8棋盘Board[8][8]的某个方格中,“马”按走棋规则进 行移动,要求每个方格只进入一次,走遍棋盘上所有的64个方格。编写(非)递归程序,求出“马” 的行走路线,并按求出的行走路线将数字1,2,…,64依次填入一个8x8的方阵并输出。

求解时可采用栈的数据结构,即将“马”的行走顺序压人栈中。每次在多个可走的位置中选 择其中一个进行试探,其余未曾试探过的可走位置必须用适当的结构妥善管理,以备试探失败时 的“回溯”(悔棋)使用。

以上实例的分析与实现将在3.4节详细介绍。

3.2栈

3.2.1 栈的概念及运算

栈(stack)是一种只允许在一端进行插人和删除操作的线性表,它是一
种操作受限的线性表

在表中允许进行插入和删除的一端称为“栈顶”(top),另一端称为“栈底”(bottom)。

栈的插入操作通常称为“入栈”或“进栈”(push),
而栈的删除操作则称为“出栈”或“退栈”(pop)。
当栈中无数据元素时,称为“空栈”。

栈具有后进先出的特性,因此又称为“后进先出”(Last In First Out,LIFO)表

栈的示意图

第三章 栈和队列【数据结构与算法】_算法

栈的基本运算有以下几种。
① InitStack(S) 初始化:初始化一个新的栈。
② Empy(S )栈的非空判断:若栈S不空,则返回TRUE;否则,返回FALSE。
③ Push(S.x) 入楼:在栈S的顶部插入元素x,若栈满,则返回FALSE;否则,返回TRUE
④ Pop(S) 出栈:若栈S不空,则返回栈顶元素,并从栈顶中删除该元素;否则,返回空元素
⑤ GetTop(S) 取栈顶元素:若栈S不空,则返回栈顶元素;否则返回空元素NULL。
⑥ SetEmpty(S) 置栈空操作:置栈S为空栈。

栈是一种特殊的线性表,因此栈可采用顺序存储结构存储,也可以使用链式存储结构存储。

3.2.2栈的顺序存储结构

1. 顺序栈

利用顺序存储的方式实现的栈称为“顺序栈”。类似于顺序表的定义,栈中的数据元素用一个
预设的足够长度的一维数组来实现:ElemType elem[MAXSIZE]。栈底位置可以设置在数组任
一个端点,而栈顶是随着插人和删除而变化的。用int top来作为栈顶的指针,指明当前栈顶的 位置,同时将elem和top封装在一个结构中。顺序栈的类型描述如下。

#define MAXSIZE<栈最大元素数>
typedef struct{
ElemType elem[MAXSIZE];
int top;
}SeqStack;

定义一个指向顺序栈的指针:

SeqStack *s;

常将0下标端设为栈底,这样空栈时校顶指针s->top=-1;入栈时,栈顶指针加1,即s->top++;出栈时,栈顶指针减1,即s->top–。

栈的基本操作如下

相关代码请看配套资源
1-顺序栈.c

#include<stdio.h>
#include<stdlib.h>

int main(){
SeqStack *s=InitStack();
printf("%d",Empty(s));//1

int x1=1;
Push(s,x1);
int x2=2;
Push(s,x2);
printf("%d",Empty(s));//0

int x3;
Pop(s,&x3);
printf("%d",x3);//2

int t=GetTop(s);
printf("%d",t);//1

}

运行结果如下

第三章 栈和队列【数据结构与算法】_算法_02

2. 多栈共享邻接空间

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

两栈共享的数据结构可定义如下。

typedef struct{
ElemType elem[ MAXSIZE];
int lefttop;//左栈栈顶位置指示器
int righttop;//右栈栈顶位置指示器
} Dupsqstack;

两个栈共享邻接空间的示意图如图3-4所示。左栈入栈时,栈顶指针加1,右栈人栈时,栈顶指针减1。由于两个栈顶均可向中间伸展,互补余缺,因此使得每个栈的最大空间均大于MAXSIZE/2。

第三章 栈和队列【数据结构与算法】_应用实例_03

为了识别左右栈,必须另外设定标志:

char status;
status ='L'; //左栈
status ='R';//右栈

在进行栈操作时,需指定栈号status=L’为左栈,status='R’为右栈;判断栈满的条件如下:

s->lefttop+1=s->righttop;

共享栈的基本操作如下

相关代码请看配套资源
2-共享栈.c

int main(){
Dupsqstack* d;
InitDupStack(d);

int l0=1;
int r0=2;
PushDupSrack(d,'L',l0);
PushDupSrack(d,'R',r0);

int l=GetDupsqTop(d,'L');
int r=GetDupsqTop(d,'R');
printf("%d",l);
printf("%d",r);

}

运行结果如下

3.2.3栈的链式存储结构

栈也可以采用链式存储结构表示,这种结构简称为“链栈”。
在一个链栈中,栈底就是结表的最后一个结点,而栈顶总是链表的第一个结点采用带头结点的单链表实现栈。因为栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针top就作为栈顶指针,top始终指向当前栈顶元素前面的头结点,即top->next为栈顶元素,当top->next==NULL,则代表栈空

1)链栈定义

//链栈的C语言定义如下。
typedef int DataType;
typedef struct Stacknode{
DataType data;
struct Stacknode * next;
}slStacktype;

2)链栈操作

相关代码请看配套资源
3-链栈.c

int main(){
slStacktype *sl=Init();

int x=1;
PushLstack(sl,x);

int y=GetLsPop(sl);
printf("%d",y);//1

int z=PopLstack(sl);
printf("%d",z);//1

}

运行结果如下

第三章 栈和队列【数据结构与算法】_出队_04

(3)多个链栈的操作

可将多个单链栈的栈顶指针放在一个一维数组中统一管理。
设一维数组top[M]:

slStacktype* top[M]

其中top[0] ,top[1],…,top[i],…,top[M-1]指向M个不同的链栈,分别是M个链栈的 栈顶指针,这样只需确定链栈号i,然后以top[i]为栈顶指针进行栈操作,即可实现各种操价。多个链栈示意图如图3-6所示。

第三章 栈和队列【数据结构与算法】_出队_05

多个链栈的基本操作

相关代码请看配套资源
4-多个链栈.c

int main(){
slStacktype * top[M];

int x1=1;
PushDupLs(top,1,x1);

int x11=GetTopDupLs(top,1);
printf("%d",x11);//1

int x2=2;
PushDupLs(top,2,x2);

int x22=GetTopDupLs(top,2);
printf("%d",x22);//2

}

运行结果如下

第三章 栈和队列【数据结构与算法】_出队_06

在上面的两个算法中,当指定栈号i(0<=i<=M-1)时,则只对第i个链栈操作,不会影响其他链栈。

3.2.4栈的应用

0. 括号匹配

判断都是括号所构成的字符串是否括号都是一一对应的
相关代码请看配套资源
5-括号匹配

int main(){
char str[10];
gets(str);
printf("%s",str);

BracketMatch(str);
}

运行结果如下

第三章 栈和队列【数据结构与算法】_出队_07

1. 算术表达式求值(上机)

表达式求值是程序设计语言编译中的一个最基本问题,它的实现方法是栈的一个典型的应用实例,
在计算机中,任何一个表达式都是由操作数(operand)、运算符(operalor)和界限符(delimiter)组成的。其中操作数可以是常数,也可以是变量或常量的标识符;运算符可以是算术运算符、关系运算符和逻辑运算值;界限符为左右括号和标识表达式结束的结束符。在本节中,仅讨论简单算术表达式的求值问题。假设在这种表达式中只含加、减、乘、除四则运算,所有的运算对象均为整型常数,表达式的结束符为“#”,即仅含符号+、一、*、/、(、)和#。

算术四则运算的规则如下。
①先乘除、后加减。
②同级运算时先左后右。
③先括号内,后括号外。

3.3 队列

3.3.1队列的概念及其运算

队列(queue)是另一种限定性线性表,它只允许插人在表的一端进行,而删除在表的另一端进行,允许插人的一端叫队尾(rear),而允许删除的一端叫队头(front)。

队列的插人操作通常为“入队”或“进队”,而队列的删除操作则称为“出队”或“退队”。当队列中无数据元素时,所“空队列”。

根据队列的定义可知,队头元素总是最先进队列,也总是最先出队列;队尾元素总最后进队列,因而也是最后出队列。这种表是按照先进先出(First In First Out,FIFO)的原则组织数据的,因此,队列也被称为“先进先出”表。

第三章 栈和队列【数据结构与算法】_数据结构_08

在队列上进行的基本操作如下。

①队列初始化:InitQutoe(ql
相始条件:队列q不存在,
操作结果:构造了一个空队列:
②人队操作:InQuene(q.x)
初始条件:队列q存在。
操作结果:对已存在的队列q,插人一个元素x到队尾。操作成功,返回值为TRUE,否则返回值为FALSE
③出队操作:OutQueue(q.x)
初始条件:队列g存在且非空。
操作结果:删除队首元素,并返回其值。操作成功,返回值为TRUE,否则返回值为FALSE
④读队头元素:FrontQueue(q,x)
初始条件:队列q存在且非空。
操作结果:读队头元素,并返回其值,队列不变。操作成功,返回值为TRUE,否则返回值为FALSE.
⑤判队空操作:EmptyQueue(q)
初始条件:队列q存在
操作结果:若q为空队列则返回为TRUE,否则返回为FALSE.

队列的顺序存储结构可以 简称为“顺序队列”,另外再设立两个指示器:一个为指向队头元素位置的指示器front,另一个为指向队尾元素位置的指示器rear

C语言中,数组的下标是从0开始的,因此为了算法设计的方便,在此约定:初始化队列时,空队列的front=rear=-1,当插人新的数据元素时,尾指示器rear加1,而当队头元素出队列时,头 指示器front加1。另外还约定,在非空队列中,头指示器front总是指向队列中实际队头元素的 前面一个位置,而尾指示器rear总是指向队尾元素(这样的设置是为了某些运算的方便,并不是唯一的方法)。

顺序队列的类型定义如下

define MAXSIZE<最大元素数>//队列的最大容量
vypedef struct{
ElemType elem[MAXSIZE];//队列元素的存储空间
int rear,front;//队尾、队头指针
}SeQueue;

定义一个指向队列的指针变量:​​SeQueue *sq;​​​ 申请一个顺序队列的存储空间:​​sq=(SeQueue*)malloc(sizeof(SeQueue));​

存在“假溢出”的问题

3.3.2循环队列

解决假溢出的方法之一是将队列的数据区elem[0~MAXSIZE-1]看成头、尾相接的循环结构,即规定最后一个单元的后维为第个单元,这样整个数据区就像一个环,我们形象地称之为循环队列

入队时队尾指针加1操作修改为

sq->rear=(sq->rear+1)%MAXSIZE

出队时队尾指针加1操作修改为

sq->rear=(sq->front+1)%MAXSIZE

又产生一个新问题队空和队满的条件相同都为​​front==rear​

解决的方法之一是少用一个元素空间,把如图所示视为队满。
队满的条件是​​​(rear+1)%MAXSIZE==front​​ 就能和队空区别开。

第三章 栈和队列【数据结构与算法】_应用实例_09

另外一种方法是附设一个存储队列中元素个数的变量,如num,当num == 0时队空,当num==MAXSIZE时队满

还有一种在习题(8)中

下面的循环队列及操作依据少用一个元素空间来实现的。

循环队列的类型定义

#define MAXSIZE 10
//下面的循环以列及操作依据少用个元素空间来实现
//循环队列的类型定义及基本运算如下。
typedef int ElemType;
typedef struct{
ElemType elem [MAXSIZE];//队列的存储区
//队头队尾指针
int front, rear;
}CSeQueue;//循环队列

循环队列的基本运算

相关代码请看配套资源
6-循环队列.c

int main(){
CSeQueue *cs=IniseQueue();

int x=1;
InSeQueue(cs,x);
printf("%d",EmptySeQueue(cs));//0

int x0;
OutSeQueue(cs,&x0);
printf("%d",x0);//1

printf("%d",EmptySeQueue(cs));//1

}

运行结果如下

第三章 栈和队列【数据结构与算法】_循环队列_10

3.3.3 链队列

链式存储的队列称为链队列
可以采用带头结点的单链表表示队列。
头指针始终指向头结点,尾指针指向当前最后一个元素结点

//链队列的数据类型描述如下。
typedef struct node{
DataType data;
struct node * next;
}QNode;
//链队列结点的类型
typedef struct{
QNode * front;
QNode * rear;
} LQueue;//将头尾指针封装在一起的链队列

链队列的基本运算

相关代码请看配套资源
7-链队列.c

int main(){
LQueue *lq=Init_LQueue();
printf("%d",Empty_LQueue(lq));//1

int x=1;
InLQueue(lq,x);
printf("%d",Empty_LQueue(lq));//0

int x0;
Out_LQueue(lq,&x0);
printf("%d",x0);//1

printf("%d",Empty_LQueue(lq));//0
}

运行结果如下

第三章 栈和队列【数据结构与算法】_循环队列_11

3.4 实例分析与实现

应用实例一 迷宫求解

应用实例二 马踏棋盘

应用实例三 计算器

第三章应用实例【数据结构】

3.5 算法总结 ——递归与分治 算法

习题3

题目

1.单项选择题

(1)(2010考研真题)若元素a,b,e,d,e,f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈操作,则不可能得到的出栈序列是____。

A. d,c,e,b,f,a
B. c,b,d,a,e,f
C. b.c,a,e,f,d
D. a,f,e,d,c,b

(2)若某堆栈的输人序列为1,2,3,n-l,n,输出序列的第1个元素为n,则第i个输出元素为____。

A. n-i+1
B. n-1
C. i
D.哪个元素都有可能

(3)以数组Q[0…m-1]存放循环队列中的元素,变量rear和qulen分别指示循环队列中队尾元素的实际位置和当前队列中元素的个数,队列第一个元素的实际位置是___。

A. rear-qulen
B. rear-qulen+m
C. m-qulen
D. (1+(rear-qulentm))mod m

(4)设输人元素为1,2,3,A,B,输人次序为123AB,元素经过栈后到达输出序列,当所有元素均到达输出序列后,序列____是不可能的。

A. BA321
B. A3B21
C. B32A1
D. AB321

(5)(2012年考研真题)已知操作符包括“+”“一”“”“/”“(”和“)”。将中缀表达式a+b-a((c+d)/e-)+g转换为等价的后缀表达式ab+acd+e/f-*-g+时,用栈来存放暂时还不能确定运算次序的操作符。若栈初始时为空,则转换过程中同时保存在栈中的操作符的最大个数是___。

A. 5
B. 7
C. 8
D.11

2.综合题

(2) 回文是指正读反读均相同的字符序列,如“abba”和“abdba"均是回文,但“good"不是回文。试写一个算法判定给定的字符串是否为回文。
(6) 假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素结点,试编写相应的置空队、判队空、入队和出队算法。
(8) 在循环队列中,可以设置一个标志域tag,以区分当尾指针和头指针相等时,队列状态是“空”还是“满”(tag的值为0表示“空”,tag的值为1表示“满”),编写此结构相应的队列初始化、入队、出队算法。

参考

1.单项选择题

(1) D
(2) A
(3) D
(4) C
(5) A

2.综合题

(2)

相关代码请看配套资源
2.c

int main(){
char str[10];
gets(str);
printf("输入字符串\n");
printf("%s",str);

BracketMatch(str);
}

运行结果如下

第三章 栈和队列【数据结构与算法】_循环队列_12

(6)

相关代码请看配套资源
6.c

int main(){
XQueue *lq=Init_XQueue();
printf("%d",Empty_XQueue(lq));//1

int x1=1;
InXQueue(lq,x1);
printf("%d",Empty_XQueue(lq));//0

int x2=2;
InXQueue(lq,x2);

int y1;
Out_XQueue(lq,&y1);
printf("%d",x1);//1

int y2;
Out_XQueue(lq,&y2);
printf("%d",y2);//2

printf("%d",Empty_XQueue(lq));//0
}

运行结果如下

第三章 栈和队列【数据结构与算法】_算法_13

(8)

相关代码请看配套资源
8.c

int main(){
CSeQueue *cs=IniseQueue();
printf("%d",EmptySeQueue(cs));//1

int x1=1;
InSeQueue(cs,x1);

int x2=2;
InSeQueue(cs,x2);

printf("%d",ManSeQueue(cs));//1

int y1;
OutSeQueue(cs,&y1);
printf("%d",y1);//1

printf("%d",EmptySeQueue(cs));//0

int y2;
OutSeQueue(cs,&y2);
printf("%d",y2);//2

printf("%d",EmptySeQueue(cs));//1

}

运行结果如下

第三章 栈和队列【数据结构与算法】_算法_14


举报

相关推荐

0 条评论