2002年7月18日

生物人該知道的演算法


未經原作者同意,請勿任意轉載,謝謝。

–-

今天,我們遇到問題了,該如何解決呢?基本上,如果是一加一這種問題,相信大家都可以處理得很好。
因此,我們擔心的是遇到比較複雜的問題。什麼是比較複雜的問題呢?這個見仁見智因人而異,
反正你覺得有點難度就算是複雜問題吧。

就系統分析的角度來看,要解決一個稍具規模的問題,第一步就是要瞭解問題
例如今天要求最大公因數,你得先知道什麼叫做最大公因數?什麼東西可以拿來求最大公因數?

第二步就是制訂規格。所謂規格就是清楚地描述問題該有的輸出與輸入。
譬如,求最大公因數該有的輸入是兩個整數,該有的輸出是可以整除輸入二數的最大整數。
換言之,10.28 與 5.74 這兩個浮點數就無法當成這個問題的輸入,
而 this is a book 這個字串也不可能是這個問題的輸出。

第三步是決定所採用的資料結構。所謂資料結構就是資料的表示與儲存方法。以上例來說,
我們至少會用到三個「整數變數」,其中兩個用來存輸入的兩個整數,剩餘的那一個用來存輸出的整數。
遇到比較難的問題,我們可能還會用到陣列、二元樹或雜湊表等等比較複雜的資料結構。
資料結構的選用與問題的本質有關;而選用的資料結構也會影響後續的效能。
例如,我們需要一個資料結構來存一個數列,如果這串數列的長度事先知道且不會改變,
並且我們主要的行為是查詢數列的內容,那麼「陣列 (array)」就是一個很好的選擇。
如果這串數列事前不知道長度,並且我們隨時會針對這串數列執行插入數字、刪除數字等動作,
那麼「鏈結串列 (linked list)」就是較佳的選擇。

第四步是決定所採用的演算法。演算法簡單來說就是解決問題的方法,它必須具備以下特性:
1. 必須有輸入 2. 必須有輸出 3. 由有限個執行步驟所組成 4. 每個執行步驟必須明確可執行,
並且在有限時間內執行完畢。當然我們可以畫蛇添足地加上第 5 點:必須會停下來。
其實由 3 跟 4 就可以得到第 5 點。當然我們預期的是:當這些步驟依序執行完畢之後,
就可以得到我們想要的正確輸出。我們特別說明一下第 4 點。
明確可執行的要求是為了日後可以交由電腦執行。
例如,「身高高的站左側,矮的站右側」就不是一個明確可執行的步驟,因為我們沒有定義什麼是「高的」。
如果改成「身高大於等於 170 公分的站左側,小於 170 公分的站右側」就是一個明確的執行步驟了。
這部分會受到資料結構的影響,如果適當地選擇資料結構的話,設計出來的演算法可能步驟變少,
並且單一步驟的執行難度也會降低。

第五步是實作。就組織來說,實作可能就是派員執行;就電腦來說,實作就是寫成程式了。
基本上演算法與程式語言應該是獨立的 (至少在相當程度上應該是獨立的),一旦演算法設計出來之後,
理論上應該用任何程式語言都可以將此演算法寫成電腦程式。

第六步是除錯。基本上,如果是小問題的話,應該不太需要大費周章地用以上的步驟來完成程式。
需要以上各個步驟來完成的程式應該都是上千甚至上萬行的程式。那麼多行的程式很難保證不出錯,
因此除錯就成為一個很重要的工作。發現錯誤不見得是個簡單的工作,有些錯誤很明顯,
程式一執行就可以發現跟預期的結果差很多,但是有的錯誤並不會很明顯地反映在輸出上,
此時就需要仔細地加以驗證程式邏輯。通常好的程式寫作方式有助於降低錯誤的發生與減低除錯的難度,
例如模組化程式設計、物件導向程式設計或是在程式中適度地加入註解。當我們發現錯誤的時候,
就要回頭審視錯誤可能的發生所在,是一開始的問題就沒搞清楚呢?還是規格制訂錯誤?
資料結構選得不好?演算法本身邏輯就錯了?還是程式寫錯了?這些都有可能,所以必須審慎處理。

第七步也是最後一步就是系統維護。如果程式要長期運作,程式維護的工作是不可或缺的。
包括隨著輸出入改變的程式修正,或是利用新技術來更新舊有的程式模組 (不改變模組的輸出入) 等等。
一般來說,除錯與系統維護所耗費的成本佔了這七項中相當大的比例,
幾乎百分之八十的心力都會花在除錯與系統維護上。

當然,這不表示演算法不重要,畢竟這是解決問題的核心。