数据结构
This presentation is the property of its rightful owner.
Sponsored Links
1 / 101

数据结构 Data Structure PowerPoint PPT Presentation


  • 76 Views
  • Uploaded on
  • Presentation posted in: General

数据结构 Data Structure. 精品课程. 第 2 章要点回顾. 顺序表 的类型定义. 顺序表的建立、输出、查找、插入、删除操作. 单链表 的类型定义. 单链表的建立、输出、查找、插入、删除操作. 第 3 章 栈与队列. 第 3 章 栈与队列. 3.1 栈. 3.2 栈的应用. 3.3 队列. 3.4 队列的应用. 3.1.1 栈的定义及基本操作 3.1.2 顺序栈 3.1.3 链栈. 3.1 栈. 入栈. 出栈. 栈顶. 栈顶元素. an. ……. (限定操作 一端).

Download Presentation

数据结构 Data Structure

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


Data structure

数据结构

Data Structure

精品课程


Data structure

第2章要点回顾

顺序表的类型定义

顺序表的建立、输出、查找、插入、删除操作

单链表的类型定义

单链表的建立、输出、查找、插入、删除操作


Data structure

第3章 栈与队列


Data structure

第3章 栈与队列

3.1栈

3.2 栈的应用

3.3 队列

3.4 队列的应用


Data structure

3.1.1 栈的定义及基本操作

3.1.2 顺序栈

3.1.3 链栈

3.1 栈


Data structure

入栈

出栈

...

栈顶

栈顶元素

an

……...

(限定操作

一端)

s=(a1,a2,……,an)

a2

栈底

a1

3.1.1 栈的定义及基本操作

1、栈的定义

栈:限定仅在表尾进行插入或删除操作的线性表。

表尾—栈顶,表头—栈底,不含元素的空表称空栈


Data structure

3.1.1 栈的定义及基本操作

1、栈的定义

栈的特点:

  根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。

也就是说,栈是一种后进先出的线性表,简称为LIFO表。


Data structure

3.1.1 栈的定义及基本操作

2、栈的基本操作

(1)初始化栈:InitStack(&s)

将栈s置为一个空栈(不含任何元素)。

(2)进栈:Push(&s,x)

将元素X插入到栈s中,也称为 “入栈”、 “插入”、 “压入”。

(3)出栈: Pop(&s)

删除栈s中的栈顶元素,也称为“退栈”、 “删除”、 “弹出”。


Data structure

3.1.1 栈的定义及基本操作

2、栈的基本操作

(4)取栈顶元素: GetTop(S,&e)

取栈S中栈顶元素。

(5)判栈空: StackEmpty(S)

判断栈S是否为空,若为空,返回值为true,否则返回值为false。


Data structure

3.1.1 栈的定义及基本操作

2、栈的基本操作

【例】对于一个栈,给出输入项A、B、C,如果输入项序列由ABC组成,试给出所有可能的输出序列。

A进 A出 B进 B出 C进 C出 ABC

A进 A出 B进 C进 C出 B出 ACB

A进 B进 B出 A出 C进 C出 BAC

A进 B进 B出 C进 C出 A出 BCA

A进 B进 C进 C出 B出 A出 CBA

可能产生输出序列CAB?


12345 43512 12345

3.1.1 栈的定义及基本操作

【例】一个栈的输入序列是12345,若在入栈的过程中允许出栈,则栈的输出序列43512可能实现吗?12345的输出呢?

2、栈的基本操作

43512不可能实现,主要是其中的12顺序不能实现;

12345的输出可以实现,只需压入一个立即弹出一个即可。

答:


Data structure

3.1.1 栈的定义及基本操作

2、栈的基本操作

【例】如果一个栈的输入序列为123456,能否得到435612和135426的出栈序列?

答:

435612中到了12顺序不能实现;

135426可以实现。


Data structure

3.1.1 栈的定义及基本操作

2、栈的基本操作

【例】设依次进入一个栈的元素序列为c,a,b,d,则可得到出栈的元素序列是:

A)a,b,c,d B)c,d,a,b

C)b,c,d,a D)a,c,d,b

答:

A、D可以( B、C不行)。


Data structure

3.1.2 顺序栈

由于栈是运算受限的线性表,因此线性表的存储结构对栈也适应。

