怎樣學演算法?


建議千萬不要一開始就看《演算法導論》,這本書有太多關於演算法的數學證明(如果你喜歡這種,那麼你就看這本)

我強烈推薦你看看這本:演算法(第4版) (豆瓣),作者是高德納的學生:塞奇威克 (Robert Sedgewick)

去年我在準備校招面試的時候偶然發現這本書,我越看越著迷,書中演算法代碼主要是用Java編寫,裡面有大量的圖來讓你明白例如:排序,查找,樹和圖的演算法運行過程。


這本書的目錄編排也很清晰,他就告訴你演算法主要就可以分為:排序,查找,圖和字元串。從這4個方面可以演化出很多演算法。


我覺得最關鍵是:這本書的作者不但是在告訴你what,而且告訴你why(分析各種演算法的優缺點)

----------------
補充一些我覺得這本書好的地方


比如講到快速排序,很多書可能講了快速排序的原理就完了。但這本書就直接講了原始的快速排序可以改進的地方:1. 在小數組上,切換到插入排序;2. 三取樣切分;3. 三向切分的快速排序。

優先隊列怎麼和排序演算法扯上關係呢?其實優先隊列就是可以用堆排序來實現,堆排序的時間複雜度和快速排序是一樣的,但是實際中為什麼堆排序的運行時間要比快速排序多呢?因為這和CPU的Cache命中率有關係,堆排序不符合演算法運行的局部性原則

還比如書中2.5節,講了排序演算法的實際用途。

這本書不光告訴你演算法的原理,還告訴你演算法的用途。


不請自來,不想系統地講如何學演算法,就隨便聊聊我自己學演算法的道路吧。

讀高中前,自己學過 VB 和 C 語言,並在初中一個市級的計算機應用競賽中拿過第一,考過微軟 ATC 證書和國二級,還自己親手創辦過一個口袋妖怪主題網站,自豪感爆棚,感覺自己是未來的比爾蓋茨。上了高中才知道有信息學奧林匹克競賽這玩意兒,也知道競賽班裡有同學是靠這個競賽初中組拿第一進的這所重高,頓時感覺自己比別人落後了一萬步。

到了我高一 NOIP 的時候,我來到了機房邊上的辦公室,和競賽的帶隊老師說:

「教練,我想學演算法。」

……

……

假的,我只是想報個名而已。

之後老師了解了一下我的情況,特別糗的一件事讓我至今記憶猶新,老師問我,「學過數據結構嗎?」我想了會兒說,「我用過數組啊鏈表什麼的。」然後老師接著問,「那圖論呢?」我沒明白老師說的是什麼,就問了「什麼圖?」老師說「就是畫圖的圖。」我彷彿突然恍然大悟了一下,正色道:「我在 VB 里畫過。」

於是我收到了一堆課件 PPT,交完報名費,黑著臉走出了辦公室。


回到家插上 U 盤,看到一個個陌生的名詞:「二叉樹」、「動態規劃」、「貪心」、「最短路」、「網路流」…… 我的演算法之路就算正式開始了。

看了一個周末的 PPT,一行代碼都沒寫,周一去的時候有一個 NOIP 賽前集訓,其實就是做歷年來的初賽卷。這個初賽卷里考的都還是比較基礎的東西,加之正好周末對什麼二叉樹的前中後序遍歷、棧和隊列的出入方式有了一點點了解後,好像拿了70多分,跟高二拿過一等獎的學長也就差了10來分嘛,頓感自己是祖國未來編程的希望。

高三有個已經在 NOI 中奪金保送的學長,開始每天為我們授課。因為我是中間插班進去的,所以錯過了前面不少基礎課。我記得第一節課聽的就是哈夫曼(Huffman)樹,對於一個只知道二叉樹遍歷的我來說,用現在的話講,那時的我就是黑人問號的好兄弟。

因為和學長並不熟,所以也沒辦法一下子就恬著臉讓人家給我單獨輔導。但我知道大家都在一個叫 USACO 的平台上做題,嗯,就是那個特別喜歡和奶牛過不去的平台。我也跟風註冊了賬號,這時候才知道信息學競賽的大概流程,在順利地獲得了幾個 Accepted 之後,心底里的好勝心一下子被激發出來了,我要刷完這個題庫!

很顯然,我馬上就被各種 WA、TLE、MLE 給卡住了,去網上搜題解,搜出了一些莫名其妙的名詞和充滿 magic number 的代碼(競賽代碼里的變數命名往往沒啥邏輯),我覺得我是時候鼓起勇氣,向學長大牛請教了。沒想到的是,學長大牛非常平易近人,不但教會了我怎麼解,還幫我羅列了相關知識點和平台上對應的題(記憶力驚人),這位學長在之後的 IOI 中總成績排名第一奪得金牌,保送清華,和樓教主比肩,應該有不少人猜到了,就是俞華程,yuhch。

就這樣,我在一個世界級頂級選手的指導下,堅強地在競賽圈裡活了下去,含淚。

USACO 馬上已經不能滿足我了,我跟著 yuhch 去征戰了 URAL,但我覺得 URAL 上題目太多太雜了,又跟風去做了一波 POJ,之後我在這裡有過一份關於 POJ 的整理:程序員必須掌握哪些演算法? - SimonS 的回答 - 知乎 我也買了《演算法導論》,買了劉汝佳的黑書,買了嚴蔚的《數據結構》,買了《Algoritms》…… 但這些書我也就囫圇吞棗地過了一遍,看書哪有刷題有趣啊。

在 OI 中,我從 NOIP 到 NOI,倒在了浙江省集訓隊的冬令營選拔賽里,含恨結束了這段競賽經歷去參加高考,結果高考也失利未能如願進入理想院府,但我並不後悔當初的選擇。

到了大學,無腦填報了計算機科學與技術,報到前就了解了自己大學的 ACM 情況,所以進校後就來到了 ACM 校隊參加了集訓。ACM 組隊的競賽形式讓我在心理上降低了一點壓力,但一個測試點不過就不給分的評分方式在當時的我看來有點無法理解,我意識到我需要更系統全面的學習演算法和數據結構,不能再像以前在 OI 中憑著一些小聰明在有限時間內去「騙」儘可能多的分數,就比如對有限測試數據打表,對所有情況離線枚舉計算,對答案隨機輸出或者輸出若干樣例等等。這時候我才重新拾起了《演算法導論》,看懂演算法原理,做到舉一反三。

