關於 C++ 隊列算法,你該瞭解這些【第一集:順序存儲隊列】

觀看本系列博文提醒:
你將學會隊列的兩種最基本的表現形式:順序存儲隊列鏈式存儲隊列
一個擴展隊列的使用方法:循環隊列
兩個企業級隊列的應用:線性池中的任務隊列優先鏈式存儲隊列


隊列的原理

隊列是一種受限的線性表,(Queue),它是一種運算受限的線性表,先進先出(FIFO First In First Out).
在這裏插入圖片描述
例如上圖中,圓球1先進,也是圓球1先出。

隊列是一種受限的線性結構

  1. 它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。
  2. 生活中隊列場景隨處可見: 比如在電影院, 商場, 或者廁所排隊。。。。。。

在這裏插入圖片描述
由上圖我們可以知道,隊列中有兩個“指針”,front指向隊首,rear指向隊尾;
至於length,他是整個隊列的總長度(因爲一個隊列他是有固定長度的)。


順序存儲隊列

採用數組來保存隊列的元素,設立一個隊首指針 front ,一個隊尾指針 rear,分別指向隊首和隊尾元素。則 rear - front 即爲存儲的元素個數!

因爲隊列的長度是有限的,所以我們可以給隊列的長度定義一個宏:
#define QUEUE_MAX 5 // 隊列的最大存儲個數

隊列的定義

typedef int dateType;	// 隊列存儲的類型

typedef struct arrayQueue {
	dateType queue[QUEUE_MAX];	// 存儲隊列元素的數組
	int front;	// 隊頭“指針”
	int rear;	// 隊尾“指針”
}seqQueue;

就是一個結構體,隊列的儲存是以數組方式存儲的。
還定義了整型變量,front用於定位到隊首元素,rear用於定位到隊尾元素。


隊列的初始化

// 隊列初始化
void inItQueue(seqQueue* seq) {
	if (!seq) {	// 合法性檢查
		return;
	}

	seq->front = seq->rear = 0;		// 對頭與隊尾“指針”都爲零
}

在這裏插入圖片描述
初始化後他是一個空隊列,所以兩個“指針”都得爲零。


判斷隊列是否爲空

// 判斷隊列是否爲空
bool estimateQueueEmpty(seqQueue* seq) {
	if (!seq) {	// 合法性檢查
		cout << "隊列不存在!";
		return false;
	}

	if (seq->front == seq->rear) {	// 當頭尾“指針”相等時,爲空隊列
		return true;
	}

	return false;
}

在這裏插入圖片描述
由上圖我們可以知道,當隊列爲空時,“指針”front和rear都是指向0的,所以我們可以以此作爲判斷。


判斷隊列是否已滿

// 判斷隊列是否已滿
bool estimateQueueFull(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!";
		return false;
	}

	if (seq->rear == QUEUE_MAX) {	// 當隊尾“指針”等於隊列最大存儲個數時,爲滿
		return true;
	}

	return false;
}

在這裏插入圖片描述
先提前跟大家說一下,因爲順序存儲隊列是以數組方式存儲的,最大下標是4,他的尾“指針”rear作爲下標的話都是指向最後一個元素的下一個位置的,所以我們插入元素時,可以直接使用rear作爲插入位置的下標。也就是說,當隊列滿時,他是與我們定義的數組長度是相等的,即我們定義的宏定義#define QUEUE_MAX 5,我們可以用rear與宏定義作比較,當他們相等時,說明隊列已經滿了。


入隊,將元素插入隊列中

// 入隊,將元素插入隊列中
bool enterQueue(seqQueue* seq, dateType date) {	// 參數二是入隊的元素
	if (!seq) {
		cout << "隊列不存在!";
		return false;
	}

	if (estimateQueueFull(seq)) {
		cout << "隊列已滿!插入 " << date << " 失敗!" << endl;
		return false;
	}

	seq->queue[seq->rear] = date;	// 在隊尾插入元素date
	seq->rear += 1;		// 隊尾“指針”後移一位

	return true;
}

在這裏插入圖片描述
就如同我們上面所說的,當需要插入一個元素時,只需要將“指針”rear作爲下標插入即可,然後再將rear加一,指向下一個空白的區域(如上圖)。front“指針”永遠都是指向頭部第一個元素的。


出隊,將元素從頭部刪除掉

他有兩種出隊方式:

  1. 將隊列中隊首的元素出隊,後面的元素向前移動。優點:保證了隊列的內存都能得到最大的利用,不浪費;缺點:假如隊列中有成千上萬個數據的話,刪除一個元素造成後續那麼多個元素的移動,會造成很大的開銷,影響性能。
  2. 將隊列中的隊首元素出隊,出隊後對頭“指針”front後移一位。優點:沒有涉及到元素的移動,效率很快;缺點:由於他是移動頭指針,所以每次出隊,都會空置出一個內存區域,浪費內存。
