数据结构与算法 循环队列相关问题

今天要讨论的是循环队列,将有限空间按用户的需求无限化。

队列与堆栈的定义与区别

队列:队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(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初始化失败。

第一次写博客,如有问题,请代佬指出。可私聊或评论~ 共勉!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值