【程序人生】數據結構雜記(三)
說在前面
個人讀書筆記
棧
棧(stack)是存放數據對象的一種特殊容器,其中的數據元素按線性的邏輯次序排列,故也可定義首、末元素。不過,儘管棧結構也支持對象的插入和刪除操作,但其操作的範圍僅限於棧的某一特定端。也就是說,若約定新的元素只能從某一端插入其中,則反過來也只能從這一端刪除已有的元素。禁止操作的另一端,稱作盲端。
棧中元素接受操作的次序必然始終遵循所謂“後進先出”(last-in-first-out, LIFO)的規律:
從棧結構的整個生命期來看,更晚(早)出棧的元素,應爲更早(晚)入棧者;反之,更晚(早)入棧者應更早(晚)出棧。
棧中可操作的一端更多地稱作棧頂(stack top),而另一無法直接操作的盲端則更多地稱作棧底(stack bottom)。
棧的典型應用——逆序輸出
在棧所擅長解決的典型問題中,有一類具有以下共同特徵:
- 首先,雖有明確的算法,但其解答卻以線性序列的形式給出;
- 其次,無論是遞歸還是迭代實現,該序列都是依逆序計算輸出的;
- 最後,輸入和輸出規模不確定,難以事先確定盛放輸出數據的容器大小。因其特有的“後進先出”特性及其在容量方面的自適應性,使用棧來解決此類問題可謂恰到好處。
示例:進制轉換
遞歸實現:
迭代實現:
棧的典型應用—— 遞歸嵌套
示例:括號匹配
實際上,只要將push、pop操作分別與左、右括號相對應,則長度爲n的棧混洗,必然與由n對括號組成的合法表達式彼此對應。比如,棧混洗{ 3, 2, 4, 1 }對應於表達式"( ( ( ) ) ( ) )"。按照這一理解,藉助棧結構,只需掃描一趟表達式,即可在線性時間內,判定其中的括號是否匹配。
實例:
算法:
棧的典型應用——逆波蘭表達式
逆波蘭表達式(reverse Polish notation,,RPN)是數學表達式的一種,其語法規則可概括爲:
操作符緊鄰於對應的(最後一個)操作數之後。比如即通常習慣的。
按此規則,可遞歸地得到更復雜的表達式,比如RPN表達式
1 2 + 3 4 ^ *
即對應於常規的表達式
( 1 + 2 ) * 3 ^ 4
波蘭表達式求值算法:
示例:
試探回溯法
八皇后問題
如圖,國際象棋中皇后的勢力範圍覆蓋其所在的水平線、垂直線以及兩條對角線。現考查如下問題:
在的棋盤上放置個皇后,如何使得她們彼此互不攻擊——此時稱她們構成一個可行的棋局。
對於任何整數,這就是皇后問題。
由鴿巢原理可知,在行列的棋盤上至多隻能放置個皇后。反之,個皇后在棋盤上的可行棋局通常也存在。
皇后類:
既然每行能且僅能放置一個皇后,故不妨首先將各皇后分配至每一行。然後,從空棋盤開始,逐個嘗試着將她們放置到無衝突的某列。每放置好一個皇后,才繼續試探下一個。若當前皇后在任何列都會造成衝突,則後續皇后的試探都必將是徒勞的,故此時應該回溯到上一皇后。
示例:
迷宮尋徑問題
間區域限定爲由個方格組成的迷宮,除了四周的圍牆,還有分佈其間的若干障礙物;只能水平或垂直移動。我們的任務是,在任意指定的起始格點與目標格點之間,找出一條通路(如果的確存在)。
格點是迷宮的基本組成單位,故首先需要實現Cell類
可見,除了記錄其位置座標外,格點還需記錄其所處的狀態。共有四種可能的狀態:
原始可用的(AVAILABLE)、在當前路徑上的(ROUTE)、所有方向均嘗試失敗後回溯過的(BACKTRACKED)、不可穿越的(WALL)。屬於當前路徑的格點,,需記錄其前驅和後繼格點的方向。
既然只有上、下、左、右四個連通方向,故以EAST、SOUTH、WEST和NORTH區分。
特別地,因尚未搜索到而仍處於初始 AVAILABLE狀態的格點,鄰格的方向都是未知的(UNKNOWN);經過回溯後處於BACKTRACKED狀態的格點,與鄰格之間的連通關係均已關閉,故標記爲NO_WAY。
在路徑試探過程中需反覆確定當前位置的相鄰格點
在確認某一相鄰格點可用之後,算法將朝對應的方向向前試探一步,同時路徑延長一個單元。
基於試探回溯策略實現尋徑算法:
從算法的中間過程及最終結果都可清晰地看出,這裏用以記錄通路的棧結構的確相當於忒修斯手中的線繩,它確保了算法可沿着正確地方向回溯。另外,這裏給所有回溯格點所做的狀態標記則等效於用粉筆做的記號,正是這些標記確保了格點不致被重複搜索,從而有效地避免了沿環路的死循環現象。
隊列
與棧一樣,隊列(queue)也是存放數據對象的一種容器,其中的數據對象也按線性的邏輯次序排列。隊列結構同樣支持對象的插入和刪除,但兩種操作的範圍分別被限制於隊列的兩端——若約定新對象只能從某一端插入其中,則只能從另一端刪除已有的元素。允許取出元素的一端稱作隊頭(front),而允許插入元素的另一端稱作隊尾(rear)。
由以上的約定和限制不難看出,與棧結構恰好相反,隊列中各對象的操作次序遵循所謂先進先出(first-in-first-out, FIFO)的規律:
更早(晚)出隊的元素應爲更早(晚)入隊者,反之,更早(晚)入隊者應更早(晚)出隊。
隊列應用——循環分配器
爲在客戶(client)羣體中共享的某一資源(比如多個應用程序共享同一CPU),一套公平且高效的分配規則必不可少,而隊列結構則非常適於定義和實現這樣的一套分配規則。
隊列應用——銀行服務模擬
結語
如果您有修改意見或問題,歡迎留言或者通過郵箱和我聯繫。
手打很辛苦,如果我的文章對您有幫助,轉載請註明出處。