《計算機程式設計藝術,卷4A:組合演算法(一)(英文版)》前言

王軍花發表於2011-12-13

欲把一切好的內容都裝進一本書中,顯然是不可能的,哪怕只是想比較全面地涉獵主題的某些方面,多半也會導致篇幅急劇增長。

——Gerald B. Folland,“編者之角”(2005)

卷4的書名是組合演算法,而當我擬書名時,曾特別想給它加一個副標題:我最喜愛的程式設計型別。但是,編輯們決定淡化個人的感情色彩,因此沒有這麼做。不過事實上具有組合風格的程式始終是我所偏愛的。

另一方面,我經常驚奇地發現,“組合”一詞在許多人的頭腦中意味著計算有難度。其實,Samuel Johnson在他著名的英語詞典(1755)中已經說明,對應組合這個名詞“現在普遍用法不當”。同事們對我講述種種不利事件時,總會說“事態的組合使我們無功而返”。對我而言,組合喚起的是純粹喜悅之情,它卻給其他許多人帶來一片驚恐,原因何在?

的確,組合問題經常同非常巨大的數字相聯絡。Johnson的英語詞典中還引用了Ephraim Chambers的一段話,說用24個字母的字母表構成長度小於或等於24的詞,總數會高達:

1 391 724 288 887 252 999 425 128 493 402 200

用10個字母的字母表,相應的數字為11 111 111 110,而當字母個數為5時,這個數字僅有3905。所以,如果問題的規模從5增長到10,再增長到24甚至更大,就必然出現“組合爆炸”。

在我的一生中,計算機一直在以驚人的速度變成越來越強大的工具。等到我寫這些字句時,我知道所用的膝上型電腦的運算速度比我著手編寫這套書時所用的IBM Type 650計算機快10萬倍以上,而且現在這臺電腦的儲存容量也比那時大10萬倍以上。明天的計算機還會更快並具備更大的容量。然而,這些驚人的進展沒有減少人們對於回答組合問題的渴求,情況恰好相反。從前無法想象的如此快速的計算能力提高了我們的期待,並且激起我們更大的慾望——事實上,n只要增加1,組合問題的規模都有可能增加10萬倍以上。

可以把組合演算法非正式地定義為對排列或圖這樣一些組合物件的高速處理方法。我們向來試圖找出一些模式或排列作為滿足約束條件的最佳方法。這種問題的數量非常大,而且編寫這類程式的技巧特別重要,很吸引人,因為有時只要一個好主意就可能節省幾年乃至幾百年計算機時間。

組合問題的優良演算法具有巨大回報,這個事實激勵了技術水平的突飛猛進。許多過去認為很難處理的問題現在可能迎刃而解,許多過去以優良著稱的演算法現在變得更好。大約從1970年起,電腦科學家們經歷了所謂的“Floyd引理”現象:看似需用n3次運算的問題實際上可能用O(n2)次運算就能求解,看似需用n2次運算的問題實際上可能用O(n lg n)次運算就能處理,而且n lg n通常還可以減少到O(n)。一些更難的問題的執行時間也從O(2n)減少到O(1.5n),再減少到O(1.3n),等等。一般說來,剩下的問題依然是很困難的,但是已經發現它們有一些非常簡單的重要特例。我曾經以為在我有生之年不會看到答案的許多組合問題,如今已經獲得解決,而且那些突破的取得主要歸功於演算法的改進而不是處理器速度的提高。

截至1975年,這方面的研究進展神速,主要的計算機期刊上發表的大量論文竟然都是有關組合演算法的。同時,這些進展不僅是由核心的電腦科學領域的研究人員取得的,而且大量成果也來自電子工程、人工智慧、運籌學、數學、物理學、統計學等各領域的研究人員。我曾想盡快完成《計算機程式設計藝術,卷4》的寫作,但是感覺自己就像坐在燒開了直冒汽的一壺水旁邊,只不過我面對的是層出不窮的新主意的大爆炸!

