【数据结构与算法】->数据结构->队列->循环数组的应用&队列工具库的创建

Ⅰ 队列的定义

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列的特点:先进先出
堆栈的特点:先进后出

在这里插入图片描述

Ⅱ 队列的实现

队列有两种实现的方法,链表和数组。对于队列来说,用链表是很麻烦的,为了提高效率和简化操作,队列还是用数组来实现比较好。

A. 普通数组的不合理之处

我先建立一个数组。
在这里插入图片描述
在还没有数据存入的时候,队首队尾指针都在下标为0的位置,当入队列时,数据存入队首指针现在的位置,并且队首指针后移。
在这里插入图片描述
这时候再出队列,队尾指针指向的位置上的元素出去。
在这个过程中,队首和队尾的指针都是不断增大的,但是数组的个数是有限的。
在这里插入图片描述
导致即使前面的数据都已出队列,数组还有这么多空间,但是无法使用,因为队尾队首指针在不断向前。

B. 循环数组的应用

在普通的数组不能很好地完成队列操作时,我在此引入循环数组
循环数组我们可以用下图来理解,类似于一个钟表:
在这里插入图片描述
当入队列时,
在这里插入图片描述
这就实现了从队首出队列,从队尾入队列。
在这里插入图片描述
还是刚才普通数组难以解决的问题,当头指针在12,尾指针在13,这时候入队列需要将其放在尾指针指向的位置,并将尾指针+1.
但是此时13+1需要将尾指针变成0,要怎么做?

这就是循环数组的精妙之处了。以这个例子来看,这是个容量为14的数组。我们设下标为index,容量为capacity。

(index + 1) % capacity就可以做到将下标增加,并使其循环。

以刚才为例,现在尾指针为13,要入队列,先将数据放在尾指针的位置,即13,再将尾指针(13 + 1) % 14 = 0,这就实现了循环。

那么数组里的有效数据个数怎么计算呢?我们来看下面两个例子。
在这里插入图片描述
在这里插入图片描述
这两种情况,刚好是head和tail的值颠倒过来,如果我们要计算有效数据个数,就需要一个公式:(tail - head + capacity) % capacity

但是这时候我们会发现,当队列满和队列空的时候,计算出来的数据个数都是0。
所以要判断队列空还是满,我们还需要再加入一个判断,即上一次操作是入队列还是出队列。

如果上一次操作是入队列,那么如果计算出来数据个数为0,就可以判断这个队列是满的,反之上一次操作是出队列,那么队列就是空的。

Ⅲ 队列工具库的创建

循环数组的规律清楚了以后,我们就可以动手来实现队列的工具库了。

A. 队列的表示

首先我们先来表示队列。在之前的分析中,可以知道,要建立一个队列,需要队首指针head,需要队尾指针tail,要建立数组我们还需要知道数组的大小capacity,然后我们还需要知道上一次操作是什么,所以众多的元素我们需要建立一个结构体。

typedef struct QUEUE {
	int head;
	int tail;
	int capacity;
	void **data;
	boolean lastAction;
}QUEUE;

这样我们就建立了一个队列的控制头。大家可能会对这个void **data有点疑惑。为什么要这么写呢?
这样其实是建立了一个数组,其存放的元素是指针。因为我们不知道用户到底要在队列里录入什么类型的数据,所以我们无论其录入什么数据,我们都将其地址存入数组中,这样就可以实现通用数组。因为无论你的数据是什么类型,几个字节,其地址都是四个字节,所以可以通过存放地址,来保证数组的元素类型统一。这其实就是一个指针数组。

关于指针数组有疑惑的同学也可以去看我下面这篇文章👇
【C语言基础】->指针与二维数组->同一个人的换装游戏

另外还有一个说明,boolean是我自己在我的头文件中定义的一个类型,其本质是unsigned char

#ifndef _TYZ_QUEUE_H_
#define _TYZ_QUEUE_H_

#include "tyz.h"

#define  IN   2
#define  OUT  3

typedef struct QUEUE {
	int head;
	int tail;
	int capacity;
	void **data;
	boolean lastAction;
}QUEUE;

#endif

通过对INOUT的宏定义,我们来判断上一次操作到底是什么。

B. 初始化&销毁

a. 初始化

要使用队列,最开始当然要先初始化。代码如下👇

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 = (void **) calloc(sizeof(void *), capacity);

	if (NULL == (*queue)->data) {
		free(*queue);
		*queue = NULL;
		return FALSE;
	}
	(*queue)->head = 0;
	(*queue)->tail = 0;
	(*queue)->capacity = capacity;
	(*queue)->lastAction = OUT;

	return TRUE;
}

可能会有人对我的形参有点疑惑,为什么是指针的指针。QUEUE **queue。我在下面列出调用这个函数的代码做为比较。

	QUEUE *queue = NULL;;

	initQueue(&queue, 30);

可以看到我们先定义了一个指针,指向控制头QUEUE,根据一个著名的例子,用指针交换两个变量的值,如果你要改变实参的值,传递形参就应该把它的地址传过去,否则是无法改变它的值的。所以参考这里,我们需要初始化这个QUEUE *queue,就需要传递它的地址,即&queue。其对应的形参就是QUEUE **queue了。

b. 销毁

申请了空间首先就要考虑释放空间,所以接下来要写的就是销毁队列。👇

void destoryQueue(QUEUE **queue) {
	if (NULL == queue || NULL != *queue) {
		return;
	}
	free((*queue)->data);
	free(*queue);
	*queue = NULL;
}

