1、基本概念
- 队列是只允许在一端进行插入,而在另一段进行删除的线性表
- 队头:允许删除的一端
- 队尾:允许插入的一端
- 空队列:没有任何元素的空表
队列是操作受限的线性表,因此不是任何对线性表的操作都可以作为队列的操作,比如,不可以随便读取队列中间某个数据
2、顺序队列
队列的顺序实现是指分配一块连续存储单元存放队列中的元素,并设两个指针front和rear分别指向队头和队尾元素的元素。队尾rear指针指向队尾元素的下一个位置(也可以让rear指向队尾元素,front指向队头元素的前一个位置),为什么要这样处理呢?当队列中只有一个元素是,rear和front将会相等,处理起来很麻烦。
- 初始状态(队空条件):front==rear==0
- 进队:不满时,送值到队尾,队尾rear加1
- 出队:不空时,取队头元素,队头front加1
/*
顺序队列
*/
typedef struct {ElemType data[MaxSize];int front, rear;
}SqQueue;
当此时队列的状态为上图的d时,在入队,现任此时rear再加一会出现溢出,但这种溢出并不是真正的溢出,在data数组里依然有位置存放数据,所以这是一种假溢出
2、循环队列
把存储队列元素的表从逻辑上看作是一个环,称为循环队列。当用循环队列时,上面顺序队列出现假溢出的问题就能解决
- 初始时,Q.front=Q.rear=0
- (出队) front指针加1:Q.front=(Q.front+1)%MaxSize
- (入队)rear指针加1:Q.rear=(Q.rear+1)%MaxSize
- 队列长度:(Q.rear-Q.front+MaxSize)%MaxSize
- 出队入队指针按顺时针方向进1
如何判断队列空和队满呢?
法1:牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是个普遍的做法,约定以:队头指针在队尾指针的下一个位置作为队满的标志
- 队满:(Q.rear+1)%MaxSize==Q.front
- 队空条件:Q.front==Q.rear(注意和顺序队列区分开,Q.front==Q.rear==0)
- 队中元素个数:(Q.rear-Q.front+MaxSize)%MaxSize
法2:类型中增设表示元素个数的数据成员。这样,
- 队空的条件为Q.size==0
- 队满:Q.size==MaxSize
法3:类型增设tag数据成员,以区分队满还是队空。tag等于0时,若因删除导致QQ.front==Q.rear,则为对空;tag等于1时,若因插入导致Q.front==Q.rear,则为队满
3、链式队列
队列的链式存储结构,其实就是线性表的单链表,只不过需要加点限制,只能在表尾插入,表头删除。
typedef struct {ElemType data;struct LinkNode* next;
}LinkNode;
typedef struct {LinkNode* front, * rear;
}LinkQueue;
- 当 Q.front ==NULL && Q.rear==NULL时,链式队列为空
4、双端队列
双端队列是指允许两端都可以进行入队和出队操作的队列,将队列的两端分别称为前端和后端,不再是队头和队尾,其逻辑结构依然是线性结构。
输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列被称为输出受限的双端队列
输入受限的双端队列:允许在一端进行插入和删除,但另一端只允许删除的双端队列称为输入受限的双端队列