關於 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;
}

總結:
操作並不是很複雜,和單向鏈表一模一樣。
只要不被指針給搞混就好!

注意:由於一篇博客內容太多,所以我將會把他分成幾篇進行講解!

祝各位學習愉快!


下集預告:
你將學會隊列的一個企業級應用:線性池中的任務隊列
請持續關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章