1 定義
隊列是只允許在一端進行插入操作,另一端進行刪除操作的線性表。
隊列是一種先進先出(FIST IN FIRST OUT)的線性表,簡稱FIFO。允許插入的一端稱為隊尾,允許刪除的一端稱為對頭。
2 隊列的順序存儲結構
(1)隊列順序存儲的不足--引出循環隊列
假設一個隊列有n個元素,則順序存儲的隊列需要建立一個大於n的數組,並把隊列的所有元素存儲在數組的前n個單元,數組下標為0的一端即為對頭。
所謂的入隊,就是在隊尾追加一個元素,不需要移動任何元素,所以時間復雜度為O(1).
隊列的出隊是在對頭,即下標為0的位置,也就意味着,隊列中的所有位置都得向前移動,以保證下標為0的位置,即對頭不為空。此時時間復雜度為O(n)。
可是有時候想想,為什么出隊列時一定要全部移動呢?如果不限制隊列的元素一定要存儲在數組的前n個單元,出隊的性能就會大大增加。也就是說,隊頭不需要一定在下標為0的位置。
為了避免當只有一個元素時,對頭和隊尾重合使得處理變得麻煩,所以引入兩個指針,front指針指向對頭元素,rear指針指向隊尾元素的下一個元素。這樣當front等於rear時,不是隊列中有一個元素,而是表示空隊列。
假設數組的長度為5,空隊列及初始狀態如左圖所示,front與rear指針都指向下標為0的位置。當隊列中有4個元素時,front指針不變,rear指針指向下標為4的位置。
此時出隊兩個元素,則front指針指向下標為2的位置,rear不變。再入隊一個元素,front指針不變,此時rear指針移動到數組之外。
假設這個隊列中的總個數不超過5個,但目前如果接着入隊的話,會導致數組越界的錯誤,但是隊列在下標為0和1的位置是沒有元素的。我們把這種現象叫做“假溢出”。
為了解決“假溢出”的問題,我們引入循環隊列。
(2)循環隊列
為了解決“假溢出”的辦法,就是隊后面滿了,再從頭開始,也就是頭尾相接的循環。我們把隊列的這種投喂相接的順序存儲結構稱為循環隊列。
繼續剛才的例子,將rear指針指向下標為0的位置,就不會導致rear指針指向不明的問題。
接着入隊兩個元素,會發現rear指針與front重合了。
此時問題又來了,剛才說了,當rear等於front時,表示是空隊列,現在當隊列滿時,rear也等於front。那么如何判斷隊列到底是空的還是滿的了?
解決辦法為:當隊列空時,判斷條件就是rear=front, 當隊列滿時,我們修改其判斷條件,保留一個元素空閑。也就是說,隊列滿時,數組中還有一個空閑單元。以下兩種情況,我們都認為隊列已經滿了。
由於rear可能比front大,也可能比front小,所以假設隊列的最大尺寸為QueueSize, 隊列滿的判斷條件改為(rear + 1)%QueueSize = front. 隊列的長度為(rear - front + QueueSize)% QueueSize.
循環隊列的順序存儲結構為
typedef int QElemType; typedef struct { QElemType data[MAXSIZE]; int rear; //頭指針 int front; //尾指針,若隊列不為空,指向隊尾元素的下一個元素 }SqQueue;
循環隊列的初始化
//初始化一個空隊列 Status InitQueue(SqQueue *Q) { Q->rear = 0; Q->front = 0; return OK; }
循環隊列求當前隊列的長度
//返回Q的元素個數,也就是隊列的當前長度 int QueueLength(SqQueue Q) { return (Q.rear - Q.front + MAXSIZE) % MAXSIZE; }
循環隊列的入隊操作
//若隊列未滿,插入元素e為新的隊尾元素 Status insertQueue(SqQueue *Q, QElemType e) { if ((Q->rear + 1) % MAXSIZE == Q->front) { //隊滿 return ERROR; } Q->data[Q->rear] = e; Q->rear = (Q->rear + 1) % MAXSIZE; return OK; }
循環隊列的出隊操作
//若隊列不為空,刪除Q的對頭元素,並用e返回 Status deleteQueue(SqQueue *Q, QElemType *e) { if (Q->rear == Q->front) { //隊空 return ERROR; } *e = Q->data[Q->front]; Q->front = (Q->front + 1) % MAXSIZE return OK; }