关于 C++ 队列算法,你该了解这些【第二集:链式存储队列】

上集回顾:顺序存储队列

第一集:顺序存储队列


观看本系列博文提醒:
你将学会队列的两种最基本的表现形式:顺序存储队列链式存储队列
一个扩展队列的使用方法:循环队列
两个企业级队列的应用:线性池中的任务队列优先链式存储队列


队列的原理

队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out).
在这里插入图片描述
例如上图中,圆球1先进,也是圆球1先出。

队列是一种受限的线性结构

  1. 它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
  2. 生活中队列场景随处可见: 比如在电影院, 商场, 或者厕所排队。。。。。。

在这里插入图片描述
由上图我们可以知道,队列中有两个“指针”,front指向队首,rear指向队尾;
至于length,他是整个队列的总长度(因为一个队列他是有固定长度的)。


链式存储队列

队列的链式存储结构,其实就是线性表的单链表,只不过它只是尾进头出而已,我们把它简称为链队列。
为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端节点。

在这里插入图片描述
由上图可知,front指针指向头节点,rear指针指向尾节点。

因为队列的长度是有限的,所以我们可以给队列的长度定义一个宏:
#define MaxSize 5 // 队列的最大容量


队列的定义

typedef int DateType;	// 队列中元素的类型

typedef struct _QNode {	// 节点结构
	DateType date;
	struct _QNode* next;
}QNode;

typedef QNode* QueuePar;	// 节点的指针类型

typedef struct Queue {	// 定义链表队列
	int lenght;		// 队列的长度
	QueuePar front;	// 队头指针
	QueuePar rear;	// 队尾指针
}LinkQueue;

DateType 是队列的存储类型
QueuePar 是节点的指针类型

我们需要先定义出节点,然后才能根据节点定义出链表队列。


队列的初始化

// 队列的初始化
bool inItLinkQueue(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	LQ->lenght = 0;		// 队列长度值为零
	LQ->front = LQ->rear = NULL;	// 把队首和队尾指针指向NULL
	return true;
}

在这里插入图片描述
空队列就是头指针front和尾指针rear都是指向NULL。而且他的长度length等于0.


判断队列是否为空

// 判断队列是否为空
bool estimateLinkQueueEmpty(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (!LQ->front) {
		return true;
	}

	return false;
}

在这里插入图片描述
判断条件也很简单,只需要判断头指针是否指向NULL就可以了。如上图。
因为空队列头指针front和尾指针rear都是指向NULL。而且他的长度length等于0.


判断队列是否已满

// 判断队列是否已满
bool estimateLinkQueuefull(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (LQ->lenght == MaxSize) {
		return true;
	}

	return false;
}

在这里插入图片描述
队列中有一个变量时用于统计队列的元素个数的,只需要将他与队列的存储仅限长度作比较,就可以得出队列是否已满!


入队,将元素插入队列中

// 入队,将元素插入队列中
bool linkQueueInsertValue(LinkQueue*& LQ, DateType date) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueuefull(LQ)) {
		cout << "队列已满!" << endl;
		return false;
	}

	QNode* NQ = new QNode;	// 新建节点
	NQ->date = date;		// 节点的值被赋值
	NQ->next = NULL;		// 因为是最后一个节点,所以next指向NULL

	if (estimateLinkQueueEmpty(LQ)) {	// 如果链表为空
		LQ->front = LQ->rear = NQ;	// 队首指针与队尾指针都要指向新插入的节点
	} else {	// 链表不为空的情况
		LQ->rear->next = NQ;	// 在队尾插入节点(旧队尾节点和新队尾节点连起来)
		LQ->rear = NQ;			// 队尾指向新插入的节点
	}
	LQ->lenght += 1;	// 队列元素加一

	return true;
}

入队他有两种情况:

  1. 队列为空
    在这里插入图片描述
    因为我们传入的是待插入的元素,所以我们先创建一个新的节点。
    队列插入元素后,队首指针和队尾指针都要指向第一个元素(如上图)。
    然后length自增一。

  2. 队列不为空
    在这里插入图片描述
    因为我们传入的是待插入的元素,所以我们先创建一个新的节点.
    头指针不需要动。尾指针rear的next需要指向新插入的节点,然后尾节点在指向新插入的节点。
    然后length自增一。

请注意理解好:插入前尾节点rear的next是指向NULL的,所以得将next指向新插入的节点(将两个节点链起来),然后再将尾节点指向新插入的节点。
这两个步骤弄完后,rear有从新指向新插入的尾节点,那么他的next就为NULL。


出队,删除队首

