《大話資料結構5》—— 佇列的鏈式儲存結構 —— C++程式碼實現
阿新 • • 發佈:2018-11-09
目錄
鏈佇列
● 實現佇列的最好的方式就是使用單鏈表來實現,佇列的鏈式儲存結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已——稱為鏈佇列。
● 那為了操作方便,頭指標指向頭結點,隊尾指標指向終端節點,即最後一個結點元素。
設隊首、隊尾指標front和rear,front指向頭結點,rear指向隊尾
佇列為空時,front 和 real 都指向頭結點:
這種實現出隊和入隊就十分方便,出隊只需要將頭結點指向的位置進行改變,入隊只需要將rear的指向進行改變。
下面看我寫的程式碼:
LinkQueue.h 標頭檔案
#include<iostream> #include<cassert> using namespace std; #ifndef TT_LINK_QUEUE_H #define TT_LINK_QUEUE_H namespace tt { class LinkQueue //佇列的連結串列結構 { public: using ElemType = int; using Status = void; public: struct QNode //佇列的結點結構 { ElemType m_data; //鏈佇列的資料域 QNode *m_next; //鏈佇列的指標域 }; enum State { TT_ERROR = 0, TT_OK = 1 }; public: LinkQueue(); ~LinkQueue(); ElemType isEmpty()const; //判斷佇列是否為空 ElemType clear(); //清空佇列 ElemType insert(ElemType elem); //插入元素致鏈佇列尾部 ElemType remove(ElemType &elemOut); //刪除鏈佇列的對頭元素 ElemType destroy(); //銷燬佇列 ElemType getHead(ElemType &eleOut); //獲取對頭元素 Status createTail(ElemType *datas, size_t length);//建立長度為length的鏈佇列,資料通過陣列指定,這裡採用尾插法 Status createHead(ElemType *datas, size_t length); //頭插法 Status getlength()const; //鏈佇列的當前個數 Status traverseElem(); //遍歷顯示鏈佇列中的所有元素 private: QNode *m_front; //鏈佇列的對頭指標,隊頭不儲存元素,只起頭結點作用,當front==rear時,佇列為空 QNode *m_rear; //鏈佇列的隊尾指標 ElemType m_queLength; //鏈佇列的當前長度 }; inline LinkQueue::ElemType LinkQueue::isEmpty()const { return (m_front == m_rear); } inline LinkQueue::Status LinkQueue::getlength()const { cout << "鏈佇列的當前的元素個數為:" << m_queLength << "\n" << endl; } } #endif //TT_LINK_QUEUE_H
testLinkQueue.cpp 原始檔
#include"LinkQueue.h" namespace tt { LinkQueue::LinkQueue() { m_front = new QNode; assert(m_front != nullptr); m_rear = m_front; //鏈佇列為空時,對頭就是隊尾 m_front->m_next = nullptr; //隊頭指標指向空 m_queLength = 0; cout << "*******************鏈佇列初始化成功************************" << endl; } LinkQueue::~LinkQueue() { this->destroy(); } LinkQueue::Status LinkQueue::createTail(ElemType *datas, size_t length)//建立長度為length的鏈佇列,資料通過陣列指定,這裡採用尾插法 { for (size_t i = 0; i < length; ++i) { this->insert(datas[i]); } } LinkQueue::Status LinkQueue::createHead(ElemType *datas, size_t length) //頭插法 { if (m_rear == m_front) { this->insert(datas[0]); //建立第一個元素 } QNode *s = m_front->m_next; //指標s指向第一個元素 for (int i = 1; i < length; ++i) { QNode *p = new QNode; p->m_data = datas[i]; p->m_next = s; // 新插入的結點的後繼是s,該結點暫時成為第一個元素 m_front->m_next = p; //頭結點後繼是p s = p; // 指標s也指向p指標指向的位置 ++m_queLength; } } LinkQueue::ElemType LinkQueue::insert(ElemType elem) { QNode *p = new QNode; //給一個新節點分配記憶體 assert(p != nullptr); p->m_data = elem; //首先把要把新元素添進資料域 p->m_next = nullptr; //新結點的後繼指向空 m_rear->m_next = p; //然後在把p變成原隊尾指標指向的後繼結點 m_rear = p; //最後再把p設定為隊尾結點,此時m_rear指標指向最後一個元素 ++m_queLength; //佇列的長度加1 return TT_OK; } LinkQueue::ElemType LinkQueue::remove(ElemType &elemOut) { if (m_front == m_rear) //先判斷鏈佇列是否為空,空就錯誤, { return TT_ERROR; } QNode *p = m_front->m_next; //宣告一個新結點,該結點等於要刪除的第一個元素 elemOut = p->m_data; //在把要刪除的元素用eleOut返回 m_front->m_next = p->m_next; //在把要隊頭結點的後繼結點,即第二個元素,m_front指標指向它,就變成頭結點的後繼 if (m_rear == p) //如果要刪除的元素剛好就是最後一個元素, 此時就一個元素 { m_rear = m_front; //刪除後,m_rear指標指向頭結點 } delete p; //釋放要刪除的結點的記憶體 --m_queLength; //鏈佇列的的長度減一 return TT_OK; } LinkQueue::ElemType LinkQueue::getHead(ElemType &eleOut) { if (m_front == m_rear) //先判斷鏈佇列是否為空,空就錯誤, { return TT_ERROR; } //QNode *q=m_front->m_next; eleOut=q->m_data; eleOut = m_front->m_next->m_data; //把m_front指標指向的後繼結點的指標域中的資料用eleOut返回 return TT_OK; } LinkQueue::ElemType LinkQueue::clear() //清空鏈佇列,不包括頭結點 { QNode *m = m_front->m_next; //首先宣告一個臨時結點m,變成隊頭元素 //m_front->m_next = nullptr; //然後m_front指標指向nullptr m_rear = m_front; //m_rear指標指向頭結點 while (m) //當m不為空時,迴圈繼續 { QNode *s = m->m_next; //先把m結點的後繼結點變成s,即第二個元素 delete m; //釋放m結點 m = s; //然後在把s 結點變成m結點,m結點永遠都是第一個元素 } m_queLength = 0; return TT_OK; } LinkQueue::ElemType LinkQueue::destroy() //銷燬佇列,包括頭結點 { this->clear(); delete m_front; m_front = m_rear = nullptr; return TT_OK; } LinkQueue::Status LinkQueue::traverseElem() //遍歷顯示鏈佇列中的所有元素 { if (m_front == m_rear) //先判斷鏈佇列是否為空,空就錯誤, { cout << "此佇列中沒有資料或者佇列沒有建立,無法顯示!" << "\n" << endl; } else { cout << "佇列從隊頭至隊尾內容依次為:"; QNode *q = m_front->m_next; //首先把第一個元素變成臨時變數q while (q) //只要q不為NULL,就繼續 { cout << q->m_data << " "; //顯示出q指標指向的節點的元素 q = q->m_next; //q指標每迴圈一次往後移動一次 } cout << endl; } } } //測試鏈式佇列 void testLinkQueue() { tt::LinkQueue mylinkQueue; //初始化一個佇列 int myLength(0); //鏈佇列的整表建立 cout << "想建立多少資料的連結串列?"; cin >> myLength; int *myDatas = new int[myLength]; cout << "請依次輸入這" << myLength << "個數據,中間以回車符隔開:" << endl; for (int i = 0; i < myLength; ++i) { cin >> myDatas[i]; //輸入要儲存的資料的值 } mylinkQueue.createTail(myDatas, myLength); //呼叫createTail函式 建立單鏈表 mylinkQueue.traverseElem(); while (true) { { cout << ("\n*********************************************************") << endl << "*************** 鏈佇列的基本功能展示 ******************" << endl << "*******************************************************" << endl << "************** 選擇1—— 資料進佇列尾. ************" << endl << "************** 選擇2—— 刪除佇列頭元素. ************" << endl << "*************** 選擇3—— 顯示佇列頭元素. ************" << endl << "*************** 選擇4—— 判斷佇列是否為空. ************" << endl << "***************************************************************" << endl << "*************** 選擇5—— 顯示佇列的元素個數. *************" << endl << "*************** 選擇6—— 清空佇列. *************" << endl << "**************** 選擇7—— 銷燬佇列. *************" << endl << "**************** 選擇8—— 顯示佇列中的所有元素. ***********" << endl << "**************** 選擇9—— 清屏. *************" << endl << "**************** 選擇0—— 退出程式! *************" << endl << "***************************************************************" << endl << "***************************************************************" << endl; } cout << "\n*************請輸入你想要使用的鏈佇列功能的序號***************" << endl; cout << "請輸入相應的序號:"; int userChoice(0); cin >> userChoice; if (userChoice == 0) { cout << "程式已退出,感謝您的使用!" << "\n" << endl; break; } switch (userChoice) { case 1: { cout << "請輸入你想新增的資料:"; int pushDatas(0); cin >> pushDatas; if (mylinkQueue.insert(pushDatas)) //新增資料進隊尾 { cout << "資料" << pushDatas << "進佇列成功!" << endl; mylinkQueue.getlength(); mylinkQueue.traverseElem(); } else cout << "記憶體分配失敗,資料" << pushDatas << "進佇列失敗!" << "\n" << endl; break; } case 2: { int popHead(0); if (mylinkQueue.remove(popHead)) //刪除隊頭元素 { cout << "資料" << popHead << "刪除成功!" << "\n" << endl; mylinkQueue.getlength(); mylinkQueue.traverseElem(); } else { cout << "目前佇列為空,資料" << popHead << "刪除失敗!" << "\n" << endl; mylinkQueue.getlength(); } break; } case 3: { int showHead(0); if (mylinkQueue.getHead(showHead)) //顯示隊頭元素 { cout << "鏈佇列的頭元素為:" << showHead << "\n" << endl; mylinkQueue.getlength(); mylinkQueue.traverseElem(); } else { cout << "目前佇列為空,無法顯示元素!" << "\n" << endl; mylinkQueue.getlength(); } break; } case 4: if (mylinkQueue.isEmpty()) //判斷鏈佇列是否為空 { cout << "此佇列為空!" << "\n" << endl; mylinkQueue.getlength(); } else { cout << "佇列不為空" << "\n" << endl; mylinkQueue.getlength(); mylinkQueue.traverseElem(); } break; case 5: //顯示鏈隊列當前的元素個數 mylinkQueue.getlength(); mylinkQueue.traverseElem(); break; case 6: if (mylinkQueue.clear()) //清空佇列 { cout << "佇列已被清空!" << "\n" << endl; mylinkQueue.getlength(); } else { cout << "佇列清空失敗!" << "\n" << endl; mylinkQueue.getlength(); mylinkQueue.traverseElem(); } break; case 7: { cout << "你確定要銷燬此佇列嗎?如若銷燬,就無法恢復,請謹慎操作.(若銷燬請輸入Y或y,表示確定)"; char yesOrNo; cin >> yesOrNo; if ((yesOrNo == 'y') || (yesOrNo == 'y')) { if (mylinkQueue.destroy()) { cout << "佇列已被銷燬!" << "\n" << endl; } else cout << "佇列銷燬失敗!" << "\n" << endl; } break; } case 8: //顯示佇列中的所有元素 mylinkQueue.getlength(); mylinkQueue.traverseElem(); break; case 9: system("cls"); cout << "螢幕已經清屏,可以重新輸入!" << "\n" << endl; break; default: cout << "輸入的序號不正確,請重新輸入!" << "\n" << endl; } } delete[]myDatas; myDatas = nullptr; } int main() { testLinkQueue(); system("pause"); return 0; }
● 注意: 把以上程式碼複製黏貼到 Visual Studio 2017 上 或者 Visual Studio 2015 上,如果是其他的編譯器,可能會出錯, 因為該程式用了一點 C++11 的語法, 如果你的編譯器不遵循C++11的語法的法,一般來說會出錯。
把該程式碼分別放進編譯器中的原始檔和標頭檔案就能編譯執行成功。 如果要合成到一塊的原始檔的話, 需要修改一下地方。你們自己修改吧。
如果有錯誤的話,歡迎指教。
如果轉載的話, 請指明出處。謝謝。
迴圈佇列和鏈式佇列的比較
(1)從時間上看,它們的基本操作都是常數時間,即O(1)的。不過迴圈佇列是事先申請好空間,使用期間不釋放;而鏈式佇列,每次申請和釋放結點也會存在一定的時間開銷,如果入隊和出隊比較頻繁,則兩者還是有細微的差別。
(2)從空間上看,迴圈佇列必須有一個固定的長度,所以就有了儲存元素個數和空間浪費的問題。而鏈式佇列不存在這個問題,儘管它需要一個指標域,會產生一些空間上的開銷,但也可以接受。所以在空間上,鏈式佇列更加靈活。
總結:總的來說,在可以確定佇列長度的最大值的情況下,建議用迴圈佇列,如果你無法估計佇列的長度,那就用鏈式佇列。