什麼叫舉一反三?當你看完快速排序後,你就能給我一個時間複雜度為 O(N) 的求中位數的演算法。從我面試的同學來看,9成以上的同學能大概解釋清楚快速排序的過程,卻有一半的同學無法在我的指引下,說清楚求中位數的線性時間複雜度解。這就是我認為大部分同學,即使學了演算法,也無法真正將其應用起來的原因。

到這個時候,其實我已經認為我掌握了基礎的演算法和數據結構(掌握的定義就是要求能舉一反三),開始向更高級的方向前進。但競賽圈真的是一個很能讓我看清自己水平的地方,雖然省賽中拿金,但也在區域賽中打過鐵,告訴你努力也並不能帶來回報,天賦在這裡可能會顯得更重要。中二的時候我也做過門薩測試和一些山寨的智商測試(比如那個純圖形的IQ-Test),大概在135-140之間浮動。但在比賽的時候,有些地方你沒想明白沒想通就是死活找不到最優解,或者掉進出題人的陷阱中。在大三大四中,我 quit 掉了一部分 ACM 精力,轉去學習 Machine Learning,最後也僅僅以亞洲區域賽銀牌的最高成績結束了我 7 年的競賽生涯。

其實競賽剛退役那會兒,我並不想像許多選手那樣,在自己博客上洋洋洒洒寫一篇退役回憶錄。因為我在這 7 年中,留下了太多遺憾,甚至在寫進簡歷中的時候,還覺得有點丟臉。直到現在工作到了第四個年頭,我才慢慢看開了這一切。在競賽這個舞台上,能擁有亮麗光環受人頂禮膜拜的,那真的是萬里挑一。但其他人就差嗎?我想並不,和我一起不幸打鐵的隊友,是學院中專業課 GPA 第一名,去了美帝 CMU 讀研,現在已在一家我聽都沒聽說過的叫 Facebook 的公司做演算法。如果你在競賽中被虐到懷疑人生,沒關係,99.9% 的人和你有相同的感受。

最後點個題,高票回答好像都是在推薦書籍,我倒覺得不必。給我兩個題庫和一個搜索引擎,我就能學完基礎演算法和數據結構,並且掌握得一定比看書要來得紮實得多得多。學演算法還是非常非常需要靠實踐的,有非常多的細節不是你在看書的時候能掌握的,就比如帶 lazy 標記的線段樹、帶 heap 優化的 Dijkstra、AC 自動機等等。不然你面試的時候,白板編程這關一定過得不漂亮。

-----------------------------------------------------------------

如果你對以下問題感興趣,歡迎來到我的 Live:

* 為什麼面試官都喜歡考程序員基礎演算法?
* 如何高效、系統性地學習演算法和數據結構?
* 為什麼大家普遍覺得動態規劃較難理解?
* 學演算法是否有必要參加 OI / ACM 等演算法編程競賽?
* 如何平衡自己在演算法、競賽上和其它方面學習的精力投入?
* 學習傳統演算法對日後工作的幫助具體有多大?
* 學習傳統演算法對學習機器學習的幫助具體有多大?

傳送門:
SimonS 的知乎 Live - 如何快速攻克傳統演算法和數據結構


我第一次學演算法直接看《演算法導論》,結果信心大受打擊。後來發現Robert Sedgewick在Coursera上出了一系列演算法視頻教程,把一個演算法以動畫的形式展現出來,非常適合新手。課程名字就叫Algorithm,普林斯頓大學的,分為Part 1和Part 2。

看視頻一邊看一邊做筆記,看完一個單元跟著把代碼寫一遍。看完整個教程之後把所有演算法都默寫一遍,忘記的演算法複習一遍。就醬。


不知道你學演算法是為什麼,目的不同,學法不同側重不同。
可能性1: 如果你對演算法理論和精髓感興趣,數學推理能力也不錯,不急著看完刷題接面試搶offer,你可以去仔細研究下《演算法導論》。
可能性2: 如果你和我一樣,半路出家,時間緊迫,看書看不進去,你可以試著看視頻。
我當年看的是princeton的演算法公開課。刷一遍視頻,有了基本的感覺,去刷題。

以面世為目的,我用的「cracking the coding interview」,題目是按照array, stackqueue, 鏈表,樹圖,遞歸這種章節安排的,每章節題目7-8個,不多,難度中等,找感覺很有幫助。第一遍自己寫不出來的話(我就是,這麼弱!),畫圖分析,抄背默。一遍做完再做一遍,第二遍就快很多,理解也深刻了,所謂讀書百遍,其意自現,演算法也一樣。

刷完這些有點信心了,就去leedcode上給自己澆澆冷水,看看面經。
這些都做完了,你就等著點錢選offer吧,演算法學的好不好你早不在乎了,因為你會用了。
(鄙人也覺得上來就推薦《演算法導論》的,不是大大牛就是裝13!!!)


不請自來。

結合自己學習演算法時的一些經歷來說,下面一句話要始終記得:

自己之前也是個技術渣,但好在是一直沒放棄學習,一方面堅持看書聽課學習理論,另一方面自己動手去寫、去實踐,積累了很多方法和心得,簡單地說就是一定要做到看書、聽課、做題相結合,缺一不可。

下面上乾貨了。

(一)
參考書目

推薦幾本書,都是我當時學習的時候看的書,個人感覺很好,內容充實,講解細緻。

1、《Algorithms》一本很經典的演算法書

Algorithms(中文翻譯的書名叫 演算法概論)


2、劉汝佳的《演算法競賽入門經典(第二版)》

內容比較基礎,適合初學者

演算法競賽入門經典(第2版)-劉汝佳(清晰非掃描版) - 下載頻道 - CSDN.NET

3、《程序員代碼面試指南:IT名企演算法與數據結構題目最優解》左程雲