// 出队,删除队首
bool deleteLinkQueueFront(LinkQueue*& LQ, DateType* date) {	// 参数二:保存删除的数据返回
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!删除失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	*date = LQ->front->date;	// 保存出队的元素返回
	QNode* tem = LQ->front;	// 定义临时节点指向头指针

	LQ->front = tem->next;	// 指向自己的下一个节点
	if (!LQ->front) {	// 如果头指针为NULL,说明队列中已经没有节点了
		LQ->rear = NULL;// 尾指针也要指向NULL
	}
	LQ->lenght -= 1;	// 队列长度要减一

	delete tem;	// 释放掉原来的头指针

	return true;
}

在这里插入图片描述

  1. 首先呢,我们需要创建一个临时的节点,让其指向头节点。
  2. 然后队首指针指向自己的下一个节点。
  3. 我们得先判断现在的队首指针是否为NULL(如果队列只有一个节点的话,那么队首指针和队尾指针都是指向他的,而他们的next下一个节点都是NULL)。所以当条件成立时,队列是已经没有节点的了,所以队尾指针也得指向NULL。
  4. 将临时节点delete释放掉。
  5. 最后再将length自减一。

获取队列的首元素

// 获取队列的首元素
bool aginLinkQueueFrontValue(LinkQueue*& LQ, DateType* date) {	// 参数二:保存首数据返回
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!获取失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	*date = LQ->front->date;
	return true;
}

也就是和删除队首差不多的代码,只是获取元素不需要后面的删除操作而已。


修改队列中任意位置的值

// 修改队列中任意位置的值
bool alterLinkQueueValue(LinkQueue*& LQ, int i, DateType date) {	// 参数二:修改位置;参数三:修改后的值
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!获取失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	if (i < 0 || i > LQ->lenght) {
		cout << "i值不合法!" << endl;
		return false;
	}

	QNode* tem = LQ->front;	// 定义临时节点指向队首指针

	for (int j = 1; j < LQ->lenght + 1; j++) {
		if (i == j) {
			tem->date = date;
			return true;
		}

		tem = tem->next;
	}

	return false;
}

定义临时指针指向队首指针,用于循环遍历。当找到相对位置时,将该位置节点的值修改后就行了。


输出队列中的元素

// 输出队列中的元素
bool linkQueuePrint(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!输出失败!" << endl;
		return false;
	}

	QNode* tem = LQ->front;	// 定义临时节点指向队首指针

	while (tem) {
		cout << tem->date << "\t";
		tem = tem->next;
	}
	cout << endl;

	return true;
}

定义临时节点指向队首指针,使用while循环遍历,将全部元素都输出!


清空队列

// 清空队列
bool clearLinkQueue(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	QNode* tem = LQ->front;

	while (tem) {
		tem = tem->next;
		delete LQ->front;
		LQ->front = tem;
	}

	LQ->front = LQ->rear = NULL;
	LQ->lenght = 0;

	return true;
}

定义临时节点指向队首指针,while循环遍历队列;

  1. 临时节点首先指向自己的下一个节点;
  2. 释放第一个节点;
  3. 再将临时节点赋值给释放掉的空节点;
  4. while循环结束后,还得将队首指针和队尾指针都指向NULL;
  5. 最后再将length赋值0.

测试代码:

#include <iostream>
#include <Windows.h>

using namespace std;

#define MaxSize 5	// 队列的最大容量

typedef int DateType;	// 队列中元素的类型

typedef struct _QNode {	// 节点结构
	DateType date;
	struct _QNode* next;
}QNode;

typedef QNode* QueuePar;

typedef struct Queue {
	int lenght;		// 队列的长度
	QueuePar front;	// 队头指针
	QueuePar rear;	// 队尾指针
}LinkQueue;

// 队列的初始化
bool inItLinkQueue(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	LQ->lenght = 0;		// 队列长度值为零
	LQ->front = LQ->rear = NULL;	// 把队首和队尾指针指向NULL
	return true;
}

// 判断队列是否为空
bool estimateLinkQueueEmpty(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (!LQ->front) {
		return true;
	}

	return false;
}

// 判断队列是否已满
bool estimateLinkQueuefull(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (LQ->lenght == MaxSize) {
		return true;
	}

	return false;
}

