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