書中都是一些IT名企真實代碼面試題,很全面地覆蓋了演算法與數據結構題型。

程序員代碼面試指南-IT名企演算法與數據結構題目最優解 - 下載頻道 - CSDN.NET

(二)課程

自己之前學習演算法時一直在聽下面這2個演算法課程,是左程雲主講的,

這兩個課程都是免費的。

講得挺細緻的, 像我這種演算法、數據結構較為薄弱的同學都能聽得很明白。

並且還有課後配套練習題,大部分是名企面試真題,聽完課後練習很好地鞏固了學習效果。

當時聽完課後收穫挺大。

牛課堂演算法精講直播講座(2015)_牛客網

牛課堂演算法精講直播講座(2016)_牛客網

(三)課後練習題

我當時做的是這幾個題庫的題,結合使用,效果不錯。

1、 專項練習

可以隨意設置難度,選擇知識點,題目挺全面的,知識點也很細。

智能專項練習_C++Java前端經典筆試面試題庫_牛客網

2、在線編程題

(1)《劍指offer》

66道在線編程題,支持js,php,java,c/c++,python,c#等多種語言。

程序員求職必刷。

劍指Offer_編程題_牛客網

(2)leetcode在線編程

leetcode在線編程_牛客網


(四)演算法網站


Jeff Erickson 演算法課程網站,有許多課程講義 Erik Demaine 演算法課程網站,有許多特殊專題 GeeksforGeeks 程序設計、數學、演算法益智問題 Algorithms Notes 演算法益智問題 VisuAlgo 數據結構可視化

Jeff Erickson

(五)數學網站
1、Matrix67 一直十分仰慕M67。數學愛好者的天堂。 Wolfram Math World 這個網站收集了豐富的數學資料。 如果遇到數學問題,可以到這裡查詢資料

Matrix67.com - Home
2、http://planetmath.org 這個網站的目標是稱為數學的百科全書,有很多好讀的數學文章 The On-Line Encyclopedia of Integer Sequences 收集了幾乎世界上所有出現過的數列。找規律題神器!

http://www.planetmath.org/


推薦我的一位朋友寫的 Book of Elementary Algorithms and Data structures 《初等演算法》

雖然書是用英文寫的,但我這位朋友是中國人,正式的工作是軟體工程師和項目經理,業餘時間對演算法很有興趣,他花了數年時間,把自己對演算法的心得體會寫成了這本書,把全部的內容以及相關源代碼都開源了。
他對演算法和編程語言有深入的研究,能熟練使用十餘種編程語言。這本書中的演算法的參考實現都提供了 C, C++, Haskell, Python, Scheme/Lisp的版本。

項目開源:
liuxinyu95/AlgoXY · GitHub

這本書還在持續更新中,幾天前還有修改。
下面的引用來自他正式發布本書時寫的一篇blog,這是我認識的最有情懷的程序員了。

參考:
有兩本書對《初等演算法》影響最大。一本是Chris Okasaki的《Purely functional data structure》另外一本是《演算法導論》。寫作過程中還參考了一些其他書籍,包括Knuth的《計算機程序設計藝術》,Richard Bird的《Pearls of functional algorithm design》,Bentley的《編程珠璣》以及一些論文。

不足:
寫這本書的六年中,我總是想起法國數學天才伽羅瓦最後寫的那句話:「我沒有時間了!」,我原計劃用10年寫完這本書,結果提前了4年。這樣的代價 很大。為了避免翻譯,過濾「雜訊」,我直接用英文寫作。由於不是native speaker,書中的英文語法和拼寫難免貽笑大方。為了趕時間,proof reading被壓縮,許多結論採取了「拿來主義」,沒有進行嚴格的數學證明。一些章節的課後習題也沒有給出答案。

未來:
理想情況下,一本嚴肅的演算法書應該在穩定、寬鬆的環境下精雕細琢。可是在社會劇烈發展的今天,在日新月異的中國,在人們習慣快餐而不再享受大餐的 快節奏生活中,在微博、微信取代文章、書信的手機網路大潮下,這樣的理想環境根本不存在。未來不可預知。對於《初等演算法》這本書,開放給社區是最好的選擇。需要做的工作很多:
* 翻譯中文版
* 社區proof reading和review,修正內容上的錯誤和英文上的不足
* 提供一本習題集
* 補足數學證明
* 採用強大的數學工具,對形式化的演算法進行分析

一些數據:
《初等演算法》黃金分割0.618版本,歷時6年,在github上總共提交1680次commit。全書600多頁,19萬字(word)。2萬2千行示例代碼。

保護:
《初等演算法》在GNU FDL許可協議下發布,所有代碼在GNU GPLv3協議下發布。


為什麼沒人推薦這本書!

看你妹的算導啊!

學演算法是學習如何解決問題,而不是把現有的演算法背下來。

我們要做能解決問題的人,而不只是會背書的人。


最初 @vczh 輪子哥點贊的是《演算法導論》的回答,而 @趙劼 老趙點贊的是《演算法》的回答。

我自己正在看的是《演算法》這本,很厚的一本書,但是排版精美,有大量插圖(這點對學習數據結構非常重要),這點和 Head First 系列圖書有點像,讀起來較為輕鬆。

--------------------------------------------------------------------------------------------------------


Google 工程師 @鞏朋 的博客里有一篇關於演算法學習的 我的演算法學習之路,詳細列出了在演算法的學習過程中看過的書籍。和輪子哥以前的 伴隨我成長的編程書 類似,都有很好的參考價值。


看了一圈貌似沒人推薦這個以動畫形式展現數據結構和演算法的網站: VisuAlgo - visualising data structures and algorithms through animation
以動畫的形式展現數據結構和演算法,同時還有代碼的運行過程,詳細到每一個步驟,值得大家看看,應該是新加坡國立大學的人搞的。


把讀《演算法》和《演算法導論》的筆記貼在這裡,僅供參考。
首發在好書一起讀(85):演算法筆記,歡迎點擊~

《演算法筆記》

這陣子看了兩本演算法書,《演算法》和《演算法導論》。