// 入队,将元素插入队列中
bool linkQueueInsertValue(LinkQueue*& LQ, DateType date) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueuefull(LQ)) {
		cout << "队列已满!" << endl;
		return false;
	}

	QNode* NQ = new QNode;	// 新建节点
	NQ->date = date;		// 节点的值被赋值
	NQ->next = NULL;		// 因为是最后一个节点,所以next指向NULL

	if (estimateLinkQueueEmpty(LQ)) {	// 如果链表为空
		LQ->front = LQ->rear = NQ;	// 队首指针与队尾指针都要指向新插入的节点
	} else {	// 链表不为空的情况
		LQ->rear->next = NQ;	// 在队尾插入节点(旧队尾节点和新队尾节点连起来)
		LQ->rear = NQ;			// 队尾指向新插入的节点
	}
	LQ->lenght += 1;	// 队列元素加一

	return true;
}

// 出队,删除队首
bool deleteLinkQueueFront(LinkQueue*& LQ, DateType* date) {	// 参数二:保存删除的数据返回
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!删除失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	*date = LQ->front->date;
	QNode* tem = LQ->front;	// 定义临时节点指向头指针

	LQ->front = tem->next;	// 指向自己的下一个节点
	if (!LQ->front) {	// 如果头指针为NULL,说明队列中已经没有节点了
		LQ->rear = NULL;// 尾指针也要指向NULL
	}
	LQ->lenght -= 1;	// 队列长度要减一

	delete tem;	// 释放掉原来的头指针

	return true;
}

// 获取队列的首元素
bool aginLinkQueueFrontValue(LinkQueue*& LQ, DateType* date) {	// 参数二:保存首数据返回
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!获取失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	*date = LQ->front->date;
	return true;
}

// 修改队列中任意位置的值
bool alterLinkQueueValue(LinkQueue*& LQ, int i, DateType date) {	// 参数二:修改位置;参数三:修改后的值
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!获取失败!" << endl;
		return false;
	}

	if (!date) {
		cout << "指针date为空!" << endl;
		return false;
	}

	if (i < 0 || i > LQ->lenght) {
		cout << "i值不合法!" << endl;
		return false;
	}

	QNode* tem = LQ->front;	// 定义临时节点指向队首指针

	for (int j = 1; j < LQ->lenght + 1; j++) {
		if (i == j) {
			tem->date = date;
			return true;
		}

		tem = tem->next;
	}

	return false;
}

// 输出队列中的元素
bool linkQueuePrint(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	if (estimateLinkQueueEmpty(LQ)) {
		cout << "链表为空!输出失败!" << endl;
		return false;
	}

	QNode* tem = LQ->front;	// 定义临时节点指向队首指针

	while (tem) {
		cout << tem->date << "\t";
		tem = tem->next;
	}
	cout << endl;

	return true;
}

// 清空队列
bool clearLinkQueue(LinkQueue*& LQ) {
	if (!LQ) {
		cout << "队列不存在!" << endl;
		return false;
	}

	QNode* tem = LQ->front;

	while (tem) {
		tem = tem->next;
		delete LQ->front;
		LQ->front = tem;
	}

	LQ->front = LQ->rear = NULL;
	LQ->lenght = 0;

	return true;
}

int main(void) {
	LinkQueue* LQ = new LinkQueue;
	DateType date = 0;

	// 初始化队列
	inItLinkQueue(LQ);

	// 插入元素
	for (int i = 0; i < 7; i++) {
		linkQueueInsertValue(LQ, i*6);
	}
	linkQueuePrint(LQ);

	cout << endl;

	// 出队,删除队首
	if (deleteLinkQueueFront(LQ, &date)) {
		cout << "出队成功!出队元素是:" << date << endl;
	}
	linkQueuePrint(LQ);

	cout << endl;

	// 获取队列的首元素
	if (aginLinkQueueFrontValue(LQ, &date)) {
		cout << "获取成功!队首数据为:" << date << endl;
	}
	linkQueuePrint(LQ);

	cout << endl;

	// 修改队列中任意位置的值
	if (alterLinkQueueValue(LQ, 3, 666)) {
		cout << "修改成功!" << endl;
	}
	linkQueuePrint(LQ);
	cout << endl;

	/*if (alterLinkQueueValue(LQ, 5, 666)) {
		cout << "修改成功!" << endl;
	}
	linkQueuePrint(LQ);
	cout << endl;*/

	// 清空链表
	if (clearLinkQueue(LQ)) {
		cout << "清空成功!" << endl;
	}
	linkQueuePrint(LQ);

	system("pause");
	return 0;
}

总结:
操作并不是很复杂,和单向链表一模一样。
只要不被指针给搞混就好!

注意:由于一篇博客内容太多,所以我将会把他分成几篇进行讲解!

祝各位学习愉快!


下集预告:
你将学会队列的一个企业级应用:线性池中的任务队列
请持续关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpp_learners

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值