棧和隊列
棧和隊列本身作為特殊的線性表,要記住他倆本身就費勁。難受的是他倆還能分別考慮順序結構和鏈式結構,很復雜,容易混淆。
其實比起FILO(先進后出)和FIFO(先進先出)等特點,更重要的是對指針的把握。進出順序是在邏輯層面的,只要理解就行,難得是如何用指針來表示這種特點,於是我就此方面進行個總結。
順序棧
雖然棧只需要對一段進行操作(棧頂),但我們除了棧頂指針(top)外,還需要設置棧底指針(base),主要是為了用於判斷棧空棧滿。
s.top == s.base; //當棧頂指針等於棧底指針時,棧為空
s.top - s.base == MAXSIZE; //當棧頂指針與棧頂指針的差為棧的大小時,棧滿
由於棧頂指針永遠指向棧頂元素的上面一個元素,所以棧頂指針並不指向棧頂元素,大部分時候是指向一個空的元素。當棧滿時,棧頂指針將指向棧外。
要注意的是,入棧時先賦值再讓棧頂指針加一;而出棧則相反,先讓棧頂指針減一,再賦值。
括號匹配的問題是一個典型的棧問題,於是就對其進行了思路整理:括號匹配
鏈棧
由於棧只需要對一端(棧頂)進行操作,所以不需要設置頭結點。
於是只需要一個頭指針指向首元結點,然后對頭指針進行操作即可,注意delete。
順序隊列(循環隊列)
不同於棧,隊列需要對兩頭進行操作,因此頭尾指針不可缺。但這里的指針並不是指針變量,而是整形。循環隊列用下標表示每個元素,因此這里的指針指的是對下標的記錄。這里仍用“指針指向”表示“下標表示”。
由於FIFO的限制,入隊會持續朝隊列的頂端增進,而出隊則導致隊列的尾端空缺,而又不能將其空間重新利用。於是順序棧會出現“假溢出”情況,即雖然棧的空間沒滿,但會導致非法訪問。
因此采取循環隊列的方式,但此時我們要額外空出一個空間作為標志空間。
q.rear = (q.rear + 1) % MAXSIZE; //取余后(q.rear+1)的值為0~MAXSIZE-1。當q.rear大於最大空間時,會回到隊頭。
q.front == q.rear; //隊列空依然是對頭等於隊尾
(q.rear + 1) % MAXSIZE == q.front; //滿足此條件隊列為滿。取余后(q.rear+1)的值為0~MAXSIZE-1
標志空間
標志空間的設置實際上是為了讓隊列的各種狀態能有唯一的表示。如果沒有標志空間,當隊列為滿時,隊頭指針和隊尾指針指向同一個元素(最后入隊的元素);而隊列為空時,隊頭指針和隊尾指針指向同一個元素(空元素)。這兩種狀態的表示都是隊頭指針等於隊尾指針。
而有了標志空間后,表示空隊列仍是隊頭指針等於隊尾指針;但是隊滿時,q.rear指向標志空間,而q.front指向q.rear上一個元素(因為如果沒有標志空間,隊列能在入一個元素,q.front+1后與q.rear指向相同的元素)。因此只要用上述方法就能判斷是否棧滿,保證了狀態的唯一表示。
雖然有一個空間不能被使用,但是比起前面的“假溢出”情況要好多啦。
於是,判斷隊滿的(q.rear+1)就可以理解了。此時q.rear指向標志空間,而q.front在它上一個,加一后正好與q.front相等。
鏈隊
兩頭操作的隊列少不了頭尾指針。
但是由於指針只能指向下一個元素,因此我們將鏈尾作為隊頭,鏈頭作為隊尾。入隊時只用將新的結點指向鏈尾,而出隊時只用將隊尾的指針指向下一個元素后刪除最有一個結點。
至於要不要頭結點呢?
在刪除元素中,不管有沒有頭結點,如果被刪除的是最后一個元素,都要進行額外的操作。如果沒有頭結點,刪除最后一個結點導致頭指針和尾指針都變為野指針,因此要為他們額外賦空;如果有頭結點,頭指針一直指向頭結點,但是尾指針會變成野指針,因此需要將尾指針額外賦空。沒有哪種情況特別優越。
但是如果考慮狀態表示的唯一性,顯然是需要頭結點的。
如果沒有頭結點,空隊的表示是頭指針與尾指針均指空,有一個元素的隊列的表示是頭指針和尾指針都指向那一個元素,兩者都是頭指針等於尾指針;有頭節點,空對的表示為頭指針等於尾指針,有一個元素的隊列表示為,頭指針指向頭結點尾指針指向那一個元素。
結語
其實這一章的學習實踐時間相比之前有些減少,因此沒有把所有四種結構的所有功能都實現一邊。但是有進行實踐的結構,如順序棧、鏈隊(因為PTA上有題目不得不打),都會有個更好的理解。因此之后希望能將各種結構的各種功能都通過代碼實現一遍。