Transcript Chapter 3
数据结构
刘家芬 Sept 2012
第三章 栈和队列
•
•
•
•
栈和队列也是线性表
栈和队列的基本操作是线性表操作的子集
栈和队列是两种应用非常广泛的数据结构
本章目标
– 栈和队列的基本概念
– 栈和队列的存储结构
– 栈和队列的基本操作
– 栈和队列的操作实现
栈的概念
• 栈(Stack):是限制在表的一端进行插入和
删除操作的线性表。又称
–后进先出LIFO (Last In First Out)线性表
–先进后出FILO (First In Last Out)线性表
出栈
进栈
栈顶
an
栈底
a3
a2
a1
栈顶和栈底
• 允许插入和删除的一端称为栈顶 (top),
也叫表尾;另一端称为栈底(bottom)。
– 指示栈顶位置的指针称为栈顶指针top。
– 栈底也可以用一个指针bottom来指示。
– 当表中没有元素时称为空栈。进栈(push)
• 栈的基本操作
– 入栈和出栈
– 初始化栈
– 判断元素个数
– 取栈顶元素
top
bottom
出栈(pop)
an
⋯⋯
ai
⋯⋯
a2
a1
栈的抽象数据类型定义
ADT Stack
{
数据对象:D ={ ai | ai∈ElemSet, i=1,2,…,n,n≥0 }
数据关系:R ={<ai-1, ai> |ai∈D, i=2,3,…,n }
基本操作: InitStack(&S)
ClearStack(&S)
StackLength(S)
DestroyStack(&S)
StackEmpty(S)
GetTop(S, &e)
Push(&S, e)
Pop(&S, &e)
StackTraverse(S, visit());
} ADT Stack
栈的表示和实现
• 与线性表类似,栈也有两种存储表示方法
– 栈的顺序存储结构
– 栈的链式存储结构
栈的顺序存储结构
• 和线性表类似,栈的顺序存储结构也是利
用一组连续的存储单元依次存放从栈底到
栈顶的数据元素,我们称为顺序栈。
#define STACK_INIT_SIZE 100;
#define STACKINCREMENT 10;
typedef struct{
SElemType *base; //栈底指针
SElemType *top;
//栈顶指针
int
stacksize;
//栈的当前最大可用容量
}SqStack;
SqStack S;
栈的顺序存储结构
• base称为栈底指针,始终指向栈底;
当base = NULL时,表明栈结构不存在。
• top为栈顶指针
a. top的初始值指向栈底,即top=base
b. 空栈:当top=base时为栈空的标记
c. 当栈非空时,top的位置:指向当前栈
顶元素的下一个位置
• stacksize ——当前栈可使用的最大容量
进栈、出栈操作
top
c
b
a
top
top
bottom
a
bottom
元素b,c进栈
bottom
元素a进栈
空栈
top
top
b
a
bottom
元素c出栈
f
e
d
b
a
bottom
元素d,e,f进栈
进栈、出栈操作说明
• 栈空条件
– s. top =s. base,此时不能出栈
• 栈满条件
– s.top-s.base>=s.stacksize,此时不能进栈
• 进栈操作
– *s.top=e;
s.top++;
• 退栈操作
– s.top--;
e=*s.top;
• 当栈满时再做进栈运算会产生空间溢出。
• 当栈空时再做退栈运算也会产生溢出。
顺序栈基本操作算法——构造空栈
顺序栈基本操作算法——取栈顶元素
顺序栈基本操作算法——入栈
顺序栈基本操作算法——出栈
栈的链式存储表示
• 栈的链式存储结构称为链栈,其插入和删
除操作只能在表头位置上进行。
• 链栈的头结点在栈底,栈顶指针top用于标
识整个链表。
top
⋀
top
a3
空栈
a2
a1
⋀
非空栈
栈的链式存储表示
•
链栈的结点类型说明如下:
typedef struct StackNode{
SElemType
data;
struct StackNode *next;
}StackNode, *StackPtr;
StackPtr S;
链栈的基本操作——初始化
Status InitStack(StackPtr &S)
{
S = (StackPtr)malloc(sizeof(StackNode));
if (!S) return ERROR;
S->next = NULL;
return OK;
}
链栈的基本操作——入栈
Status Push(StackPtr &S, SElemType e)
{
p = (StackPtr)malloc(sizeof(StackNode));
if (!p) return ERROR;
p
p->data = e;
p->next = S;
S = p;
return OK;
}
S
e
链栈的基本操作——出栈
Status Pop(StackPtr &S, SElemType &e)
{
if (!(S->next)||!S) return ERROR; //空栈
p = S;
S
e = S->data;
S = S->next;
free(p);
return OK;
}
栈的应用举例——数制转换
• 十进制整数N向d进制数的转换(d=2,8,16)是计
算机的一个基本问题。
• 转换规则对应于一个简单算法原理:
n=(n div d)*d + n mod d
其中:div为整除运算, mod为求余运算
• 例如 (255)10= ( ?)8
n
n div 8
n mod 8
255
31
7
31
3
7
3
0
3
栈的应用举例——数制转换
• 要求:输入一个非负十进制整数,输出对
应的八进制数。
栈的应用举例——括号匹配
• 匹配思想:从左至右扫描一个字符串(表达式),
每个右括号将与最近遇到的那个左括号相匹配。
• 在从左至右扫描过程中把所遇到的左括号存放到
堆栈中。每当遇到一个右括号时,就将它与栈顶
的左括号(如果存在)相匹配,同时从栈顶删除该
左括号。
• 算法思想:设置一个栈,当读到左括号时,左括
号进栈。当读到右括号时,则从栈中弹出一个元
素,与读到的左括号进行匹配,若匹配成功,继
续读入;否则匹配失败,返回FLASE。
栈的应用举例——行编辑程序
• 设立输入缓冲区以接收用户输入的一行字
符,并逐行存入用户数据区。
• 允许用户修改输入,用#表示前一个字符无
效,用@表示当行字符无效。
• 例如:
whli##ilr#e(s#*s)
outcha@putchar(*s=#++);
实为
while(*s)
putchar(*s++)
栈的应用举例——行编辑程序
• 算法:每当从终端接受一个字符就进行判
断:
– 当前为普通字符,则入栈
– 当前为#,则出栈一个字符
– 当前为@,则清空栈。
栈的应用举例——迷宫探索
• 思路
栈的应用举例——表达式求值
• 算术表达式组成要素
– 运算符、操作数、界限符,不同的运算符优先级不同
• 基本思想
–
–
–
–
设置数栈OPND,寄存操作数或运算结果;
设置运算符栈OPTR,寄存运算符
首先置数栈为空,表达式起始符#进栈
依次读入表达式中的每个字符,若是操作数则入数栈
,若是运算符则与OPTR栈顶运算符进行优先级比较
后作相应操作,直至表达式求值完毕
• 若当前运算符小于栈顶运算符,数栈栈顶两个元素出栈,运
算符栈栈顶元素出栈,进行计算并将结果存入数栈
• 若当前运算符等于栈顶运算符,运算符栈栈顶元素出栈
• 若当前运算符大于栈顶运算符,则新运算符入栈
上机练习
• 表达式求值的算法请大家自行实现。
• Please implement the algorithm to
compute a mathematical expression with
+,-,*,/,(,) by yourself.
栈与递归的实现
• 栈的另一个重要应用是在程序设计语言中实现递
归调用。
• 递归调用:一个函数直接或间接地调用自己本身
,简称递归(Recursive)。
• 有效的递归调用函数应包括两部分:递推规则和
终止条件。
递归举例
如:Fibonacci数列的递归实现
• Fib(n)=
0
若n=0
1
若n=1
Fib(n-1) + Fib(n-2)
其他情形
int Fib(int n)
1 {
2
if (n == 0 || n ==1)
3
return n;
4
else
5
return Fib(n-1) + Fib(n-2);
6 }
回忆函数调用
• 被调函数运行之前,系统的处理事项:
– 将所有的实参、返回地址等信息传递给被调函数保存
– 为被调函数的局部变量分配存储区
– 将控制转移到被调函数的入口
• 从被调函数返回调用函数之前,系统的处理事项:
– 保存被调函数的计算结果
– 释放被调函数的数据区
– 依照被调函数保存的返回地址将控制转移到调用函数
• 调用函数和被调函数之间的连接和信息交换是通过
栈进行的
栈与函数调用
• 系统将整个程序运行时所需的数据空间安
排在一个栈中。
– 每当调用一个函数时,就为它在栈顶分配一个
存储区(存放实参、局部变量及返回上一层的
地址等,即工作记录)。
– 每当从一个函数返回时,执行:
• 从栈顶弹出一个工作记录。
• 将工作记录中的参数值、局部变量值赋给相应的变
量;读取返回地址。
• 将函数值赋给相应的变量。
• 转移到返回地址。
队列的基本概念
• 队列是一种先进先出(First In First Out ,简
称FIFO)的线性表。
• 限定插入操作只能在队尾进行,而删除操
作只能在队首进行。
• 例如:排队购物、操作系统中的作业排队
等,先进入队列的成员总是先离开队列。
队列的抽象数据类型定义
ADT Queue{
数据对象:D ={ ai|ai∈ElemSet, i=1, 2, …, n, n >= 0 }
数据关系:R = {<ai-1, ai> | ai∈D, i=2,3,…,n }
约定a1端为队首,an端为队尾。
基本操作:
Create():创建一个空队列;
EmptyQue():若队列为空,则返回true ,否则返回flase ;
⋯⋯
InsertQue(x) :向队尾插入元素x;
DeleteQue(x) :删除队首元素x;
} ADT Queue
队列的表示和实现
• 队列也可以采用顺序存储结构或链表结构
来实现,分别称为
– 顺序队列
– 链队列
链队列
• 链队列:只能在表头进行删除操作、只能在表尾
进行插入操作的线性链表。为了方便起见,链队
列也设有一个头结点。
• 两个指针:
– 队头指针Q.front指向头结点
– 队尾指针Q.rear指向尾结点 Q.front
• 初始态:队空条件
– 头指针和尾指针均指向头结点
Q.front = Q.rear
data
next
头结点
队首
Q.rear
队尾
队列运算指针变化状况
Q.front
空队列
Q.rear
Q.front
元素x入队
x
Q.rear
Q.front
Q.rear
Q.front
Q.rear
x
y
元素y入队
x
y
元素x出队
队列的链式存储实现
• 链队列由头指针和尾指针唯一确定
链队列的基本操作——构造空队列
链队列的基本操作——销毁队列
• 注意:这里销毁队列的前提是队列为空。
链队列的基本操作——元素入队
链队列的基本操作——元素出队
• 注意:当删除队列中最后一个元素时,队尾指针会丢失,
因此需对其重新赋值,指向头结点
队列的顺序表示和实现
• 利用一组连续的存储单元(一维数组) 依次
存放从队首到队尾的各个元素,称为顺序
队列。设两个指针:
– Q.front 指向队列头元素;
– Q.rear 指向队列尾元素的下一个位置
• 初始状态(空队列):
– Q.front = Q.rear=0
队列操作与指针变换
•
•
•
•
•
当插入新元素到队尾时,rear加1
当删除队首元素时,front加1
队首指针front始终指向队头元素
队尾指针rear 始终指向队列元素的下一个位置
front = rear表示队空
队列的假溢出
• 在入队和出队操作中,头、尾指针永远只
增加不减小,使被删除元素的空间无法重
新利用。
• 尽管队列中实际元素个数可能远远小于数
组大小,但可能由于尾指针巳超出分配空
间的上界而不能进行入队操作。该现象称
为假溢出。
• 解决办法:循环队列
循环队列
• 为了克服假溢出,将为队列分配的向量空
间看成为一个首尾相接的圆环,并称这种
队列为循环队列(Circular Queue)。
– 在循环队列中进行出队、入队操作时,
仍对队首、队尾指针加1。
– 当队首、队尾指针指向数组
上界 (maxsize-1)时再退回0。
maxsize-1
c
d 0
b
a
1
Q.front
2
Q.rear
循环队列的操作
•
•
•
•
•
队头指针进1: Q.front = (Q.front + 1)% MAXSIZE
队尾指针进1: Q.rear = (Q.rear + 1)% MAXSIZE;
队列初始化:Q.front = Q.rear = 0;
Q.front == Q.rear可能是队空也可能是队满
解决方法:入队前,测试尾指针在循环意义下加1后是否等
于头指针,若相等则认为队满。
一般情况
队列满
队列空
循环队列的实现
循环队列的基本操作——构造、长度
循环队列的基本操作——入队、出队
银行客户排队系统模拟
• 某银行有四个窗口。编制程序记录银行的客户服
务情况,并计算当天客户的平均逗留时间。P65
• 事件
– 类型:客户到达和四个窗口的客户离开。
– 时间:时间发生时间
• 事件考虑用“链表”进行存储。
窗口排队模拟
• 四个窗口队列
–
–
–
–
每个窗口队列中含有多个客户信息
每个客户信息包括:客户到达时间和办理业务所需时间
每个窗口队列中的队首客户是正在办理业务的客户
每个队头客户即将驱动一个客户离开事件
• 窗口队列考虑用“队列”进行存储。
业务记录算法概要
业务记录算法实现
• 客户到达时间服从平均分布,程序实现时用随机
数进行模拟。
– 假设第一个客户到达时间为0分
– 每个客户到达时需要
• 生成一个随机数,模拟该客户办理业务所需时间
• 生成另一个随机数,模拟下一个客户到达的时间间隔
• 客户到达:一个客户到达,将产生另一个新
客户到达事件,并将其插入事件链表;同时
将客户信息插入到当前最短的窗口队列中。
– 如果客户是队首客户,将同时生成该窗口的离开事件,并插入事
件链表。
• 客户离开:计算客户逗留时间,并生成下一
个客户离开事件
具体算法