前一本讀著很輕鬆,內容基本與大學數據結構課程重疊,示例代碼用java編寫,學習曲線平緩,對應用程序員來說,讀它就挺好。

後一本我是邊看麻省理工的《演算法導論》公開課邊讀的,力不從心,因為我數學基礎不好(詳下),如果不看數學證明,其內容跟前一本就差不多了,數學基礎比較好、對演算法感興趣的朋友,可以嘗試之。

強烈建議各公開課平台的學習資源,質量非常高,相見恨晚!足不出戶,就能學習名校的課程。但學習時別忘了跟著做習題、做筆記、預習複習。

下面是我的《演算法》筆記。


零、思想

重中之重。演算法真正的精髓。內功心法所在。相比之下,後面講到的都是具體招式。

內功心法的意義就在,有朝一日你遇到一個沒有既有經驗的問題,能自己設計出適當的數據結構和演算法,而不是只會用別人的輪子。

演算法的根本思想,是大問題化為多個小問題,逐漸解決之。(《孫子兵法》:治眾如治寡,分數是也;斗眾如斗寡,形名是也。)

1.分治

如果能將大問題拆為同性質的小問題,當小到一定程度,能直接解決之。

則採用分治法,遞歸解決。

2.貪心

如果為小問題找到的最優解,能通過增加一點步驟,解決一個大一點的問題,並依然是最優解。

則採用貪心法,從0開始,逐步蠶食掉大問題。

3.動態規劃

如果用分治法拆出的小問題中常見到相同問題,重複計算它們,成本太高。

則採用備忘法,計算完每個小問題的結果後,記錄之,下次見到同一小問題,不再重複計算。


一、數學基礎

《演算法導論》在附錄中給出了4項必備的數學基礎。

我看了之後,很羨慕考研的同學,他們這幾門課都很紮實。

這4門數學除了是考研數學科目、演算法的基礎外,其實更是觀察世界、指導生活(不僅僅是生產!)的工具,我決定要以看公開課的方式,逐步重新學之。

1.微積分

2.離散數學

3.概率論

4.線性代數


二、線性表
1.按實現分類

順序表:隨機訪問方便,插入刪除成本高,因為一側的元素要「噠噠噠」依次移動一格。

鏈表:隨機訪問麻煩,因為要從頭「一二三」數過去,插入刪除方便,將特定兩三個節點的指針「咔吧」重新接一下就好了。

2.按作用分類

棧:後進先出,直觀理解就是坐電梯。用處主要在處理大問題時,細化為小問題,小問題解決完再繼續處理大問題的時候。計算機的函數調用的核心數據結構。如果不幸小問題沒完沒了,如無限遞歸,則stackoverflow。

隊列:先進先出,講「先來後到」的數據結構。典型使用就是「任務隊列」,先到的先處理。如果允許「插隊」,就是「優先順序隊列」了,讓領導先走!


三、排序

假設有一個初始隊列4132,目標是從小到大排序成1234。

1.冒泡排序

最簡單也最慢的演算法,慢到不少書都不講了。

思想是讓最後一個元素儘力向上爬,遇見比它大的就壓過之(交換次序),如果遇到比它小的就停下,由小的繼續往上爬。爬到第一個元素時,就保證了第一個元素是全部元素中最小的一個。

不再考慮第一個元素,重複這一過程。4132,1/423,12/43,123/4。

2.選擇排序

每次都從隊列中選出一個最小的元素,放到「已排序隊列」里,直到選完。

/4132,1/432,12/43,12/34。

3.插入排序

每次都從「未排序隊列」中選第一個元素,插到「已排序隊列」的適當位置,直到插完。

我玩撲克時理牌就用這法。

4/132,14/32,134/2,1234

4.希爾排序

先把「相距較大間距」的元素們排序一遍,再適度減小間距再排一遍,最後一次相距零間距排。

4132,先間隔較大間距(這個簡陋例子,姑且間隔1),4與3排序,1與2排序,得3142,再間隔較小間距(本例中直接到0了)排序。

5.快速排序

最被喜歡的排序,既容易理解,又快。

選一個數字為軸,比它小的放左邊,比它大的放右邊,然後對其兩邊各進行這一變化即可。

4132,第一步選4,第二步選1,都是把全部剩餘元素放到同側,不划算,所以隨機選擇軸元素很關鍵。

假設第一步選了3,12放左側,4放右側,右側完結,左側繼續,假設選2為軸,1放左側,右側沒有,即得結果1234。

6.歸併排序

所謂歸併,比如一班和二班已經各排成了從矮到高的有序隊列,想歸併成一隊,怎麼辦?比較一班和二班的排頭,更矮的選出來站到「結果隊列」里,該班的新排頭再跟對方排頭比,以此類推。

歸併成一個新隊列後,再跟別的隊列歸併,直到統統歸併為一隊。

14/23 -&> 1|4/23 -&> 12|4/3 -&> 123|4 -&> 1234

7.堆排序

所謂堆,就是一棵每個父節點數值都大於其一切子孫節點數值的完全二叉樹,如果看不懂,先跳到下面看樹那節。

為方便說(放圖太麻煩了,我相信純文字能說清),這次咱們從大到小排序,道理當然是一樣的。

分兩步,一是從無序隊列構造樹,二是從樹輸出有效隊列,結合起來,就是從無序隊列,得到了有序隊列。

第一步,拿一個新節點,放到原樹的最後一個位置,然後使之儘力向上冒泡,直到冒不動為止,再拿下一個新節點。如處理4132:

4 -&> 4左下1 -&> 4左下1右下3 -&> 4左下1右下3,1左下2 -(2冒泡)-&> 4左下2右下3,2左下1

第二步,取出根節點放到結果隊列,把最後一個節點僭越到根位置,然後考察其兩個葉子節點,如果有更有實力的,則向最有實力的退位讓賢,交換位置,到新崗位後繼續考察下屬能力,直到降到自己勝任的位置。

4左下2右下3,2左下1 -(取出4,1上位)-&> 1左下2右下3 -(3篡位)-&> 3左下2右下1 -(取出3,1上位)-&> 1左下2 -(2篡位)-&> 2左下1 -(取出2)-&> 1孤家寡人 -(取出1)-&> 結果4321