這套書誕生於1962年之初,當時我天真地擬好一共12章的提綱。未經深思熟慮,我便決定用其中一章來簡述組合演算法,心想:“請看,大多數人利用計算機處理數字,而我還能夠編寫處理計算模式的程式!”在那個時期,對於已知的每個組合演算法,都能很容易給出一個非常完備的描述。即使到1966年,當我把這本已經過度膨脹的書寫好約3000頁初稿的時候,其中第7章的內容還不到100頁。我絕對不會想到,當初預計作為“沙拉”的小菜最終竟然會升格成一道主菜。

1975年興起的組合學熱潮,在越來越多人的參與下一浪高過一浪。新思想不斷改善著舊思想,但是很少取而代之,或者使其過時。所以,我自然就得拋棄曾經的希望,已經不可能圍繞這個領域撰寫一本一勞永逸的書,把一切題材組織得井然有序,讓需要解決組合問題的每個人“一冊在手,別無他求”。各種各樣可用的技術如雨後春筍般破土而出,我幾乎對任何支節問題都不可能做到說:“這是最後的解決方案:故事結束了。”相反,我必須把自己嚴格限制在只闡明一些最重要的原理,而這些原理應該是迄今我所遇見的所有有效的組合方法的基礎。現在我為卷4積累的原始資料是卷1至卷3全部資料的兩倍還不止。

這些堆積如山的資料意味著實際上必須把我以往計劃的“卷4”變成若干卷。讀者現在見到的是卷4A。假設我身體健康,以後還會有卷4B和卷4C,並且(天知道?)可能還有卷4D、卷4E……當然肯定不會出現卷4Z。

現在的計劃是通過自1962年以來積累的文件,盡我所能,系統地講述(其實仍然有待進一步討論的)組合方法。我無意追求完美,但是一定要把應有的榮譽歸於所有那些提出關鍵思想的先驅,所以對於歷史情況我不會惜墨如金。除此之外,凡是我以為在今後50年仍然具有重要性的某些內容,以及能用一兩段文字簡潔說明的某些內容,我都不能割愛。反過來,我沒有把那些需要長篇累牘證明的艱深話題收錄進來,除非它確實是基礎性的。

不錯,組合演算法這個領域顯然是廣闊的,我不能顧及它的所有方面。那麼,我忽視了什麼最重要的東西呢?我以為我的最大盲點是在幾何上,因為我最擅長的始終是呈現和操作代數公式而非空間物件。所以,在這幾本書中,我不準備討論與計算幾何有關的組合問題,如球的密堆積,n維歐幾里得空間中資料點的分類,甚至也不討論平面內的Steiner樹問題。更重要的是,我力求避開多面體組合學,以及主要基於線性規劃、整數規劃或半定規劃的各種方法。那些內容在有關這個主題的其他很多書中已被較好地涉及,而且它們依賴於幾何直覺知識。單純從組合問題展開討論對我來說是更容易理解的。

我還必須承認,對於那些僅在漸近意義下有效的組合演算法,以及優越效能直到問題規模超越乎想象時才開始顯現的組合演算法,我不太感冒。現在有大量出版物討論這類演算法。我能理解有的人喜歡思考極限問題,認為它是個智力挑戰並且能帶來學術聲望,但是我這本書對於我自己在實際程式中不考慮使用的任何方法,一般只是輕描淡寫。這條規則的運用自然也有例外,特別是對那些處於主題中心的基本概念就不是這樣。(某些不實用的方法實在是非常優美或者含義深刻,讓我不能割捨,還有些方法則被我作為反例引用。)

此外,在這套書的前幾卷中,我有意講的都是順序演算法,儘管計算機的平行計算能力日益增強。對於哪些並行演算法可能在今後5年或者10年中會很有用,我判斷不好,更不用說50年後了,所以我樂於把這樣的問題留給那些比我聰明的人。明日才華橫溢的程式設計師們應該具備什麼樣的順序演算法知識,單是這個問題就足夠考驗我自己的能力了。

