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