课件下载

Download Report

Transcript 课件下载

第三章 栈与队列
两种重要的,限定操作的线性结构。
学习要点:
 栈的基本概念、结构特征和操作要点。
 栈在顺序存储和链式存储结构下各种操作的实现,栈满与栈
空判定条件。
 队列基本概念、结构特征和操作要点。
 队列在两种存储结构下各种操作如何实现,队满与队空判定
条件。
 循环队列基本概念,循环队列的队空与队满判定条件。
 栈和队列的基本应用和应用要点。
§3.1 栈
3.1.1 栈的基本概念
栈的例子:操作系统中中断现场的保留等。
出栈
进栈
定义:限定只能在表的一端进行插入或删
除操作的线性表。
栈顶
an-1
…
修改原则:后进先出(LIFO)
a1
栈底
a0
图 3-1 栈示意图
练习:
 设进栈顺序为1、2、3、4,请说出以下的出栈
顺序是否可能?
A、1 2 3 4
B、4
× 3 1 2
C、2 1 4 3
D、3 2 1 4
E、3 4 2 1
F、3
× 4 1 2
栈的抽象数据类型:












ADT Stack is
{数据对象:D={ai|ai∈DataType,i=0,1,2,…,n-1,n≥0}
数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=1,2,…,n-1}约定an-1为栈
顶,a0为栈底。
基本操作:
(1)Stack InitStack() 初始化并返回一个空栈;
(2)ClearStack (Stack S) 清空栈S中的元素;
(3)int IsEmpty(Stack S) 判断栈是否为空,返回栈空或非空标志;
(4)int IsFull(Stack S)
判断栈是否满,返回栈满或不满标志;
(5)Stack Push(Stack S, DataType x)
若栈未满,将数据元素x入栈;
(6)Stack Pop(Stack S) 若栈非空,删除栈顶元素;
(7)DataType GetTop(Stack S)
若栈非空,返回栈S中的取栈顶元素;
} ADT Stack
3.1.2 栈的顺序存储结构
 数组定义栈:stack[max]
 规定:top指向栈顶元素所在位置
 初始:top=-1(空栈)