四、樹

若干節點,及若干連接節點的無向邊,構成的圖,如果從每個節點出發,都能到達任意一個節點(強連通),同時圖中不存在環,即稱為樹。

數據結構中說的樹,在此基礎上再嚴格點,分為若干層。如某團長手下有若干連長,每個連長手下有若干班長,畫組織結構圖,把直接領導和直接下屬連接起來。直接領導稱為父節點,如團長是連長的父節點,連長是其屬下每個班長的父節點;直接下屬稱為子節點;最高長官稱為根節點,即沒有父節點的節點;沒有子節點的節點稱為葉子節點,比如《士兵突擊》里鋼七連只剩老七(連長)和三多(班長)的時候,三多是葉子節點,如果這時三多被老A挖走(實際劇情是老七先走,不贅),則老七堂堂連長,將不幸成為葉子節點。

1.二叉樹

如果樹的每個父節點最多只有兩個子節點,該樹即稱為二叉樹。

最典型用途是二叉查找,即如果每個根節點數值都比其左子樹(此概念望文生義,不贅)任意節點數值更大,比其右子樹的任意節點數值更小,該樹即稱為二叉排序樹。在想尋找某個數值時,可以先將該數值與根節點比,如果比根節點大,則去與其右子樹根節點比,如果比根節點小,則去與其左子樹根節點比,直到找到或比到葉子節點為止。

好像「猜數字」遊戲,一本好書價值1元到256元之內,假設你蠢到看不出那書不可能1元,又聰明到知道採用二叉查找,則該先猜128元,根據主持人說「高了」「低了」再決定猜64還是192元,假如64還是「高了」,則該繼續猜32,以此類推。

2.紅黑樹

強烈推薦看《演算法》中紅黑樹的章節,從2-3樹開始講,講到紅黑樹,學習曲線非常平滑。

二叉樹和紅黑樹都有著清晰的用於保持樹平衡的邏輯,限於篇幅不多講。平衡的目的是保持最差情況下的查找程度為logN(以2為底,總節點數的對數)。

3.散列表

這個不是樹……但在邏輯上放在這裡最好,因為都是用於「查找」的數據結構。

編程中最基本的兩種數據結構,list和map,list就是上文說的線性表,核心是有順序,map就是這裡說的散列表(hashmap)或樹(treemap),核心是鍵值對。set呢,多以map做底層結構。

樹上面說了,這裡說散列表。

散列表又叫字典,從這字面可知,主要用途是查找,通過鍵(key)查找值(value),首先,要把鍵算成一個數字,這演算法(散列函數)就是java里類們都要實現的hashcode方法。

然後,有兩種可選的實現方式,一是鏈接法,首先有個主list裝著鍵們,比如123,然後1對應一個list,2對應一個list,3對應一個list,我把鍵算出是1之後,就到1對應的list元素里去找元素。顯然,1對應的list長度越短,查找時間越短,那似乎主list的鍵們越多越好,比如1到30,那每個子list的長度將變為原長度的十分之一,但主list的鍵們也不能太多,比如1到30000,那大部分鍵對應的其實是空,裡面一個元素都沒有,就浪費空間了,所以要權衡。

另一種實現方式是散列法,不存在次list,把鍵算成數字後,直接往主list對應的位置存,如果該位置已經被別的元素佔了(碰撞)怎麼辦?則用某種演算法,算出一個新數,再嘗試往新位置放(再散列),以此類推。顯然,這種方法,主list的長度也很關鍵,如果太短,則碰撞太多,插入、查找都麻煩,如果太長,又浪費地方。

對兩種方法,散列函數都非常重要,如果所有元素的散列函數都計算出同一個值,散列表就毫無意義了。


五、圖

終於到圖了!這是我最喜歡的部分,好玩,而且熟悉,因為我認識一個同學,數據結構年年掛,直到大四,所以我每年都陪他複習備考一次,老師考卷中的高分的題目,就是考圖的幾個演算法,迪傑斯特拉,普里姆,克魯斯卡爾……好懷念那段日子。

什麼是圖?一堆節點,及若干邊,就構成了圖,根據線路是否有方向性,分為有向圖、無向圖,根據圖中是否有環,分為有環圖、無環圖。

如果任意兩個節點都有線路連接,則稱為強連通圖,這裡主要聊的都是這種。

1.深度優先、廣度優先遍歷

A連B,B連C,B連D,C連E

想把圖裡所有節點都研究一遍,怎麼辦?

從一個節點開始,走向下一個節點,然後再走向下一個節點的下一個節點,直到「眼前無路想回頭」,就是「深度優先遍歷」。在例子里,從A到B,從B到C,從C到E,E回頭到C,C回頭到B,從B到D,完成遍歷。

「廣度優先遍歷」則是,從一個節點開始,先扇面狀把能到的節點都掃一遍(廣嘛),然後再「移步」到子節點們。在例子里,從A到B,從B到C,從B到D,從C到E,完成遍歷。

2.最小生成樹

如果樹的每條邊都有個權值,要求選出總權值最小的一些邊,讓所有節點都能互相連通,怎麼辦?

這就是「最小生成樹」問題,顯然,選出的結果肯定是一棵樹,樹的兩個條件,連通和無環,連通不必講,為什麼無環?如果有環,去掉任意一條邊,節點們依然相互連通,權值變小了,由此反證,必然無環。

解此問題,兩種方法,一是普里姆演算法,二是克魯斯卡爾演算法,好專業,終於有點演算法的感覺了。

其實都很簡單,先說普里姆,將圖劃為「已解決」和「未解決」兩個部分,一開始「已解決」為空,「未解決」是全集,首先,任選一個點放到「已解決」,然後考察所有從「已解決」連向「未解決」的邊,哪條最短,就把該邊連向的「未解決」節點納入「已解決」集合,如此反覆,直到全部節點都「已解決」,結果就是最小生成樹。

再說克魯斯卡爾,一開始,把每個節點視為一個「分量」,尋找全部「連接兩個不同分量的邊」中最短的一條,將其連接的兩個分量合併為一個分量,並重複這一行為,直到全部節點都屬於同一分量,結果就是最小生成樹。

