第四講---隊列
一、隊列
隊列簡稱隊,一種操作受限的線性表。只允許在一端(隊尾)插入,一端(隊頭)刪除(出去)。也就是先進先出的線性表。空隊列是指不含任何元素的空表。
舉個栗子
上次我們講到唐僧師徒五人春遊時候掉進了一口井裏,費了老大的勁幾個人纔出來。此時,唐僧以及半殘了。他們幾個準備找個村莊,讓師傅休息一下。他們就一路上沿着一條小溪走,林盡水源,便得一山,山有小口,彷彿若有光..初極狹,才通人。(他們看到了一個洞洞)。心想洞洞後面一定有人家,於是乎...這幫人又開始鑽洞了:
1.初極狹,才通人~不存在在洞裏面,後面那個人把前面那個人超了,或者是並排走的情況。
2.八戒要想出去,必須唐僧先出去,然後猴哥出去,八戒才能出去。
(先進先出)
二、循環隊列
傳統隊列的存儲方式一般會造成很大的空間浪費,比如我們申請了50個空間,剛開始時候隊頭是0,隊尾也是0,現在我們加入了40個人,隊頭是0,隊尾是39。現在讓着40個人全部出去,隊頭是39,隊尾是39。因爲加人是從隊尾加的,隊尾最大是49,所以只能在加10個人了。前面0~39這40個元素空間就全部浪費了。所以傳統的隊列一般是完全用不了的。
所以我們採用循環隊列,使得隊伍真的能達到其預先申請的最大人數。循環隊列完全可以理解爲一個鐘錶的形式。在最開始時候:
我們把整個表分成8個小格,理論上,我們最大可以存儲8個個體,但是我們一般存7個(後面會講到爲什麼)。
front爲隊頭,指向排在第一個的人;rear爲隊尾,指向排在最後一個人的下一位(這是規定,這樣的話後面一些操作會很方便)。
剛開始時候,隊列的頭front和尾rear都指向第0個格子,表示隊裏還沒有人。
現在唐僧加進來以後,整個循環隊列成了這個樣子:
所有人都加進來以後:
我們可以再加幾個人,方便說明隊滿時候的狀態:
假設我們一共加了8個人,這個時候,可以看到rear和front又同時指向了0號位置,這個狀態和我們之前隊空時候是一摸一樣的。那麼如何去檢測隊列是否滿了呢?你可以另外開闢一個變量,記錄當前隊列人數,通過檢測這個變量是否和最大人數相等,來檢測隊列是否滿了。但是我們一般不另外開闢,我們會用一個比較騷的操作去解決這個問題(身爲一個程序員,騷是第一位)。我們一般讓最大人數是MaxSize-1,這裏就是7,這樣的話:
我們只需要判斷(rear+1)%MaxSize == front就好了,而且只有隊滿時候,返回值纔會是true。
我這裏就只講判斷是否隊滿的操作,插入刪除時候指針變化可以看書上,講的很清楚,也比較簡單。和傳統隊列相比,無非就是增加了一個模運算。
三、循環隊列的順序存儲結構
1.老規矩,先說下結構體:
#define MaxSize 50 //最大棧的容量(井裏最多放50人)
typedef struct{
int data[MaxSize];
int front;//用來表示隊頭的位置
int rear;//用來表示隊尾的位置
}SqQueue;//一直沒有說這個,Sq表示的是線性表的順序表示Sequence
2.InitQueue(&Q):初始化隊列
void InitStack(SqQueue &Q){
Q.front = 0;
Q.rear = 0;
}
3.isEmpty(Q):判斷是否爲空
bool isEmpty(SqQueue Q){
if(Q.rear == Q.front)//隊頭等於隊尾當然是隊裏面沒人了
return true;
else
return false;
}
4.EnQueue(&Q,x):將x加入隊列中
bool EnQueue(SqQueue &Q, int x){
//1.檢查
if((Q.rear+1)%MaxSize == Q.front)//循環隊列判斷是否隊滿
return false;
//2.賦值
Q.data[Q.rear] = x;
//3.修改
Q.rear = (Q.rear+1)%MaxSize; //循環隊列下遞增的方式
//4.返回成功
return true;
}
5.DeQueue(&Q,&x):將隊首元素出隊並賦值給x
bool DeQueue(SqQueue &Q,int x){
//1.檢查
if(Q.rear==Q.front){
return false;
}
//2.賦值
x = Q.data[Q.front];
//3.修改
Q.front = (Q.front+1)%MaxSize;
return true;
}
6.isFull(Q)檢查是否隊滿:
bool isFull(SqQueue Q){
if((Q.rear+1)%MaxSize==Q.front){
return true;
}
else{
return false;
}
}
7.getSum(Q)
int getSum(SqQueue Q){
//這裏面有個騷操作 + MaxSize
//這樣可以保證%前面那一項始終爲正
//因爲我們有可能遇到Q.rear = 1,Q.front= 6的情況
//而 (a+b)%b == a%b
//這是取模運算裏面的一個定理
return (Q.rear - Q.front + MaxSize)%MaxSize;
}
四、鏈式隊列和雙端隊列
和鏈式棧一樣,鏈式隊列也不是考試的主要內容。所以不需要太多的瞭解,核心內容是頭指針(front)指向隊頭結點,尾指針(rear)指向隊尾結點(沒有頭結點,除非特殊說明)。
我們主要看雙端隊列。在循環隊列中,只能在隊頭出隊,隊尾入隊。但是雙端隊列就不一樣了,其兩端都可以出隊。爲了更好理解,我們就把雙端隊列的兩端叫做前端和後端。雙端隊列一般細分爲兩種(兩端都可以進出的隊列一般不考,因爲太簡單了),一種是輸出受限(倆插一刪,輸出受限,意思就是把出隊的那一個操作限制了,出隊的操作是刪除嘛,所以少一個刪除),一種是輸入受限(倆刪一插)。
書上對雙端隊列裏面,前段進入的元素排列在隊列中後端進的元素的前面....xxxx..xx...x。反正我是記不住這些東西~雙端隊列一般考的是選擇題,隊列元素一般不超過5個,我們完全可以一個一個試,就可以得到答案了。現在直接做一個題把:
五、習題