今天要讨论的是循环队列,将有限空间按用户的需求无限化。
队列与堆栈的定义与区别
队列:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列有队首队尾,入队列总从队尾入,出队列总从队首出。
所以队列的特点就是先进先出,后进后出。
堆栈:堆栈是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址,通常用来保护断点和现场。
所以堆栈的特点是先进后出。
分析队列如何实现
结合队列的特点,我们可以用线性的数组表示它,也可以用非线性的链表来表示它。
为了提高存储效率,以及简化队列操作,我们采用顺序结构的数组来实现队列。(若采用链表则没有以下问题,也可以实现队列)
分析过程:
如果我们用数组来表示队列的话,首先就跟下图一样队尾入队首出
如上图所示:刚开始队尾队首指针都指向最右边的第一个空间,若一个元素入队列,则队尾指针++,若一个元素出队列,则队首指针++。(这里的++指向左移动一个单位)
如上图,当移动到此时时,我们发现如果有元素想要入队列,却不行了,因为没有空间了!
可以发现,队首和队尾指针一直向同一个方向移动,但因为我们申请的数组空间是有一定限度的,所以当队尾队首指针都向左移动到左边第一个空间时,队列的功能就丧失了!这可不行,于是我们想到了让数组变成一个环形数组,这样就解决了上述问题!没错聪明的你也一定想到了!如下图:
队列与堆栈的定义与区别
假设环形数组容量为12 。( head:队首指针 tail:队尾指针)
规则跟上面的普通数组相似。若一个元素入队列,则队尾指针++,若一个元素出队列,则队首指针++。
下面我们计算数组内有效元素个数,为编写循环队列做准备。
请问下图分别有多少个有效元素,给出通用公式。
答案是 : (tail - head + capacity) % capacity
可以发现环形数组与普通数组几乎一样,都遵循顺序结构,这个环形数组必须顺时针进行。
叮咚!敲重点啦!如果使用环形数组,由于队列满和队列空在形式上是相同的,必须加以区分,不然会造成程序错误!
我们可以想象,当队列放入最后一个元素时,队尾指针tail的动作是从下标为11的空间移动到下标为0的空间,做tail++操作。而这时恰好队列满,不能再执行入队列操作,所以从这里入手。分析可得:
以(tail + 1)% capicaty 的值作为判断标准, 若值为0时则是一个循环,也就是队列满的情况。
下面分析代码如何实现:
队列有四个要素:
1、队列空间
2、容量 capacity
3、队首指针 head
4、队尾指针 tail
队列空间可以采用两种方式:
1、USER_TYPE *data
2、void **data
这里大概简述一下:
1、我们可以把USER_TYPE单独写在一个头文件中,将用户想用的数据类型定义成USER_TYPE类型,这样大大提高了代码复用性,最重要的是不用更改内部代码,让用户使用起来更加方便。
2、void **data 顾名思义,data指针所指向的数组空间存储的是一些指向不同数据类型数据的指针(指针的类型为void * 类型)!这个就厉害了,一般的数组只能存储相同长度的数据(相同数据类型的数据),但是若定义为void **data ,这个数组实际存储的内容是一个个四字节指针,而指针指向不同数据类型的数据。
这里我们采用第一种方法,若处理的数据有特殊要求也可采用第二种方法,如果使用第二种定义手法,需要准备类型说明服务,不然无法处理它的类型。
好像话有点多哦…下面就…上代码八!
代码部分
queue.h
#ifndef _MEC_QUEUE_H_
#define _MEC_QUEUE_H_
#include "mec.h"
#define IN 1
#define OUT 2
typedef struct QUEUE {
USER_TYPE *data;
int capacity;
int head;
int tail;
boolean lastAction;
}QUEUE;
boolean initQueue(QUEUE **queue, int capacity);
void destoryQueue(QUEUE **queue);
boolean isQueueEmpty(const QUEUE *queue);
boolean isQueueFull(const QUEUE *queue);
boolean in(QUEUE *queue, USER_TYPE data);
boolean out(QUEUE *queue, USER_TYPE *data);
boolean readQueueHead(const QUEUE *queue, USER_TYPE *data);
#endif
queue.c
#include <stdio.h>
#include <malloc.h>
#include "queue.h"
#include "mec.h"
boolean initQueue(QUEUE **queue, int capacity) {
if (NULL == queue || NULL != *queue || capacity <= 0) {
return FALSE;
}
*queue = (QUEUE *) calloc(sizeof(QUEUE), 1);
if (NULL == *queue) {
return FALSE;
}
(*queue)->data = (USER_TYPE *) calloc(sizeof(USER_TYPE), capacity);
if (NULL == (*queue)->data) {
free(*queue);
*queue = NULL;
return FALSE;
}
(*queue)->capacity = capacity;
(*queue)->head = (*queue)->tail = 0;
(*queue)->lastAction = OUT;
return TRUE;
}
void destoryQueue(QUEUE **queue) {
if (NULL == queue || NULL == *queue) {
return;
}
free((*queue)->data);
free(*queue);
*queue = NULL;
}
boolean isQueueEmpty(const QUEUE *queue) {
if (NULL == queue) { // 调用该函数时,所提供的不是有效队列指针!
return FALSE;
}
return OUT == queue->lastAction && queue->head == queue->tail;
}
boolean isQueueFull(const QUEUE *queue) {
if (NULL == queue) { // 调用该函数时,所提供的不是有效队列指针!
return FALSE;
}
return IN == queue->lastAction && queue->head == queue->tail;
}
boolean in(QUEUE *queue, USER_TYPE data) {
if (NULL == queue || isQueueFull(queue)) {
return FALSE;
}
queue->data[queue->tail] = data;
queue->tail = (queue->tail + 1) % queue->capacity;
queue->lastAction = IN;
return TRUE;
}
boolean out(QUEUE *queue, USER_TYPE *data) {
if (NULL == queue || NULL == data || isQueueEmpty(queue)) {
return FALSE;
}
*data = queue->data[queue->head];
queue->head = (queue->head + 1) % queue->capacity;
queue->lastAction = OUT;
return TRUE;
}
boolean readQueueHead(const QUEUE *queue, USER_TYPE *data) {
if (NULL == queue || NULL == data || isQueueEmpty(queue)) {
return FALSE;
}
*data = queue->data[queue->head];
return TRUE;
}
test.c
#include <stdio.h>
#include "mec.h"
#include "queue.h"
int main() {
QUEUE *queueOne = NULL;
QUEUE *queueTwo = NULL;
initQueue(&queueOne, 30);
initQueue(&queueTwo, 40);
destoryQueue(&queueOne);
destoryQueue(&queueTwo);
return 0;
}
mec.h里面写了定义了boolean
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1
代码中有一点非常重要,若不注意会导致内存泄漏!
上图所示,传参为什么要传QUEUE **queue ?
而不直接传QUEUE *queue ?
原因如下图:若用户不小心初始化两次并将首地址赋值给了queue1指针,第一次申请的控制头和队列空间的首地址都会被后面语句所覆盖!造成内存泄漏!所申请的队列空间将处于游离状态,不能再被利用和回收。
所以我们传QUEUE **queue,其作用是可以检测出queue所指向的空间(检测控制头有没有被初始化过)有没有被过赋值;若这个指针被赋值过了,就直接return初始化失败。
第一次写博客,如有问题,请代佬指出。可私聊或评论~ 共勉!