0. 引言
队列的特点是先进者先出。队列最基本的操作只有两个,入队enqueue(),放一个数据到队列尾部;出队dequeue(),从队列头部取一个元素。它和栈一样,是一种操作受限的线性表数据结构。队列可以用数组来实现,也可以用链表来实现。用数组实现的队列叫顺序队列,用链表实现的队列叫链式队列。如果队列的大小事先确定,则一般用数组来实现。如果队列的大小事先不确定,则一般是用链式队列。
1. 用数组实现队列
首先,用数组实现的队列的数据结构如下:
typedef struct tagarray_queue {
int capacity; // 队列的大小
int head; // 队头的位置
int tail; // 队尾的下一个位置
int *data; // 数据存储区
} array_queue;
复制代码
这里需要注意两点: (1) 我们这里假设存储的数据data是int类型的数据,但其实在实际的开发过程中一般不是int类型。 (2) 注意tail表示的含义:队尾的下一个位置。
首先,创建一个队列:
array_queue *create_queue(int size) {
if (size <= 0) {
return NULL;
}
array_queue *queue = (array_queue *)malloc(sizeof(array_queue));
if (queue == NULL) {
return NULL;
}
queue->data = (int *)malloc(size * sizeof(int));
if (queue->data == NULL) {
free(queue);
queue = NULL;
return NULL;
}
queue->head = 0;
queue->tail = 0;
queue->capacity = size;
return queue;
}
复制代码
这里create_queue 传入的参数size是数组的大小,即队列的大小。刚开始时,队列的头head和尾tail都指向数组的第0个位置。
其次,我们需要入队列操作:
// 入队列
int enqueue(array_queue *queue, int data) {
// 如果queue为NULL,或者队列满时,返回-1
if ((queue == NULL) || (queue->tail == queue->capacity)) {
return ERR;
}
queue->data[queue->tail++] = data;
return OK;
}
复制代码
这里首先对队列作了判断:如果队列满了,则直接返回失败。否则将数据添加在下标为tail的位置,并且tail++。
最后还需要出队操作:
// 出队列
int dequeue(array_queue *queue, int *data) {
// 没有数据时,出队列返回错误
if ((queue == NULL) || (data == NULL) || (queue->head == queue->tail)) {
return ERR;
}
(*data) = queue->head;
queue->head++;
return OK;
}
复制代码
这里,dequeue有两个参数,其中, data是一个出参,用来存储出队列的数据。出队操作也是先做一个判断:如果队列为空,则直接返回失败。否则,将队头传给data,并且队头++。
完整的代码可以见 附录1。
1.1 改进
上面的代码实现其实有一个问题:随着不停的进行入队和出队的操作,head和tail都会往后移动。当tail移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。这样会极大的浪费内存。为了解决这个问题,需要进行数据搬移,当tail到最后的时候,当再次有数据入队时,如果还有空闲空间,则进行一次数据的搬移操作。所以,对于出队函数保持不变,需要改造一下入队函数的实现。具体代码如下:
// 入队列
int enqueue(array_queue *queue, int data) {
// 队列为空,或者队列满时,返回-1
if (queue == NULL) {
return ERR;
}
if (queue->tail == queue->size) {
// 如果head为0,说明确实是满了
if (queue->head == 0) {
return ERR;
}
int i;
int baseIndex = queue->head;
for (i = queue->head; i < queue->tail; i++) {
queue->data[i - baseIndex];
}
queue->tail -= baseIndex;
queue->head = 0;
}
queue->data[queue->tail++] = data;
return OK;
}
复制代码
2. 循环队列
上面基于数组的队列的实现中,当tail == capacity时,需要数据搬移,这样会导致入队的操作性能降低。为了避免数据搬移,可以用循环队列。
关于循环队列需要理解以下内容:
- 循环队列入队的伪算法
- 把值存在tail所在的位置
- tail = (tail + 1) % capacity
- 循环队列出队的伪算法:
- 先保存出队的值
- head = (head + 1) % capacity
- 判断循环队列是否为空
- head == tail
- 判断循环队列是否为满
如图所示,假如数组的大小是7,并且队列中此时已经放入数据1,2,3,4,5,6了。如果再放一个数据,则此时head == tail了,这跟上面判断循环队列是否为空的条件是一样的。没法区分到底是队列为空,还是队列满了的。所以为了区分,我们最后一个存储空间不用。所以判断循环队列是否为满的条件是:
(tail + 1) % capacity == head。
此时循环队列的tail其实是没有存储数据的。所以会浪费一个数组的存储空间。
至于为啥这里要对capacity取余,是因为假如现在tail = capacity -1,而head = 0。则 tail + 1 = capacity。而head = 0,所以用 (tail + 1) % capacity = capacity % capacity = 0。完整的代码实现可以见 附录2。
当然,上面只是一种方法来判断循环链表是否为空。这里还有另外一种方法:额外定义一个当前数组大小的值size。这样队列满的条件就是”当前队列的大小 等于 数组的大小”了。这样数组最后一个空间也不会浪费了。而且也更容易理解。(当然这里数据类型是int型,我多了个int 型的参数size,和上面不用最后一个空间,其实是一样的。但是当数据类型不是int型,而是大小比int型大时, 下面的这种实现更省内存。)
typedef struct {
int capacity; // 队列数组的容量
int size; // 当前队列数组的大小
int head; // 队头的位置
int tail; // 队尾的下一个位置
int *data; // 数组
} MyCircularQueue;
复制代码
具体实现可以参考附录3。
3. 用链表实现队列
用链表实现的队列就不存在队列满的情况了。用链表实现的队列的数据结构如下。这里还是需要两个指针head和tail。head指向链表的第一个结点,tail指向链表的最后一个结点。当要入队时,tail->next = new_node, tail = tail->next。出队时, head = head->next。
具体实现可以见 附录4。
typedef struct tagQueue_node {
int data;
struct tagQueue_node *next;
} queue_node;
typedef struct tagList_queue {
int num; // 当前元素的个数
queue_node *head;
queue_node *tail;
} list_queue;
复制代码
4. Golang中的channel
了解Go语言的同学应该知道在Go语言中,channel(通道)是Golang在语言层面上提供的goroutine间的通信方式,你去读源码就会发现,channel底层的数据结构就用到了队列。
这里给出两篇博客,剖析了channel的底层数据结构。推荐大家看看:
5. 参考文献
附录1:队列的数组C语言实现
数组实现队列的C语言实现完整代码:
// array_queue.h头文件相关代码
#ifndef ARRAY_QUEUE_H
#define ARRAY_QUEUE_H
#define OK 0
#define ERR 1
typedef struct tagarray_queue {
int capacity; // 队列的大小
int head; // 队头的位置
int tail; // 队尾的下一个位置
int *data; // 数据存储区
} array_queue;
#endif
// array_queue.c 相关代码
#include <stdlib.h>
#include <stdio.h>
#include "array_queue.h"
array_queue *create_queue(int size) {
if (size <= 0) {
return NULL;
}
array_queue *queue = (array_queue *)malloc(sizeof(array_queue));
if (queue == NULL) {
return NULL;
}
queue->data = (int *)malloc(size * sizeof(int));
if (queue->data == NULL) {
free(queue);
queue = NULL;
return NULL;
}
queue->head = 0;
queue->tail = 0;
queue->capacity = size;
return queue;
}
// 入队列
int enqueue(array_queue *queue, int data) {
// 队列为空,或者队列满时,返回-1
if ((queue == NULL) || (queue->tail == queue->capacity)) {
return ERR;
}
queue->data[queue->tail++] = data;
return OK;
}
// 出队列
int dequeue(array_queue *queue, int *data) {
// 没有数据时,出队列返回错误
if ((queue == NULL) || (data == NULL) || (queue->head == queue->tail)) {
return ERR;
}
(*data) = queue->head;
queue->head++;
return OK;
}
// 销毁队列
void destory_queue(array_queue *queue) {
if (queue == NULL) {
return;
}
if (queue->data != NULL) {
free(queue->data);
queue->data = NULL;
}
free(queue);
return;
}
// 打印队列
void print_queue(array_queue *queue) {
printf("\n\n");
if (queue == NULL) {
printf("\nqueue is NULL.");
return;
}
printf("\n size:%d,head:%d, tail:%d.",
queue->capacity, queue->head, queue->tail);
int i;
for (i = queue->head; i < queue->tail; i++) {
printf("\n\t array[%d]:%d",i, queue->data[i]);
}
return;
}
int main() {
int size = 4;
array_queue *queue = create_queue(size);
if (queue == NULL) {
printf("\n queue create fail.");
return ERR;
}
// 队列为空时,出队列返回错误
int data = 0;
int ret = dequeue(queue, &data);
if (ret != OK) {
printf("\n dequeue failed.");
}
int i;
// 队列大小是4,入队5个
for (i = 0; i < size + 1; i++) {
ret = enqueue(queue, i);
if (ret != OK) {
printf("\n queue %d enqueue failed.", i);
}
}
// 打印队列
print_queue(queue);
ret = dequeue(queue, &data);
if (ret != OK) {
printf("\n dequeue failed.");
}
print_queue(queue);
ret = dequeue(queue, &data);
if (ret != OK) {
printf("\n dequeue failed.");
}
print_queue(queue);
ret = dequeue(queue, &data);
if (ret != OK) {
printf("\n dequeue failed.");
}
print_queue(queue);
ret = dequeue(queue, &data);
if (ret != OK) {
printf("\n dequeue failed.");
}
print_queue(queue);
ret = enqueue(queue, 9);
if (ret != OK) {
printf("\n queue 9 enqueue failed.");
}
print_queue(queue);
ret = enqueue(queue, 10);
if (ret != OK) {
printf("\n queue 10 enqueue failed.");
}
print_queue(queue);
destory_queue(queue);
return OK;
}
复制代码
附录2:循环队列的C语言实现
循环队列的实现:
int enqueue(array_queue *queue, int data) {
// 队列为空,或者队列满时,返回-1
if (queue == NULL) {
return ERR;
}
if ((queue->tail + 1) % queue->capacity == queue->head) {
printf("\ntail:%d, capacity:%d, head:%d", queue->tail, queue->capacity, queue->head);
return ERR;
}
queue->data[queue->tail] = data;
queue->tail = (queue->tail + 1) % queue->capacity;
return OK;
}
int dequeue(array_queue *queue, int *data) {
// 没有数据时,出队列返回错误
if ((queue == NULL) || (data == NULL) || (queue->head == queue->tail)) {
return ERR;
}
(*data) = queue->head;
queue->head = (queue->head + 1) % queue->capacity;
return OK;
}
// 打印队列
void print_queue(array_queue *queue) {
printf("\n\n");
if (queue == NULL) {
printf("\nqueue is NULL.");
return;
}
printf("\n capacity:%d,head:%d, tail:%d.",
queue->capacity, queue->head, queue->tail);
int i;
if (queue->head < queue->tail) {
for (i = queue->head; i < queue->tail; i++) {
printf("\n\t array[%d]:%d",i, queue->data[i]);
}
} else {
for (i = queue->head; i < queue->capacity; i++) {
printf("\n\t array[%d]:%d",i, queue->data[i]);
}
for (i = 0; i < queue->tail; i++) {
printf("\n\t array[%d]:%d",i, queue->data[i]);
}
}
return;
}
复制代码
附录3:带长度的循环队列的C语言实现
大家可以先在LeetCode上自己实现一遍:
typedef struct {
int capacity; // 队列数组的容量
int size; // 当前队列数组的大小
int head; // 队头的位置
int tail; // 队尾的下一个位置
int *data; // 数组
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue *queue = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));
if (queue == NULL) {
return NULL;
}
queue->size = 0;
queue->head = 0;
queue->tail = 0;
queue->capacity = k;
queue->data = (int *)malloc(sizeof(int) * k);
return queue;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (obj->size == obj->capacity) {
return false;
}
obj->data[obj->tail] = value;
obj->tail = (obj->tail + 1) % (obj->capacity); // 这里取余是为了当tail == capacity - 1时, tail + 1 = capacity,取余后 tail = 0
obj->size++;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (obj->size == 0) {
return false;
}
obj->head = (obj->head + 1) % (obj->capacity);
obj->size--;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (obj->size == 0) {
return -1;
}
return obj->data[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (obj->size == 0) {
return -1;
}
return obj->data[(obj->tail - 1 + obj->capacity) % (obj->capacity)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return (obj->size == 0) ? true :false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->size == obj->capacity) ? true : false;
}
void myCircularQueueFree(MyCircularQueue* obj) {
if (obj == NULL) {
return;
}
free(obj->data);
obj->data = NULL;
free(obj);
return;
}
复制代码
附录4: 队列的链表C语言实现
链表实现队列完整代码:
// list_queue.h
#ifndef LIST_QUEUE_H
#define LIST_QUEUE_H
#define OK 0
#define ERR 1
typedef struct tagQueue_node {
int data;
struct tagQueue_node *next;
} queue_node;
typedef struct tagList_queue {
int num; // 当前元素的个数
queue_node *head;
queue_node *tail;
} list_queue;
#endif
// list_queue.c
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include "list_queue.h"
#include <pthread.h>
// 判断队列是否为空
bool list_queue_is_empty(list_queue *queue)
{
return queue->num == 0;
}
// 创建队列
list_queue *list_queue_create()
{
list_queue *queue = (list_queue *)malloc(sizeof(list_queue));
if (queue == NULL) {
return NULL;
}
queue->num = 0;
queue->head = NULL;
queue->tail = NULL;
return queue;
}
// 入队列
int list_queue_enqueue(list_queue *queue, int data)
{
if (queue == NULL) {
printf("enqueue fail, queue is NUll.\n");
return ERR;
}
queue_node *tmp = (queue_node *)malloc(sizeof(queue_node));
if (tmp == NULL) {
printf("enqueue fail, malloc queue_node fail.\n");
return ERR;
}
tmp->data = data;
tmp->next = NULL;
if (queue->head == NULL) {
queue->head = tmp;
} else {
queue->tail->next = tmp;
}
queue->tail = tmp;
queue->num++;
return OK;
}
// 出队列
int list_queue_dequeue(list_queue *queue, int *data)
{
if (queue == NULL) {
printf("dequeue fail. queue is NULL.\n");
return ERR;
}
if (list_queue_is_empty(queue)) {
printf("dequeue fail. queue is empty.\n");
return ERR;
}
queue_node *tmp = queue->head;
(*data) = queue->head->data;
queue->head = queue->head->next;
queue->num--;
if (queue->head == NULL) {
queue->tail = NULL;
}
free(tmp);
return OK;
}
// 销毁队列
void list_queue_destory(list_queue *queue)
{
if (queue == NULL) {
return;
}
int data = 0;
while (!list_queue_is_empty(queue)) {
(void)list_queue_dequeue(queue, &data);
}
free(queue);
return;
}
// 打印队列
void list_queue_print(list_queue *queue)
{
if (queue == NULL) {
return;
}
printf("print queue: num:%d: ", queue->num);
queue_node *tmp = queue->head;
while (tmp != NULL) {
printf("%d ", tmp->data);
tmp = tmp->next;
}
printf("\n\n");
return;
}
int main() {
list_queue *queue = list_queue_create();
if (queue == NULL) {
return ERR;
}
int i;
int num = 6;
int data = 0;
for (i = 0; i < num; i++) {
(void)list_queue_enqueue(queue, i);
}
list_queue_print(queue);
int ret = list_queue_dequeue(queue, &data);
if (ret != OK) {
return ret;
}
printf("dequeue number is %d.\n", data);
list_queue_print(queue);
ret = list_queue_dequeue(queue, &data);
if (ret != OK) {
return ret;
}
printf("dequeue number is %d.\n", data);
list_queue_print(queue);
list_queue_destory(queue);
return OK;
}
复制代码