資料結構:四 棧和佇列
阿新 • • 發佈:2020-09-13
第四章:棧和佇列
1. 棧的定義
棧(stack):是限定僅在表尾進行插入和刪除操作的線性表
棧頂(top)允許插入和刪除的一端稱為棧頂;另一端稱為棧底(bottom)
空棧:不包含任何資料元素的棧
棧又被稱為後進先出(Last In First Out)的線性表,簡稱 LIFO 結構
棧的插入操作,叫做進棧,也稱壓棧,入棧
棧的刪除操作,叫做出棧,也有的叫作彈棧
2. 棧的抽象資料型別
ADT 棧(stack) Data 同線性表。元素具有相同的型別,相鄰元素具有前驅和後堆關係。 Operation InitStack ( *S ):初始化操作.建立一個空棧S。 DestroyStack (*S ):若棧存在,則銷燬它。 ClearStack (*S):將棧清空。 StackEmpty ( S ):若棧為空,返回true,否則返回 false。 GetTop (S,*e):若棧存在且非空,用e返回S的棧頂元素。 Push (*S,e):若棧S存在,插入新元素e到棧S中併成為棧頂元素。 Pop (*S,*e):刪除棧S中棧頂元素,並用e返回其值。 StackLength (S):返回回棧S的元素個數。 endADT
3. 棧的順序儲存結構及實現
定義一個 top 變數來指示棧頂元素在陣列中的位置
進棧操作
- 棧頂指標加一
- 將新插入元素賦值給棧頂空間
出棧操作
- 將要刪除的棧頂元素賦值給 e
- 棧頂指標減一
進棧和出棧都未涉及迴圈語句,時間複雜度為 O(1)
4. 兩棧共享空間
陣列有兩個端點,兩個棧有兩個棧底,讓一個棧的棧底為陣列的始端,即下標為 0 處,另一個棧為棧的末端,即下標為陣列長度 n-1 處
這樣,兩個棧如果增加元素,就使兩端點向中間延伸
關鍵思路
- 它們是在陣列的兩端,向中間靠攏
- top1 和 top2 是棧 1 和棧 2 的棧頂指標
5. 棧的鏈式儲存結構及實現
儲存結構
- 把棧頂放在單鏈表的頭部,已經有了棧頂在頭部了,單鏈表中常用的頭結點就失去了意義
- 通常對於鏈棧來說,是不需要頭結點的
- 對於空棧來說,連結串列原定義是頭指標指向空,那麼鏈棧的空其實就是 top=NULL 的時候
進棧操作
- 假設元素值為 e 的新結點是 s,top 為棧頂指標
- 插入元素 e 為新的棧頂元素
- 把當前的棧頂元素賦值給新結點的直接後繼,如圖中 1
- 將新的結點 s 賦值給棧頂指標,如圖中 2
出棧操作
- 假設變數 p 用來儲存刪除的棧頂結點,將棧頂執政下移一位,最後釋放 p 即可
- 將棧頂結點賦值給 p ,如圖 3
- 使得棧頂指標下移一位,指向後一節點,如圖 4
- 釋放結點 p
鏈棧的進棧 push 和出棧 pop,時間複雜度均為 O(1)
順序棧,需先確定一個固定的長度,可能會存在記憶體空間浪費問題;優勢是存取時定位方便
鏈棧,要求每個元素都有指標域,這同時也增加了一些記憶體開銷,但對於棧的長度無限制
選擇
- 如果棧的使用過程中元素變化不可預料,有時很小,有時非常大,那麼最好是用鏈棧
- 反之,如果他的變化在可控範圍內,建議使用順序棧會更好一些
6. 棧的作用
棧的引用簡化了程式設計的問題,劃分了不同關注層次,使得思考範圍縮小,更加聚焦於我們要解決的問題核心
7. 棧的應用——遞迴
遞迴定義
-
一個直接呼叫自己或通過一系列的呼叫語句間接地呼叫自己的函式
-
每個遞迴定義必須至少有一個條件,滿足時遞迴不再進行,即不再引用自身而是返回值退出
-
迭代和遞迴的區別
- 迭代使用的時迴圈結構,不需要反覆呼叫函式和佔用額外的記憶體
- 遞迴能使程式更清晰,更簡潔,更易理解,從而減少讀懂程式碼的時間
- 大量的遞迴呼叫會建立函式的副本,會耗費大量的時間和記憶體
8. 棧的應用——四則運算表示式求值
字尾(逆波蘭)表示法定義
- 一種不需要括號的字尾表示法
- 所有的符號都是在要運算數字的後面出現
- ”9+(3-1)*3+10/2“ 的字尾表示式:”9 3 1 - 3 * + 10 2 / +“
字尾表示式計算結果
-
字尾表示式:9 3 1 - 3 * + 10 2 / +
-
規則
- 從左到右遍歷表示式的每個數字和符號,遇到數字就進棧;遇到符號,就將處於棧頂兩個數字出棧,進行運算,運算結果進棧,一直到最終獲得結果
中綴表示式轉字尾表示式
-
”9+(3-1)*3+10/2“,平時所用的這種標準四則運算表示式,叫做中綴表示式
-
中綴轉字尾規則
- 從左到右遍歷中綴表示式的每個數字和符號,若是數字就輸出,即成為字尾表示式的一部分;
- 若是符號,則判斷其與棧頂符號的優先順序,是右括號或優先順序低於棧頂符號(乘除優先於加減)則棧頂元素依次出棧並輸出,並將當前符號進棧,一直到最終輸出字尾表示式為止
-
中綴轉化為字尾,棧用來進出運算的符號
-
字尾轉化為中綴,棧用來進出運算的數字
9. 佇列的定義
佇列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表
佇列是一種先進先出(First In First Out)的線性表,簡稱FIFO
允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭
10. 佇列的抽象資料型別
ADT 佇列(Queue) Data 同線性表。元素具有相同的型別,相鄰元素具有前驅和後繼關係。 Operation InitQueue(*Q):初始化操作,建立一個空佇列Q。 DestroyQueue(*Q):若佇列Q存在,則銷毀它。 ClearQueue(*Q):將佇列 Q 清空。 QueueEmpty(Q):若佇列Q為空,送回true,否則退回false。 GetHead(Q, *e):若佇列Q存在且非空,用e返因佇列Q的隊頭元素。 EnQueue(*Q,e):若佇列Q存在,插入新元素e到佇列Q中併成為隊尾元素。 DeQueue(*Q, *e):刪除佇列Q中隊頭元素,並用e返回其值。 QueueLength(Q):送回佇列Q的元素個教。 endADT
11. 迴圈佇列
不足
- 為了避免當只有一個元素時,隊頭和隊尾重合使處理變得麻煩,所以引入兩個指標
- front 指標指向隊頭元素,rear 指標指向隊尾元素的下一個位置
- 當 front 等於 rear 時,此佇列不是還剩一個元素,而是空佇列
定義
- 我們把佇列的頭尾相接的順序儲存結構稱為迴圈佇列
問題:當 front 等於 rear 時,如何判斷此時佇列是空還是滿?
-
辦法一
- 設定一個標誌變數 flag,當 front == rear,且 flag = 0時為佇列空,當 front == rear,且 flag = 1 時為佇列滿
-
辦法二
- 當佇列孔時,條件就是 front = rear,當佇列滿時,我們修改其條件,保留一個元素空間。
- 也就是說,佇列滿時,陣列中還有一個空閒單元
- 若佇列的最大長度為 QueueSize ,佇列滿的條件是:(reat + 1) % QueueSize == front
12. 佇列的鏈式儲存結構及實現
佇列的鏈式儲存結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已,簡稱鏈佇列
為了操作上的方便,我們將隊頭指標指向鏈佇列的頭結點
入隊操作
- 在連結串列尾部插入結點
- 把擁有元素 e 新結點 s 賦值給原隊尾結點的後繼,如圖 1
- 把當前的 s 設定為隊尾結點,rear 指向 s,如圖 2
出隊操作
- 出隊操作時,就是頭結點的後繼結點出隊,將頭結點的後繼改為它後面的結點
- 若連結串列除頭結點外只剩一個元素時,則需將 rear 指向頭結點