Upload
tola
View
179
Download
3
Embed Size (px)
DESCRIPTION
第三章 栈和队列. 栈 栈的应用 队列 队列的应用. 3.1 栈 3.1.1 栈的定义及基本运算 栈 (Stack) 是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶 (Top) ,另一端为栈底 (Bottom) 。当表中没有元素时称为空栈。 - PowerPoint PPT Presentation
Citation preview
2
3.1 栈3.1.1 栈的定义及基本运算 栈 (Stack) 是限制在表的一端进行插入和删
除运算的线性表,通常称插入、删除的这一端为栈顶 (Top) ,另一端为栈底 (Bottom) 。当表中没有元素时称为空栈。
假设栈 S=(a1 , a2 , a3 ,… an) ,则 a1 称为栈底元素, an 为栈顶元素。栈中元素按 a1 ,a2 , a3 ,… an 的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的。因此,栈称为后进先出表( LIFO )。
4
3.1.2 顺序栈 由于栈是运算受限的线性表,因此线性表
的存储结构对栈也适应。 栈的顺序存储结构简称为顺序栈,它是运
算受限的线性表。因此,可用数组来实现顺序栈。因为栈底位置是固定不变的,所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作而变化的,故需用一个整型变量 top
5
来指示当前栈顶的位置,通常称 top 为栈顶指针。因此,顺序栈的类型定义只需将顺序表的类型定义中的长度属性改为 top 即可。顺序栈的类型定义如下:
# define StackSize 100
typedef char datatype;
typedef struct {
datatype data[stacksize];
int top;
}seqstack;
7
设 S 是 SeqStack 类型的指针变量。若栈底位置在向量的低端,即 s–>data[0] 是栈底元素,那么栈顶指针 s–>top 是正向增加的,即进栈时需将 s–>top 加 1 ,退栈时需将 s–>top 减 1 。因此, s–>top<0 表示空栈, s–>top =stacksize-1 表示栈满。当栈满时再做进栈运算必定产生空间溢出,简称“上溢” ; 当栈空时再做退栈运算也将产生溢出,简称“下溢”。上溢是一种出错状态,应该设法避免之;下溢则可能是正常现象,因为栈在程序中使用时,其初态或终态都是空栈,所以下溢常常用来作为程序控制转移的条件。
8
1 、置空栈 void initstack(seqstack *s)
{ s–>top=-1; }
2 、判断栈空 int stackempty(seqstack *s)
{ return(s–>top==-1); }
9
3 、判断栈满 int stackfull(seqstack *s)
{ return(s–>top==stacksize-1); }
4 、进栈 void push(seqstack *s , datatype x)
{
if (stackfull(s)) error(“stack overflow”);
s–>data[++s–>top]=x;
}
10
5 、退栈 datatype pop(seqstack *s)
{
if(stackempty(s))
error(“stack underflow”);
x=s–>data[top];
s–>top--;
return(x);
//return(s–>data[s–>top--]);
}
11
6 、取栈顶元素 Datatype stacktop(seqstack *s)
{
if(stackempty(s)
error(“stack is empty”);
return (s–>data[s–>top]);
}
12
3.1.3 链栈 栈的链式存储结构称为链栈,它的运算是
受限的单链表,插入和删除操作仅限制在表头位置上进行。由于只能在链表头部进行操作,故链表没有必要像单链表那样附加头结点。栈顶指针就是链表的头指针。
链栈的类型说明如下:
13
栈的链接存储结构:
typedef int datatype;
typedef struct node
{ datatype data;
struct node *next;
} linkstack;
16
链栈的进栈算法:
linkstack *PUSHLSTACK(linkstack*top, datatype x)
{
linkstack *p;
p = (linkstack *)malloc(sizeof(linkstack));
p->data = x;
p->next = top;
return p;
}
17
链栈的出栈算法 :
linkstack *POPLSTACK(linkstack *top,
datatype datap)
{ linkstack *p;
if (top==NULL)
{printf(“under flow\n”); return NULL;}
else
{ *datap = top->data;
p = top; top = top->next;
free(p); return top;
}
}
18
3.2 栈的应用举例 由于栈结构具有的后进先出的固有特性,致
使栈成为程序设计中常用的工具。以下是几个栈应用的例子。
栈的应用举例 -----数制转换 十进制 N 和其它进制数的转换是计算机实现
计算的基本问题 , 其解决方法很多 , 其中一个简单算法基于下列原理 :
N=(n div d)*d+n mod d ( 其中 :div为整除运算 ,mod 为求余运算 ) 例如 (1348)10=(2504)8,其运算过程如下:
20
void conversion( ){ initstack(s); scanf (“%d”,&n); while(n){ push(s,n%8); n=n/8; } while(! Stackempty(s)){ pop(s,e); printf(“%d”,e); } }
21
seqstack s;
EDIT( )
{ char c;
initstack(&s);
c = getchar();
while (c!=‘*’)
{ if (c==‘#’) POP(&s);
else if (c==‘@’) initstack(&s);
else PUSH(&s,c);
c = getchar();
}
}
输入缓冲区为一堆栈
# 为退格符, @ 为退行符 P49
输入 # ,执行退栈操作
输入 @ ,执行清栈空
输入其他,进栈
栈的应用举例--文字编辑器
23
后缀表达式的处理过程 :
操作顺序 后缀表达式
ABCD/- E×+
T1 ← C/D ABT1- E×+
T2 ← B – T1 AT2E×+
T3 ← T2 × E AT3+
T4 ← A + T3 T4
24
中缀表达式转换为后缀表达式
+ - × / ( ) #
+ > > < < < > >
- > > < < < > >
× > > > > < > >
/ > > > > < > >
( < < < < < =
) > > > > > >
# < < < < < =
当前运算符
栈顶运算符
25
步骤 中缀表达式 STACK 输出1 A+ (B- C/
D)×E##
2 + (B- C/ D)×E# # A
3 (B- C/ D)×E# # + A
4 B- C/ D)×E# # + ( A
5 - C/ D)×E# # + ( AB
6 C/ D)×E# # +(- AB
7 / D)×E# # + (- ABC
8 D)×E# # + (- / ABC
9 )×E# # + (- / ABCD
26
中缀表达式转换为后缀表达式的处理规则 :
1 、如为操作数,直接输出到队列;2 、如当前运算符高于栈顶运算符,入栈;3 、如当前运算符低于栈顶运算符,栈顶运算符退栈, 并输出到队列,当前运算符再与栈顶运算符比较;4 、如当前运算符等于栈顶运算符,且栈顶运算符为 “ (”,当前运算符为“)”,则栈顶运算符退栈, 继续读下一符号;5、如当前运算符等于栈顶运算符,且栈顶运算符为 “#” ,当前运算符也为“ #” ,则栈顶运算符退栈, 继续读下一符号;
27
步骤 中缀表达式 STACK 输出10 ×E# # + (- ABCD/
11 ×E# # + ( ABCD/ -12 ×E# # + ABCD/ -13 E# # + × ABCD/ -14 # # + × ABCD/ - E
15 # # + ABCD/ - E ×
16 # # ABCD/ - E × +
28
算法步骤:
设置一堆栈,将栈顶预置为 #。顺序读入中缀表达式,当读到的单词为操作数就将其输出,并等着读下一个单词;
当读到的单词为运算符就赋予 x2 ,比较 x2 与栈顶运算符 xl的优先级,若 x2 的优先级高于 xl的,将 x2 进栈,继续读下一个单词;
若 x2 的优先级低于 xl的, xl退栈并作为后缀表达式的一个单词输出,然后继续比较 x2 与新的栈顶运算符 x1 ;
若 x2 的优先级等于 xl的,且 xl为“ (” , X2 为“ )” ,则 xl退栈,然后继续读下一个单词;若 x2 的优先级等于 xl的,且 xl为 #, x2 也为 #,则算法结束
29
算法:中缀表达式变换为后缀表达式int Postfix(qstype *s,char *a)
{ char x1,x2;
char x;
int j=0;
s->Stack[0]='#',
s->Top=0;
x2=a[j];
if ((x1=GetTopQStack(s))==NIL) exit(0);
while(1){
if (x2!='+'&&x2!='-'&&x2!='*'&&
x2!='/'&&x2!='/'&&x2!='(' &&x2!=')' &&x2!='#')
30
{ printf("%c",x2); // 操作数输出 j++;
x2=a[j]; // 取下一个单词} // 此种情况下 x2 读到的是操作数 else if(Proceed(x1,x2)=='<')
{ if(!PushQStack(s,x2)) exit(0); // 读到的操作符入栈 if((x1=GetTopQStack(s))==NIL) exit(0);
// 更新 xl 的值为新入栈的操作符 j++;
x2=a[j]; // 取下一个单词 } // 栈顶操作符 X1 小于读到的操作符 X2else if(Proceed(x1,x2)=='>')
31
{ if((x=PopQStack(s))==NIL) exit(0); // 退栈 printf("%c",x); // 输出原栈顶操作符 if ((x1=GetTopQStack(s))==NIL) exit(0); // 更新 xl 的值} // 栈顶操作符大于读到的操作符 else if(Proceed(x1,x2)=='='&&x1=='('&&x2==')')
{ if((PopQStack(s))==NIL) exit(0);
if ((x1= GetTopQStack(s)) == NIL) exit(0);
j++;
x2=a[j];
} // 去掉后缀表达式中的一对括号 else if(Proceed(x1,x2)=='='&&x1=='#'&&x2=='#') return 1 ;
}
}
32
中缀表达式转换成后缀表达式的过程:A+(B-C/D)*E步骤
中缀表达式 STACK
输出 步骤
中缀表达式
STACK
输出
1 A+(B-C/D)*E#
# 9 )*E#
#+(-/
ABCD
2 +(B-C/D)*E#
# A 10 *E#
#+(-
ABCD/
3 (B-C/D)*E#
#+ A 11 *E#
#+( ABCD/-
4 B-C/D)*E#
#+( A 12 *E#
#+ ABCD-
5 -C/D)*E#
#+( AB 13 E#
#+* ABCD-
6 C/D)*E#
#+(- AB 14 #
#+* ABCD-E
7 /D)*E#
#+(- ABC 15 #
#+ ABCD-E*
8 D)*E#
#+(-/
ABC 16 #
# ABCD-E*+
35
3.4 队列
3.4.1 抽象数据类型队列的定义队列 (Queue) 是一种运算受限的线性表。
表的一端进行插入,而在另一端进行删除
队头 (front) 允许删除的一端
队尾 (rear) 允许插入的一端
先进先出 (First In First Out) 的线性表,简称 FIFO表
空队列 当队列中没有元素
37
ADT Queue {数据对象: D= {ai | ai ElemSet, i=1,2,...,n, n≥0}∈数据关系: R1= { <ai-1,ai > | ai-1, ai D, i=2,...,n}∈约定其中 a1 端为队列头, an-1 端为队列尾
} ADT Queue
基本操作 :
Initiate(q) 初始化 Enter (q , x) 入队列 Delete(q) 出队列 Gethead(q) 取队头元素 Empty(q) 判队空否
38
3.4.2 循环队列-队列的顺序表示和实现
队列的顺序存储结构简称顺序队列
#define MAXNUM // 队列允许存放的最大元素数
typedef struct
{elemtype queue[MAXNUM];
int front;
int rear;
}qtype;
39
RearFront
ARear
Front
DCBA
Rear
Front
CB
DRear
Front
RearFront
队尾、队头、队尾指示器、队头指示器
空队列、进队列、出队列
DRearFront
DRear
Front
E若删除 D :
若插入 E :
41
(2) 进队列 int enterq( qtype *q , elemtype x)
{
if(q->rear==MAXNUM-l)
return(false) ; // 队列已满 else {
q->rear++; q->queue[q->rear]=x;
return(true);
}
}
42
(3) 出队列 elemtype deleteq(qtype *q)
{
if(q->front==q->rear) //队列已空 return(ERR); //ERR 为 elemtype 类型常量 else{
q->front++;
return(q->queue[q->front]);
}
}
43
队列中亦有上溢和下溢现象。
此外,顺序队列中还存在“假上溢”现象。
因为在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。
因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作。该现象称为假上溢。
44
5
4
3
2
1
0
q->rear=-1
q->front=-1
5
4
3
2 a3
1 a2
0 a1
5
4
3
2 a3
1
0
5 a6
4 a5
3 a4
2 a3
1
0
q->rear=2
q->front=-1
q->rear=2
q->front=1
q->rear=5
q->front=1
45
解决顺序队列假溢出问题的四种方法:
①按最大可能的进队列操作次数设置顺序队列的最大元素个数
②修改出队列算法
an
an-1
.
.
.a1
a0
q->Front
q->Rear an
an-1
.
.
.a1
q->Front
q->Rear
an
.
.
.a2
a1
q->Front
q->Rear
时间复杂度由 O(1) 增大为 O(n)
46
即把向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的队列称为循环队列(Circular Queue) 。在循环队列中进行出队、入队操作时,头尾指针仍要加 1 ,朝前移动。只不过当头尾指针指向向量上界(MAXNUM-1 )时,其加 1 操作的结果是指向向量的下界 0 。 这种循环意义下的加 1 操作可以描述为:
if (q->rear+1== MAXNUM) q->rear =0;
else q->rear++;
利用模运算可简化为:
q->rear =(q->rear +1)%MAXNUM
③修改进队列算法
0
12
3
45
q-> rearq-> front
J4
J5J6
0
12
3
45
q-> rear
q-> front
J9J8
J7
J4
J5J6
0
12
3
45
q->rear
q-> front
初始状态
J4,J5,J6 出队
J7,J8,J9 入队
队空: q->front== q-> rear队满: q->front== q-> rear
如何解决?
48
解决方案:(1)少用一个数据元素空间,以队尾指示器加 l 等于队头指示器判断队满,即队满条件为 : (q-> rear+l)%MAXNUM=q->front队空条件仍为 q->rear=q->front 。队空: q->front =q-> rear队满: q->front =(q->rear + 1) % MAXNUM入队 : q->rear = (q->rear + 1) % MAXNUM出队 : q->front = (q->front + 1) % MAXNUM求队长 :(q->rear - q->front+MAXNUM)%MAXNUM
50
#define MAXNUM 100
typedef char elemtype;
typedef struct{
int front;
int rear;
int count;
elemtype data[MAXNUM] ; }cirqueue;
循环队列的进队列和出队列算法如下:
51
( 1 )置空队 void initqueue(cirqueue *q){
q–>front=q–>rear=-1;
q–>count=0;
}( 2 )判断队空 int queueempty(cirqueue *q) {
return q–>count==0;
}
( 3 )判断队满 int queuefull(cirqueue *q){
return q–>count== MAXNUM -1;
}
52
( 4 )入队 void enqueue(cirqueue *q, elemtype x) {
if(queuefull(q))
{printf(“queue overflow”);
return;}
q–>count++;
q–>rear=(q–>rear+1)%MAXNUM;
q–>data[q–>rear]=x;
}
53
( 5 )出队 elemtype dequeue(cirqueue *q) {
if(queueempty(q)){
printf(“queue underflow”);
return(ERR);
}
q–>count--;
q–>front=(q–>front+1)%MAXNUM;
return q–>data[q–>front];
}
54
( 6 )取头元素 elemtype queuefront(cirqueue *q)
{
if (queueempty(q)){
printf(“queue is empty.”);
return(ERR);
}
return q–>data[q–>front+1];
}
55
3.4.3 链队列
队列的链式存储结构简称为链队列,它是限制仅在表头删除和表尾插入的单链表。显然仅有单链表的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点。于是,一个链队列由头指针和尾指针唯一确定。
57
和顺序队列类似,我们也是将这两个指针封装在一起,将链和顺序队列类似,我们也是将这两个指针封装在一起,将链队列的类型队列的类型 LinkQueue 定义为一个结构类型:定义为一个结构类型:
typedef struct queuenode{
elemtype data;
struct queuenode *next;
}queuenode;
typedef struct{
queuenode *front;
queuenode *rear;
}linkqueue;
58
下面给出链队列上实现的基本运算:(1) 构造一个空队列void initqueue(linkqueue *q)
{// 用指针类型传引用调用 q->front=(queuenode*)malloc(sizeof(queuenode));
q->rear=q->front;
q->front->next=NULL;
}
60
(2)队列的判空:
int queueempty(linkqueue *q)
{
return (q->front->next= =NULL &&
q->rear->next= =NULL);
}
61
(3) 进队列操作算法 void enterlq(linkqueue *q,elemtype x)
{ queuenode *p;
p=(queuenode*)malloc(sizeof(queuenode)); // 申请结点 p->data=x; // 新结点数据域赋值 p->next=NULL; // 新结点指针域赋值 q->rear->next=p; //链在尾结点后 q->rear=p; //修改队尾指针
}
63
(4) 出队列操作算法elemtype deletelq(linkqueue *q)
{
queuenode *p;
elemtype x ; if (q->front->next==NULL) return(NUIL); //队列空 else {p=q->front->next; //暂存原队头指针 q->front->next=p->next; // 删除队头元素 x=p->data; //暂存原队头元素 free(p); //释放原队头结点空间 return(x); //返回原队头元素 }
}
65
队列应用举例 划分子集问题问题描述:已知集合 A={a1,a2,……an} ,及集合上的关系 R={ (ai,aj) | ai,ajA, ij} ,其中 (ai,aj) 表示 ai 与 aj
间存在冲突关系。要求将 A划分成互不相交的子集 A1,A2,……Ak,(kn) ,使任何子集中的元素均无冲突关系,同时要求分子集个数尽可能少
例: A={1,2,3,4,5,6,7,8,9} R={ (2,8), (9,4), (2,9), (2,1), (2,5), (6,2), (5,9), (5,6), (5,4), (7,5), (7,6), (3,7), (6,3) } 可行的子集划分为: A1={ 1,3,4,8 } A2={ 2,7 } A3={ 5 } A4={ 6,9 }