《大話資料結構4》—— 佇列的順序儲存結構 (迴圈佇列)—— C++程式碼實現
佇列
● 佇列的概念:
佇列(簡稱作隊,Queue)也是一種特殊的線性表,佇列的資料元素以及資料元素間的邏輯關係和線性表完全相同,其差別是線性表允許在任意位置插入和刪除,而佇列只允許在其一端進行插入操作在其另一端進行刪除操作。
佇列中允許進行插入操作的一端稱為隊尾,允許進行刪除操作的一端稱為隊頭。佇列的插入操作通常稱作入佇列,佇列的刪除操作通常稱作出佇列。
下圖是一個依次向佇列中插入資料元素a0,a1,...,an-1後的示意圖:
上圖中,a0是當前 隊頭資料元素,an-1是當前 隊尾資料元素。
為了避免當只有一個元素時,對頭和隊尾重合使得處理變得麻煩,所以引入兩個指標:front指標指向隊頭元素,rear指標指向隊尾元素的下一個位置,
順序佇列
● 順序佇列 佇列的順序儲存結構稱為順序佇列,順序佇列實際上是運算受限的順序表,和順序表一樣,順序佇列也是必須用一個數組來存放當前佇列中的元素。由於佇列的隊頭和隊尾的位置是變化的,因而要設兩個指標和分別指示隊頭和隊尾元素在佇列中的位置。
● 順序佇列中的溢位現象:
上圖中,front指標指向隊頭元素,rear指標指向隊尾元素的下一個位置。
圖(d)中b、c、d出隊後,front指標指向元素e,rear指標在陣列外面。假設這個佇列的總個數不超過5個,但目前如果接著入隊的話,因陣列末尾元素已經被佔用,再向後加就會產生陣列越界的錯誤,可實際上佇列在下標為0、1、2、3、4的地方還是空閒的,我們把這種現象叫做“假溢位”——也叫假上溢。
下溢:佇列為空時,做出隊操作產生的溢位現象。
真上溢:當佇列滿時,做入隊操作產生的空間溢位現象。
順序佇列的基本操作
入隊時:將新元素插入rear所指的位置,並將rear+1。
出隊時:刪除front所指的元素,並將front+1。
迴圈佇列
● 所以解決假溢位的辦法就是後面滿了,就再從頭開始,也就是頭尾相接的迴圈。我們把佇列的這種邏輯上首尾相連的順序儲存結構稱為迴圈佇列。
如何判斷迴圈佇列究竟是空的還是滿的:
現在問題又來了,我們之前說,空佇列時,front指標等於rear指標,那麼現在迴圈佇列滿的時候,也是front等於rear,那麼如何判斷迴圈佇列究竟是空的還是滿的?有如下辦法:
辦法1:設定一個標誌位flag。初始時置flag=0;每當入佇列操作成功就置flag=1;每當出佇列操作成功就置flag=0。則佇列空的判斷條件為:rear == front && flag==0;佇列滿的判斷條件為:rear = = front && flag= =1。
辦法2:保留一個元素的儲存空間。此時,佇列滿時的判斷條件為 (rear + 1) % maxSize == front;佇列空的判斷條件還是front == rear。
辦法3:設計一個計數器count,統計佇列中的元素個數。此時,佇列滿的判斷條件為:count > 0 && rear == front ;佇列空的判斷條件為count == 0。
我們在接下來的程式碼中採用方法2來實現。
CirQueue.h 標頭檔案
#include<iostream>
#include<cassert>
using namespace std;
#ifndef TT_CIR_QUEUE_H
#define TT_CIR_QUEUE_H
namespace tt
{
class CirQueue
{
public:
using ElemType = int;
using Status = void;
enum State
{
TT_ERROR = 0,
TT_OK = 1
};
public:
CirQueue(ElemType INI_SIZE); //初始化佇列
~CirQueue();
ElemType isEmpty()const; //判斷佇列是否為空。
ElemType isFull()const; //確定佇列是否滿了
ElemType clear(); //將佇列清空
ElemType getHead(ElemType &elemOut); //若佇列存在且非空,用elem返回佇列的隊頭元素
ElemType insert(ElemType elem); //若佇列存在,插入新元素elem到佇列中併成為隊尾元素。
ElemType remove(ElemType &elemOut);// 刪除佇列中的隊頭元素,並用elem返回。
ElemType destroy(); //若佇列存在,則銷燬它
Status getLength()const; //返回佇列中當前元素的個數。
Status show(); //顯示佇列中的所有元素
private:
ElemType *m_data; //資料域
ElemType m_front; //指向隊頭元素
ElemType m_rear; //指向隊尾元素的下一個位置
ElemType m_queueSize; //佇列的最大容量
};
inline CirQueue::ElemType CirQueue::isEmpty()const //判斷佇列是否為空
{
return (m_front == m_rear);
}
inline CirQueue::Status CirQueue::getLength()const //返回佇列中當前元素的個數
{
cout<< "當前佇列中的元素個數為:" << (m_rear - m_front + m_queueSize) % m_queueSize << endl;
}
inline CirQueue::ElemType CirQueue::clear() //將佇列清空
{
m_front = m_rear = 0;
return TT_OK;
}
inline CirQueue::ElemType CirQueue::isFull()const //確定佇列是否滿了
{
return (m_rear + 1) % m_queueSize == m_front;
}
}
#endif //TT_CIR_QUEUE_H
testCirQueue.cpp 原始檔
#include"CirQueue.h"
namespace tt
{
CirQueue::CirQueue(ElemType INI_SIZE)
{
assert(INI_SIZE != 0);
m_data = new int[INI_SIZE];
assert(m_data != nullptr);
m_queueSize = INI_SIZE; //把佇列的初始化的最大容量賦值給成員資料
m_front = m_rear = 0; //m_front等於m_rear 就是空佇列
cout << "*********** 迴圈佇列初始化成功! **************" << endl;
}
CirQueue::~CirQueue()
{
this->destroy();
}
CirQueue::ElemType CirQueue::insert(ElemType elem) //插入元素致佇列的尾部
{
if (((m_rear + 1) % m_queueSize) == m_front)//判斷佇列滿的情況
{
return TT_ERROR;
}
m_data[m_rear] = elem; //將元素elem 新增到佇列的末尾
m_rear = (m_rear + 1) % m_queueSize; //尾指標應以此種方式加1,才會實現迴圈佇列, 若到末尾轉到陣列的頭部
return TT_OK;
}
CirQueue::ElemType CirQueue::remove(ElemType &elemOut) //刪除佇列的隊頭元素
{
if (m_front == m_rear) //判斷迴圈佇列是否為空
{
return TT_ERROR;
}
elemOut = m_data[m_front]; //將對頭元素賦給elem返回
m_front = (m_front + 1) % m_queueSize; //m_front指標向後移動一位,若到最後則轉到陣列頭部
return TT_OK;
}
CirQueue::ElemType CirQueue::getHead(ElemType &elemOut) //若佇列存在且非空,用elem返回佇列的隊頭元素
{
if (m_front == m_rear) //判斷迴圈佇列是否為空
{
return TT_ERROR;
}
elemOut = m_data[m_front]; //把隊頭元素用elem返回
return TT_OK;
}
CirQueue::ElemType CirQueue::destroy() //若佇列存在,則銷燬它
{
delete[] m_data;
m_data = nullptr;
m_front = m_rear = m_queueSize = 0;
return ((!m_data) && (m_front == m_rear == m_queueSize == 0)); //多一個判斷看佇列是否被銷燬
}
CirQueue::Status CirQueue::show() //顯示佇列的所有元素
{
if (m_front == m_rear)
{
cout << "錯誤,此佇列中沒有資料或者佇列沒有建立,無法顯示!" << endl;
}
else
{
auto count = (m_rear - m_front + m_queueSize) % m_queueSize; //一個臨時變數儲存該佇列的元素個數
cout << "佇列從隊頭至隊尾內容依次為:";
for (size_t i = m_front; i < m_front + count; ++i)
{
cout << m_data[i] << ' ';
}
cout << endl;
}
}
}
//測試迴圈佇列的功能
void testCirQueue()
{
int allocMemory(0);
cout << "請輸入佇列初始化的最大容量:";
cin >> allocMemory;
tt::CirQueue myCirQueue(allocMemory); //初始化一個佇列
while (true)
{
{
cout << ("\n***************************************************") << endl
<< "*************** 迴圈佇列的基本功能展示 **************" << endl
<< "*******************************************************" << endl
<< "************** 選擇1—— 資料進佇列尾. ************" << endl
<< "************** 選擇2—— 刪除佇列頭元素. ************" << endl
<< "*************** 選擇3—— 顯示佇列頭元素. ************" << endl
<< "*************** 選擇4—— 判斷佇列是否為空. ************" << endl
<< "*************** 選擇5—— 判斷佇列是否滿了. ************" << endl
<< "***************************************************************" << endl
<< "*************** 選擇6—— 顯示佇列的元素個數. *************" << endl
<< "*************** 選擇7—— 清空佇列. *************" << endl
<< "**************** 選擇8—— 銷燬佇列. *************" << endl
<< "**************** 選擇9—— 顯示佇列中的所有元素. ***********" << endl
<< "**************** 選擇10—— 清屏. *************" << endl
<< "**************** 選擇0—— 退出程式! *************" << endl
<< "***************************************************************" << endl
<< "***************************************************************" << endl;
}
cout << "\n***************** 請輸入你想要使用的佇列功能的序號 ***************" << endl;
cout << "請輸入你的選擇:";
int userChoice(0);
cin >> userChoice;
if (!userChoice)
{
cout << "程式已退出,感謝您的使用!" << "\n" << endl;
break;
}
switch (userChoice)
{
case 1:
{
int pushDatas(0);
cout << "請輸入想要新增的資料:";
cin >> pushDatas;
if (myCirQueue.insert(pushDatas)) //進佇列
{
cout << "資料" << pushDatas << "成功進入佇列中!" << endl;
myCirQueue.getLength();
myCirQueue.show(); //顯示所有元素
}
else
cout << "目前佇列已滿, 資料" << pushDatas << "進入失敗!" << endl;
break;
}
case 2:
{
int popDatas(0);
if (myCirQueue.remove(popDatas)) //刪除佇列頭元素
{
cout << "資料" << popDatas << "從佇列中成功刪除!" << endl;
myCirQueue.getLength();
myCirQueue.show();
}
else
{
cout << "目前佇列為空, 資料" << popDatas << "刪除失敗!" << endl;
myCirQueue.getLength();
}
break;
}
case 3:
{
int disHead(0);
if (myCirQueue.getHead(disHead)) //獲取隊頭元素
{
cout << "佇列頭元素為:" << disHead << endl;
myCirQueue.getLength();
myCirQueue.show();
}
else
{
cout << "目前佇列為空, 資料" << disHead << "獲取失敗!" << endl;
myCirQueue.getLength();
}
break;
}
case 4:
if (myCirQueue.isEmpty()) //判斷佇列是否空
{
cout << "佇列為空,或者佇列尚未建立!" << endl;
myCirQueue.getLength();
}
else
{
cout << "佇列非空!" << endl;
myCirQueue.getLength();
myCirQueue.show();
}
break;
case 5:
if (myCirQueue.isFull()) //判斷佇列是否滿
{
cout << "目前佇列已滿,不能再新增資料了!" << endl;
myCirQueue.getLength();
myCirQueue.show();
}
else
{
cout << "目前佇列不滿,還可以繼續輸入資料進棧!" << "\n" << endl;
myCirQueue.getLength();
myCirQueue.show();
}
break;
case 6:
myCirQueue.getLength(); //顯示佇列的元素個數
myCirQueue.show();
break;
case 7:
if (myCirQueue.clear())
{
cout << "佇列已清空!" << endl;
myCirQueue.getLength();
}
else
{
cout << "佇列清空失敗!" << endl;
myCirQueue.getLength();
myCirQueue.show();
}
break;
case 8:
{
cout << "你確定要銷燬一個佇列嗎?(若銷燬請輸入輸入(Y/y))";
char yesOrNo;
cin >> yesOrNo;
if ((yesOrNo == 'Y') || (yesOrNo == 'y'))
{
if (myCirQueue.destroy())
{
cout << "佇列已被銷燬!" << endl;
}
else
cout << "佇列銷燬失敗!" << endl;
}
break;
}
case 9:
myCirQueue.getLength();
myCirQueue.show();
break;
case 10:
system("cls");
cout << "螢幕已經清屏,可以重新輸入!" << "\n" << endl;
break;
default:
cout << "輸入的序號不正確,請重新輸入!" << "\n" << endl;
}
}
}
int main()
{
testCirQueue();
system("pause");
return 0;
}
● 注意: 把以上程式碼複製黏貼到 Visual Studio 2017 上 或者 Visual Studio 2015 上,如果是其他的編譯器,可能會出錯, 因為該程式用了一點 C++11 的語法, 如果你的編譯器不遵循C++11的語法的法,一般來說會出錯。
把該程式碼分別放進編譯器中的原始檔和標頭檔案就能編譯執行成功。 如果要合成到一塊的原始檔的話, 需要修改一下地方。你們自己修改吧。