第三章:棧和隊列
上篇文章中我們講了 學習數據結構–第三章:棧和隊列(棧的基本操作) 下面講解隊列的基本操作。
1.隊列的基本概念
隊列(Queue) 只允許在表的 一端(隊尾) 進行插入,表的 另一端(對頭) 進行刪除操作的 線性表。
在隊列中先進入隊列的元素會先出隊列即:先進先出 (FIFO)
2.隊列的基本操作
InitQueue(&Q)
初始化隊列,構造一個空隊列QQueueEmpty(Q)
判隊列空,若隊列Q爲空返回true,否則返回 falseEnQueue(&Q,x)
入隊,若隊列Q未滿,則將x加入使之成爲新的隊尾。DeQueue(&q,&x)
出隊,若隊列Q非空,則刪除隊頭元素,並用x返回。GetHead(Q,&x)
讀隊頭元素,若隊列Q非空則用x返回隊頭元素。ClearQueue(&Q)
銷燬隊列,並釋放隊列Q佔用的內存空間。
3.隊列順序存儲結構
順序隊 採用順序存儲的隊列。
在上篇棧的文章中我們直到,棧使用一個top變量存儲棧頂的下標,用來出棧和入棧,在隊列中是否也可以使用一個top變量存儲隊頭的變量,然後操作棧呢?
答案是不行的
,注意棧的出棧和入棧都只在一端進行操作,所以一個變量存儲下標完全夠了,但是隊列不同,隊列的入隊和出隊分別在兩端,所以我們需要兩個變量分別存儲入隊地址(front
)和出隊地址(rear
),注意:我們規定rear
存儲隊尾元素的下一個位置。
隊列的定義:
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int front,rear;
}SqQueue;
隊列中需要注意:
front
指向隊頭元素,rear
指向隊尾元素的下一位置(或front
指向隊頭元素的前一位置,rear
指向隊尾元素)- 平時使用中我們一般讓
front
指向隊頭元素,rear
指向隊尾元素的下一位置 - 初始時
front==rear==0
3.1順序隊列判空&長度&滿
判空
隊空條件:Q.front == Q.rear == 0
???
這個判斷條件是不對的,如果入隊一個元素,接着出隊,此時rear
和front
都是會移動的,此時不是0
但是隊列也是空的。所以說,上述條件是:不是充分必要條件
,真正的條件是:Q.front == Q.rear
長度
front
指向隊頭元素,rear
指向隊尾元素的下一位置,故隊列的長度是:Q.rear - Q.front
隊滿
隊滿條件 Q.rear ==MaxSize
???
當隊列滿的時候,此時進行出隊操作,此時出隊的時候只是修改了front
,並未修改rear
,如果按照上述條件判斷,隊列是滿的,但是實際上是空的,這就是假溢出問題
.
那麼怎麼解決這個問題,同時利用上剛剛出隊的那個空間呢?我們可以使用一個特殊隊列-----循環隊列
4.順序存儲的循環隊列
循環隊列 把存儲隊列的順序隊列在邏輯上視爲一個環。
改成循環隊列,將最後一個數據元素單元和第一個數據元素單元連接起來,方法就是取餘(%MaxSize)操作
front指針移動
Q.front=(Q.front + 1) % MaxSize
rear指針移動
Q.rear = (Q.rear + 1) % MaxSize
隊列長度
(Q.rear + MaxSize - Q.front) % MaxSize
4.1循環隊列判空&滿
隊列判空:Q.front == Q.rear
隊列判滿:Q.front == Q.rear
此時發現判空和判滿條件一致了,這就出現矛盾
了。怎麼解決呢?
方法一:犧牲一個存儲單元
這是最常用的一種方法,具體就是,最後一個元素的位置不存儲元素,當Q.front=(Q.rear+1)%MaxSize
的時候,表示隊列已經滿了。這種方法
- 隊列判空條件不變:
Q.front == Q.rear
- 隊列判滿條件變成:
Q.front=(Q.rear+1)%MaxSize
方法二:增加一個變量代表元素的個數
我們初始化一個變量:Q.size=0
。這種方法
- 判空條件:
Q.size == 0
- 判滿條件:
Q.size == MaxSize
方法三:增加tag標識
我們發現隊列爲空是因爲刪除操作導致,隊列爲滿是因爲插入操作導致,所以我們可以使用一個變量來標識隊列當前的狀態,當隊列爲空的時候對於循環隊列首先Q.front==Q.rear
,此時我們標識tag=0
,當隊列滿的時候首先Q.front==Q.rear
,我們標識tag=1
。這種方法
- 判空條件:
Q.front==Q.rear&&Q.tag == 0
- 判滿條件:
Q.front==Q.rear&&Q.tag == 1
4.2循環隊列的基本操作
初始化
void Initqueue(SqQueue &Q){
Q.rear = Q.front = 0;
}
判空
bool isEmpty(SqQueue Q){
if(Q.rear == front) {
return true;
}else{
return false;
}
入隊
bool EnQueue(SqQueue &Q, ElemType x){
if((Q.rear+1)%MaxSize==Q.front){//判滿
return false;
}
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1)%MaxSize;
return true;
}
出隊
bool DeQueue(SqQueue &Q,ElemType &x){
if(Q.rear == Q.front){//判空
return false;
}
x = Q.data[Q.front];
Q.front = (Q.front+1)%MaxSize; //隊頭指針向後移動一位
return true;
}
5.隊列鏈式存儲結構
鏈隊 採用鏈式存儲的隊列
下面是有頭節點的鏈隊,使用front指針指向隊頭,使用rear指針指向隊尾。
隊列中每個結點的結構體
typedef struct{
ElemType data;
struct LinkNode *next;
}LinkNode;
鏈隊的結構體也就是隊頭和隊尾的兩個指針
typedef struct{
LinkNode *front,*rear;
}LinkQueue;
6.鏈隊的基本操作
初始化
判空
入隊
入隊就相當於單鏈表的尾插法
出隊
出隊相當於單鏈表的刪除頭結點的操作
注意:最後一個
if
判斷就是,如果當前隊列只有一個元素,我們刪除最後一個元素後需要將rear
指針指向頭節點。
7.輸出序列
輸入和輸出連續的情況下
棧
- 輸入序列:1,2,3…n
- 輸出序列:n…3,2,1
隊列
- 輸入序列:1,2,3…n
- 輸出序列:1,2,3…n
對於隊列無論輸入序列是否連續,輸出序列一定是一樣的,都是輸入順序。
棧 輸入和輸出非連續的情況下
- 輸入序列:1,2,3
- 輸出序列:3 2 1 、2 1 3 、2 3 1 、1 2 3 、1 3 2
輸出序列總共是上面五種加3 1 2 六種(按照排列組合),其中3 1 2不合法,按照這種輸出序列則入棧爲一次性將1 2 3全部入棧,此時出棧序列必爲3 2 1.
例子:
其中橙色標識出的序列都是不合法的,我們發現:出棧序列中每一個元素後面所有比它小的元素組成一個遞減序列
,這樣的序列纔是合法的序列
。
比如:3142 這個不和法的序列,3後面比他小的1、2組成了遞增序列。
合法出 棧 序列的個數
進棧序列:1,2,3…n
f(n)=C(2n,n)/(n+1)
求算合法出棧序列的個數公式
8.隊列雙端序列
雙端隊列 允許兩端都可以進行入隊以及出隊操作的隊列
無論哪一端先出的元素在前,後出的元素在序列後。
如果我們把某一端的插入與刪除操作屏蔽,就構成了一個棧。
如果我們把一端的刪除屏蔽,一端的插入屏蔽,就構成了一個隊列。
8.受限的雙端隊列
輸出受限的雙端隊列
輸入受限的雙端隊列
下面我們找受限的雙端隊列的輸出序列
輸入序列:1,2,3,4
輸出受限的雙端隊列
對於上面的受限的雙端隊列,棧的判斷輸出序列完全適用,其中不合法序列有:
如果隊列改成下面的樣式我們測試上面的不合法序列可以得到,4231
和 4132
不是合法的序列。
輸入受限的雙端隊列
對於上面的受限的雙端隊列,棧的判斷輸出序列完全適用,其中不合法序列有:
如果隊列改成下面的樣式我們測試上面的不合法序列可以得到,4213
和 4231
不是合法的序列。