上集回顾:顺序存储队列
第一集:顺序存储队列
观看本系列博文提醒:
你将学会队列的两种最基本的表现形式:顺序存储队列 和 链式存储队列;
一个扩展队列的使用方法:循环队列;
两个企业级队列的应用:线性池中的任务队列 和 优先链式存储队列。
队列的原理
队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,先进先出(FIFO First In First Out).
例如上图中,圆球1先进,也是圆球1先出。
队列是一种受限的线性结构
- 它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。
- 生活中队列场景随处可见: 比如在电影院, 商场, 或者厕所排队。。。。。。
由上图我们可以知道,队列中有两个“指针”,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;
}
入队他有两种情况:
-
队列为空
因为我们传入的是待插入的元素,所以我们先创建一个新的节点。
队列插入元素后,队首指针和队尾指针都要指向第一个元素(如上图)。
然后length自增一。 -
队列不为空
因为我们传入的是待插入的元素,所以我们先创建一个新的节点.
头指针不需要动。尾指针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;
}
- 首先呢,我们需要创建一个临时的节点,让其指向头节点。
- 然后队首指针指向自己的下一个节点。
- 我们得先判断现在的队首指针是否为NULL(如果队列只有一个节点的话,那么队首指针和队尾指针都是指向他的,而他们的next下一个节点都是NULL)。所以当条件成立时,队列是已经没有节点的了,所以队尾指针也得指向NULL。
- 将临时节点delete释放掉。
- 最后再将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循环遍历队列;
- 临时节点首先指向自己的下一个节点;
- 释放第一个节点;
- 再将临时节点赋值给释放掉的空节点;
- while循环结束后,还得将队首指针和队尾指针都指向NULL;
- 最后再将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;
}
总结:
操作并不是很复杂,和单向链表一模一样。
只要不被指针给搞混就好!
注意:由于一篇博客内容太多,所以我将会把他分成几篇进行讲解!
祝各位学习愉快!
下集预告:
你将学会队列的一个企业级应用:线性池中的任务队列;
请持续关注!