程式設計珠璣,字字珠璣

出版圈郭志敏發表於2012-02-08

文章來源:http://www.infoq.com/cn/news/2011/11/programming-pearls

作者:丁雪豐

無論你自稱是“程式猿”還是“攻城師”,只要在寫程式,都免不了要和演算法打交道。說起演算法,第一本從你的記憶中檢索出的圖書是什麼呢?是經典的大部頭《演算法導論》?還是之前大紅大紫的《程式設計之美》?以前它們幾乎是同時映入我腦海的,分不清誰先誰後,這兩本書我都讀過,前者是在學校的演算法課上,而後者則是在畢業求職前。

最近,我終於有了一個明確的答案,在這些演算法相關的書籍中,最讓我愛不釋手的是《程式設計珠璣》這本薄薄的小冊子,因為它真正激發起了我對學習演算法的熱情。不愧是培養了James Gosling(Java之父)、Charles Leiserson(《演算法導論》作者)等眾多大師的“超級大師”的傳世之作。與學校裡接觸的“教材式”的書不同,《程式設計珠璣》更像是“問答式”的,丟擲一個由實際問題簡化而來的問題,然後一步步進行分析解答,作者將想要傳達給讀者的知識與技巧貫穿其中,期間還經常讓人發自內心地感嘆一下,原來還能這麼做。

以書中第8章為例,我把問題簡單描述為“輸入一個長度為n的陣列x,求其中任意連續子元素相加的最大和”。最常規的思路就是寫一個三層巢狀的迴圈,演算法的執行時間為O(n3),時間似乎有點長,做點優化,充分利用之前的計算結果,可以節省一層迴圈,於是得出了一個O(n2)的演算法。如果引入“分治”的思路,將陣列拆開,分別求解,然後再合併起來,這樣只需O(nlogn)的時間。別以為這樣就結束了,終極方案總是出現在最後的,直接從頭開始掃描陣列,一次掃描得出結果(具體演算法請允許我賣個關子),O(n)時間內就能解決問題,神奇吧。千萬不要以為這是專門用來教授演算法知識的假想的“教學問題”,這可是源自布朗大學的Ulf Grenander曾經遇到過的真實問題,可見設計一個好的演算法是多麼重要。

在日常工作中,估計大多數人都不太有機會遇到太複雜的演算法,就算遇到了,也可以僥倖依靠強大的計算和儲存能力來解決問題。誠然現在的伺服器計算能力越來越強,1個核心可以抵得上從前的幾個龐然大物,更何況CPU還是多核的,記憶體都按GB計算,但不能因為這樣就認為現在演算法和資料結構的重要性不如從前了。假設上文提到的問題中不是計算陣列元素,而是每次迴圈需要運算元據庫或者呼叫遠端服務,一次操作就要耗時幾毫秒,甚至是幾百毫秒,那麼O(n3)和O(n)的區別就顯而易見了。加伺服器不是唯一的解決方案,有時簡單地調整演算法能讓你省下大把的金錢和時間。

再來說說空間的問題,節省空間似乎已經不再重要了,對某些人來說是這樣,但不可否認還是存在很多場景,需要錙銖必較,仔細地設計演算法和資料結構。嵌入式裝置就不說了,來說說眼下流行的Redis,為了能最大限度地使用好伺服器的記憶體空間,減少浪費,antirez在編寫Redis時就煞費苦心地改良資料結構,真的是能省1位元組算1位元組。HDFS和BigTable面向海量儲存,照理說它們都是不缺空間的主,可是其中還是提供了LZO、Snappy等眾多壓縮演算法,用“閒置”的CPU運算時間來換取更多的空間……類似的例子還有很多,所以在編寫程式碼時,如果條件允許,請再多考慮一下自己的實現。

多數Java程式設計師應該都有GC調優的經歷,遇到GC過於頻繁,耗時太長的情況,通常會對JVM的堆配置做調整,如果調整的效果都不明顯呢?來看看程式碼吧,也許對程式碼稍作修改,優化下演算法,就能把陡峭的記憶體增長曲線將下來了。啊哈,演算法!

書中還時不時地回顧下歷史,比如二分搜尋,相信大多數人都知道是怎麼回事,可是你知道麼,第一篇二分搜尋的論文1946年就發表了,可是直到1962年才有人寫出了第一個完全正確的二分搜尋程式,太讓人驚訝了,這個可是如今演算法教材上的標配啊。還有那些在編碼規範中經常出現的最長不要超過80個字元,其中80的由來原來和早期的打孔卡有關(如果對這個話題感興趣,可以閱讀阮一峰的這篇文章)。

薄薄的《程式設計珠璣》,兩百多頁捧在手上完全沒有板磚的壓力,可以將其作為教材以外的輔助讀物,工具書以外的休閒讀物,亦或者是和我一樣,將其作為睡前讀物,每晚睡前讀上幾頁,和演算法聊上幾句,和資料結構打個招呼。

相關文章