数据结构杂谈(六)——队列
本文的所有代碼均由C++編寫
六 隊(duì)列
文章目錄
- 六 隊(duì)列
- 6.1 隊(duì)列的定義
- 6.2 隊(duì)列的抽象數(shù)據(jù)類型
- 6.3 順序隊(duì)列(循環(huán)隊(duì)列)
- 6.3.1 順序隊(duì)列的定義及初始化
- 6.3.2 入隊(duì)
- 6.3.3 出隊(duì)
- 6.3.4 獲取隊(duì)頭元素
- 6.3.5 獲取隊(duì)列長度
- 6.3.6 回過頭來
- 6.4 鏈?zhǔn)疥?duì)列
- 6.4.1 鏈?zhǔn)疥?duì)列的定義及初始化
- 6.4.2 入隊(duì)
- 6.4.3 出隊(duì)
- 6.5 循環(huán)隊(duì)列和鏈?zhǔn)疥?duì)列的對(duì)比
6.1 隊(duì)列的定義
與棧相反,隊(duì)列(Queue)是一種先進(jìn)先出(Fisrt in first out,FIFO)的結(jié)構(gòu)。在此,我們先給出隊(duì)列的定義:
隊(duì)列是只允許在一端進(jìn)行插入操作,而在另一端進(jìn)行刪除操作的線性表
舉一個(gè)生活中的例子,如下圖所示:
對(duì)的,你沒看錯(cuò),隊(duì)列實(shí)際上就這么簡(jiǎn)單,我們把往隊(duì)列中添加結(jié)點(diǎn)叫做入隊(duì),往隊(duì)列中刪除結(jié)點(diǎn)叫做出隊(duì)。入隊(duì)的一端叫隊(duì)尾,出隊(duì)的一端叫隊(duì)頭,這都是可以和生活中聯(lián)系起來的。
我們把上面的圖抽象一下,變成下面的形式:
隊(duì)列在程序設(shè)計(jì)中應(yīng)用地非常頻繁,比如鍵盤輸入字母到記事本上,你依次輸入god,總不能記事本上面寫的是dog吧。
學(xué)過操作系統(tǒng)的朋友也了解就緒隊(duì)列這個(gè)名詞,在進(jìn)程調(diào)度中,隊(duì)列也經(jīng)常出現(xiàn),在時(shí)間片調(diào)度算法中,一個(gè)進(jìn)程從隊(duì)頭上處理器運(yùn)行的時(shí)候,當(dāng)時(shí)間片輪換時(shí),這個(gè)進(jìn)程若未執(zhí)行完成,將會(huì)回到該就緒隊(duì)列的隊(duì)尾。
6.2 隊(duì)列的抽象數(shù)據(jù)類型
同樣,隊(duì)列也可以用線性表來表示,當(dāng)然也就分為了順序隊(duì)列和鏈?zhǔn)疥?duì)列了。在探討這些之前,我們先了解一下隊(duì)列的抽象數(shù)據(jù)類型:
ADT 隊(duì)列(Queue) Data同線性表。元素具有相同的類型,相鄰元素具有前驅(qū)和后繼關(guān)系。 OperationInitQueue(*Q):初始化操作,建立一個(gè)空隊(duì)列Q。DestroyQueue(*Q):若隊(duì)列Q存在,則銷毀它。ClearQueue(*Q):將隊(duì)列Q清空。QueueEmpty(Q):若隊(duì)列Q為空,返回true,否咋返回false。Gethead(Q,*e):若隊(duì)列Q存在且非空,用e返回隊(duì)列Q的隊(duì)頭元素。EnQueue(*Q,e):若隊(duì)列Q存在,插入新元素e到隊(duì)列Q中并成為隊(duì)尾元素。DeQueue(*Q,e):刪除隊(duì)列Q中隊(duì)頭元素,并用e返回其值。QueueLength(Q):返回隊(duì)列Q的元素個(gè)數(shù)。 endADT6.3 順序隊(duì)列(循環(huán)隊(duì)列)
6.3.1 順序隊(duì)列的定義及初始化
對(duì)于隊(duì)列的定義,同樣無需多講,上代碼。
//靜態(tài)數(shù)組大小 #define MaxSize 10//順序隊(duì)列定義 typedef int QElemType;typedef struct SqQueue {QElemType data[MaxSize];//靜態(tài)數(shù)組存放隊(duì)列元素int front, rear;//隊(duì)頭和隊(duì)尾指針 }SqQueue;//初始化 void InitQueue(SqQueue& Q) {Q.front = 0;Q.rear = 0; }如果需要判斷隊(duì)列是否為空,只需判斷隊(duì)頭指針是否等于隊(duì)尾指針即可。
//判空 bool QueueEmpty(SqQueue Q) {if (Q.rear == Q.front)return true;elsereturn false; }6.3.2 入隊(duì)
對(duì)于入隊(duì),我們需要注意一個(gè)事情。
假設(shè)我們用一個(gè)靜態(tài)數(shù)組來存放隊(duì)列,那么隊(duì)頭指針指向隊(duì)頭元素,隊(duì)尾指針指向隊(duì)尾元素。當(dāng)添加隊(duì)列元素時(shí),隊(duì)尾指針+1,而當(dāng)刪除隊(duì)列元素時(shí),隊(duì)頭指針+1,也就是說,當(dāng)隊(duì)尾指針等于靜態(tài)數(shù)組最大容量的時(shí)候,只能說明添加隊(duì)列的元素剛好處于數(shù)組最后,而不能說明隊(duì)列容量不足。這個(gè)情況在《大話數(shù)據(jù)結(jié)構(gòu)》中叫做假溢出。
換而言之,隊(duì)頭元素不一定在數(shù)組的0號(hào)索引,隊(duì)尾數(shù)組不一定在數(shù)組的MaxSize號(hào)索引。
這樣的做法有別于我們前面講到的順序表的插入,在順序表中,我們插入是當(dāng)插入一個(gè)元素,所有往后的元素都要移動(dòng)一位,但是這樣就會(huì)造成時(shí)間復(fù)雜度過大,其花的時(shí)間都在移動(dòng)元素上。為了解決時(shí)間問題,我們才拋出上述的做法。
那我們?nèi)绾蝸硖幚眄樞蜿?duì)列判斷是否溢出呢?
這個(gè)問題的拋出,引出了順序隊(duì)列的本名:循環(huán)隊(duì)列。我們?cè)诖私o出循環(huán)隊(duì)列的定義:
我們把隊(duì)列中頭尾相接的順序存儲(chǔ)結(jié)構(gòu)稱為循環(huán)隊(duì)列。
我們來嘗試處理這個(gè)問題,采用的方法有兩種。什么意思呢?我們發(fā)現(xiàn)當(dāng)新元素入隊(duì)時(shí),隊(duì)尾指針跟著新元素,如上圖所示,我們最開始初始化是讓front指針和rear指針重合,所以再次重合時(shí)就是隊(duì)列溢出的時(shí)候,由此我們可以引出第一個(gè)方法:
設(shè)置一個(gè)標(biāo)志flag,最開始初始化時(shí),front == rear,此時(shí)flag = 0,表示隊(duì)列為空,第二次front == rear,此時(shí)flag = 1,表示隊(duì)列溢出。
我們還可以有一種方法,即犧牲一個(gè)位置。當(dāng)隊(duì)列滿的時(shí)候,數(shù)組中還有一個(gè)空閑單元。
由此我們可以引入第二個(gè)方法:
在判別滿的情況時(shí)我們的條件是(rear+1)% == front。
rear和front在數(shù)組中的位置那個(gè)在0號(hào)位那個(gè)在MaxSize號(hào)位是可以自己指定的,考慮到循環(huán)的問題,它們相遇有可能是在同一圈,也有可能相差了一圈后相遇,所以我們的判別溢出條件應(yīng)該為(rear+1)%MaxSize = front。
上面可能有點(diǎn)亂,我們來看看下面的例子:
在上圖的左邊f(xié)ront處于0號(hào)位,rear處于4號(hào)位,按照上面我們說的,(4+1)%5 = 0,說明隊(duì)列已滿;如上圖所示右邊f(xié)ront處于2號(hào)位,rear處于1號(hào)位,如上所說(1+1)%5 = 2,說明隊(duì)列已滿。
取余運(yùn)算是除法中的術(shù)語,取余數(shù)是指整數(shù)除法中被除數(shù)未被除盡部分,如7%2 = 1。這是大的數(shù)取余一個(gè)小的數(shù);而當(dāng)一個(gè)小的數(shù)取余一個(gè)大的數(shù)時(shí),商為0,所以余數(shù)為自己,如2%5 = 2。
讓我們回到本小節(jié)的主題,入隊(duì)。既然弄清楚原理,代碼也好寫了。
//入隊(duì) bool EnQueue(SqQueue& Q, QElemType x) {if ((Q.rear + 1) %MaxSize == Q.front) {return false;}//新元素插入隊(duì)尾Q.data[Q.rear] = x;//移動(dòng)隊(duì)尾指針Q.rear = (Q.rear + 1) % MaxSize;return true; }6.3.3 出隊(duì)
當(dāng)然的,如果是要?jiǎng)h除隊(duì)列中的元素,即把隊(duì)頭元素刪除,當(dāng)然需要注意的是注意判空。
//出隊(duì) bool DeQueue(SqQueue& Q, QElemType& x) {//判斷隊(duì)列是否為空if (Q.rear == Q.front) {return false;//隊(duì)列為空無法刪除}x = Q.data[Q.front];Q.front = (Q.front + 1) % MaxSize;return true; }6.3.4 獲取隊(duì)頭元素
//獲取隊(duì)頭元素 bool GetHead(SqQueue Q, QElemType& x) {if (Q.rear == Q.front)return false;//隊(duì)空則報(bào)錯(cuò)x = Q.data[Q.front];return true; }6.3.5 獲取隊(duì)列長度
//獲取隊(duì)列長度 bool QueueLength(SqQueue Q,QElemType &x) {x = (Q.rear + MaxSize - Q.front) % MaxSize;return true; }6.3.6 回過頭來
上面講述了兩種方法來判斷是否溢出,我們?cè)阪湕V羞€采用了統(tǒng)計(jì)表長的方式來判斷棧棧,那么在隊(duì)列中我們同樣可以這么做,用一個(gè)整形的length來記錄隊(duì)列長度,當(dāng)隊(duì)列長度大于MaxSize時(shí),即隊(duì)列溢出,所以我們可以用條件if(length == MaxSize)來作為判斷條件。
根據(jù)初始化的不同,實(shí)際上對(duì)應(yīng)的代碼都會(huì)有所不同,采用什么方法去判斷滿隊(duì)列就會(huì)有三種不同寫法,而在空隊(duì)列時(shí),初始化兩個(gè)指針指向何處也會(huì)導(dǎo)致代碼的不同。在本筆記中,我們采用的是隊(duì)頭和隊(duì)尾指向同一個(gè)位置,當(dāng)添加一個(gè)元素時(shí),隊(duì)尾指針會(huì)移向隊(duì)尾元素的下一位;而在某些題目或教材中,隊(duì)尾指針可能是指向隊(duì)尾元素的。
在考研中,以上三種方法均有可能出現(xiàn),望周知。
6.4 鏈?zhǔn)疥?duì)列
隊(duì)列的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),其實(shí)就是線性表的單鏈表,只不過它只能尾進(jìn)頭出罷了。我們把它簡(jiǎn)稱為鏈隊(duì)列。
對(duì)于鏈隊(duì)列來說,用帶頭結(jié)點(diǎn)的方式去實(shí)現(xiàn)就會(huì)更好一些。其中隊(duì)頭指針指向頭結(jié)點(diǎn),隊(duì)尾指針指向隊(duì)尾元素,以便進(jìn)行插入操作。接下來我們來看看如何實(shí)現(xiàn)。
6.4.1 鏈?zhǔn)疥?duì)列的定義及初始化
typedef struct LinkNode {ElemType data;struct LinkNode* next; }LinkNode;typedef struct {LinkNode* front, * rear; }LinkQueue;對(duì)于初始化來說,代碼實(shí)現(xiàn)如下:
void InitQueue(LinkQueue& Q) {Q.front = Q.rear = new LinkNode;Q.front->next = NULL; }6.4.2 入隊(duì)
鏈?zhǔn)浇Y(jié)構(gòu)入隊(duì)只需在添加結(jié)點(diǎn)于隊(duì)尾元素之后,更改隊(duì)尾指針指向即可。
//入隊(duì) void EnQueue(LinkQueue& Q, ElemType x) {LinkNode* s = new LinkNode;//生成一個(gè)新結(jié)點(diǎn)s->data = x;s->next = NULL;Q.rear->next = s;//新結(jié)點(diǎn)插入rear之后Q.rear = s;//修改rear指針位置 }6.4.3 出隊(duì)
//出隊(duì) bool DeQueue(LinkQueue& Q, ElemType& e) {if (Q.front == Q.rear)return false;LinkNode* p = Q.front->next;e = p->data;Q.front->next = p->next;if (Q.rear == p)Q.rear = Q.front;delete(p);return true; }6.5 循環(huán)隊(duì)列和鏈?zhǔn)疥?duì)列的對(duì)比
對(duì)于循環(huán)隊(duì)列和鏈?zhǔn)疥?duì)列來說,在時(shí)間復(fù)雜度上它們的基本操作都是常數(shù)時(shí)間,即都為O(1)。而對(duì)于空間上來說,循環(huán)隊(duì)列一開始長度就定死,而鏈?zhǔn)疥?duì)列則較為靈活。所以我們可以總結(jié)如下:
在可以確定隊(duì)列長度最大值的情況下,建議用循環(huán)隊(duì)列。如果無法預(yù)估隊(duì)列長度,則建議用鏈隊(duì)列。
總結(jié)
以上是生活随笔為你收集整理的数据结构杂谈(六)——队列的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不要设计模式
- 下一篇: 将权限授予文件夹和程序集