觀看本系列博文提醒:
你將學會隊列的兩種最基本的表現形式:順序存儲隊列 和 鏈式存儲隊列;
一個擴展隊列的使用方法:循環隊列;
兩個企業級隊列的應用:線性池中的任務隊列 和 優先鏈式存儲隊列。
隊列的原理
隊列是一種受限的線性表,(Queue),它是一種運算受限的線性表,先進先出(FIFO First In First Out).
例如上圖中,圓球1先進,也是圓球1先出。
隊列是一種受限的線性結構
- 它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。
- 生活中隊列場景隨處可見: 比如在電影院, 商場, 或者廁所排隊。。。。。。
由上圖我們可以知道,隊列中有兩個“指針”,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“指針”永遠都是指向頭部第一個元素的。
出隊,將元素從頭部刪除掉
他有兩種出隊方式:
- 將隊列中隊首的元素出隊,後面的元素向前移動。優點:保證了隊列的內存都能得到最大的利用,不浪費;缺點:假如隊列中有成千上萬個數據的話,刪除一個元素造成後續那麼多個元素的移動,會造成很大的開銷,影響性能。
- 將隊列中的隊首元素出隊,出隊後對頭“指針”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;
}
總結:
順序存儲隊列就跟數組一樣,其操作都不難,只要注意下標就行!
注意:由於一篇博客內容太多,所以我將會把他分成幾篇進行講解!
祝各位學習愉快!
下集提醒:
你將學會隊列的另一種最基本的表現形式:鏈式存儲隊列;
請持續關注!