1、顺序栈的存储结构

栈的顺序存储结构简称为顺序栈,它是运算受限的线性表。因此,可用数组来实现顺序栈。因为栈底位置是固定不变的,所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作而变化的,故需用一个整型变量top来指示当前栈顶的位置,通常称top为栈顶指针。


Data structure

3.1.2 顺序栈

顺序栈的类型定义只需将顺序表的类型定义中的last域改为top即可。顺序栈的类型定义如下:

1、顺序栈的存储结构

# define StackSize 100

typedef struct {

ElemType data[StackSize];

int top;

}SeqStack;


Data structure

F

E

5

5

5

top

top

top

top

top

top

top

D

4

4

4

C

3

3

3

B

top

top

top

top

top

top

2

2

2

A

1

1

1

top=-1

0

0

0

栈空

3.1.2 顺序栈

栈空

栈顶top 的值指示栈顶元素在栈中的位置。

栈满

F

E

D

C

B

A

出栈

进栈

设数组下标最大值为M

top=-1,栈空,此时出栈,则下溢

top=M,栈满,此时入栈,则上溢

栈顶指针top,指向实际栈顶位置,初值为-1


Data structure

3.1.2 顺序栈

设S是SeqStack类型的指针变量。若栈底位置在向量的低端,即s–>data[0]是栈底元素,那么栈顶指针s–>top是正向增加的,即:

进栈时需将s–>top加1 ;

退栈时需将s–>top 减1

因此,s–>top=-1表示空栈

s–>top =StackSize-1表示栈满。

当栈满时再做进栈运算必定产生空间溢出,简称“上溢”;

当栈空时再做退栈运算也将产生溢出,简称“下溢”。


Data structure

3.1.2 顺序栈

(1)初始化栈

2、顺序栈的基本运算

void InitStack(SeqStack *s)

{ s–>top=-1;

}

(2)判断栈空

int StackEmpty(SeqStack *s)

{ return(s–>top==-1);

}


Data structure

3.1.2 顺序栈

(3)判断栈满

2、顺序栈的基本运算

int StackFull(SeqStack *s)

{ return(s–>top==StackSize-1); }

(4)进栈

void Push(SeqStack *s,ElemType x)

{ if (StackFull(s))

error(“Stack overflow”);

s–>data[++s–>top]=x; }


Data structure

3.1.2 顺序栈

(5)退栈

2、顺序栈的基本运算

void Pop(SeqStack *s,ElemType *x)

{

if (StackEmpty(s))

error(“Stack underflow”);

*x=s–>data[top];

s–>top--;

}


Data structure

3.1.2 顺序栈

(6)取栈顶元素

2、顺序栈的基本运算

ElemType GetTop(SeqStack *s)

{

if (StackEmpty(s))

error(“Stack is empty”);

return s–>data[s–>top];

}


Data structure

3.1.3 链栈

1、链栈的存储结构

栈的链式存储结构称为链栈,它是运算受限的单链表,插入和删除操作仅限制在表头位置上进行。由于只能在链表头部进行操作,故链表没有必要像单链表那样附加头结点。栈顶指针就是链表的头指针。


Data structure

3.1.3 链栈

1、链栈的存储结构

链栈的类型定义

typedef struct node

{ ElemType data;

struct node *next;

} Node, *LinkStack;


Data structure

3.1.3 链栈

2、链栈的基本运算

(1)链栈的进栈

LinkStack *PushLStack(LinkStack *top, ElemType x)

{ LinkStack *p;

p=malloc(sizeof(LinkStack));

p->data=x; p->next=top;

return p;

}


Data structure

3.1.3 链栈

(2)链栈的出栈

LinkStack *PopLStack(LinkStack *top, ElemType *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;} }


Data structure

3.2.1 数制转换

3.2.2 文字编辑器

3.2.3 表达式求值

3.2.4 基于栈的迷宫求解

3.2 栈的应用


Data structure

3.2.1 数制转换

十进制N和其它进制数的转换是计算机实现计算的基本问题,其解决方法很多,其中一个简单算法基于下列原理:

N=(n div d)*d+n mod d

( 其中:div为整除运算,mod为求余运算)


Data structure

计算顺序

输出顺序

3.2.1 数制转换

如:(1348)10=(2504)8,其运算过程如下:

