李開復:演算法的力量
演算法是電腦科學領域最重要的基石之一,但卻受到了國內一些程式設計師的冷落。許多學生看到一些公司在招聘時要求的程式語言五花八門就產生了一種誤解,認為學計算機就是學各種程式語言,或者認為,學習最新的語言、技術、標準就是最好的鋪路方法。其實大家都被這些公司誤導了。程式語言雖然該學,但是學習計算機演算法和理論更重要,因為計算機演算法和理論更重要,因為計算機語言和開發平臺日新月異,但萬變不離其宗的是那些演算法和理論,例如資料結構、演算法、編譯原理、計算機體系結構、關係型資料庫原理等等。在“開復學生網”上,有位同學生動地把這些基礎課程比擬為“內功”,把新的語言、技術、標準比擬為“外功”。整天趕時髦的人最後只懂得招式,沒有功力,是不可能成為高手的。演算法與我當我在1980年轉入計算機科學系時,還沒有多少人的專業方向是電腦科學。有許多其他系的人嘲笑我們說:“知道為什麼只有你們系要加一個‘科學’,而沒有‘物理科學系’或‘化學科學系’嗎?因為人家是真的科學,不需要畫蛇添足,而你們自己心虛,生怕不‘科學’,才這樣欲蓋彌彰。”其實,這點他們徹底弄錯了。真正學懂計算機的人(不只是“程式設計匠”)都對數學有相當的造詣,既能用科學家的嚴謹思維來求證,也能用工程師的務實手段來解決問題——而這種思維和手段的最佳演繹就是“演算法”。記得我讀博時寫的Othello對弈軟體獲得了世界冠軍。當時,得第二名的人認為我是靠僥倖才打贏他,不服氣地問我的程式平均每秒能搜尋多少步棋,當他發現我的軟體在搜尋效率上比他快60多倍時,才徹底服輸。為什麼在同樣的機器上,我可以多做60倍的工作呢?這是因為我用了一個最新的演算法,能夠把一個指數函式轉換成四個近似的表,只要用常數時間就可得到近似的答案。在這個例子中,是否用對演算法才是能否贏得世界冠軍的關鍵。還記得1988年貝爾實驗室副總裁親自來訪問我的學校,目的就是為了想了解為什麼他們的語音識別系統比我開發的慢幾十倍,而且,在擴大至大詞彙系統後,速度差異更有幾百倍之多。他們雖然買了幾臺超級計算機,勉強讓系統跑了起來,但這麼貴的計算資源讓他們的產品部門很反感,因為“昂貴”的技術是沒有應用前景的。在與他們探討的過程中,我驚訝地發現一個O(n*m)的動態規劃(dynamic programming)居然被他們做成了O(n*n*m)。更驚訝的是,他們還為此發表了不少文章,甚至為自己的演算法起了一個很特別的名字,並將演算法提名到一個科學會議裡,希望能得到大獎。當時,貝爾實驗室的研究員當然絕頂聰明,但他們全都是學數學、物理或電機出身,從未學過電腦科學或演算法,才犯了這麼基本的錯誤。我想那些人以後再也不會嘲笑學電腦科學的人了吧!網路時代的演算法有人也許會說:“今天計算機這麼快,演算法還重要嗎?”其實永遠不會有太快的計算機,因為我們總會想出新的應用。雖然在摩爾定律的作用下,計算機的計算能力每年都在飛快增長,價格也在不斷下降。可我們不要忘記,需要處理的資訊量更是呈指數級的增長。現在每人每天都會創造出大量資料(照片,視訊,語音,文字等等)。日益先進的紀錄和儲存手段使我們每個人的資訊量都在爆炸式的增長。網際網路的資訊流量和日誌容量也在飛快增長。在科學研究方面,隨著研究手段的進步,資料量更是達到了前所未有的程度。無論是三維圖形、海量資料處理、機器學習、語音識別,都需要極大的計算量。在網路時代,越來越多的挑戰需要靠卓越的演算法來解決。再舉另一個網路時代的例子。在網際網路和手機搜尋,如果要找附近的咖啡店,那麼搜尋引擎該怎麼處理這個請求呢?最簡單的辦法就是把整個城市的咖啡館都找出來,然後計算出它們的所在位置與你之間的距離,再進行排序,然後返回最近的結果。但該如何計算距離呢?圖論裡有不少演算法可以解決這個問題。這麼做也許是最直觀的,但絕對不是最迅速的。如果一個城市只有為數不多的咖啡館,那麼這麼做應該沒什麼問題,反正計算量不大。但如果一個城市裡有很多咖啡館,又有很多使用者都需要類似的搜尋,那麼伺服器所承受的壓力就大多了。在這種情況下,我們該怎樣優化演算法呢?首先,我們可以把整個城市的咖啡館做一次“預處理”。比如,把一個城市分成若干個“格子(grid)”,然後根據使用者所在的位置把他放到某一個格子裡,只對格子裡的咖啡館進行距離排序。問題又來了,如果格子大小一樣,那麼絕大多數結果都可能出現在市中心的一個格子裡,而郊區的格子裡只有極少的結果。在這種情況下,我們應該把市中心多分出幾個格子。更進一步,格子應該是一個“樹結構”,最頂層是一個大格——整個城市,然後逐層下降,格子越來越小,這樣有利於使用者進行精確搜尋——如果在最底層的格子裡搜尋結果不多,使用者可以逐級上升,放大搜索範圍。上述演算法對咖啡館的例子很實用,但是它具有通用性嗎?答案是否定的。把咖啡館抽象一下,它是一個“點”,如果要搜尋一個“面”該怎麼辦呢?比如,使用者想去一個水庫玩,而一個水庫有好幾個入口,那麼哪一個離使用者最近呢?這個時候,上述“樹結構”就要改成“r-tree”,因為樹中間的每一個節點都是一個範圍,一個有邊界的範圍(參考: