上集回顧:順序存儲隊列
第一集:順序存儲隊列
觀看本系列博文提醒:
你將學會隊列的兩種最基本的表現形式:順序存儲隊列 和 鏈式存儲隊列;
一個擴展隊列的使用方法:循環隊列;
兩個企業級隊列的應用:線性池中的任務隊列 和 優先鏈式存儲隊列。
隊列的原理
隊列是一種受限的線性表,(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;
}
總結:
操作並不是很複雜,和單向鏈表一模一樣。
只要不被指針給搞混就好!
注意:由於一篇博客內容太多,所以我將會把他分成幾篇進行講解!
祝各位學習愉快!
下集預告:
你將學會隊列的一個企業級應用:線性池中的任務隊列;
請持續關注!