《數據結構與算法分析》5000字縮寫(下)

《數據結構與算法分析》5000字縮寫(下)

     有一個女人的男人很幸福。事實上,這是片面的。應該說,有不止一個女人的男人更幸福。但是,這樣會壞了我的人品,而且被女的知道了也不好。兩個耍得好的女人話很多,祕密在女人中傳得很快。於是,我打算不同時和兩個耍得好的女的耍朋友。後來我意識到,這樣也不行。女人太無敵了,即使A與B耍得好,B與C耍得好,A和C的消息也是互通的。哪怕只有一個朋友關係也能把兩羣人聯繫在一起。我不得不改變策略,使得我的女朋友之間沒有任何渠道傳遞信息。也就是說,在上面的A、B、C三個人中,雖然A和C沒有直接的聯繫,但我也不能同時和A、C耍。不久後,我想知道,某兩個女人是否可以通過某條“朋友鏈”傳遞信息。這就是所謂的等價關係——基本上算是判斷一個無向圖的連通性。就像很多個集合,每次選兩個併成一個,而且我們隨時想知道某兩個元素經過前面的合併後是否在同一個集合內。怎麼辦呢?後來有一天,我發現那些小女生喜歡玩些認親戚的遊戲,什麼誰是誰媽,誰是誰姐,誰是誰女兒之類的(不知道爲什麼這些瘋女人喜歡搞這些)。我突然恍然大悟,我的問題可以用樹結構來完成。親戚的親戚還是親戚,但有一點總相同:所有親戚的始祖總是一樣的。始祖一樣的都是一夥的。因此,把兩個集合並在一起,只要讓其中一個集合的根成爲另一個集合中的某個元素的一個兒子就行了,這種家譜關係的改變將使前面的集合中所有的元素擁有和後面那個集合一樣的鼻祖,而這將成爲這些元素的“標誌”。這個想法的靈感是來自女人世界的,因此女人還是有一定的作用。
    這就叫並查集,又叫不相交集。它可以合併兩個集合並且查詢兩個元素是否在同一集合。我們有一個很有效的剪枝:遞歸時順便把路上經過的祖祖輩輩全部變成根的兒子。這樣的程序只用2行來解決。
程序代碼 程序代碼
function find_set(x:integer):integer;
   begin
   if x<>p[x] then p[x]:=find_set(p[x]);
   exit(p[x]);