3.單源最短路徑

最小生成樹研究的是無向圖,最短路徑則研究有向圖。

顧名思義,最短路徑是要求給出從某地到另外某地的最便捷走法,這有兩個演算法,一是迪傑斯特拉演算法,二是貝爾曼福特演算法。

先說迪傑斯特拉,這是類似普里姆的「貪婪演算法」,從一個極小的「已解決」開始,逐步蠶食,直到將「未解決」吃掉。差別在這回每個節點上都有個數字,代表其到起點的距離,每次都選全部「從已解決到未解決的邊」指向的「未解決」節點中,數字最小的一個。比如起點是家,家距離學校500米,家距離市場600米,學校距離公園200米,根據演算法,把學校標註500,市場標註600,500小於600,把學校納入「已解決」,學校進入「已解決」之後,公園也變成了從「已解決」可以到達的「未解決」的節點,用學校的500加200,公園標註為700,與市場的600米相比,600較小,把市場納入「已解決」。注意,這個步驟,如果是普里姆演算法,納入的是公園而非市場,因為普里姆不存在起點,都連上就ok,只考慮哪條邊最短,但迪傑斯特拉有起點,要比較的是「從起點出發直到這裡的距離」。

再說貝爾曼福特,這演算法是把每個節點賦予一個表示「距離起點有多遠」的數值,除了起點外,其他起點一開始都是無窮大,然後依次考察每條邊,如果這條邊能讓指向的節點的數值變小,就將該節點數值變小(收縮),把所有邊都考察一遍之後,如果這一遍中執行了「收縮操作」,則再把所有邊都考察一遍,直到某一遍完全沒收縮為止。上面的例子,比如考察次序是200-500-600,則第一遍200啥用沒有,500將學校的+∞變為500,600將市場的+∞變為600,這遍進行了收縮,再來一遍,這回200有用了,將公園的+∞變為700,500和600都沒有,這遍也收縮了,再來一遍,200/500/600都沒用,完結。


六、字元串

字元串本質是字元數組。說到演算法,最常說的就是字元串查找的KMP演算法。

比如想從「子龍子龍世無雙」里尋找「子龍子翼」,「子」等於「子」,「龍」等於「龍」,「子」等於「子」,「龍」不等於「翼」,糟糕!這時源文本的「已搜索序列」的「後綴們」有「子龍子龍」「龍子龍」「子龍」「龍」四個,而目標文本的「前綴們」有「子龍子翼」「子龍子」「子龍」「子」四個,考察兩個「們」,發現有個相同元素,即「子龍」,長度為2(如果有多個相同元素,則取最長的),意味著源文本的「已搜索序列」中最後兩個字與目標文本的前兩個字相同,下次用源文本的下一個字「世」與目標文本的第三個字「子」相比即可。如果是樸素演算法,則要用「子龍子龍世無雙」的第一個「龍」字與「子龍子翼」的第一個「子」字相比,KMP演算法比樸素演算法快多了!


七、寫在後面

演算法的水太深,我僅僅入了個門,回頭看看,上面所寫,全是大學《數據結構》的內容,長嘆一聲。

也有更深入學習的意願,但在這之前要鞏固數學基礎,我在看網易公開課上的《線性代數》和《微積分》的公開課。

好在,這些「入門知識」,平日工作倒也夠用,而數學、計算機基礎的鞏固和學習,是一輩子的事,不用太急。

不要哀嘆過去的荒蕪,唯一的方法是立足於現在的狀態,選取最優的進步路線,勤勉地躬行之。

學過一點點經濟學基礎的人,不會為「沉沒成本」沮喪。

數學太美,邏輯太美,演算法太美。

我卻近來才發現。讀書的時候全沒省得。

不省得也沒關係,老老實實聽話就好,現在想想,是「獨立思考」得過於早了。

這是我的人生歷程,沒什麼可後悔,寫這只是想規諫還在念書的朋友。

別急著自行其是,以後的人生里,有的是機會讓你自行決策,還在念書的時候,請聽話。

聽老師的話,好好學習,天天向上。


選一本出色的教材.有條件就看英文的.
然後給自己一個環境,例如LINUX+GCC+GDB,用純粹的語言去在解決問題的過程中學習演算法.
沒有目的性去學習,往往效率不高.
可以找一些ONLINE JUDGE的題目做做.例如
Welcome To PKU JudgeOnline
點頭網
對著裡面的問題,先自己思考,嘗試編程解決,如果不能解決,就翻翻演算法書,想想為什麼.
如果還是不行,那就上網看看別人有沒有解決掉,怎麼做,看看他們用到什麼演算法,比對著,然後進一步自己去實現.
有時候對於演算法的問題的實現,你在實現之前也許會卡住,但是在編程過程中,隨著你的鍛煉和熟練度的提高.會有那麼一天你覺得什麼都通了.
而且,你是在用的過程中學習.堅持走下去,一定事半功倍.


看《演算法導論》,重複看。在略過複雜度攤還分析的情況下,初中數學基礎足夠弄明白絕大部分內容了。

同樣推薦的還有《數據結構與演算法分析》,以及鄧俊輝老師的MOOC課程《數據結構》。