N N div 8 余数1348 168 4168 21 021 2 52 0 2


Data structure

3.2.1 数制转换

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); } }


Data structure

3.2.2 文字编辑器

功能:接受用户从终端输入的程序或数据,并存入用户数据区。

较好做法:设立输入缓冲区,接受一行字符,有错可及时纠正。

#:退格符; @:退行符


Data structure

3.2.2 文字编辑器

SqStack s;

EDIT()

{

char c;

InitStack(&s);

c=getchar();

while (c!=‘`’)

{ if (c==‘#’) Pop(&s);

else

if ([email protected]) InitStack(&s);

else Push(&s,c);

c=getchar();}}


Data structure

3.2.3 表达式求值

( 这是栈应用的典型例子 )

这里,表达式求值的算法是 “算符优先法”。

如:3*(7 – 2 )

(1)要正确求值,首先了解算术四则运算的规则:

从左算到右;先乘除,后加减;先括号内,后括号外

由此,此表达式的计算顺序为:

3*(7 – 2 )= 3 * 5 = 15


Data structure

Ø1Ø2

 +  -  *   / ( ) #

+

-

*

/

(

)

#

> > < < < > >

> > < < < > >

> > > > < > >

> > > > < > >

< < < < < =

> > > > > >

< < < < < =

3.2.3 表达式求值

(2)根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符1和2,都要比较优先权关系。

算符优先法所依据的算符间的优先关系见下表

算符间的优先关系


Data structure

3.2.3 表达式求值

  • (3)算法思想:

  • 设定两栈:操作符栈 OPTR ,操作数栈 OPND

  • 栈初始化:设操作数栈 OPND 为空;操作符栈 OPTR 的栈底元素为表达式起始符 ‘#’;

  • 依次读入字符:是操作数则入OPND栈,是操作符则要判断:

  • 若操作符 < 栈顶元素,则退栈、计算,结果压入OPND栈;

  • 操作符 = 栈顶元素且不为‘#’,脱括号(弹出左括号);

  • 操作符 > 栈顶元素,压入OPTR栈。


Data structure

OPTR

OPND

INPUT

OPERATE

#

3*(7-2)#

Push(opnd,’3’)

#

3

*(7-2)#

Push(optr,’*’)

#,*

3

(7-2)#

Push(optr,’(’)

#,*,(

3

7-2)#

Push(opnd,’7’)

#,*,(

3,7

-2)#

Push(optr,’-’)

#,*,(,-

3,7

2)#

Push(opnd,’2’)

)#

#,*,(,-

3,7,2

Operate(7-2)

#,*,(

3,5

)#

Pop(optr)

#,*

3,5

#

Operate(3*5)

#

15

#

GetTop(opnd)

3.2.3 表达式求值


Data structure

3.2.3 表达式求值

Status EvaluateExpression( OperandType &result) {

InitStack(OPND); InitStack(OPTR);Push(OPTR ,’#’);c=getchar();

while((c!=‘#’)||(GetTop(OPTR)!=‘#’)) {

if (!In(c,OP) { Push(OPND,c); c=getchar();}

else switch(compare(c,GetTop(OPTR)))

{case ‘>’ : Push(OPTR , c); c=getchar();break;

case ‘=’: Pop(OPTR);c=getchar();break;

case ‘<’ : temat=Pop(OPTR); b=Pop(OPND);a=Pop(OPND);

result= Operate(a,temat,b);Push(OPND,result);


Data structure

3.2.3 表达式求值

c=getchar();break;

} //switch

}//while

result=GetTop(OPND);

}//EvaluateExpression


Data structure

3.2.3 表达式求值

float Operate(float a,unsigned char theta, float b) {

switch(theta) {

case '+': return a+b;

case '-': return a-b;

case '*': return a*b;

case '/': return a/b;

default : return 0; } }

Status In(char Test,char* TestOp) {

bool Find=false;

for (int i=0; i< OPSETSIZE; i++) {

if (Test == TestOp[i]) Find= true;

}

return Find; }


Data structure

3.2.3 表达式求值

int ReturnOpOrd(char op,char* TestOp) {

int i;

for(i=0; i< OPSETSIZE; i++) {

if (op == TestOp[i]) return i;

}

return 0; }

char precede(char Aop, char Bop) {

return

Prior[ReturnOpOrd(Aop,OPSET)][ReturnOpOrd(Bop,OPSET)];

}


Data structure

3.2.3 表达式求值

#define OPSETSIZE 7

unsigned char Prior[7][7] = { // 表3.1 算符间的优先关系

'>','>','<','<','<','>','>',

'>','>','<','<','<','>','>',

'>','>','>','>','<','>','>',

'>','>','>','>','<','>','>',

'<','<','<','<','<','=',' ',

'>','>','>','>',' ','>','>',

'<','<','<','<','<',' ','=‘ };

float Operate(float a, unsigned char theta, float b);

char OPSET[OPSETSIZE]={'+' , '-' , '*' , '/' ,'(' , ')' , '#'};

Status In(char Test,char* TestOp);

char precede(char Aop, char Bop);


Data structure

3.2.4 基于栈的迷宫求解

通常用的是“穷举求解”的方法


Data structure

3.2.4 基于栈的迷宫求解

求迷宫路径算法的基本思想是:(回溯)

若当前位置“可通”,则纳入路径,继续前进;

若当前位置“不可通”,则后退,换方向继续探索;

若四周“均无通路”,则将当前位置从路径中删除出去并退回到前一位置继续下一方向的探索。

为了保证在任何位置上都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。因此,在本算法中应用“栈”是很自然的事。


Data structure

3.2.4 基于栈的迷宫求解

需要解决的4个问题:

(1)表示迷宫的数据结构。设迷宫为M行N列,利用maze[M][N]来表示一个迷宫,maze[i][j]=0或1,其中0表示通道,1表示墙(不通)。当从某点向下试探时,中间点有8个方向可以试探,而4个角点有3个方向,其他边缘点有5个方向,为使问题简单化我们用maze[M+2][N+2]来表示迷宫,而迷宫四周的值全部为1。这样,每个点都有8个试探方向,不用再判断当前点的试探方向有几个,同时与迷宫周围是墙壁这一实际问题相一致。


Data structure

3.2.4 基于栈的迷宫求解

设入口坐标为(1,1),出口坐标为(6,8)

迷宫的定义如下:

#define M 6 //迷宫的实际行

#define N 8 //迷宫的实际列

int maze [M+2][N+2];


Data structure

3.2.4 基于栈的迷宫求解


Data structure

3.2.4 基于栈的迷宫求解

(2)试探方向。在上述迷宫中,每个点有8个方向可以试探,若当前点的坐标为(x,y),则与其相邻的8个点的坐标都可根据与该点的相邻方位而得到。因为出口在(M,N),因此试探顺序规定为:从当前位置向前试探的方向为从正东沿顺时针方向进行。为了简化问题,方便地求出新点的坐标,将从正东开始沿顺时针进行的这8个方向的坐标增量放在一个结构体数组move[8]中,在move数组中,每个元素由两个域组成,x:横坐标增量,y:纵坐标增量。


Data structure

3.2.4 基于栈的迷宫求解


Data structure

3.2.4 基于栈的迷宫求解

move数组定义如下:

typedef struct

{ int x, y;

} item ;

item move[8] ;

这样可以很方便地求出从某点(x,y)按某一方向v(0≤v≤7)到达的新点(i,j)的坐标:i=x+move[v].x;j=y+move[v].y。


Data structure

3.2.4 基于栈的迷宫求解

(3)栈的设计。当到达了某点而无路可走时需返回前一点,再从前一点开始向下一个方向继续试探。因此,压入栈中的不仅是顺序到达的各点的坐标,而且还要有从当前点到达下一点的方向序号。对于前述迷宫,依次入栈的顺序如下图


Data structure

3.2.4 基于栈的迷宫求解


Data structure

3.2.4 基于栈的迷宫求解

栈中每一组数据是所到达的每点的坐标及从该点沿哪个方向向下走的,对于所述迷宫,走的路线为:(1,1)1→(2,2)1→(3,3)0→(3,4)0→(3,5)0→(3,6)0(下标表示方向)。当从点(3,6)沿方向0到达点(3,7)之后,无路可走,则应回溯,即退回到点(3,6),对应的操作是出栈,接着沿下一个方向即方向1继续试探,方向1、2试探失败,在方向3上试探成功,因此将(3,6,3)压入栈中,即到达了点(4,5)。


Data structure

3.2.4 基于栈的迷宫求解

栈中元素是一个由行、列、方向组成的三元组,栈中元素的类型定义如下:

typedef struct

{ int x , y , d ; //横纵坐标及方向

}ElemType ;

SqStack s ; //栈的定义


Data structure

3.2.4 基于栈的迷宫求解

(4)如何防止重复到达某点,以避免发生死循环。

一种方法是另外设置一个标志数组mark[M][N],它的所有元素都初始化为0,一旦到达了某一点(i,j)后,使mark[i][j]置1,下次再试探这个位置时就不能再走了。另一种方法是当到达某点(i,j)后使maze[i][j]置-1,以便区别未到达过的点,同样也能起到防止走重复点的目的,我们采用后面一种方法。


Data structure

3.2.4 基于栈的迷宫求解

①栈初始化。

②将入口点坐标及到达该点的方向(设为-1)入栈。

③while(栈不空)

{ 栈顶元素出栈; 求出下一个要试探的方向d++;

while (还有剩余试探方向)

{ if (d方向可走)

则{(x,y,d)入栈; 求新点坐标(i,j);

将新点(i,j)切换为当前点(x,y);

if (点(x,y)为出口点)结束;

else 重置 d=0; }

else d++; } }


Data structure

3.2.4 基于栈的迷宫求解

int mazepath(int maze[M+2][N+2], item move[8]){

//若迷宫maze中存在从入口(1,1)到出口(M,N)的通

//道, 则 求得一条存放在栈中, 并返回1;

SqStack s; ElemType temp; int x,y,d,i,j;

InitStack(&s); //栈初始化

temp.x=1; temp.y=1; temp.d=-1;

Push(&s,temp);

while(!StackEmpty(s))

{ Pop(&s,&temp);

x=temp.x; y=temp.y; d=temp.d+1;


Data structure

3.2.4 基于栈的迷宫求解

while(d<8)

{ i=x+move[d].x; j=y+move[d].y;

if (maze[i][j]==0)

{ temp.x=x; temp.y=y; temp.d=d; //坐标及方向

Push(&s,temp); //坐标及方向入栈

x=i; y=j; maze[x][y]=-1; //到达新点

if (x==M&&y==N)

{ printpath (s); //输出路径

return 1; } //迷宫有路

else d=0; }// if

else d++;

} } return 0; } //迷宫无路


Data structure

3.2.4 基于栈的迷宫求解

void printpath(SqStack s)

{ //输出迷宫路径,栈中保存的就是一条迷宫的通路

ElemType temp;

printf("(%d,%d)<--", M, N);

while(!StackEmpty(s))

{ Pop(&s, &temp);

printf ("(%d,%d)<--", temp.x, temp.y);

}

printf("\n"); }


Data structure

3.3.1 队列的定义及基本操作

3.3.2 顺序队列

3.3.3 链队列

3.3 队列


Data structure

3.3.1 队列的定义及基本操作

队列(Queue)也是一种运算受限的线性表。它只允许在表的一端进行插入,而在另一端进行删除。允许删除的一端称为队头(front),允许插入的一端称为队尾(rear)。

如:排队购物。操作系统中的作业排队。先进入队列的成员总是先离开队列。因此队列亦称作先进先出(First In First Out)的线性表,简称FIFO表。

1、队列的定义


Data structure

3.3.1 队列的定义及基本操作

1、队列的定义

   当队列中没有元素时称为空队列。在空队列中依次加入元素a1,a2,…an之后,a1是队头元素,an是队尾元素。显然退出队列的次序也只能是a1,a2,…an,也就是说队列的修改是依先进先出的原则进行的。


Data structure

3.3.1 队列的定义及基本操作

队列的抽象数据定义:

ADTQueue{

数据对象:D={ai|ai ∈ ElemSet,i=1,…n,n ≥ 0}

数据关系:R1={<ai-1,ai>|a i-1,ai∈ D, i=1,…n}约定a1 端为队列头,an端为队列尾。

基本操作:(见后面)

} ADT Queue


Data structure

3.3.1 队列的定义及基本操作

2、队列的基本操作

(1)初始化队列 InitQueue(&Q)

将队列Q设置成一个空队列。

(2)入队列 EnQueue(&Q,X)

将元素X插入到队尾中,也称“进队” ,“插入”。

(3)出队列 DeQueue(&Q,&e)

将队列Q的队头元素删除,并用e返回其值,也称“退队”、“删除”。


Data structure

3.3.1 队列的定义及基本操作

2、队列的基本操作

(4)取队头元素 GetHead(Q,&e)

得到队列Q的队头元素之值,并用e返回其值。

(5)判队空 QueueEmpty(Q)

判断队列Q是否为空,若为空返回1,否则返回0。


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

队列的顺序存储结构称为顺序队列。由于队列的队头和队尾的位置是变化的,因而要设两个指针分别指示队头和队尾元素在队列中的位置,它们的初始值在队列初始化时均应置为0。入队时将新元素插入所指的位置,然后尾指针加1。出队时,删去所指的元素,然后头指针加1并返回被删元素。由此可见,当头尾指针相等时队列为空。在非空队列里,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一位置。


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

#define MAXQSIZE 100

typedef struct

{

ElemType data[MAXQSIZE];

int front;

int rear;

int len;

}SqQueue;


Data structure

3.3.2 顺序队列

0  1 2  3 0 1 2 3

Front

rear

Front rear

(a)队列初始为空

(b)a,b,c入队

 0 1  2  3    0  1  2  3

front rear

front

rear

(c) a出队

(d) b,c出队,队为空


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

非循环队列

Q.front==Q.rear

队空:

Q.rear==maxsize(假溢出)

队满:

求队长:

Q.rear-Q.front

入队:

新元素按rear指示位置加入,再将队尾指针加一 ,即 rear = rear + 1

出队:

将front指示的元素取出,再将队头指针加一,即front = front + 1


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

和栈类似,队列中亦有上溢和下溢现象。此外,顺序队列中还存在“假上溢”现象。因为在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作。该现象称为假上溢。


Data structure

3.3.2 顺序队列

为充分利用向量空间,克服上述假上溢现象,可以将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的队列称为循环队列(Circular Queue)。在循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界(QueueSize-1)时,其加1操作的结果是指向向量的下界0。

1、顺序队列的存储结构


Data structure

3.3.2 顺序队列

显然,因为循环队列元素的空间可以被利用,除非向量空间真的被队列元素全部占用,否则不会上溢。因此,除一些简单的应用外,真正实用的顺序队列是循环队列。

1、顺序队列的存储结构


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

由于入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,我们无法通过Q.front==Q.rear来判断队列“空”还是“满”。

解决此问题的方法至少有两种:

其一是另设一个布尔变量以区别队列的空和满;其二是少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空)


Data structure

front

rear

5

4

0

3

1

2

rear

J5

J4

5

4

0

J3,J4,J5出队

3

1

2

J3

front

J6,J7,J8入队

J5

初始状态

J6

J4

5

4

0

3

1

J7

J3

2

J8

front

rear

队空:front==rear

队满:front==rear

解决方案:

(1)另设一个标志区别队空、队满

(2)少用一个元素空间:

队空:front==rear

队满:(rear+1)%M==front


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构

循环队列

Q.front ==Q. rear

队空:

Q.front ==(Q.rear + 1) % maxSize

队满:

Q.rear = (Q.rear + 1) % maxSize

入队:

Q.front = (Q.front + 1) % maxSize

出队:

求队长:

(Q.rear-Q.front+maxSize)%maxSize


Data structure

3.3.2 顺序队列

1、顺序队列的存储结构


Data structure

3.3.2 顺序队列

2、顺序队列的基本运算

(1)进队列

①检查队列是否已满,若队满,则进行溢出处理;

②将新元素赋给队尾指针所指单元;

③将队尾指针后移一个位置,指向下一单元。

Status EnQueue (SqQueue &Q, ElemType e)

{

if((Q.rear+1)%MAXQSIZE==Q.front)

return(ERROR);

Q.data[Q.rear]=e;

Q.rear=(Q.rear+1)%MAXQSIZE;

return(OK); }


Data structure

3.3.2 顺序队列

2、顺序队列的基本运算

(2)出队列

①检查队列是否为空,若队空,则进行下溢处理;

②取队首元素的值。

③将队首指针后移一个位置(即加1);

Status DeQueue (SqQueue &Q, ElemType &e)

{

if (Q.rear== Q.front) return(ERROR);

e=Q.data[Q.front];

Q.front=(Q.front+1)%MAXQSIZE;

return(OK); }


Data structure

3.3.2 顺序队列

(3)队列初始化

Q.front=Q.rear=0;

(4)取队头元素

ElemType GetHead(SqQueue Q )

{

if (Q.front= =Q.rear) return(ERROR);

return (Q.data[Q.front]); }

(5)判队列空否

int QueueEmpty(SqQueue Q )

{

if (Q.front= =Q.rear) reurn (1);

else return (0); }


Data structure

3.3.3 链队列

队列的链式存储结构简称为链队列,它是限制仅在表头删除和表尾插入的单链表。显然仅有单链表的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点。于是,一个链队列由头指针和尾指针唯一确定。

1、链队列的存储结构


Data structure

3.3.3 链队列

1、链队列的存储结构

*q

q.front

q.rear

非空队列

*q

q.front

q.rear

空队列


Data structure

3.3.3 链队列

和顺序队列类似,我们也是将这两个指针封装在一起,将链队列的类型LinkQueue定义为一个结构类型:

1、链队列的存储结构

typedef struct queuenode{

ElemType data;

struct queuenode *next;

}QueueNode;

typedef struct{

QueueNode *front;

QueueNode *rear;

}LinkQueue;


Data structure

3.3.3 链队列

2、链队列的基本运算

(1)构造一个空队列

void InitQueue(LinkQueue &Q)

{

Q.front=Q.rear=(queuenode *)malloc

(sizeof(queuenode ));

Q.front->next=Q.rear->next=NULL;

}


Data structure

3.3.3 链队列

2、链队列的基本运算

(2)队列的判空

int QueueEmpty(LinkQueue Q)

{

return (Q.front->next= =NULL &&

Q.rear->next= =NULL);

}


Data structure

3.3.3 链队列

void EnQueue(LinkQueue &Q,ElemType e)

{

2、链队列的基本运算

(3)入队

*q

q.front

x

q.rear

p

QueueNode *p;

p=(QueueNode * )malloc(sizeof(QueueNode));

p–>next=NULL;

p–>data=x;

Q.rear–>next=p;

Q.rear=p; }


Data structure

3.3.3 链队列

p

(4)出队

*q

q.front

x

q.rear

存储池

Status DeQueue(LinkQueue &Q,ElemType &e)

{ QueueNode *p;

if (QueueEmpty(Q)) return ERROR;

p=Q.front->next; e=p–>data;

Q.front->next=p–>next;

if(Q.rear = =p) Q.rear=Q.front;

free(p); return OK; }


Data structure

空队列

^

front

NULL

front

空队列

rear

rear

^

front

元素x入队

x

^

rear

front

元素y入队

x

y

^

rear

front

元素x出队

x

y

^

rear

3.3.3 链队列

2、链队列的基本运算


Data structure

3.4.1 CPU资源的竞争问题

3.4.2 主机与外设速度不匹配问题

3.4.3 基于队列的迷宫求解

3.4 队列的应用


Data structure

3.4.1 CPU资源的竞争问题

第一个例子:CPU资源的竞争问题。在具有多个终端的计算机系统中,有多个用户需要使用CPU各自运行自己的程序,它们分别通过各自终端向操作系统提出使用CPU的请求,操作系统按照每个请求在时间上的先后顺序,将其排成一个队列,每次把CPU分配给队头用户使用,当相应的程序运行结束,则令其出队,再把CPU分配给新的队头用户,直到所有用户任务处理完毕。


Data structure

3.4.2 主机与外设速度不匹配问题

第二个例子:主机与外部设备之间速度不匹配的问题。以主机和打印机为例来说明,主机输出数据给打印机打印,主机输出数据的速度比打印机打印的速度要快得多,若直接把输出的数据送给打印机打印,由于速度不匹配,显然是不行的。


Data structure

3.4.2 主机与外设速度不匹配问题

解决的方法是设置一个打印数据缓冲区,主机把要打印输出的数据依次写入到这个缓冲区中,写满后就暂停输出,继而去做其它的事情,打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求,主机接到请求后再向缓冲区写入打印数据,这样利用队列既保证了打印数据的正确,又使主机提高了效率。


Data structure

3.4.3 基于队列的迷宫求解

【例】求迷宫的最短路径,要求设计一个算法找一条从迷宫入口到出口的最短路径。

【算法思想】

从迷宫入口点(1,1)出发,向四周搜索,记下所有一步能到达的坐标点;然后依次再从这些点出发,再记下所有一步能到达的坐标点,……,依此类推,直到到达迷宫的出口点(M,N)为止,然后从出口点沿搜索路径回溯直至入口。这样就找到了一条迷宫的最短路径,否则迷宫无路径。


Data structure

3.4.3 基于队列的迷宫求解

搜索路径存储:在搜索过程中必须记下每一个可到达的坐标点,以便据此继续向四周搜索。由于先到达的点先向下搜索,故引进队列来保存已到达的坐标点。到达迷宫的出口点(M,N)后,为了能够从出口点沿搜索路径回溯直至入口,对于每一点,记下坐标点的同时,还要记下到达该点的前驱点。


Data structure

3.4.3 基于队列的迷宫求解

因此,用一个结构体数组elem[MAXSIZE]作为队列的存储空间,因为迷宫中每个点至多被访问一次,所以MAXSIZE至多等于M×N。该结构体有三个域:x、y和pre,其中x、y分别为所到达的点的坐标,pre为前驱点在elem中的下标。除此之外,还需设定头、尾指针:front和rear分别指向队头和队尾元素 。


Data structure

3.4.3 基于队列的迷宫求解

队列的类型定义:

#define M 6

#define N 8

#define MAXSIZE M*N

typedef struct{

int x, y;

int pre;

}ElemType;

Typedef struct{

ElemType elem[MAXSIZE];

int front, rear;

}SqQueue;

初始状态:队列中只有一个元素elem[0],记录的是入口点的坐标(1,1),因为该点是出发点,因此没有前驱点,pre域为-1,头指针front和尾指针rear均指向它,此后搜索时都是以front所指点为搜索的出发点。


Data structure

3.4.3 基于队列的迷宫求解

【算法】基于队列的迷宫求解算法

int mazepath(int maze[M+2][N+2], item move[8]){

//采用队列的迷宫算法。Maze[M+2][N+2]表示迷宫

// 数组, move[8]表示坐标增量数组

SqQueue q; int x,y,i,j,v; ElemType head,e;

InitQueue(&q); e.x=1; e.y=1; e.pre=-1;

EnQueue(&q, e); //入口点入队

maze[1][1]=-1;

while (!QueueEmpty(q))

{ GetHead(q, &head); //读出队头元素

x=head.x; y=head.y;


Data structure

3.4.3 基于队列的迷宫求解

for(v=0; v<8; v++)

{ i=x+move[v].x; j=y+move[v].y;

if(maze[i][j]==0)

{ e.x=i; e.y=j; e.pre=q.front;

EnQueue(&q, e); //将可到达点入队

maze[i][j]=-1; }

if(i==M&&j==N)

{ printpath(q); //输出路径

return 1; } //迷宫有路

}

DeQueue(&q, &head); //当前点搜索完8个方向后出队

}// while

return 0; } //迷宫无路


Data structure

3.4.3 基于队列的迷宫求解

void printpath (SqQueue q){

//输出迷宫路径

int i;

i=q.rear-1;

do{

printf("(%d,%d)<--", (q.elem[i]).x, (q.elem[i]).y);

i=(q.elem[i]).pre; //回溯

}while(i!=-1);

printf("\n");

}


Data structure

3.4.3 基于队列的迷宫求解

运行结果:

(6,8)←(5,7)←(4,6)←(4,5)←(3,4)←(3,3)←(2,2)←(1,1)

该例若用“基于栈的迷宫求解”算法得到的路径是:

(6,8)←(5,8)←(5,7)←(4,6)←(4,5)←(3,4)←(3,3)←(2,2)←(1,1)


Data structure

顺序栈的存储结构及基本运算

链栈的存储结构及基本运算

栈的应用:数制转换、表达式求值、迷宫问题

顺序队列的存储结构及基本运算

链队列的存储结构及基本运算

栈的应用:迷宫问题

本章小结


Data structure

作业

第3章习题:2、3、5、7、8


Data structure

Thank You!


  • Login