深度解析数据结构与算法篇之队列及环形队列的实现

描述

01

队列简介

队列是种先进先出的数据结构,有个元素进入队列称为入对(enqueue),删除元素称为出队(dequeue),队列有对头(head)和对尾(tail),当有元素进入队列时就放在对尾的位置。

02

环形队列的实现

要想将元素放入队列我们必须知道对头和队尾,在队列长度不能无限大的条件下我们还要知道队列的最大容量,我们还想知道队列大小,所以队列内部能必须记录当前元素数量。现在我们定义一个结构体如下用于描述队列。

#define NAN (0xFFFFFE) typedef struct queue_arr{ int head; int tail; int cap; int size; int *arr; }queue_arr_t;

head是对列头,tail是对尾,cap记录队列的最大容量,size记录当前队列大小,arr指针保存一块内存的首地址。下面我们定义队列操作函数。

extern void queue_arr_init(queue_arr_t *q, int capacity); //队列初始化 extern void enqueue_arr(queue_arr_t *q, int data); //入队 extern int dequeue_arr(queue_arr_t *q); //出队 extern void queue_arr_destroy(queue_arr_t *q); //销毁队列 extern bool queue_arr_is_full(queue_arr_t *q); //判断队列是否满 extern bool queue_arr_is_empty(queue_arr_t *q); //判断队列是否为空 extern int queue_arr_length(queue_arr_t *q); //获取队列长度

队列初始化

void queue_arr_init(queue_arr_t *q, int capacity){ if(q == NULL || capacity 《= 0){ return; } q-》head = 0; q-》tail = 0; q-》size = 0; q-》arr = malloc(capacity * sizeof(int)); q-》cap = capacity; }

入队