end;

    p[x]表示元素x的父親的位置。一開始,p[x]都等於x自己,表示自己一個人是一個集合。函數find_set(x)將返回x所在集合(一棵樹)的根。
    並查集還有些其它的剪枝和一些很複雜的效率分析問題,這裏不多說了。

    寫到這裏,《數據結構與算法分析》中的幾個大塊內容算是說清楚了。由於本文的敘述調整了原書各章節的順序且至此還沒有涉及書裏的一些小問題,因此這裏想把遺漏下的一些小東西提一下。
    有一些樹結構可能要求同時滿足多個要求。比如一個簡單的問題:如果要求構造一個堆使得既能查找最小元素又能查找最大元素怎麼辦?這時,我們可以用一個特殊的方法來實現:樹的單數層滿足一種性質,樹的雙數層滿足另一種性質。我們用一個叫做最小-最大堆的東西來實現前面說的問題。這個堆的雙數層的數據小於它爸大於它爸的爸,單數層的數據反過來,大於它爸小於它爸的爸。用類似的方法,我們還可以設計一個二叉查找樹,使得它能夠支持含有2種不同類型元素的數據。在單數層按其中一種操作,在雙數層按另一種操作,這樣可以方便的查找同時位於兩個不同類元素的指定區間內的數據。這種二叉查找樹叫做2-d樹。擴展2-d 樹,我們可以得到k-d樹。這些數據結構的具體實現方法這裏不說了,書上本來也是作爲一個習題介紹的。
    書裏的第7章花了近50頁介紹並分析各種排序算法,分析得很全。其中第11節花了10頁介紹外部排序。所謂外部排序,就是說怎樣快速地把一個根本無法全部讀入內存的大文件進行排序。很多排序之所以可行是因爲它們可以隨意讀寫任意一個指定的數。但在大文件裏,我們無法實現“第1234567890個元素和第 123個元素交換位置”,更無法實現遞歸之類的操作,而只能像磁帶一樣“過一遍”,從頭到尾掃一遍,由於文件太大內存不能接受,因此必須要讀一截扔一截。於是,外部排序產生了。不要以爲這個限制會把排序速度拖得很慢。事實上,外部排序同樣突破了O(n^2)的界限。它藉助了歸併排序中的“合併兩個已經有序的數組”的思想,因爲這個操作可以邊讀就邊做。把文件先拆成兩個文件,再把每個文件處理成一段一段的等長有序序列(一段多大取決於內存能一次處理多大),然後不斷從兩個文件中各取一段出來合併。可以看到,每段有序序列的長度變長了,變成了2倍長。過不了幾次,這個長度將變成文件的總長。注意,我們必須要讓每次合併時爲下次合併做好準備(就是說合並後的結果仍然要是兩個分了段的文件)。一個好的方法是將合併的結果交替存在兩個不同的新文件中。
    第9章講圖論算法。講了圖的遍歷(廣搜和深搜)、AOV、AOE、Dijkstra、網絡流、Prim、Kruskal和NP問題。在講深搜時,我學到了兩個新東西,用線性時間查找割點(去掉了的話圖就不連通了的點)和強分支(有向圖中的一個分支滿足其中任兩個點之間都可以互相到達)。後來發現黑書上也有,又覺得這個東西很不好說,因此這裏不想說了。說到了黑書還想順便補一句:黑書真的看不得——太多錯誤了。不是說LRJ怎麼了,LRJ在真正的大問題上有他的思想和經驗,但很多細節的概念他也是昏的,這不利於初學者接受知識。不信哪天我還要寫一篇日誌糾正黑書的錯誤。引用政治書上抨擊“人性自私論”的經典語言:“從理論到實踐都是錯的”。
    第10章講“算法設計技巧”,大概是些貪心啊,分治啊,動規啊,回溯啊,隨機化啊之類的。調度問題、Huffman樹、裝箱問題近似算法、最近點距分治算法、最優二叉查找樹、Floyd-Warshall、跳躍表、Miller-Rabin素性測試、博弈算法等都在這章中有講,並且講得相當好。由於這不是本書的重點內容,這裏也不說了。
    第11章整章都在講攤還分析。這是一個相當複雜的問題,是分析時間複雜度的一個有力工具。它的分析告訴我們的不是某一個操作的複雜度,而是重複執行某一個操作的平均複雜度。研究這個是很有必要的,因爲我們會遇到一些“越變越慢”的退化情形和“自我保持不變”的自調整性等數據結構,單個操作並不能反映它真正的效率。

    到這裏,這本書的所有東西都已經介紹完了。總的來說,這本書很值得一看(雖然有些地方翻譯得很差)。它的理論性很強,證明過程完整(再複雜的分析它也證明得很清楚,滿足那些刨根問底的人);整本書自成一個體系,前後呼應;習題具有研究性,與課文互相補充。事實上,這些都是國外教材共有的特點。這算是我完整讀過的第一本國外教材,今後我還會讀一些。這幾天在看《組合數學》(仍然是這個出版社出版的),看完後也打算寫一下“對《組合數學》一書中部分內容的形象理解”。讀一本國外教材,你會發現它與國內書籍的不同並會從中獲益更多。

    這篇文章就寫到這裏了。號稱是一個5000字縮寫,沒想到寫着寫着已經超過8000字了。而且,這個並不是縮寫,而是一些簡單的、系統的、清晰的、形象化的思想和理解。這篇文章或許對已經知道一些有關知識的人有用,但不適合一點也沒有接觸過數據結構與算法分析的人。如果有一個人能從中收穫一件東西,我寫這個的目的也就達到了。

(完)

Matrix67原創
做人要厚道 轉帖請註明出處 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章