在陳述這些材料時,我需要作出的一個主要決定是按問題還是按方法組織它們。例如,卷3中的第5章專門討論一個問題,即資料排序,我們從不同方面應用了20餘種方法。相比之下,組合演算法涉及許多不同的問題,而解決問題的方法則少很多。最終我斷定,採用一種混合策略會比任何一種單純的方法能夠更好地組織材料。於是在這幾本書中,7.3節處理求最短路徑問題,7.4.1節處理連通性問題,但是其他許多節則專門討論基本方法,如布林代數的應用(7.1節)、回溯(7.2節)、擬陣理論(7.6節)或者動態規劃(7.7節)。著名的流動推銷員問題,以及與覆蓋、著色和填充有關的其他經典組合問題,沒有單闢章節討論,但是它們在書中多次出現,每次都用不同的方法處理。

我已經提到過組合計算技術的巨大進步,但是我並不是暗示人們已經解決了所有的組合問題。在計算機程式的執行時間難以掌控之際,程式設計師們不能指望從本書中找到無堅不摧的“銀彈”。這裡描述的方法通常會比一個程式設計師先期嘗試的方法快很多,但是讓我們面對這樣一個現實:組合問題一不小心就會迅速地成為巨大的問題。我們甚至可以嚴格證明,就連一個自然的小問題也不存在現實的可解,儘管原則上它是可行解的(參見7.1.2節的Stockmeyer和Meyer定理)。還有些情況下,我們尚不能證明一個給定問題不存在合適的演算法,只是知道可能沒有這樣的演算法,因為任何有效的演算法將會產生一種有效方法,幫我們解決一大批難倒世上無數英雄好漢的問題(參見7.9節關於NP問題的討論)。

經驗表明,新的組合演算法將不斷湧現,以解決新出現的組合問題以及老問題的變型或特例,並且人們對於這樣演算法的期望也會繼續提高。當程式設計師總是面臨這樣一些挑戰時,計算機程式設計藝術必將會不斷達到新的高度。不過,今天的方法也可能仍然會長期發揮重大作用。

本書的大部分內容是相對獨立的,但與卷1至卷3的內容時有關聯。前幾卷深入地討論了機器語言程式設計的底層細節,所以本書中的演算法常常是在抽象級別上說明,與任何具體機器無關。然而,組合程式設計確有某些方面與過去未出現過的底層細節密切相關。出現這種情況時,書中相應例子都是基於MMIX計算機的,這種計算機替代了卷1前幾版中定義的MIX計算機。關於MMIX的詳細材料在卷1的一本補充讀物中介紹,即The Art of Computer Programming, Volume 1, Fascicle 1,其中包含1.3.1節、1.3.2節,等等。另外,這些材料也可以從因特網上獲得,配套的彙編程式和模擬程式也可從網上下載。

另外一種可以從網上下載的資源是稱為《史丹佛相簿》(Stanford GraphBase)的一組程式和資料,它在本書的例子裡經常引用。我鼓勵讀者多利用它,以便既高效又快樂地學習組合演算法。

順便說一句,在我撰寫第7章開頭這個前言的時候,我很高興地說明,書中提到了我的博士論文導師Marshall Hall, Jr.(1910—1990)的一些成果,以及Hall的論文導師Oystein Ore(1899—1968)的一些成果,還有Ore的論文導師Thoralf Skolem(1887—1963)的一些成果。至於Skolem的論文導師Axel Thue(1863—1922)的一些成果,我在第6章中已經作過介紹。