情況一:將隊列中隊首的元素出隊,後面的元素向前移動
// 出隊一,將隊列中隊首的元素出隊,後面的元素向前移動
bool deleteQueueFront_1(seqQueue* seq, dateType* date) {	// 參數二:保存出列的元素返回
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指針爲空!" << endl;
		return true;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	// 從隊頭元素的下一個元素開始遍歷,將後續的所有元素都往前移動一個位置
	for (int i = seq->front + 1; i < seq->rear; i++) {
		seq->queue[i - 1] = seq->queue[i];
	}
	seq->rear -= 1;

	return true;
}

在這裏插入圖片描述
從隊頭元素的下一個元素開始遍歷,將後續的所有元素都往前移動一個位置。

上圖就是出隊前後的效果圖。

可能有些朋友會有疑問,a1使出對了沒錯,但是爲什麼會有兩個a3啊?
有這樣的疑問是正常的。因爲我們使用的刪除方式是將後面的元素都往前移動一個位置,所以最後一個元素還是會保留在原來的位置的。但是,我們的尾“指針”rear最後自減了一,現在等於二,所以,就算是保留在哪裏也不會出什麼問題,因爲我們根本訪問不了他。
我們遍歷隊列最多隻能訪問前兩個元素。

如果還是不懂的朋友可以把他理解成下圖,就知道了。
在這裏插入圖片描述
準確來講,還是第一幅圖貼近現實!

情況二:將隊列中的隊首元素出隊,出隊後對頭“指針”front後移一位
// 出隊二,將隊列中的隊首元素出隊,出對後對頭“指針”front後移一位
bool deleteQueueFront_2(seqQueue* seq, dateType* date) {	// 參數二:保存出列的元素返回
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	if (seq->front >= QUEUE_MAX) {
		cout << "隊列已到盡頭!" << endl;
		return false;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	seq->front += 1;	// 隊首“指針”後移一位

	return true;
}

在這裏插入圖片描述

第二種方式要比第一種方式要多一次判斷合法性檢查。如果front“指針”已經大於或者等於隊列的最大元素存儲,那後面的操作就沒有意義了。

如果合法性都正確,那麼只需要將隊首“指針”front自增一就行了。

如上圖就是出隊前和出隊後的效果圖。

也跟第一種刪除方式差不多,現在是頭元素保留在原來的位置,只是front“指針”只增一了,我們也就無法訪問到原來的元素了。

不懂的朋友也可以把他理解成下圖:
在這裏插入圖片描述
準確來講,還是第一幅圖貼近現實!


獲取隊列的首元素

// 獲取隊列的首元素
bool gainQueueFrontValue(seqQueue* seq, dateType* date) {
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	return true;
}

也就是和出隊的代碼差不多,合法性檢查之後,就像元素賦值給date返回。


修改隊列中任意位置的值

// 修改隊列中任意位置的值
bool alterQueueValue(seqQueue* seq, int i, dateType date) {	// 參數二:修改位置;參數三:修改後的值
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	if (i < seq->front || i >= seq->rear) {
		cout << "修改位置不合法!" << endl;
		return false;
	}

	for (int j = seq->front; j < seq->rear; j++) {
		if (i-1 == j) {
			seq->queue[j] = date;
			break;
		}
	}

	return true;
}

這個也是很簡單的,從隊首開始遍歷,直到找到對應位置位置。將元素修改後break退出,然後結束函數。

注意:代碼中,我們判斷時對 i 進行了減一,我們不是從0開始數的,而是從1開始數。


獲取隊列中的元素個數

// 獲取隊列中的元素個數
int size(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	return seq->rear - seq->front;
}

我們可以將隊尾“指針”rear減去隊首“指針”front,就可以得到隊列的元素個數了。


清空隊列

// 清空隊列
void clearQueue(seqQueue* seq) {
	if (!seq) {
		return ;
	}

	seq->front = seq->rear = 0;		// 對頭與隊尾“指針”都爲零
}

清空隊列和初始化隊列都是一樣的,將隊首和隊尾“指針”賦值0即可。


輸出隊列中的元素

// 輸出隊列中的元素
void queuePrint(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!";
		return;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return;
	}

	for (int i = seq->front; i < seq->rear; i++) {
		cout << seq->queue[i] << " ";
	}
	cout << endl;
}

測試代碼:

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

using namespace std;

#define QUEUE_MAX 5		// 隊列的最大存儲個數

typedef int dateType;	// 隊列存儲的類型

typedef struct arrayQueue {
	dateType queue[QUEUE_MAX];	// 存儲隊列元素的數組
	int front;	// 對頭“指針”
	int rear;	// 隊尾“指針”
}seqQueue;


// 隊列初始化
void inItQueue(seqQueue* seq) {
	if (!seq) {
		return;
	}

	seq->front = seq->rear = 0;		// 對頭與隊尾“指針”都爲零
}

// 判斷隊列是否爲空
bool estimateQueueEmpty(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!";
		return false;
	}

	if (seq->front == seq->rear) {	// 當頭尾“指針”相等時,爲空隊列
		return true;
	}

	return false;
}

