队列的显著特征为先进先出 ,类似排队买票,先来的人先买,后来的人只能排在队尾,先到先得。队列跟栈很相似,支持的操作有限,基本操作有入队 enqueue 和出队dequeue()。队列和栈一样,也是一种操作受限的线性表数据结构。
具有额外特性的队列,比如 循环队列、阻塞队列、并发队列。在很对偏底层的系统开发中起着关键作用。比如linux 环形缓存 就用到了循环并发队列。
用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。
下面是用数组实现的顺序队列:
arr_queue.h
#ifndef ARR_QUEUE_
#define ARR_QUEUE_
#include "stdbool.h"
typedef int ElementType;
typedef struct _arr_queue {
ElementType arr[100];
ElementType capacity;
ElementType head; //标记队头索引
ElementType tail; //标记队尾索引
}arrQueue;
void CreateArrQueue(arrQueue *aqueue);
bool enqueue(arrQueue *aqueue, ElementType date);
ElementType dequeue(arrQueue *arrqueue);
#endif
#include <stdio.h>
#include <stdlib.h>
#include "arr_queue.h"
/*
@parameter
*/
void CreateArrQueue(arrQueue *aqueue)
{
aqueue->capacity = sizeof(aqueue->arr) / sizeof(ElementType);
aqueue->head = 0;
aqueue->tail = 0;
}
/*
@func : insert a date to tail of queue
@parameter1: address of queue
@parameter1: the date insert to tail of queue
@return: true: enqueue success false: enqueue failed
*/
bool enqueue(arrQueue *arrQueue, ElementType date)
{
if (!arrQueue) {
perror("arrQueue is NULL.");
exit(1);
}
/*判断队列是否满了*/
if(arrQueue->tail == arrQueue->capacity) {
printf("Queue is full.\n");
return false;
}
arrQueue->arr[arrQueue->tail] = date;
arrQueue->tail++;
return true;
}
/*
@func : delete the head of queue
*/
ElementType dequeue(arrQueue *arrQueue)
{
int val = 0;
if (!arrQueue) {
perror("arrQueue is NULL.");
exit(1);
}
/*判断队列是否为空*/
if (arrQueue->head == arrQueue->tail) {
printf("Queue is empty.\n");
return false;
}
val = arrQueue->arr[arrQueue->head];
arrQueue->head++;
return val;
}
int main()
{
int i;
//test code
arrQueue aq;
CreateArrQueue(&aq);
enqueue(&aq, 10);
enqueue(&aq, 20);
printf("val --->%d\n", dequeue(&aq));
printf("val --->%d\n", dequeue(&aq));
printf("val --->%d\n", dequeue(&aq));
return 0;
}
这样实现有个缺陷, 当tail 移到数组的最右边,即使数组中还有空闲的空间,也无法再进行入队操作,造成空间浪费。我们可以通过数据搬移来解决这个问题。在入队时集中触发一次搬移操作,出队函数dequeue()保持不变,修改入队函数enqueue()。
bool optimize_enqueue(arrQueue *aqueue, ElementType date)
{
if (!aqueue) {
perror("aqueue is NULL.");
exit(1);
}
if(aqueue->tail == aqueue->capacity) {
if (aqueue->head == 0) {
printf("Don't have useful space.\n");
return false;
}
int i;
int head = aqueue->head;
/*数据搬移*/
for(i = aqueue->head; i < aqueue->tail; ++i) {
aqueue->arr[i-head] = aqueue->arr[i];
}
/*更新tail 索引*/
aqueue->tail -= aqueue->head;
}
aqueue->arr[aqueue->tail] = date;
aqueue->tail++;
return true;
}
链式队列的实现,同样需要两个指针:head指针和tail指针,他们分别指向链表的第一个节点和最后一个节点。入队时,tail->next = new_node,tail = tail->next;出队时,head = head->next。具体实现如下:
linklist_queue.h
#ifndef LINKLIST_QUEUE_
#define LINKLIST_QUEUE_
#include "stdbool.h"
typedef int ElementType;
typedef struct _node {
ElementType date;
struct _node* next;
}q_node;
typedef struct _link_queue {
q_node *head;
q_node *tail;
}queue;
bool initLinklistQueue(queue *queue);
bool enqueue(queue* head, int val);
ElementType dequeue(queue* head);
#endif
linklist_queue.c
#include <stdio.h>
#include <stdlib.h>
#include "linklist_queue.h"
/*
@func: creat a linklist queue
*/
bool initLinklistQueue(queue *queue)
{
if (!queue) {
perror("queue is NULL.");
exit(1);
}
queue->tail = (q_node *)malloc(sizeof(q_node));
if (!queue->head) {
perror("malloc fialed.");
exit(1);
}
queue->head = queue->tail;
queue->head->next = NULL;
return true;
}
/*
@func : insert a date to linklist queue
*/
bool enqueue(queue* h_queue, int val)
{
if (!h_queue) {
perror("h_queue is NULL.");
exit(1);
}
q_node* node = (q_node *)malloc(sizeof(q_node));
if (!node) {
perror("malloc failed.");
exit(1);
}
node->date = val;
h_queue->tail->next = node;
h_queue->tail = h_queue->tail->next;
return true;
}
/*
@func : delete a date from head of linklist queue
*/
ElementType dequeue(queue* h_queue)
{
int val = 0;
if (!h_queue) {
perror("queue is empty.");
exit(1);
}
h_queue->head = h_queue->head->next;
if (h_queue->head) {
val = h_queue->head->date;
free(h_queue->head);
h_queue->head = NULL;
return val;
}else {
printf("linklist queue is empty...\n");
exit(1);
}
}
int main()
{
/*test code*/
queue que;
initLinklistQueue(&que);
enqueue(&que, 100);
printf("val ---> %d\n", dequeue(&que));
return 0;
}
在用数组实现队列中,当tail = n时,会有数据搬移操作,入队性能会受到影响,我们可以看看循环队列的解决思路。
循环队列,顾名思义就是一个环,将数组收尾相连,要写好循环队列,最关键的应该是确定好队空队满的判定条件。
循环队列队空判断条件仍然为head = tail,队满的判断条件为 (tail + 1) % n = head。
下面是用数组实现循环队列实例。
robin_queue.h
#ifndef ROBIN_QUEUE_
#define ROBIN_QUEUE_
#include "stdbool.h"
typedef int ElementType;
typedef struct _robin_queue {
ElementType arr[100];
ElementType capacity;
ElementType head; //标记队头索引
ElementType tail; //标记队尾索引
}robinQueue;
void initRobinQueue(robinQueue *aqueue);
bool enqueue(robinQueue *aqueue, ElementType date);
ElementType dequeue(robinQueue *robinqueue);
#endif
robin_queue.c
#include <stdio.h>
#include <stdlib.h>
#include "robin_queue.h"
/*
@parameter
*/
void initRobinQueue(robinQueue *aqueue)
{
aqueue->capacity = sizeof(aqueue->arr) / sizeof(ElementType);
aqueue->head = 0;
aqueue->tail = 0;
}
/*
@func : insert a date to tail of queue
@parameter1: address of queue
@parameter1: the date insert to tail of queue
@return: true: enqueue success false: enqueue failed
*/
bool enqueue(robinQueue *aqueue, ElementType date)
{
if (!aqueue) {
perror("aqueue is NULL.");
exit(1);
}
/*判断队满*/
if ((aqueue->tail + 1)%aqueue->capacity == aqueue->head) {
printf("**queue is full**\n");
return false;
}
aqueue->arr[aqueue->tail] = date;
printf("date[%d]: %d\n", aqueue->tail, aqueue->arr[aqueue->tail]);
aqueue->tail = (aqueue->tail + 1) % aqueue->capacity;
return true;
}
/*
@func : delete the head of queue
*/
ElementType dequeue(robinQueue *robinqueue)
{
if (!robinqueue) {
perror("robinQueue is empty.");
exit(1);
}
/*判断队空*/
if (robinqueue->head == robinqueue->tail) {
printf("robinQueue is empty.\n");
exit(1);
}
ElementType val = robinqueue->arr[robinqueue->head];
robinqueue->head = (robinqueue->head + 1) % robinqueue->capacity;
return val;
}
int main()
{
/*test code*/
robinQueue rqueue;
int i;
initRobinQueue(&rqueue);
for(i = 0; i < 100; i++) {
enqueue(&rqueue, i);
}
printf("val ---> %d\n", dequeue(&rqueue));
printf("val ---> %d\n", dequeue(&rqueue));
printf("val ---> %d\n", dequeue(&rqueue));
enqueue(&rqueue, 17);
enqueue(&rqueue, 18);
}
在队列的基础上添加阻塞操作,在队列为空的时候,从队头取数据会被阻塞,此时队列中没有数据可取,知道队列中有数据可取才能返回;如果队列满了,那么插入数据的操作将会被阻塞,知道队列中有空闲的空间才能继续插入数据,然后再返回。
是否有种似曾相似的感觉,上述描述的实际上就是一个“生产者-消费者模型”!我们可以用阻塞队列实现一个“生产者-消费者模型”。代码后续补上。
在多线程的情况下,会有多个线程同时操作队列,会存在线程安全问题,并发队列是一种线程安全队列。最简单最直接的实现方式是直接在enqueue()、dequeue()方法上枷锁,同一时刻只允许一个存或者取操作。