演算法導論這本書,從初三到高二,自己斷斷續續的看了三年時間。對於演算法導論,自己的閱讀路徑比較曲折艱難,這是當時自己只有中學基礎的緣故。好在演算法導論
偏向於培養構造性的思維,解題、證明技巧是「演算法的方式」而非「數學的方式」,因而得以勉強讀了下來。不過平攤分析這樣的部分就無能為力了,選擇跳過。

  1. 循環不變式是算導最開端的內容,也是演算法正確性證明最重要的鑰匙。本質上,循環不變式是演算法歸納證明的形式化。理解算導中每個演算法循環不變式的證明過程,就是在理解演算法的運行原理。
  2. 算導閱讀不需要很深的知識儲備(你看我這樣的初中生也能勉強看)。在看高斯消元LUP分解的時候,我只是通過附錄補習了一下矩陣的基本知識,然後就可以看前面的LUP分解演算法了。理解演算法的正確性是相對容易的,理解演算法設計的精妙,反推演算法設計的過程難之又難。
  3. 代碼實現是最好的學習過程。因為競賽的緣故我使用的是c,當然你也可以用python、java或者brainf**k(霧)。啃完二十多頁的二項堆,並且
    敲出代碼成功運行後,當時的我崩潰的發現還有三十多頁的Fibonacci堆在後面等著我。為了記住Fibonacci堆的設計細節,我重複寫了20多遍
    以至於閉著眼睛都能寫出來,結果發現在競賽中根本用不到,我們有好用又好寫的Pairing
    heap。儘管如此,Fibonacci堆的證明簡單而直觀,演算法設計有趣得很。
  4. 嘗試修改優化演算法導論上的代碼。在編寫線性規劃單純性
    的代碼時,我發覺(n+m)*(n+m)的矩陣異常浪費,稍作思考發現可以改成n*m的矩陣加上幾個附加向量信息;進一步,對全幺模的情況,可以使用稀疏
    矩陣常見的優化方法——鏈表替代行向量。幾個優化過後,我終於可以在競賽允許的時間、空間、編碼量內寫一個非多項式的線性規劃單純形演算法了。

  5. 速傅里葉變換也是個有趣的例子。我們都知道,快速傅里葉變換的計算是在複數域上的,而計算機中複數的數值精度會導致FFT在向量比較長的時候丟失信息。後
    來學過數論部分,發現複數域是可以由一些特定的模整數運算取代的,於是FFT就可以被用來加速高精度乘法。再後來,發覺這個方法叫做快速數論變換。
  6. 每一章的課後習題是檢驗本章內容是否掌握的準則。如果課後習題有二分之一以上無法獨立解決,不妨重新閱讀本章內容,給深入思考留些時間。結合習題閱讀章節也是可行的。(我記得網上可以搜到部分章節的答案)
  7. 說到底,演算法導論只是本基礎教材,其中無論數據結構、圖論,還是動態規劃,貪心演算法,都只是基礎內容。如果看不懂,你需要重新看一下這一章;如果一直看不懂,你需要重新從頭讀這本書;如果你發覺能看懂了,說明通過培養,你習得了構造性的、「演算法」的思考能力。

原回答:你是如何堅持讀完《演算法導論》這本書的? - 李四的回答


推薦:http://www.comp.nus.edu.sg/~stevenha/visualization/index.html
尤其是在《演算法導論》看得雲里霧裡的時候,這個項目能幫助你直觀地理解各種演算法。


初學演算法的話,推薦這本吧 http://book.douban.com/subject/1968704/
演算法導論過於詳細和深入,比較計較理論上的證明,很容易打擊信心,而這本上的內容由淺入深,思考題還比較有趣,內容也很充實,祝你成功。


我在三維動畫製作軟體MAYA中用Python編程實現了排序演算法的可視化效果。上傳到了騰訊視頻。希望對初學者有一點參考。它們完全是由演算法執行流程驅動的,不是動畫,因此我是用屏幕錄像工具錄下來的。現在只是直接插入排序和希爾排序。後續會陸續實現和上傳其他的。
直接插入排序的可視化效果:
視頻封面MAYA中Python編程實現直接插入排序演算法的可視化 - 騰訊視頻視頻 希爾排序的可視化效果:
視頻封面MAYA中Python編程實現希爾排序演算法可視化模擬 - 騰訊視頻視頻 在兩種可視化演示中,不論位置遠近,數據交換的時間都是相同的,這是合理。因為在內存中,交換的數據位置無論遠近,耗時相同,這也是希爾排序優於直接插入排序的原理之一。在兩種可視化模擬中,我們可以很直觀地看出,對同一數組的排序,希爾排序的效率是高的,它耗時較少。
如果掌握了可視化工具的話,或者現在開始學一點基本的,用可視化的方法學演算法,我是很推薦的。有趣,有動力,實現的演算法也不容易忘掉。


樓上覺得直接啃很難的。
其實,有一個辦法,MIT有這門課的公開課,百度搜索 MIT:演算法導論,課本就是演算法導論,一邊看一邊認真完成這門課的課後題。
一定不要小看這些課後題,往往是很多演算法的關鍵所在,理解了這些課後題,你才能夠說真正的懂了這個演算法,只是會寫代碼根本不算學會。
這是一個挺不錯的辦法。

自己學沒有任何指導的話,的確會有一些困難。當年自己啃的時候,不注意方法,直接硬啃,直接把書都給翻舊了,還是沒有學好。哎,都是血淚呀。。

而如果你一邊看課程,一邊學,還覺得艱難的話。我真的覺得,你需要想辦法提高下自己的數學能力了。

你要演算法入門的話,演算法導論的確是本好書。它的知識點覆蓋面,框架的確沒的說。


補充一下 @白瓦力的答案,賽老爺子在Coursera上開了同名的演算法課,用的就是他的那本書做教材。演算法課之後還有一門演算法分析的課也很不錯。

這門課學完後,可以接著上Coursera上Stanford開的演算法課,這門課更接近《演算法導論》的感覺。


