数据结构-顺序队列

单向循环队列

单向循环队列:是一种基于数组实现的队列,具有循环特性。在单向循环队列中,当队列的尾部到达数组的末尾时,如果队列前面有空闲位置,尾部会回绕到数组的开头,从而避免了数组空间的浪费。
基本特性: 队列是遵循FIFO(先进先出)原则的线性数据结构。
循环队列:队列头和队列尾会在数组的两端循环,使得队列可以在数组中有效地利用空间。
单向循环队列的实现: 单向循环队列通常使用数组来存储数据,同时用两个指针: front:指向队列的头部,表示队列中第一个元素的位置。 rear:指向队列的尾部,表示队列中最后一个元素的下一个位置。
循环队列的优点:空间利用率高:通过循环,队列的尾部可以回绕到队列的头部,避免了浪费空间。
固定大小:使用数组实现时,队列的大小是固定的,内存管理简单。

队列定义

typedef struct {
	int data[N];
	int front; //指向队头
	int rear; //指向插入数据的下一个位置,可以理解为队尾
}queue_t;

函数声明

queue_t* queue_creat();//队列创建
int queue_enter(queue_t* sq, int val);//入队
int queue_go_out(queue_t* sq);//出队
int queue_empty(queue_t* sq);//是否空队
int queue_full(queue_t* sq);//是否满队
int queue_clear(queue_t* sq);//队列数据逻辑清空
queue_t* queue_free(queue_t* sq);//队列内存释放

队列创建

在单向循环队列的设计中,队列的实际存储元素数量与数组的总容量之间存在一个差值,即数组的容量比队列能存储的元素数量
多一个位置。这样设计的目的是为了避免队列空和满时指针重合,从而能够方便且准确地判断队列是否为空或已满。
进一步解释: 为了避免队列头 (front) 和队列尾 (rear) 在队列为空和队列满的情况下发生重合,我们通常会设计队列的容量比实际能够存储的元素数量多一个位置。
队列空:当 front == rear 时,表示队列为空。
队列满:当 (rear + 1) % max_size ==front 时,表示队列已满。 这样,通过留出一个位置作为区分空和满的标志,就可以避免 front 和 rear 指针重合的情况,从而准确判断队列的状态。

queue_t* queue_creat() {
	//初始化队头队尾
	queue_t* sq = NULL;
	sq = (queue_t*)malloc(sizeof(queue_t));//申请空间
	//判断是否申请成功
	if (sq == NULL) {
		printf("malloc is failed_creat\n");
		return NULL;
	}
	sq->front = 0;
	sq->rear = 0;
	// 将data数组所有数据全部初始化
	memset(sq->data, 0, sizeof(sq->data));
	return sq;
}

入队

入队比较简单,但是注意队满的条件
在这里插入图片描述

//入队
int queue_enter(queue_t* sq, int val) {

	if (sq == NULL) {
		printf("queue enter failed_enter\n");
		return -1;
	}

	if (queue_full(sq) == 1) {
		printf("queue is full");
		return -1;
	}

	if ((sq->rear + 1) % N == sq->front) {
		printf("sequeue is full\n");
		return -1;
	}
	sq->data[sq->rear] = val;
	sq->rear = (sq->rear + 1) % N;
	return 0;
}

出队

出队也比较简单,但是大家注意看后边的两个图,出队两个之后,怎么进行的入队,这个很重要,建议自己画一画,体会一下取
余运算,以及为什么叫做循环队列。
在这里插入图片描述

//出队
int queue_go_out(queue_t* sq) {
	int val;

	if (sq == NULL) {
		printf("queue not creat-go_out\n");
		return -1;
	}
	if (queue_empty(sq) == 0) {
		printf("queue is empty\n");
		return 0;
	}
	val = sq->data[sq->front];
	sq->front = (sq->front + 1) % N;
	return val;
}

在这里插入图片描述
注意这里的出队的指针运算和空队的条件

队列判空判满

判空判满先看图,再看最后的说明,主要还是自己先画图

//队列判空
int queue_empty(queue_t* sq) {

	if (sq == NULL) {
		printf("queue not failed-empty\n");
		return -1;
	}
	//空队列返回0 否则返回1
	return (sq->front == sq->rear) ? 0 : 1;
}

//队列判满
int queue_full(queue_t* sq) {

	if (sq == NULL) {
		printf("queue not failed-full\n");
		return -1;
	}
	// 不能直接判断差值,判断队列是否已经满了
	//存在如下情况
	/*
		*			J6
		*			J5
		*	front-> J4
		*	rear->  J9
		*			J8
		*			J7
		*			J6
		* 经历了一次入队满,出三个,再入三个的操作后,队列已经在数组的逻辑上不存在顺序性质
		*此类情况即便abs(front-rear)+1 = N  也不足以判断队列是否满 出现了环绕情况
	*/
	// 满为1  否则为0
	return ((sq->rear + 1) % N == sq->front) ? 1 : 0;
}

队列清空

//队列清空(逻辑清空 物理上没有清空)
int queue_clear(queue_t* sq) {
	if (sq == NULL) {
		printf("sq is NULL\n");
		return -1;
	}
	sq->front = sq->rear = 0;
	return 0;
}

队列释放

queue_t* queue_free(queue_t* sq) {

	if (sq == NULL) {
		printf("queue not failed-free\n");
		return NULL;
	}
	free(sq);
	sq = NULL;
	return NULL;
}

总之,队列的学习,一定要画图,进行取余运算,才能深刻理解,为什么队列实际储存长度,比数组少一个,以及判断满队,空
队,以及队列环绕后,怎么出队的操作。

个人总结

假设你有一个数组 array[] 用来实现队列,数组的最大容量为 n。 如果你允许队列满时队列大小等于 n,那么满队和空队的状态
就会非常难以区分。 因为无论队列是满的还是空的,头指针 (front) 和尾指针 (rear) 都可能相同,从而无法判断队列是空的还是
满的。
解决方法:使用一个额外的约定 为了避免这一困境,我们通常会规定队列的最大容量是 n-1,也就是说,队列的实际存储
空间比数组的容量少一个元素。 通过这种方式,我们可以通过队列的头指针和尾指针来准确区分队列是否为空或已满。
具体的逻辑:
空队列: 队列为空时,front 和 rear 指针指向同一个位置,表示队列没有元素。
满队列: 队列满时,rear 指针与 front 指针之间的距离等于 n-1,即它们之间有 n-1 个元素。
为什么满队列和空队列能区分?
假设队列有 n-1 个元素时,数组的大小为 n。假设 front 是队列的头指针,rear 是队列的尾指针。 空队列:front == rear,即队列没有元素。 满队列:(rear + 1) % n ==front,即队列已满,rear 后面一个位置就是 front,这意味着队列已经占满了 n-1 个位置。 这样,通过 front 和 rear 指针的相对位置,我们可以判断队列的状态: 如果 front == rear,队列为空。 如果 (rear + 1) % n == front,队列为满。
举个例子: 假设我们有一个队列的数组容量为 5 (n=5),即数组 array[5],但我们最多只能存储 4 个元素。 空队列:front == rear,这时队列为空。满队列:如果我们将 4 个元素依次加入队列,rear 会指向最后一个元素的位置, 而 front 会指向队列的第一个元素。如果此时再添加一个元素,rear 会跳到数组的开头, 并且 (rear + 1) % n == front,表示队列已经满了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值