声明:博文所有gif图均为本人原创……
队列(queue)
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表 ,简称FIFO。允许插入的一段叫做队尾,允许删除的一段称为队头。
队列的抽象数据类型
ADT 队列(Queue)
{
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitQueue(&Q) 队列初始化
DestroyQueue(&Q) 若队列存在,销毁队列
ClearQueue(&Q) 将队列清空
QueueEmpty(Q) 若队列为空返回true,否则返回false
GetHead(Q,&e) 若队列存在,则用e返回队头
EnQueue(&Q,e) 入队,插入新元素e到队尾
DeQueue(&Q,*e) 出队,用e返回其值
QueueLength(Q) 返回队列的个数
}ADT Queue
队列也是一种线性表,也有顺序存储和链式存储两种存储形式。我们先看队列的顺序存储模式。
循环队列
队列顺序存储形式的不足:
我们假设一个队列有n个元素,则顺序存储的队列至少要建立一个大于n的数组,并把数据存入数组的前n个单元,队头为下标为0的元素。我们在对队列进行插入操作的时候,其实就是在队尾追加一个元素,不需要移动任何元素,所以他的时间复杂度为O(1);但是在进行删除操作的时候,要移除第一个元素,很显然,移除完之后需要把后面的元素全都向前移一位,需要浪费很多的时间,时间复杂度为O(n)。如图所示:
上图为入队操作,错落有致,把每个元素都插入倒最后一位。
上图为出队操作,看的出来,每次出队后续元素都要向前移一位,时间复杂度极高。
所以,我们为了避免重复移动数据元素,如果我们去除队列元素必须存储在前n个单元的条件,出队的性能就会大大的增加,换句话说,队头不一定在下标为0的位置。我们还是拿图说话吧!
上图为循环队列的入队操作,我们可见插入一个元素到队尾,尾指针rear就会向后移动一位,front不变。
上图为循环队列的出队操作, 出队时头指针front后移,rear不变;当后面的空间插入满时,可以插在存储单元的前面。
由上图可见,我们少用一个元素空间,即队列空间大小为m时,有m-1个元素就认为是队满。所以,当头、尾指针的值相同时,则认为队空;而当尾指针在循环意义上加1后时等于头指针,则认为队满。因此,在循环队列中有:
- 队空的条件:Q.front == Q.rear
- 队满的条件:(Q.rear+1) % MAXSIZE == Q.front
循环队列的顺序存储结构
#define MAXQSIZE 100
typedef struct {
QElemType *base; //存储空间的基地址
int front; //头指针
int rear; //尾指针
}SqQueue;
循环队列的初始化
【算法步骤】
- 为队列分配一个最大容量为MAXQSIZE的数组空间,若分配失败返回OVERFLOW
- 头指针尾指针置为0,表示队列为空
Status InitQueue(SqQueue &Q)
{
Q.base=new QElemType[MAXQSIZE]; //为队列分配一个最大容量为MAXSIZE的数组空间
if(!Q.base)
return OVERFLOW;
Q.front=Q.rear=0; //头指针和尾指针置为0,队列为空
return true;
}
求循环队列的长度
【算法步骤】
- 直接返回(Q.rear-Q.front+MAXQSIZE)% MAXQSIZE
int QueueLength(SqQueue Q)
{
return (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;
}
循环队列的入队
【算法步骤】
- 判断队列是否已满,若满则返回OVERFLOW
- 将新元素插入到队尾
- 队尾指针加一
Status EnQueue(SqQueue &Q,QElemType e)
{
if((Q.rear+1)%MAXQSIZE==Q.front)
return false;
Q.base[Q.rear]=e;
Q.rear=(Q.rear+1)%MAXQSIZE; //队尾指针加一
return true;
}
循环队列的出队
【算法步骤】
- 判断队列是否为空,若为空,返回false
- 保存队头元素
- 队头指针加一
Status DeQueue(SqQueue &Q,QElemType &e)
{
if(Q.front==Q.rear) //队空
return false;
e=Q.base[Q.front];
Q.front=(Q.front+1)%MAXQSIZE;
return true;
}
取队头元素
【算法步骤】
- 判断队列是否为空,然后直接返回队头
SElemType GetHead(SqQueue Q)
{
if(Q.front!=Q.rear)
return Q.base[Q.front];
}
若未知最大队列长度,则宜用链队
链队
链队是采用链式存储结构来存储的,通常用单链表来表示。一个链队显然需要队头和队尾的指针才能唯一确定。为了方便起见,给链队添加一个头结点,并令头指针始终指向头结点。
链队的链式存储结构
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
QueuePtr front; //tou
QueuePtr rear; //wei
}LinkQueue;
链队的初始化
【算法步骤】
- 生成新结点作为头结点,队头和队尾指针指向此结点
- 头结点指针域置空
Status InitQueue(LinkQueue &Q)
{
//生成头结点,队头和队尾指向此结点
Q.front=Q.rear=new QNode;
Q.front->next=NULL; //头结点的指针域置空
return true;
}
链队的入队
【算法步骤】
- 为入队元素分配结点空间,用指针p指向
- 将新结点的数据域置为e
- 将新结点插入到队尾
- 修改队尾指针为p
Status EnQueue(LinkQueue &Q,QElemType e)
{
QNode *p=new QNode;
p->data=e;
p->next=NULL;
Q.rear->next=p; //将新结点插入到队尾
Q.rear=p; //修改队尾指针
return true;
}
链队的出队
【算法步骤】
- 判断队列是否为空,若空返回false
- 临时保存队头的元素空间,以备释放
- 修改队头指针,指向下一个结点
- 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值,指向头结点
- 释放原队头元素空间
Status DeQueue(LinkQueue &Q,QElemType &e)
{
if(Q.front==Q.rear)
return false;
QNode p=Q.front->next; //指向队头
e=p->data;
Q.front->next=p->next; //修改头指针
if(Q.rear==p)
Q.rear=Q.front; //最后一个元素被删,队尾指针指向头结点
delete p;
return true;
}
取队头元素
【算法步骤】
- 若队不为空,返回当前首元结点的数据域
QElemType GetHead(LinkQueue Q)
{
if(Q.front!=Q.rear)
return Q.front->next->data;
}
栈的基本操作请访问:https://blog.csdn.net/lesileqin/article/details/88381142
博客内容借鉴于:
①《数据结构》 作者:严蔚敏
②《大话数据结构》 作者:程杰