寫給實踐者的算法學習指南
幾乎是所有最頂尖的互聯網和軟件公司都會用算法和數據結構來考察軟件工程師,然而我并不打算在這里再討論算法的重要性和對實際工作是否有用(我認為這對一個優秀的程序員是不可或缺的基本技能),也不討論「Google式」的「算法面試」和「白板編程」的有效性和合理性,僅僅是作為一個非「競技編程」背景的工程師,分享一點自己實踐過的算法和數據結構學習過程,歡迎探討批判。
結合我自己的學習經歷,以及做「信息學競賽」教練的教學實踐,我通常認為下面三件事情對于學習算法是至關重要的:
一. 先練好「表達能力」
經常碰到有考研的同學問,課本上的算法我都「懂」,選擇題或者手工推導過程的題目我都能做對,但是就是沒法寫成「代碼」或者「偽代碼」,因此對算法題就更加不知所措了。這在我看來,首先是缺乏「表達能力」。
除非是有經驗的程序員,否則我認為學習程序語言的語法時,一開始最重要的是要學會如何「表達」邏輯和流程。很多算法競賽書中都會有一種方法叫「模擬法」,這種方法說白了就是把我們自然地處理問題的步驟「直白」地「程序化」。比如「尋找最大數」的過程 、「矩陣運算」的過程、「進制轉換」或者「求最大公約數」時用到的「輾轉相除法」、模擬豎式運算的「大整數加法」等等,我們可以很直白地通過程序「模擬」這種「流程」。大多數的編程書在介紹語法時,都會提供類似這些問題的習題,幫助我們增強語言的表達能力。如果你使用Python作為主要語言,「Learn Python the Hard Way」中有大量練習表達能力的題目,學完全程,一定收獲不小。「hackerrank」上有專門的「domain」練習「程序表達」,阿三哥出的題目大多數還不錯。
開發一些簡單的應用型「demo」,也能學會一些語言表達,但是由于其邏輯往往過于簡單,并不利于這種練習。
二. 先讀書再刷題
實際上真正讀完《算法導論》,做過上面練習的人并不多,我更多時候是把它當做工具書查找,斷斷續續也只精讀過部分章節。我更建議算法基礎并不好的入門者選用Robert Sedgewick的《算法》,這是一本更簡單易懂的書。理解算法的原理、執行過程,然后學會手工模擬過程,再嘗試自己實現,在配合練習一些題目,不斷強化對算法的理解。
多練習書上的習題通常會比你自己從「OJ」上找題做效果更好,尤其是一些證明和分析題并不是讓你直接寫代碼,這樣能促進對算法本質的理解。比如「快速排序」的最壞和最佳情形到底是怎么產生的,使用隨機和固定策略劃分優劣性分析,以及為什么這個排序算法是「不穩定」的。在此基礎上再進行「OJ」上的刷題訓練,提高「Coding」實現能力,加深算法理解,更加有效。
通過這種訓練,至少我們對于明確算法的問題,可以正確地寫出代碼,再通過「debug」來解決細節問題。
三. 問題的歸約和轉換
有時候明確地跟有些同學說,你寫個「二叉樹的層次遍歷」吧,他可能能寫出來。但是當我讓他在一個「無權圖」上求「最短路徑」時,卻不知所措。甚至有時候,「圖」不是一個我們「數據結構」中所學的「鄰接矩陣」或者「鄰接表」表示時,便不知如何把一個問題轉發為「圖」。他們沒有理解,這里的「圖」可以是一個無形的圖,我們需要把某個「結點」所有相連「結點」找出來,這個找出的過程可以是一條用指針指向的「有形的邊」,也可以是經過某種數學運算或者變換后得到的一個值。而像「八皇后」問題,則是我們在深度優先遍歷整個狀態圖時,不斷地檢查一種狀態轉換為另外一種狀態時的合理性,從而得到一個正確的解。我們的計算機本質上是一個「狀態機」,動態規劃或者搜索,都是在從狀態轉換中找到正確的解。關于每種具體的算法,比如動態規劃、貪心、遞推的意義,「quora」和「知乎」上都有很多優質的解答分析,在學習的過程中閱讀,也能幫助我們解惑。
問題歸約和轉化,需要不斷的練習。所以除了之前說的《算法》,找一本配合練習刷題的書,也是非常有必要的。我自己的學習過程中使用的是秋葉拓哉的《挑戰程序設計競賽》和劉汝佳翻譯的《挑戰編程:程序設計競賽訓練手冊》。對于基礎不太好的同學,則可以選擇劉汝佳的《算法競賽入門經典(第2版)》。
我們的目的是學習算法,而不是刷題,訓練成為一名「競技編程選手」,所以我覺得只需要選擇經典的題目練習即可。我自己也不是一名「競技編程選手」,但是通過學習和練習,除非特別「tricky」的問題,否則「Leetcode」或者「CC150」或者研究生入學考試這種難度的題目都可以輕松應對。當然,我們學習算法的目的絕不是為了面試,這對我們工作的幫助也是巨大的,這里就不展開討論了。
當你信心滿滿的時候,可以嘗試去解決「Leetcode」上的經典面試題,美帝大廠面試題翻來覆去都是這些題,實在是太沒有創意了。或者你也可以去「hackerrank」參加企業的「招聘目的」的比賽,說不定還能翻墻去美帝。