队列是一种“先进先出的数据结构”,可分为静态队列和链式队列。静态队列一般使用数组实现,数组需要预先定义内存大小,为了避免内存浪费,一般使用循环队列。接下来讲述循环队列的原理以及实现代码。
循环队列数据结构定义:
int front;//指向队列头,指向第一个数据节点
int rear;//指向队列尾(并不是指向最后一个数据节点,而是最后一个数据节点后面的位置)
char data[];//节点数据,根据实际需要可以是不同的数据类型,但是因为是数组,需要在声明时指定大小
循环队列操作方法:
void circQue_Init(circQue_t* queue);`//初始化data数组的数据,front和rear设为0
void circQue_Deinit(circQue_t* queue);//销毁队列
bool isQueEmpty(circQue_t* queue);//检查队列是否为空,在获取队列最前面的数据前需要检查队列是否为空,判断依据是front是否和rear相等(不一定都是0)
bool isQueFull(circQue_t* queue);//检查队列是否已满,在往队列插入新的节点前需要检查队列是否已经满,判断依据是(rear+1)%dataLen == front,因为rear永远指向最后一个节点后面的位置,也就是说数组大小虽然是dataLen,但是实际最多只能存储dataLen-1个节点
bool circQue_add(circQue_t* queue, char newData);//往队列插入节点(即队列最后一个节点)
char circQue_remove(circQue_t* queue);//从队列移除节点(即队列第一个节点)
void printQueueData(circQue_t* queue);//打印队列数据
通常会比较疑惑的是,为什么rear不指向最后一个数据节点,而是指向最后一个节点后面的位置?
考虑这个情况:为什么我们要用(rear+1)%dataLen == front作为队列满的条件?因为如果使用rear==front作为判断条件,那么就和队列空的判断条件重复了,无法知道到底是满还是空。因此需要预留一个空间,当rear的后一个位置是front,说明队列已经满了。
还有一个容易犯的错误,循环队列的遍历。简单的数组或者单链表的遍历,都是从首位置开始,然后每次++,但是循环队列存在这样的情况:rear比front小,这个时候如果从front开始,每次自增1直到等于rear,条件将永远不成立。
举个例子,一个最多可存储5个元素的循环队列
插入三个节点,此时:
front为0,rear为3
将两个节点移除出队列,此时:
front为2,rear为3
插入三个节点,此时:
front为2,rear为1
可以看到,这就是循环队列的特点,rear的值已经比front小了
因此,采用下面的循环遍历队列:
for(int i = queue->front; i != queue->rear; i=(i+1)%MAX_NODE_NUM)
或
i = queue->front; while(i% MAX_NODE_NUM!= queue ->rear){…;i++}
以下是实现循环队列的源码:
#include <string.h>
#include <malloc.h>
#include <iostream>
#define MAX_NODE_NUM 10
using namespace std;
typedef struct circQueue{
int front;
int rear;
char data[MAX_NODE_NUM];
}circQue_t;
void circQue_Init(circQue_t* queue){
queue->front = 0;
queue->rear = 0;
memset(queue->data, 0, MAX_NODE_NUM);
}
void circQue_Deinit(circQue_t* queue){
if(queue){
cout << "will free queue,front is:" << queue->front <<",rear is:" << queue->rear << endl;
free(queue);
}
}
bool isQueEmpty(circQue_t* queue){
return (queue->front == queue->rear);//when front equal rear means circular queque is empty
}
bool isQueFull(circQue_t* queue){
return (((queue->rear + 1) % MAX_NODE_NUM == queue->front));//get the value of rear plus 1, then mod capacity of circular queue, queue is full if the result of mod is equal front
}
bool circQue_add(circQue_t* queue, char newData){
if(isQueFull(queue)){
cout << "queue is full, add node failed!" << endl;
return false;
}
queue->data[queue->rear] = newData;
queue->rear = (queue->rear+1)%MAX_NODE_NUM;
cout << "front is:" << queue->front << ",rear is:" << queue->rear << endl;
return true;
}
char circQue_remove(circQue_t* queue){
if(isQueEmpty(queue)){
cout << "queue is empty, remove node failed!" << endl;
return 'x';
}
char nodeValue = queue->data[queue->front];
queue->front = (queue->front+1)%MAX_NODE_NUM;
return nodeValue;
}
void printQueueData(circQue_t* queue){
for(int i = queue->front; i != queue->rear; i=(i+1)%MAX_NODE_NUM){
cout << queue->data[i] << " ";
}
cout << endl;
}
int main(){
circQue_t* que = (circQue_t*)malloc(sizeof(circQue_t));
if(!que){
cout << "malloc for circular queque failed!" << endl;
return -1;
}
circQue_Init(que);
circQue_add(que, 'a');
circQue_add(que, 'b');
circQue_add(que, 'c');
printQueueData(que);
circQue_remove( que);
printQueueData(que);
}