max-1
空栈:top=-1,出栈下溢
…
栈满:top=max-1,进栈上溢
1
0
3.1.2 栈的顺序存储结构2
top
aMAX-1
MAX-1
MAX-1
MAX-1
…
…
…
…
2
2
2
a2
1
a1
0
top
1
1
a1
0
0
a0
top
(a)空栈
(b)栈中有两个元素
图3-2 顺序栈各种情况
a0
(c)栈满
3.1.2 栈的顺序存储结构3
用C语言定义栈的顺序存储结构如下:
#define MAXSIZE 100
/* MAXSIZE指的是栈的最大长度 */
typedef struct
{DataType elem[MAXSIZE];
/* 定义数组依次存放栈里的数据元素*/
int top;
}Stack;
/* 指向栈顶元素的下标 */
基本操作:顺序栈初始化
算法要求返回一个空的栈,空栈中数据元素个数为0,top值为-1。
00
Stack InitStack()
01
{
02
Stack S;
03
S.top=-1;
04
return(S);
05
}
/* 空栈的top值为-1 */
基本操作:进栈
已知栈S及数据元素x,需要将x放置进栈S
00
01
02
03
04
05
06
07
08
09
10
Stack Push(Stack S, DataType x)
/* 若栈未满,将数据元素x入栈 */
{
if (S.top==MAXSIZE-1)
printf(“栈已满,无法入栈!”);
else
{
S.top=S.top+1;
S.elem[S.top]=x;
}
return(S);
}
基本操作:出栈
00
Stack Pop(Stack S) /* 若栈非空,删除栈顶元素 */
01
{
02
if (S.top== -1)
03
04
printf(“栈是空的,无法出栈!”);
else
05
06
07
S.top=S.top-1;
return(S);
}
基本操作:取栈顶元素
已知栈S,取出栈顶元素
00
DataType GetTop(Stack S)
/* 若栈非空,取栈顶元素赋值给x */
01
02
{
if (S.top== -1)
03
04
printf(“栈是空的,无法取栈顶!”);
else
05
06
07
x=S.elem[S.top];
return(x);
}
多栈共享技术:双端栈
原理:利用栈只能在栈顶端进行操作的特性,将两个栈的栈底分别设在
数组的头和尾,两个栈的栈顶在数组中动态变化,栈1元素比较多时就
占用比较多的存储单元,元素少时就让出存储单元供栈2可用,提高了
数组的利用率。
0
S1
1
2 ……
S1.top
……
共享区间
图3-3 双端栈
S2.top
MAX-1
S2
3.1.3 栈的链式存储结构
规定:top指向栈顶元素地址
链栈中结点结构和单链表一样,指针域指向
次顶元素。
data
link
用C语言可定义如下:
00
01
02
03
04
05
06
struct node
{
DataType data;
struct node *next;
};
typedef struct node StackNode;
StackNode *top;
设指针域指向次顶结点
3.1.4 栈的链式存储结构
top
an-1
…
a1
an-1
栈顶
a0
图3-5 链栈
…
a1
a0 ^
栈底
基本操作:链栈初始化
初始化链栈即建立一个空的链栈,只有top结点,其指针域为空。
00
StackNode *InitStack()
01
{
02
StackNode *top;
03
top=( StackNode *) malloc (sizeof(StackNode));
04
top->next=NULL;
05
return(top);
06
}
top
^
基本操作:进栈
已知一个数据元素x以及链栈top,要求完成x进栈操作。
top
p
×
75
x
^
33
26
…
18 ^
基本操作:进栈2
00
StackNode * Push(StackNode *top,DataType x)
01
{
02
StackNode *p;
03
p=( StackNode *) malloc (sizeof(StackNode)); /*
申请结点p */
04
p->data=x;
05
p->next=top->next;
06
top->next=p;
07
return(top);
08
}
/* x存储到新结点p中 */
/* p指向栈顶结点 */
/* top结点指向p,p成为新的栈顶 */
基本操作:出栈
top
×
75
33
26
…
18
^
基本操作:出栈2
00
StackNode * Pop(StackNode *top)
01
{
02
StackNode *p;
03
if (top->next==NULL)
04
/* 判断栈是否为空 */
{
05
printf(“栈空,无法出栈!”);
06
return(top);
07
}
08
p=top->next;
09
top->next=p->next;
10
free(p);
11
return(top);
12
}
/* p指向栈顶,待删除 */
/* top结点指针域跳过p,指向p的后继 */
/* 释放p占用的空间 */
3.1.4 栈的应用
1.栈在递归程序中的应用——阶乘运算
递归问题的解决可以大致做这样的算法描述:
if (递归结束条件)
return
(递归结束条件下的返回值);
else
return (递归计算公式);
3.1.4 栈的应用
1.栈在递归程序中的应用——阶乘运算
解释:
●递归计算公式:是大问题在变成次大问题中的表现出来的规律,
要表达清楚这个大问题与下一级的次大问题有什么联系。
●递归结束条件:解决递归问题中的分解不能无终止地分解下去,
需有一个结束的条件。这样才可以由结束递归再返回层层解套,
最终解决整个问题。递归的结束条件也称为递归出口。
3.1.4 栈的应用
例子:n的阶乘的计算
n!=
1 (n=0,1)
n*(n-1)!
(n>1)
3.1.4 栈的应用
例子:n的阶乘的计算
00
int fact (int n)
01
{
fact (3)=3*fact (2)
02
int fac;
03
if (n==0)
04
fac=1;
05
else
06
07
08
④
结果输出
fact (0)=1
fact (1)
=1*f act (0)=1
loc 1
loc0
return fac;
fact (1)=1*fact (0)
②
①
fac=fact (n-1)*n;
}
fact (2) =2*fact (1)
③
3.1.4 栈的应用2
2.栈在递归程序中的应用——Hanoi塔问题
解释Hanoi游戏:
(1)设有三根杆子A,B,C。A杆上有n个盘子,从小到
大相叠着,编号1—n;
(2)每次移动一块盘子到另外一个杆上,但要求每个杆
上的盘子只能是小的叠在大的上面;
(3)把所有盘子从A杆全部移到C杆上,可利用B杆作
为过渡。
3阶Hanoi问题求解过程:
A
B
C
A
1
2
3
1
2
3
A
B
3
1
2
A B
B
C
2
3
C
A
B
C
1
2
A B
3
C
1
2
3
C
A
B
C
1
3
2
1
A
B
1
2
C
3
3.1.4 栈的应用2
n阶Hanoi递归算法:
00
void Hannoi(int n,char A,char B,char C)
01
{
02
if(n==1)
03
Move(1,A,C);
04
else
05
{
/* 将1号盘从A杆移到C杆 */
06
Hannoi(n-1,A,C,B);
07
Move(n,A,C);
08
Hannoi(n-1,B,A,C);
09
10
}
}
/* 将A杆上n-1个盘借助C杆移动到B杆 */
/* 将n号盘从A杆移到C杆 */
/* 将B杆上n-1个盘借助A杆移动到C杆 */
3.1.4 栈的应用3
n阶Hanoi非递归算法见3.1.4节算法3-11。
3.1.4 栈的应用3
3.栈在括号匹配中的应用
问题理解:
假设一个算术表达式中包含圆括号、方括号和花括号
三种类型的括号,编写一个算法,判别表达式中括号是否
正确配对。
如“fha{bh(gf[hg]gt)q}gfr”是正确匹配的,
“fds[fsd(gfd]g”则缺少“)”。
3.1.4 栈的应用3
3.栈在括号匹配中的应用
程序见3.1.4节算法3-12。
程序设计要点:
顺序扫描被判别的表达式,每当遇到"("、"["或"{"时,
将其压入栈中(算法3-12第11-14行);当遇到")"、"]"或"}
"时,先检查栈是否是空的,如果是空的,表示缺少对应的左边括
号,接着检查当前栈顶元素是否是对应的"("、"["或"{",若是
则退栈,否则返回表示不匹配。如对")"的处理(程序中第1528)。当整个算术表达式检查完毕时,还要对栈的情况进行判断,
如栈是空的说明所有左右括号相匹配,否则缺少栈内左括号相对应
的右括号(程序中第56-59行)。
3.1.4 栈的应用4
4.栈在表达式求值中的应用
(1)由计算规则得算符优先关系表
3+5*3
θ1:先来的算符
θ1 θ2
+
\
(
3+5+5
θ2:后来的算符
+
-
*
)
>
>
>
<
>
>
>
<
<
<
>
<
>
>
>
=
4.栈在表达式求值中的应用2
(2)原理
两工作栈:一个算符栈,一个操作数栈
a.读取到操作数,直接入操作数栈
b.读取到运算符,取运算符栈栈顶元素与该算符比较优
先级:
“<”:读取符号入运算符栈;
“>”:运算符栈出一元素,操作数栈出两元素,
运算后,结果放回操作数栈;
“=”:运算符栈出一元素,与之相抵消。
4.栈在表达式求值中的应用3
计算#3*(15-5)/2#栈的变化过程
运算符栈
操作数栈
剩余表达式
执行操作
#
∧
(15-5)/2#
操作符“#”入栈
#,
(
∧
15-5)/2#
操作符“
(”入栈
#,
(
15
-5)/2#
#,
(,-
15
5)/2#
操作符“-”入栈
#,
(,-
15,5
)/2#
操作数“5”入栈
#,
(,-
15,5
)/2#
进行运算:
“15” “-” “5”#
#,
(
10
)/2#
“-”
,
“15”
,
“5”出栈,
“10”入栈
#
10
/2#
“
(”与 “)
”抵消:
“
(”出栈
#,/
10
2#
操作符“/”入栈
#,/
10,2
#
操作数“2”入栈
#
10,2
#
进行运算:
“10” “/” “2”
#
5
#
“/”
,
“10”
,
“2”出栈,
“5”入栈
∧
5
∧
操作数“15”入栈
# 与 #抵消:#出栈,10 为表达式结果
4.栈在表达式求值中的应用2
(2)原理
算法结束标志:运算符栈为空,操作数栈有一元
素,即为结果。
表达式求值算法见3.1.4节算法3-13。
上机(选做):
 输入十进制数,转换成R进制数。
 输入一包含“(”和“)”的字符串,检测括号是否匹配(其中括
号中能嵌套括号),并输出括号是否匹配的信息(匹配、缺少
左括号、缺少右括号);
分析:
 gets(fun)
 while (fun[i]!=’\0’)

{if (fun[i]==’(‘):入栈;

if (fun[i]==‘)’):

if 栈空:错,缺左边,return;

else 出栈;


i=i+1;
}
 如栈空
 否则
对!!!
错,缺少右边
§3.2 队列
3.2.1 队列基本概念
 队列的例子:排队等。
定义:限定只能在表的一端进行插入,在表的另一端
进行删除的线性表。
修改原则:先进先出(FIFO)
出队列
进队列
a0
队头
a1
a2
…
an-1
队尾
图3-11 队列示意图
队列抽象数据类型:
ADT Queue iQ
{基本操作:
数据对象:D={ai|ai∈DataType,i=0,1,2,…,n-1,n≥0}
数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=1,2,…,n-1}约定an-1为队尾,a0为队头。
(1)Queue InitQueue()
初始化并返回一个空队列;
(2)ClearQueue (Queue Q)
清空队列Q中的元素;
(3)int IsEmpty(Queue Q)
判断队列是否为空,返回队列空或非空标志;
(4)int IsFull(Queue Q)
判断队列是否满,返回队列满或不满标志;
(5)Queue InserQ(Queue Q, DataType x)
若队列未满,将数据元素x入队列;
(6)Queue DeleteQ(Queue Q)
若队列非空,删除队头元素;
(7)DataType GetHead(Queue Q)
(8)DataType GetRear(Queue Q)
} ADT Queue
若队列非空,返回队列Q中的队头元素;
若队列非空,返回队列Q中的队尾元素;
3.2.2 顺序队列与循环队列
 数组定义队列:queue[max]
 规定:front指向队头元素位置
rear指向队尾元素的下一个空位
 初始:front=rear=0
 队空:front=rear
 队满:rear=max
max-1
…
1
0
3.2.2 顺序队列与循环队列2
rear
n-1
n-1
…
…
n-1
…
rear
an-1
rear
…
…
front
3
a3
3
3
2
2
a2
2
2
1
a1
1
1
0
a0
0
0
1
rear
0
front
(a)空队列
3
n-1
front
(b)队列中有3个元素
(c)队列3个元素出队
图3-12 顺序队列各种情况
front
(d)队满
3.2.2 顺序队列与循环队列3
 假溢出的问题:用循环队列来解决
 下一个rear计算公式:rear=(rear+1)mod max
rear
rear
front
d
c
b
67 01
2
5
4 3
a
front
c
b
67 01
2
5
4 3
a
front
d
c
b
front
e
f
67 01
2
5
4 3
g
a h
rear
6
5
7 01
2
4 3
rear
3.2.2 顺序队列与循环队列4
 循环队列队空队满标志冲突的问题:
 以牺牲一个存储空间为代价,当判断到(下一个
rear==front)时,即认为队满。
 实际上,max个空间只存放了(max-1)个元素
3.2.2 顺序队列与循环队列5
 循环队列队空、队满标志:
 队满:(rear+1)mod max==front
 队空:front=rear
冲突解决!!
练习:
 设循环队列容量为70(序号0~69),现经过一系
列的入队和出队后,问下列情况下循环队列中各有几
个元素?
(1)front=14,rear=21
(2)front=23,rear=12
基于C语言的顺序队列的类型定义:
00
01
02
03
04
05
06
#define MAX 100
typedef struct
{
DataType elem[MAX];
/* 定义数组依次存放队列里的数据元素 */
int front;
/* 指向队头元素的下标 */
int rear;
/* 指向队尾元素的下一个空位 */
}Queue;
基本操作:初始化队列
00
Queue InitQueue()
01
{
02
Queue Q;
03
Q.front=Q.rear=0;
/* 队列初始化时front=rear=0 */
04
05
return(Q);
}
基本操作:进队列
00
01
02
03
04
05
06
07
08
09
10
Queue InserQ (Queue Q, DataType x)
{
if ((Q.rear+1) % MAX== Q.front)
printf(“队列已满,无法进队!”);
else
{
Q.elem[Q.rear]=x;
/* x进队列 */
Q.rear = (Q.rear+1) % MAX;
/* rear指向队尾下一个空位 */
}
return(Q);
}
基本操作:出队列
00
Queue DeleteQ (Queue Q)
/* 若栈非空,删除栈顶元素 */
01
02
{
if (Q.rear == Q.front)
03
04
printf(“队列是空的,无法出队!”);
else
05
06
07
Q.front=(Q.front+1)% MAX;
return(Q);
}
基本操作:访问队头元素
00
DataType GetHead (Queue Q)
/* 若队列非空,取队头元素赋值给x */
01
02
{
if (Q.rear == Q.front)
03
04
printf(“队列是空的,无法取队头!”);
else
05
06
07
x=Q.elem[Q.front];
return(x);
}
3.2.3 队列链式存储结构
rear
front
a0
队头










1
2
3
4
5
6
7
8
9
10
a1
…
an-1 ^
队尾
struct node
{ DataType data;
/* 链队列结点数据域类型及名称 */
struct node *next; /* 指针域类型及名称,指向下一结点 */
};
typedef struct node QueueNode;
struct node2
{ QueueNode *front ;
QueueNode *rear ;
};
typedef struct node2 Queue;
基本操作:链队列初始化
00
Queue InitQueue()
01
{
02
Queue Q;
03
Q.front=( QueueNode *) malloc (sizeof(QueueNode));
04
Q.front->next=NULL;
05
Q.rear=Q.front
06
return(Q);
07
}
∧
front rear
基本操作:进队列
00
Queue InserQ(Queue Q, DataType x)
01
{
02
QueueNode *p;
03
p=(QueueNode *) malloc (sizeof(QueueNode));
04
p->data=x;
/* x存储到新结点p中 */
05
p->next=NULL;
/* p的指针域置空 */
06
Q.rear->next=p;
/* 队尾指针域指向p,p成为新的队尾 */
07
Q.rear=p;
08
return(Q);
09
}
/* 队尾指针rear指向p,保证rear指向新的队尾 */
front
75
33
26
18 ^
…
p
x
rear
^
基本操作:出队列
00
01
02
03
04
Queue DeleteQ(Queue Q)
{
QueueNode *p;
if (Q.front==Q.rear)
/* 判断队列是否为空 */
{ …… }
08
09
10
11
p= Q.front->next;
/* p指向队头结点,待删除 */
Q.front->next=p->next; /* top结点指针域指向p的后继 */
if (p== Q.rear)
/* 如被删结点也是队尾结点 */
Q.rear= Q.front;
/* rear指向头结点 */
12
13
free(p);
return(Q);
14
/* 释放p占用的空间 */
front
rear
}
×
75
33
26
…
18
^
3.2.4 队列应用
1.共享打印机
2.CPU资源分配
3.2.4 队列的应用2
3.打印杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
……
过程分3步骤:
(1)第6行中的第一个元素1进队。
(2)输出第5行中的前5个元素,并生成第6
行中的5个元素。循环5次,做:输出队头,
队头出队列赋值给x,x与新队头相加进队列。
(3)输出第5行中的最后一个元素1并换行,
出队列。
(4)第6行中的最后一个元素1进队。
程序见3.2.4节算法3-21。
基于循环队列的杨辉三角显示:
front
第三行显示
1
rear
第三行第 1 数据出队
3
3
1
3
3
1
1
3
1
1
1
1
front
第四行行首进队
1
rear
第四行第 2 数据进队
rear
front
4
3
rear
front
第四行第 3 数据进队
第三行第 2 数据出队
4
4
3
rear
第四行第 4 数据进队
第三行第 3 数据出队
4
6
4
1
rear
第四行第 5 数据进队
第三行第 4 数据出队
front
4
6
4
1
1
front
1
练习:
 设有一顺序栈S,元素s1,s2,s3,s4,s5,s6依次入栈,
如果出栈顺序为s2,s3,s4,s6,s5,s1,栈的容量至少
为( B )。
A. 2
B. 3
C. 5
D. 6
 设有一栈已有a1,a2,a3三个元素,栈顶为a3,a4正等待
入栈,则以下序列是不可能的出栈序列是(
A
A. a3,a1,a4,a2
B. a3,a2,a4,a1
C. a3,a4,a2,a1
D. a4,a3,a2,a1
)。
 ( T )在链队中,即使不设置尾指针也能进行入队操作;
 ( T )在带头结点的链队中进行出队操作,不会改变头结点地址
front的值;
 (F )循环队列中元素个数为rear-front
 以S和X分别代表入栈和出栈,对输入序列a、b、c、d、
e进行一系列栈操作SSXSXSSXXX后得到的序列为:
bceda
 在一个链队中,如以f和r分别代表队头、队尾指针,则
插入s结点的操作为:
r->next=s; r=s;
本章小结
顺序存储
栈的存储
链式存储
栈
栈的应用
队列存储
队列
递归算法
括弧匹配
顺序存储
循环队列
表达式求值
链式存储
队列应用
打印共享
本章基本内容
CPU 资源
杨辉三角形