有幾百位讀者幫助我查出在這卷書幾份初稿中遺留的大量錯誤,謹對他們表示特別的感謝,這些稿子原先公佈在因特網上,後來又印成幾冊平裝書。特別是Thorsten Dahlheimer、Marc van Leeuwen和Udo Wermuth三人的大量意見對本書產生了很大影響。但是,我擔心還有其他錯誤隱藏在彙整合卷的字裡行間,希望能儘快改正它們。因此,我樂於對首先發現任何一處技術錯誤、排版錯誤或者史料錯誤的人獎勵2.56美元。在http://www-cs-faculty.stanford.edu/~Knuth/taocp.htm/中包括一張勘誤表,列出了當前已知的所有錯誤的修正。

D. E. Knuth

2010年10月於加州史丹佛

在第1版序言中,我曾請求讀者不要專門注意辭典中的錯誤,現在我反倒希望自己未這樣說過,而且要感謝對我的請求置之不理的那些讀者。

——Stuart Sutherland,《國際心理學辭典》(1996)

我自然應該對遺留下的錯誤負責——不過在我看來,我的朋友們本來還可以發現出更多錯誤。

——Christos H. Papadimitriou,《計算複雜性》(1994)

我願意從事種種不同領域的工作,以便使我的錯誤分散得更稀疏。

——Victor Klee(1999)

關於參考文獻的註釋 若干經常引用的雜誌和會議會刊有特別的程式碼名稱,它們出現在書後的Index和Glossary中。但是各種IEEE會刊的引用中包含一個代表會刊類別的字母程式碼,置於卷號前,用粗體表示。例如“IEEE Tranc. C-35”是指IEEE Transactions on Computers,Volume 35(《IEEE計算機會刊》,第35卷)。IEEE現在不再使用原來這些簡便的字母程式碼,其實這些程式碼不難辨認:“EC”曾經代表“電子計算機”,“IT”代表“資訊理論”,“SE”代表“軟體工程”,“SP”代表“訊號處理”等,“CAD”的含義是“電路和系統的計算機輔助設計”。

類似“習題7.10-00”這樣的寫法是指7.10節中一道還不知道題號的習題。

關於記號的註釋 對於數學概念的代數表示,簡單而直觀的約定始終會促進發展,尤其是當世界上多數研究人員使用一種共同符號語言的時候。可惜在這方面,組合數學當前的事態多少有些混亂,因為同樣一些符號在不同的人群中有時完全代表了不同意義;在比較狹窄的分支領域從事研究工作的某些專家,也會在無意中引進了彼此衝突的符號表示。電腦科學——它與數學中許多主題相關——應儘可能採用內部一致的記號來避開這種危險。所以我經常不得不在若干對立的方案中作出選擇,明知結果不能使人人滿意。我盡全力選用自以為是未來最好的記號,靠著多年的經驗以及同事之間進行的討論,還經常在不同方案之間反覆試驗,找出適用的記號。在其他人尚未認同對立的方案時,通常有可能找到適用的共同約定。

附錄B給出了本書中使用的所有主要記號的綜合索引,其中不可避免也包含若干還不是標準的記號。如果讀者偶然遇見一個有點奇怪或不好理解的公式,基本上可通過附錄B找到說明我的意圖的章節。不過我仍然應該在此舉出幾個例子,供你初次閱讀本書時加以留意。

  • 十六進位制常數前面冠一個#字元號。例如,#123是指(123)16。

  • “非虧減”運算x y有時稱為點減或者飽和減,結果為max(0, x-y)。

  • 三個數{x,y,z}的中位數用表示。

  • 像{x}這樣含單個元素的集合,在文中通常簡單地用x表示,例如X x或X\x。

  • 如果n是一個非負整數,n的二進位制表示中取1的位數記為νn。另外,如果n>0,n最左邊的1和最右邊的1分別用2λn和2ρn表示。例如,ν10=2, λ10=3, ρ10=1。

  • 圖G和圖H的笛卡兒積用G□H表示,例如,Cm□Cn表示一個m×n環面,因為Cn表示n個頂點的一個環。

相關文章