这里的原理是一样的,要传递(QUEUE **queue)。因为要销毁指向控制头的那个指针。

C. 判断队列空&队列满

在之前的分析中,我们已经知道了如何判断空满,只需要满足两个条件,一个是headtail值相等,一个是上一次操作是入队列还是出队列。

a. 判断队列满
boolean isQueneFull(const QUEUE *queue) {
	if (NULL == queue) {
		return TRUE;
	}
	return IN == queue->lastAction && queue->head == queue->tail;
}

如果传递进去的是一个无效的指针,那么我将其看做的满的。因为只有要入队列的时候,需要判断队列是不是满的,这时候无效的指针是无法将数据入队列的,所以我将其看做是满的,意思也是不能再入队列了。

b. 判断队列空
boolean isQueneEmpty(const QUEUE *queue) {
	if (NULL == queue) {
		return TRUE;
	}
	return OUT == queue->lastAction && queue->head == queue->tail;
}

遵从和上面一样的原则。

D. 入队列&出队列

a. 入队列
boolean in(QUEUE *queue, void *data) {
	if (NULL == queue || isQueneFull(queue)) {
		return FALSE;
	}
	queue->data[queue->tail] = data;
	queue->tail = (queue->tail + 1) % queue->capacity;
	queue->lastAction = IN;

	return TRUE;
}

这个逻辑就是之前分析的,从尾指针的地方入队列,并将尾指针向前走一格,方式就是(queue->tail + 1) % queue->capacity

b. 出队列
boolean out(QUEUE *queue, void *data) {
	if (NULL == queue || isQueneEmpty(queue)) {
		return FALSE;
	}
	data = queue->data[queue->head];
	queue->head = (queue->head + 1) % queue->capacity;
	queue->lastAction = OUT;
}

其逻辑是一样的,从队首出去,然后将头指针向前挪动一格。

出队列和入队列都是很简单的,需要注意的就是要将lastAction设置为对应的INOUT

E. 读取队首数据

读取队首数据是和出队列差不多的,只是不需要将头指针挪动。

void readHead(const QUEUE *queue, void *data) {
	if (NULL == queue || isQueneEmpty(queue)) {
		return;
	}
	data = queue->data[queue->head];
}

这样我们就完成了一个队列的工具库,其基本功能就是入队列和出队列。

Ⅳ. 完整代码

首先是我的自己的头文件tyz.h👇

#ifndef _TYZ_H_
#define _TYZ_H_

#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1

#define SET(value, index)    (value |= 1 << (index ^ 7))
#define CLEAR(value, index)  (value &= ~(1 << (index ^ 7)))
#define GET(value, index)    ((value) & (1 << (index ^ 7)) != 0)

typedef unsigned char boolean;
typedef unsigned int u32;
typedef unsigned short u16;
typedef boolean u8;

int skipBlank(const char *str);
boolean isRealStart(int ch);

#endif

然后是队列的头文件,queue.h👇

#ifndef _TYZ_QUEUE_H_
#define _TYZ_QUEUE_H_

#include "tyz.h"

#define  IN   2
#define  OUT  3

typedef struct QUEUE {
	int head;
	int tail;
	int capacity;
	void **data;
	boolean lastAction;
}QUEUE;

boolean initQueue(QUEUE **queue, int capacity);
void destoryQueue(QUEUE **queue);
boolean isQueneFull(const QUEUE *queue);
boolean isQueneEmpty(const QUEUE *queue);
boolean in(QUEUE *queue, void *data);
boolean out(QUEUE *queue, void *data);
void readHead(const QUEUE *queue, void *data);

#endif

最后是队列的函数部分代码👇

#include <stdio.h>
#include <malloc.h>

#include "tyz.h"
#include "queue.h"

void readHead(const QUEUE *queue, void *data) {
	if (NULL == queue || isQueneEmpty(queue)) {
		return;
	}
	data = queue->data[queue->head];
}

boolean out(QUEUE *queue, void *data) {
	if (NULL == queue || isQueneEmpty(queue)) {
		return FALSE;
	}
	data = queue->data[queue->head];
	queue->head = (queue->head + 1) % queue->capacity;
	queue->lastAction = OUT;
}

boolean in(QUEUE *queue, void *data) {
	if (NULL == queue || isQueneFull(queue)) {
		return FALSE;
	}
	queue->data[queue->tail] = data;
	queue->tail = (queue->tail + 1) % queue->capacity;
	queue->lastAction = IN;

	return TRUE;
}

boolean isQueneEmpty(const QUEUE *queue) {
	if (NULL == queue) {
		return TRUE;
	}
	return OUT == queue->lastAction && queue->head == queue->tail;
}

boolean isQueneFull(const QUEUE *queue) {
	if (NULL == queue) {
		return TRUE;
	}
	return IN == queue->lastAction && queue->head == queue->tail;
}

void destoryQueue(QUEUE **queue) {
	if (NULL == queue || NULL != *queue) {
		return;
	}
	free((*queue)->data);
	free(*queue);
	*queue = NULL;
}

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 = (void **) calloc(sizeof(void *), capacity);

	if (NULL == (*queue)->data) {
		free(*queue);
		*queue = NULL;
		return FALSE;
	}
	(*queue)->head = 0;
	(*queue)->tail = 0;
	(*queue)->capacity = capacity;
	(*queue)->lastAction = OUT;

	return TRUE;
}

这样我们就完成了队列工具库的创建。
如果对指针的操作还有疑问的可以看我下面的文章👇
【C语言基础】->指针与二维数组->同一个人的换装游戏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值