推薦《演算法導論》,但不是一上來就看,我想強調的有如下幾點:
(1)學習演算法不是件易事,應該有一個系統化的學習過程;
(2)學習演算法要有很好的數學基礎,否則學到的演算法只能是生搬硬套,遇到實際問題還是不會;(3)學習演算法最好的兩本書仍然是演算法導論 (豆瓣)和演算法設計 (豆瓣)。
不管是計算機專業,還是其它專業想自學演算法,學習的路線大致是這樣的:
階段一:
基礎數學課:微積分,線性代數,概率論
沒有微積分極限,收斂等概念,怎麼去理解演算法中時間複雜度,空間複雜度的概念;不懂矩陣演算法中的圖演算法怎麼去入手,不懂概率論隨機演算法應該不好學,時間複雜度估計算不來。學習書籍可參考學校設的相關課程,概率論的話推薦一本書:概率論與數理統計 (豆瓣)。這三門基礎課是一定要學好的。
階段二:
計算機相關數學:離散數學,圖論
離散數學裡面有演算法,計數,歸納等基本概念,圖論里更是囊括了樹,圖和流的所有理論知識,這些是樹,圖,流等演算法的基礎。離散數學推薦書籍:離散數學及其應用 (豆瓣),圖論推薦書籍:圖論 (豆瓣)。
計算機相關:C語言,數據結構
數學畢竟只是理論,還得有實際的編程工具,推薦學一門入門的編程語言,C語言最佳(也有高校一上來就學C++或者java的,個人不推薦),還有數據結構更是重中之重,這是你能把樹,圖能用計算機語言表達出來的基礎,我敢說,叫你實現一顆簡單的二叉樹,你都不一定能寫出來,那你還談學什麼演算法,數據結構推薦書籍:數據結構 (豆瓣),C語言和數據結構可以在學習上述數學知識之中穿插著一起學。
階段三:
系統學習演算法,啃《演算法導論》吧
其實通過階段一,階段二的學習,已經基本掌握了演算法相關的所有知識了,那還缺什麼呢?系統學習演算法,而《演算法導論》就能很好的給我們一個學習演算法的大框架,深入其中吧,你會發現上面講的內容你是那麼熟悉,但是你的收穫又是那麼多。在整個過程,可結合《演算法設計》這本書一起學,《演算法導論》看不懂內容的去《演算法設計》上看,兩本互補。
階段四:
演算法實踐,思維擴展。
到這兒已經沒有什麼方法而言了,就是多練習,彌補不懂的知識,繼續練習,練習題可參考網上的一些演算法題集,如leetcode或者ACM題或者各大互聯網公司筆試面試題。個人推薦兩本書籍:編程珠璣 (豆瓣),編程之美 (豆瓣),兩本書都有一定難度,但是如果前幾個階段都能做好的話,你獲得的一定是趣味。
最後,還是要強調一點,也是我反覆強調的,厚積才能薄發,不要浮躁,不要想著看一本書就把演算法學好,演算法是貫穿整個編程生涯的,畢竟這個式子不是說著玩的:程序=演算法+數據結構。
希望大家能靜下心來,從最基礎的數學知識和數據結構學起,不要貪多,學紮實才是王道。希望大家都能把演算法學好,能寫出高質量的代碼,與大家一同進步,共勉。


感謝 @錢富貴@毛尹航 推薦我的書,也就是那本《程序員代碼面試指南》,這本書叫這個名字是出版社老師建議的,畢竟好賣好營銷也很重要啊。但其實整本書500多頁都是演算法題,以及在複雜度指標上做到最優的實現,說嘔心瀝血不為過。

我刷題刷了6年,還整出一本書來,也算一個努力的過來人,談談自己的心得吧。

首先,你得會一門編程語言。最好是底層數據結構實現比較乾淨的一門語言。C/C++/Java等。腳本語言都不太合適。

原因1,因為你用一些基本數據結構的時候,腳本語言為了讓不強調底層優化的業務做起來快,或多或少都把這些結構進行了「深加工」,就讓基本的數據結構沒那麼基本和乾淨了。當然這個仁者見仁,演算法本身和具體用啥語言也沒什麼關係。

原因2,你練演算法的時候總歸是希望搜一搜別人寫的更好的實現吧。網上大部分的演算法實現是C++和Java的。所以也要求你最好能夠做到C++和Java的代碼都能理解。

其次,《演算法和數據結構》(教材),你一定要看完+理解。這裡面講的都是不能再基礎的東西了,覺得講得不好,自己搜維基百科(不要碰百度)。沒辦法,如果堅持不下來,你後面就受罪去吧。

然後,先別急著讀書。先在leetcode 或 lintcode 或 careercup 或 topcoder 或 牛客網上找個200題猛練。刷題圈有句名言,是我說的,「練過200題後再無庸手」。練的方式是,先不找解答,先自己實現,實現的多爛,複雜度多差,都堅持寫完。然後分析出複雜度。接下來去網上找答案,看到複雜度和你一樣或比你低的,直接略過。看到比你好的,重點看,一定要理解,然後分析為什麼比你的好,如果你真的理解了,你一定能找到別人優化的點。這個過程可能是最奇妙的過程,不要給自己太大壓力,這個過程其實可以很歡樂,你有想法並創造出來,練習了自己的coding能力。別人有更好的實現,推翻了你的所有模型和幻想,你幻滅了,卻也因此找到了讓你血脈噴張方法。這個階段看似苦,實際上其樂無窮。你在學習別人解法的過程中,又了解了很多演算法和數據結構。而且你付出的每一滴汗水,都是結果導向的,可量化的,實實在在的。寫寫簡單的測試函數就可以發現自己方法的運行時間和更好的解法就是沒法比。這是一個非常培養自驅力的階段,這是一個只追求解法更快更強的階段。你看到很多經典的結構,你學到很多細思極妙的優化。比讀那些讓你吃力的書更加快樂,也能夠一直啟發你走下去。你苦苦尋找啊,覺得好的不能再好的方法啊,直到有一天,你突然看到一個更優的解法,相信我,你一定會一整天都在賢者時間裡。如果覺得用腦是件快樂的事,這個階段會讓你以後徹底的離不開演算法的學習;如果覺得用腦不快樂,天生我材必有用,但圖靈祖師爺也真的沒有賞你演算法這碗飯,看開吧。

最後,讀書,只讀最經典的經典。不用推薦了吧。其他答案已經很多了。

別慌。要練。

最後還是推薦一下我的書吧,在《程序員代碼面試指南》里,有非常多的解法比你簡單就能搜到的解法好得多。也算是一個刷題的過來人留給這個世界的一些「明白」吧。

再次感謝 @錢富貴@毛尹航 。大家刷題愉快。


演算法其實通常可以找到論文,然後最重要的是看懂,看懂了以後能夠工程化,外面提供的開源工具包,也是這些演算法工程化而已。最重要的是理清業務特徵庫。


推薦閱讀:

為什麼發現身邊「努力卻得不到回報」的現象越來越普遍?
審計過程中,有哪些好的習慣從一開始就值得堅持?

TAG:演算法 | 學習方法 | 計算機科學 |