25计算机考研,数据结构知识点整理(内容借鉴了王道408+数据结构教材),还会不断完善所整理的内容,后续的内容也会不断更新(可以关注),若有错误和不足欢迎各位朋友指出!
目录
一.队列的基本概念
队列是另一种限定性的线性表,只允许在表的一端(表尾),而在表的另一端(表头)删除元素,所以队列具有先进先出的( first in first out,FIFO)的特性,这与我们日常生活中的排队是一样的,最早进入队列的人最早离开,新来的总是加入队尾。在队列中,允许插入的一端称为队尾,允许删除的一端则称为队头(假设队列为 ,... ,,,...,),那么就是队头元素,则是队尾元素。队列中的元素按照 ,... ,,,...,顺序进人的,退出队列也必须按照同样的次序依次出队,也就是说,只有在 ,... ,,,...,都离开队列之后,才能退出队列。
抽象数据类型定义:
①InitQueue(Q)
操作前提:Q为未初始化的队列。
操作结果:将Q初始化为一个空队列。
② IsEmpty(Q)
操作前提:队列Q已经存在。
操作结果:若队列为空,则返回TRUE,否则返回FALSE。
③IsFull(Q)
操作前提:队列Q已经存在。
操作结果:若队列为满,则返回TRUE,否则返回FALSE。
④EnterQueue(Q,x)
操作前提:队列Q已经存在。
操作结果:在队列Q的队尾插入。操作成功,返回值为TRUE,否则返回值为FALSE。
⑤DeleteQueue(Q,x)
操作结果:将队列Q的队头元素出队,并用x带回其值。操作成功,返回值为TRUE,否则返回值为FALSE。
⑥ GetHead(Q ,x)
操作前提:队列Q已经存在。
操作前提:队列Q已经存在。操作结果:取队列Q的队头元素(该元素不出队),并用x带回其值。操作成功,返回值为 TRUE,否则返回值为 FALSE。
⑦ ClearQueue(Q)
操作前提:队列Q已经存在。
操作结果:将队列Q置为空队列。
二.队列的实现和表示
与线性表类似,队列也可以有两种存储表示,即顺序表示和链式表示
1.队列的顺序存储结构
1.1队列的顺序存储(顺序链)
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front指向队头元素,队尾指针rear指向队尾素的下一个位置(不同教材对front和rear的定义可能不同,例如,可以让rear指向队尾元素、front 指向队头元素。对于不同的定义出队入队的操作是不同的。
队列的顺序存储类型可描述为:
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //用数组存放队列元素
int front,rear; //队头指针和队尾指针
}SqQueue;
- 初始时:Q->rear=Q->front==NULL。
- 进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
- 出队操作:队不空时,先取队头元素值,再将队头指针加1。
图3.16(a)所示为队列的初始状态,有Q->rear==Q->front==NULL成立,该条件可以作为队列判空的条件。但能否用Q->rear==Maxsize作为队列满的条件呢?显然不能,图3.16(d)中,队列中仅有三个元素,但仍满足该条件。这时入队出现“上溢出”,但这种溢出并不是真正的溢出,在data数组中依然存在可以存放元素的空位置,所以是一种“假溢出”。真正队满的条件应是rear-front==MAXSIZE。
1.2循环队列
循环队列是队列的一种顺序表示和实现方法。与顺序栈类似,队列中的元素无法自动移,为了解决假溢出现象并使队列空间得到充分利用,一个巧妙的办法是将顺序队列的数组看成一个环状的空间,即规定最后一个单元的后继为第一个单元,可形象地称之为循环队列。
假设队列数组为Queue[MAXSIZE],当rear+1==MAXSIZE时,令rear=0,即可求得最后一个单元Queue[MAXSIZE-1]的后继Queue[0]。通过数学中的取模(求余)运算来支持实现rear=(rear+1)%MAXSIZE。显然,当rear+1==MAXSIZE时,rear=0,同样可求得最后一个单元Oueue[MAXSIZE-1]的后继Queue[0]。所以借助于取模(求余)运算,可以自动实现队尾指针、队头指针的循环变化。进队操作时,队尾指针的变化是rear=(rear+1)mod MAXSIZE;而出队操作时,队头指针的变化是front=(front+1)mod MAXSIZE。
循环队列的几种情况:
在非空循环队列中,队头指针始终指向当前的队头元素,而队尾指针始终指向真正队尾元素后面的单元。在图3.17(a)所示的循环队列中,队列头元素是,队列尾元素是,当,和相继入队后,队列空间均被占满,如图3.17(b)所示,此时队尾指针追上队头指针,所以有front==rear。反之,若、和相继从图3.17(a)的队列中删除,则得到空队列,如图3.17(c)所示,此时队头指针追上队尾指针,所以也存在关系式front==rear。可见,只凭front==rear无法判别队列的状态是“空”还是“满”。
对于这个问题,可有三种处理方法:
- 一种方法是损失一个元素的存储空间,当队尾指针所指向的空单元的后继单元是队头元素所在的单元时,则停止入队。这样一来,队尾指针永远追不上队头指针,所以队满时不会有front==rear。现在队列“满”的条件为(rear+1)%MAXSIZE==front;判队空的条件不变,仍为rear==front,队列中元素的个数为(Q->rear-Q->front+MAXSIZE)%MaxSize。
- 另一种方法是增设一个标志量,以区别队列是“空”还是“满”,这种方法不损失空间。类型中增设tag数据成员,以区分是队满还是队空。删除成功置tag=0,若导致Q->front=Q->rear,则为队空;插入成功置tag=1,若导致Q->front=Q->rear,则为队满。
- 类型中增设size数据成员,表示元素个数。删除成功size减1,插入成功size 加1。队空时Q->size==0;队满时Q->size=MaxSize,两种情况都有Q->front=Q->rear。
下面主要介绍损失一个元素的存储空间以区分队列空与满的方法实现
循环队列的类型定义如下:
#define MAXSIZE 50 /*队列的最大长度*/
typedef struct
{
QueueElementTypeelement[MAXSIZE]; /*队列的元素空间*/
int front; /*头指针指示器*/
int rear; /*尾指针指示器*/
}SeqQueue;
循环队列的基本操作包括初始化、判队空、入队、出队等,具体算法描述如下
1.2.1初始化操作
void InitQueue( SeqQueue *Q)
{ /*将*Q初始化为一个空的循环队列*/
Q->front=O->rear=0;
}
1.2.2判队空
int isEmpty(SeqQueue *Q)
{ if(Q->rear==Q->front) //队空条件
return(TRUE);
else
return(FALSE);
}
1.2.3入队操作
int EnterQueue( SeqQueue *Q,QueueElementType x)
{ /*将元素x入队*/
if((Q->rear+1)%MAXSIZE==Q->front) /*尾指针加1追上头指针,标志队列已经满了*/
return(FALSE);
Q->element[Q->rear]=x; //新元素加入队尾
Q->rear=(Q->rear+1)%MAXSIZE; //重新设置队尾指针,队尾指针+1
return(TRUE); //操作成功
}
1.2.4出队操作
int DeleteQueue(SeqQueue *Q,QueueElementType *x)
{ /*删除队列的队头元素,用x返回其值*/
if(Q->front==Q->rear) /*队列为空*/
return(FALSE);
*x=Q->element[Q->front]; //保存队头元素
Q->front=( Q->front+1)%MAXSIZE; //重新设置队头指针,队头指针+1
return(TRUE); /*操作成功*/
}
上面给出了第一种处理假溢出问题的方法。如果采用第二种方法,则需要设置一个标志量tag。初始化操作即产生一个空的循环队列,设 Q->front=Q->rear=0,tag=0;当tag==0且Q->front==Q->rear时,表示队空;当tag=1且Q->front==Q->rear 时,表示队满。除了栈和队列之外,还有一种限定性数据结构是双端队列。双端队列是限定插人和删除操作都可以在表的两端进行的线性表。这两端分别称作端点1和端点2。在实际使用中,还可以有输出受限的双端队列(即一个端点允许插入和删除,另一个端点只允许插入),以及输人受限的双端队列(即一个端点允许插入和删除,另一个端点只允许删除)。如果限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就变为两个栈底相邻接的栈了。尽管双端队列看起来比栈和队列更灵活,但实际上在应用程序中远不及栈和队列常用,故在此不做详细讨论。
2.队列的链式表示(链队列)
若用户无法估计所用队列的长度,则宜采用链队列,用链表表示的队列简称为链队列。为了操作方便,这里采用带头结点的链表结构,并设置一个队头指针fiont和一个队尾指针rear,队头指针始终指向头结点,队尾指针指向最后一个元素。对于空的链队列,队头指针和队尾指针均指向头结点。链队列如图3.15所示。
通常将队头指针和队尾指针封装在一个结构体中,并将该结构体类型重新命名为链队列类型。链队列定义如下:
#defne MAXQSIZE 100 //最大队列长度
typedef struet Node //链式队列结点
{
QueueElementType data; /*数据域*/
struct Node *next; /*指针城*/
}LinkQueueNode;
typedef struct //链式队列
{
LinkOueueNode *front; //队列的对头和队尾指针
LinkOueueNode *rear;
}LinkQueue;
链队列的基本操作包括初始化、入队、出等操作,具体算法如下。
2.1初始化
int InitOueue(LinkOueue *Q)
{ /*将Q初始化为一个空的链队列*/
Q->front=(LinkQueueNode * ) malloc(sizeof(LinkQueueNode));
if(Q->front!=NULL)
{
Q->rear=Q->front;
Q->front->next=NULL;
return(TRUE);
}
else return(FALSE); //溢出
}
2.2判队空
int IsEmpty(LinkQueue Q){
if(Q->front->Q.rear)//判空条件
return TRUE;
else
return FALSE;
}
2.3入队操作
int EnterQueue( LinkQueue *Q,QueueElementType x)
{ /*将数据元素x插人队列中*/
LinkQueueNode * NewNode;
NewNode=(LinkQueueNode * )malloc(sizeof(LinkQueueNode));
if(NewNode!=NULL)
{
NewNode->data=x;
NewNode->next=NULL;
Q->rear->next=NewNode;
Q->rear=NewNode ;
return(TRUE);
}
else return(FALSE); /*溢出!*/
}
2.4出队操作
int DeleteQueue( LinkQueue * Q,QueueElementType *x)
{ /*将队列Q的队头元素出队,并存放到x所指的存储空间中*/
LinkOueueNode *p;
if( O->front ==Q->rear)
return(FALSE);
p=Q->front->next;
Q->front->next=p->next; /*队头元素p出队*/
if(Q->rear==p)/*如果队中只有一个元素p,则p出队后成为空队*/
Q->rear=Q->front;
*x=p->data;
free(p); /*释放存储空间*/
return(TRUE);
}
3.双端队列
双端队列是指允许两端都可以进行插入和删除操作的线性表,如图3.10所示。双端队列两端的地位是平等的,为了方便理解,将左端也视为前端,右端也视为后端。
在双端队列进队时,前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列出队时,无论是前端还是后端出队,先出的元素排列在后出的元素的前面。
3.1输出受限的双端队列
输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列,如图3.11所示。
3.2输入受限的双端队列
输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列,如图3.12所示。
若限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻接的栈。