void enqueue_arr(queue_arr_t *q, int data){ if(q == NULL){ return; } if(queue_arr_is_full(q)){ return; } //循环重用使用过的内存 if(q-》tail 》= q-》cap){ q-》tail = 0; } q-》arr[q-》tail++] = data; q-》size++; }

队列容量有限,在进行入队前一定对队列进行判断是否已满。

出队

int dequeue_arr(queue_arr_t *q){ if(q == NULL){ return NAN; } if(queue_arr_is_empty(q)){ return NAN; } if(q-》head 》= q-》cap){ q-》head = 0; } q-》size--; return q-》arr[q-》head++]; }

出队时一定要对队列进行判断是否已空。

判断队列是否已满

bool queue_arr_is_full(queue_arr_t *q){ if(q == NULL){ return false; } return q-》size == q-》cap ? true : false; }

判断队列是否已空

bool queue_arr_is_empty(queue_arr_t *q){ if(q == NULL){ return true; } return q-》size == 0 ? true : false; }

获取队列长度

int queue_arr_length(queue_arr_t *q){ if(q == NULL){ return 0; } return q-》size; }

销毁队列

void queue_arr_destroy(queue_arr_t *q){ if(q == NULL){ return; } if(q-》arr){ free(q-》arr); } q-》arr = NULL; q-》tail = 0; q-》head = 0; q-》cap = 0; q-》size = 0; }

03

链式队列的实现

为了避免队列元素的移动我们实现了环形队列,但是通过申请一块内存空间实现队列在队列大小未知的场景下无法满足我们不断加入元素进入队列的需求,这个时候就需要一种无需知道队列的最大容量,且能动态插入数据和取输出的队列实现。

我们知道链表能满足这样的需求,那么我们可以采用链表的实现方式实现队列。下面我们分别定义一个结构体用于描述链式队列和队列结点并声明队列操作函数。

typedef struct node{ int data; struct node *next; }node_t; typedef struct queue{ node_t *head; node_t *tail; }queue_t; extern void queue_init(queue_t *q); //队列初始化 extern void enqueue(queue_t *q, int data); //数据入队 extern int dequeue(queue_t *q); //数据出队列 extern int queue_length(queue_t *q); //获取队列长度 extern void queue_destroy(queue_t *q); //销毁队列

队列结点的next指针像单链表一样指向下一个结点,我们重点关注queue_t的定义,head是队列头,tail是对尾,有了对头和对尾就能快速的从对尾插入数据从对头取出数据。

队列初始化

void queue_init(queue_t *q){ if(q == NULL){ return; } q-》head = NULL; q-》tail = NULL; }

入队

元素每次进入队列都需要新建一个结点,为了方便我们定义一个新建结点函数new_node,函数实现如下:

static node_t *new_node(int data){ node_t *node = (node_t *)malloc(sizeof(node_t)); if(node == NULL){ return NULL; } node-》next = NULL; node-》data = data; return node; }

入队函数实现如下:

void enqueue(queue_t *q, int data){ if(q == NULL){ return; } node_t *node = new_node(data); //新建一个结点 if(node == NULL){ return; } if(q-》head == NULL && q-》tail == NULL){ //首次插入一个结点 q-》tail = node; q-》head = node; return; } q-》tail-》next = node; q-》tail = node; }

入队前要进行判断队列里是否有结点,这里通过队头和对尾是否为空指针进行判断,如果队列里没有结点则直接将对头和队尾指向插入的结点,否则结点则通过队尾tail获取到队列最后的结点,并将最后结点的next指针指向新插入的结点,最后更新队尾。

出队

每次从队列移除一个元素都需要释放队列结点内存,为了方便我们定义一个是否结点内存函数,函数实现如下:

static void free_node(node_t *node){ if(node == NULL){ return; } free(node); node-》next = NULL; }

出队函数实现如下:

int dequeue(queue_t *q){ if(q == NULL){ return NAN; } if(q-》head == NULL && q-》tail == NULL){ return NAN; } node_t *node = q-》head; int data = node-》data; if(q-》head-》next == q-》tail){ //只有一个结点 q-》head = NULL; q-》tail = NULL; }else{ //不止一个结点 q-》head = q-》head-》next; } node-》next = NULL; free_node(node); return data; }

出队同样要判断队列里是否有结点,没有结点则返回一个非法值,否则进行判断是否只有一个结点,若只有一个结点则直接返回头结点,并将对头和队尾指针置为空指针,否则获取队列头结点并更新对头指针。

获取队列长度

int queue_length(queue_t *q){ if(q == NULL){ return 0; } node_t *node = q-》head; if(node == NULL){ return 0; } int length = 1; while(node != q-》tail){ node = node-》next; length++; } return length; }

获取队列长度只要从队列头结点不断的遍历队列,判断当前结点是否已到队列尾部,最后返回队列长度。

销毁队列

void queue_destroy(queue_t *q){ if(q == NULL){ return; } node_t *node = q-》head; if(node == NULL){ return; } node_t *temp = NULL; while(node-》next != q-》tail){ temp = node; node = node-》next; free_node(temp); temp = NULL; } free_node(node); q-》tail = NULL; q-》head = NULL; }

04

结果验证

下面我们写一个小程序验证队列实现是否正确。

#include 《stdio.h》 #include “queue.h” int main() { queue_t queue; int i = 0; queue_init(&queue); //队列初始化 printf(“入队顺序 ”); for(i = 0; i 《 5; i++){ printf(“%d ,”, i + 1); enqueue(&queue, i + 1); } printf(“ ”); printf(“队列长度: %d ”, queue_length(&queue)); printf(“取出队列一个数据:%d ”, dequeue(&queue)); printf(“取出队列一个数据:%d ”, dequeue(&queue)); printf(“队列长度: %d ”, queue_length(&queue)); printf(“销毁队列 ”); queue_destroy(&queue); printf(“ ”); printf(“大小固定的队列测试 ”); queue_arr_t queue2; queue_arr_init(&queue2, 6); printf(“入队顺序 ”); for(i = 10; i 《 16; i++){ printf(“%d ,”, i); enqueue_arr(&queue2, i); } printf(“ ”); if(queue_arr_is_full(&queue2)){ printf(“队列已满 ”); }else{ printf(“队列未满 ”); } printf(“取出队列一个数据:%d ”, dequeue_arr(&queue2)); printf(“取出队列一个数据:%d ”, dequeue_arr(&queue2)); if(queue_arr_is_full(&queue2)){ printf(“队列已满 ”); }else{ printf(“队列未满 ”); } printf(“队列长度是:%d ”, queue_arr_length(&queue2)); printf(“销毁队列 ”); queue_arr_destroy(&queue2); if(queue_arr_is_empty(&queue2)){ printf(“队列已空 ”); }else{ printf(“队列未空 ”); } return 0; }

编译输出如下:

入队顺序 1 ,2 ,3 ,4 ,5 , 队列长度: 5 取出队列一个数据:1 取出队列一个数据:2 队列长度: 3 销毁队列 大小固定的队列测试 入队顺序 10 ,11 ,12 ,13 ,14 ,15 , 队列已满 取出队列一个数据:10 取出队列一个数据:11 队列未满 队列长度是:4 销毁队列 队列已空

输出完全正确。

编辑:jq

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分