队列的定义
队列是一种操作受限的线性表 ,只允许在表的前端(front)进行删除操作又称作出队,在表的后端进行插入操作,称为入队,符合先进先出(First in First out)的特性。在队尾插入元素叫做入队,对头删除元素叫做出队。
队列实现方式
队列有顺序队列和链式队列,两者的区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
顺序队列(基于数组实现)
可以看到,要实现一个顺序队列,我们需要以下结构:
- 存储数据的数组 ——`data[]`
- 表示队列的最大容量的值 ——`MAXSIZE`
- 标识队头端的队头下标——`front`
- 标识队尾端的队尾下标 ——`rear`
#define MAXSIZE 5 //顺序队列的最大存储容量
/*顺序队列的结构体*/
typedef struct {
int data[MAXSIZE];
int front; //队头下标
int rear; //队尾下标
} QueueArray;
front 和 rear 会随着入队和出队操作而变化,当元素入队时,rear加一,当元素出队时 front+1。为了方便起见,我们规定在非空队列中,队尾下标是队尾元素的下一个元素的下标 。
注意:
假溢出:指队列进行多次入队与出队操作之后,队尾指针已经指向数组最后一个位置,但队列并没有被填满,如果再插入新的元素,就会超过数组的长度,这种溢出我们称为假溢出。
真溢出:指队列进行多次入队与出队操作之后,队尾指针已经指向数组最后一个位置,并且此时队列已经被填满,如果再插入新的元素,就会超过数组的长度,这种溢出我们称为真溢出。
链式队列(基于链表实现)
可以看到,要实现一个链队列,需要以下结构:
1)单链表的基本单元结点 ——`QueueNode`
1.1)存储数据的数据域 ——`data`
1.2)指向下一个结点的指针域 ——`next`
2)指向链表的头指针 ——`head`
3)标识队头端的队头指针 ——`front`
4)标识队尾端的队尾指针 ——`rear`
/*单链表的结点的结构体*/
typedef struct QueueNode {
int data; //数据域
struct QueueNode *next; //指针域
} QueueNode;
/*链队列的结构体*/
typedef struct {
QueueNode *front; //队头指针
QueueNode *rear; //队尾指针
} QueueLink;
其中,头指针 head 和队头指针 front都指向了单链表的第一个结点,所以这个指针可以合二为一,队头指针即头指针。
如此一来,我们可以借助链表的尾插法实现队列的入队操作,借助链表的头删法实现队列的出队操作。
循环队列
元素进行多次入队出队后,用于实现队列结构的开头部分的数组的开头部分空间会浪费,并且为了克服“假溢出”的情况可以将顺序队列优化成循环队列。循环队列是把顺序队列首位相连,在逻辑上把它看成一个环。
入队:队尾循环后移, rear =(rear+1)%Maxsize;
出队:队首循环后移, front =(front+1)%Maxsize;
队空:front=rear; // rear 和 front 指向同一个位置
队满:(rear+1) %Maxsize=front; // rear 向后移一位正好是 front