// 判斷隊列是否已滿
bool estimateQueueFull(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!";
		return false;
	}

	if (seq->rear == QUEUE_MAX) {	// 當隊尾“指針”等於隊列最大存儲個數時,爲滿
		return true;
	}

	return false;
}

// 入隊,將元素插入隊列中
bool enterQueue(seqQueue* seq, dateType date) {	// 參數二是入隊的元素
	if (!seq) {
		cout << "隊列不存在!";
		return false;
	}

	if (estimateQueueFull(seq)) {
		cout << "隊列已滿!插入 " << date << " 失敗!" << endl;
		return false;
	}

	seq->queue[seq->rear] = date;	// 在隊尾插入元素date
	seq->rear += 1;		// 隊尾“指針”後移一位

	return true;
}

// 出隊一,將隊列中隊首的元素出隊,後面的元素向前移動
bool deleteQueueFront_1(seqQueue* seq, dateType* date) {	// 參數二:保存出列的元素返回
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

	if (!date) {
		cout << "date指針爲空!" << endl;
		return true;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	// 從頭元素的下一個元素開始遍歷,將後續的所有元素都往前移動一個位置
	for (int i = seq->front + 1; i < seq->rear; i++) {
		seq->queue[i - 1] = seq->queue[i];
	}
	seq->rear -= 1;

	return true;
}

// 出隊二,將隊列中的隊首元素出隊,出對後對頭“指針”front後移一位
bool deleteQueueFront_2(seqQueue* seq, dateType* date) {	// 參數二:保存出列的元素返回
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	if (seq->front >= QUEUE_MAX) {
		cout << "隊列已到盡頭!" << endl;
		return false;
	}

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	seq->front += 1;	// 隊首“指針”後移一位

	return true;
}

// 獲取隊列的首元素
bool gainQueueFrontValue(seqQueue* seq, dateType* date) {
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	// 保存出列的元素返回
	*date = seq->queue[seq->front];

	return true;
}

// 修改隊列中任意位置的值
bool alterQueueValue(seqQueue* seq, int i, dateType date) {	// 參數二:修改位置;參數三:修改後的值
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return false;
	}

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

	if (i < seq->front || i >= seq->rear) {
		cout << "修改位置不合法!" << endl;
		return false;
	}

	for (int j = seq->front; j < seq->rear; j++) {
		if (i-1 == j) {
			seq->queue[j] = date;
			break;
		}
	}

	return true;
}

// 獲取隊列中的元素個數
int size(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!" << endl;
		return false;
	}

	return seq->rear - seq->front;
}


// 輸出隊列中的元素
void queuePrint(seqQueue* seq) {
	if (!seq) {
		cout << "隊列不存在!";
		return;
	}

	if (estimateQueueEmpty(seq)) {
		cout << "隊列爲空!" << endl;
		return;
	}

	for (int i = seq->front; i < seq->rear; i++) {
		cout << seq->queue[i] << " ";
	}
	cout << endl;
}

// 清空隊列
void clearQueue(seqQueue* seq) {
	if (!seq) {
		return ;
	}

	seq->front = seq->rear = 0;		// 對頭與隊尾“指針”都爲零
}

int main(void) {
	seqQueue que;

	// 初始化隊列
	inItQueue(&que);

	// 插入元素
	for (int i = 0; i < 6; i++) {
		enterQueue(&que, i * 5);
	}

	// 輸出隊列中的元素
	queuePrint(&que);

	// 元素出隊1
	dateType date = 0;
	deleteQueueFront_1(&que, &date);
	cout << "出隊的元素是:" << date << endl;
	cout << "出隊元素後:" << endl;
	queuePrint(&que);

	cout << endl;

	// 元素出隊2
	deleteQueueFront_2(&que, &date);
	cout << "出隊的元素是:" << date << endl;
	cout << "出隊元素後:" << endl;
	queuePrint(&que);

	cout << endl;

	/*for (int i = 0; i < 5; i++) {
		if (deleteQueueFront_2(&que, &date)) {
			cout << "出隊的元素是:" << date << endl;
		} else {
			cout << "出隊失敗!" << endl;
		}
		
	}*/

	// 獲取隊首的元素
	gainQueueFrontValue(&que, &date);
	cout << "隊首的元素是:" << date << endl;

	cout << endl;

	// 修改隊列中任意位置的值
	if (alterQueueValue(&que, 2, 666)) {
		cout << "修改成功!" << endl;
	} else {
		cout << "修改失敗!" << endl;
	}
	cout << "修改後:" << endl;
	queuePrint(&que);

	cout << endl;

	cout << "隊列的元素個數是:" << size(&que) << endl;


	// 清空隊列
	clearQueue(&que);

	system("pause");
	return 0;
}

總結:
順序存儲隊列就跟數組一樣,其操作都不難,只要注意下標就行!

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

祝各位學習愉快!


下集提醒:
你將學會隊列的另一種最基本的表現形式:鏈式存儲隊列
請持續關注!

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