此处选择了略过一般队列而直接实现循环队列。一般队列如果以队尾指针rear==MaxSize
作为队满条件,可能会产生一个问题:“假溢出”。队列是一种操作受限的线性表,入队是在队尾入队,出队是在队头出队,在不出队的情况下,如果出现rear==Maxsize
,即表示已经队满,但是如果在此时进行出队,由于出队是在队头操作,即使队尾满了,进行出队操作之后,队头前会空置出大量的空间,直至最后一个元素,此时队头指针、队尾指针都指向一个位置,这也是判空的条件,队尾指针等于MaxSize,表示队已经满了。但事实上,队列还有一个元素未出队,且线性表中还有大量的空间未曾使用,此种溢出并不是真正的溢出,而是“假溢出”。
对于此种情况,可使用循环队列来解决,当队尾指针指向线性表的最后时,不会令rear==MaxSize
,可以通过取模运算,使rear重新指向队头,当然,此前需要进行判定。此种循环队列一般有以下三种思路:
(rear+1)%MaxSize==front
,此种方法的优点是rear==front
此条件仍然是表示队空的条件,且不需要额外的存储空间;缺点就是会浪费一个单元的存储空间,因为rear是指向下一个应该插入的位置,队满时其实rear指向的存储单元中并没有数据。rear==front
也能够轻易判断,但会占据额外的空间。rear==front
表示队空,其等于1时rear==front
表示队满。此处实现的是第一种,默认队头指针front指向第一个元素,队尾指针rear指向下一个应该插入的位置。
typedef struct {
int data[MaxSize]; // 用静态数组存放队列元素
// 队头指针与队尾指针
// 队头指针指向队头元素,队尾指针指向队尾元素的后一个位置
// 即队尾指针应指向下一个应该插入的位置
int front, rear;
} SeqQueue;
// 初始化
void InitQueue(SeqQueue &Q) {
// 初始时,队头队尾指针指向0
// 队头指针指向第一个,队尾指针指向下一个应该插入的位置,也即是0
Q.rear = Q.front = 0;
}
// 判断队列是否为空
bool QueueEmpty(SeqQueue Q) {
// 队空条件
// 即队头指针和队尾指针都指向第一个位置
if(Q.rear == Q.front) {
return true;
} else {
return false;
}
}
// 入队操作
// 此循环队列队列元素计算方法:(rear + MaxSize - front) % MaxSize
bool EnQueue(SeqQueue &Q, int x) {
// 判断队列是否已经满了
// 注意:即使rear==MaxSize也不一定能代表队列已经满了
// 因为出队是从队头出队,可能队头有元素出队,导致线性表前部分空置下来
// 虽然以下操作可能会导致浪费一个存储单元,但是如果Q.rear==Q.front,就表示队列空置,难以判定
// 所以不能让Q.rear==Q.front
if((Q.rear + 1) % MaxSize == Q.front) {
return false;
}
// 由于队尾指针指向下一个要插入的位置
// 所以直接把要插入的数据插入队尾指针位置即可,即队尾
Q.data[Q.rear] = x;
// 队尾指针后移
// 此处使用了取模运算,将无限的整数域映射到了有限的整数集合上,即{0, 1, 2, ..., MaxSize - 1}
// 此运算将存储空间从逻辑上变为了环状
// 即在rear指向队尾后,即rear == MaxSize时,会取模使其再次指向0
Q.rear = (Q.rear + 1) % MaxSize;
return true;
}
// 出队操作
// 由于队列的操作受限性,所以删除只能在队头进行
bool DeQueue(SeqQueue &Q, int &x) {
// 判断队列是否为空
if(Q.rear == Q.front) {
return false;
}
// 将队头元素的值赋给x
x = Q.data[Q.front];
// 队头元素后移
// 类似于之前队尾元素后移,形成了环状循环
Q.front = (Q.front + 1) % MaxSize;
return true;
}
// 获取队头元素的值
// 类似于出队操作,不过队头指针位置不变
bool GetHead(SeqQueue Q, int &x) {
if(Q.rear == Q.front) {
return false;
}
x = Q.data[Q.front];
return true;
}
// 输出队列中所有元素
// 由于队列只能在队头出队,所以只能将所有元素出队输出
void PrintQueue(SeqQueue Q) {
if(QueueEmpty(Q)) {
printf("the queue is empty!\n");
}
while(!QueueEmpty(Q)) {
int x;
DeQueue(Q, x);
printf("dequeue:%d\n", x);
}
}
#include
#define MaxSize 10 // 定义队列中元素最大个数
typedef struct {
int data[MaxSize]; // 用静态数组存放队列元素
// 队头指针与队尾指针
// 队头指针指向队头元素,队尾指针指向队尾元素的后一个位置
// 即队尾指针应指向下一个应该插入的位置
int front, rear;
} SeqQueue;
// 初始化
void InitQueue(SeqQueue &Q) {
// 初始时,队头队尾指针指向0
// 队头指针指向第一个,队尾指针指向下一个应该插入的位置,也即是0
Q.rear = Q.front = 0;
}
// 判断队列是否为空
bool QueueEmpty(SeqQueue Q) {
// 队空条件
// 即队头指针和队尾指针都指向第一个位置
if(Q.rear == Q.front) {
return true;
} else {
return false;
}
}
// 入队操作
// 此循环队列队列元素计算方法:(rear + MaxSize - front) % MaxSize
bool EnQueue(SeqQueue &Q, int x) {
// 判断队列是否已经满了
// 注意:即使rear==MaxSize也不一定能代表队列已经满了
// 因为出队是从队头出队,可能队头有元素出队,导致线性表前部分空置下来
// 虽然以下操作可能会导致浪费一个存储单元,但是如果Q.rear==Q.front,就表示队列空置,难以判定
// 所以不能让Q.rear==Q.front
if((Q.rear + 1) % MaxSize == Q.front) {
return false;
}
// 由于队尾指针指向下一个要插入的位置
// 所以直接把要插入的数据插入队尾指针位置即可,即队尾
Q.data[Q.rear] = x;
// 队尾指针后移
// 此处使用了取模运算,将无限的整数域映射到了有限的整数集合上,即{0, 1, 2, ..., MaxSize - 1}
// 此运算将存储空间从逻辑上变为了环状
// 即在rear指向队尾后,即rear == MaxSize时,会取模使其再次指向0
Q.rear = (Q.rear + 1) % MaxSize;
return true;
}
// 出队操作
// 由于队列的操作受限性,所以删除只能在队头进行
bool DeQueue(SeqQueue &Q, int &x) {
// 判断队列是否为空
if(Q.rear == Q.front) {
return false;
}
// 将队头元素的值赋给x
x = Q.data[Q.front];
// 队头元素后移
// 类似于之前队尾元素后移,形成了环状循环
Q.front = (Q.front + 1) % MaxSize;
return true;
}
// 获取队头元素的值
// 类似于出队操作,不过队头指针位置不变
bool GetHead(SeqQueue Q, int &x) {
if(Q.rear == Q.front) {
return false;
}
x = Q.data[Q.front];
return true;
}
// 输出队列中所有元素
// 由于队列只能在队头出队,所以只能将所有元素出队输出
void PrintQueue(SeqQueue Q) {
if(QueueEmpty(Q)) {
printf("the queue is empty!\n");
}
while(!QueueEmpty(Q)) {
int x;
DeQueue(Q, x);
printf("dequeue:%d\n", x);
}
}
int main() {
SeqQueue Q;
InitQueue(Q);
PrintQueue(Q);
int x;
EnQueue(Q, 3);
GetHead(Q, x);
printf("enqueue:%d\n", x);
x = 5;
EnQueue(Q, x);
printf("enqueue:%d\n", x);
x = 7;
EnQueue(Q, x);
printf("enqueue:%d\n", x);
PrintQueue(Q);
}