演算法

演算法（algorithm），在數學（算學）和計算機科學之中，指一個被定義好的、計算機可施行其指示的有限步驟或次序，常用於計算、資料處理和自動推理。演算法可以使用條件語句透過各種途徑轉移程式碼執行（稱為自動決策），並推匯出有效的推論（稱為自動推理），最終實現自動化。

相反，啟發式是一種解決問題的方法，可能沒有完全指定，也可能不能保證正確或最優的結果，尤其是在沒有明確定義的正確或最優結果的問題領域。例如，社交媒體推薦系統依賴於啟發式，儘管在 21 世紀的流行媒體中被廣泛稱為演算法，但由於問題的性質，它無法提供正確的結果。

早在嘗試解決希爾伯特提出的判定問題時，演算法的不完整概念已經初步定型；在其後的正式化階段中人們嘗試去定義 “” 或者 “”。這些嘗試包括庫爾特・哥德爾、雅克・埃爾布朗和斯蒂芬・科爾・克萊尼分別於 1930 年、1934 年和 1935 年提出的遞迴函式，阿隆佐・邱奇於 1936 年提出的 λ 演算，1936 年的波斯特 - 圖靈機和艾倫・圖靈 1937 年提出的圖靈機。即使在當下，依然常有符合直覺的想法難以定義為形式化演算法的情況。

演算法是，包含一系列定義清晰的指令，並可於有限的時間及空間內清楚的表述出來。演算法中的指令描述的是一個計算，它時從一個初始狀態和初始輸入（可能為空）開始，經過一系列有限而清晰定義的狀態最終產生輸出並停止於一個終態。 一個狀態到另一個狀態的轉移不一定是確定的。 包括隨機化演算法在內的一些演算法，都包含了一些隨機輸入。

歷史

演算法在中國古代文獻中稱為 “術”，最早出現在《周髀算經》、《九章算術》。特別是《九章算術》，給出四則運算、最大公約數、最小公倍數、開平方根、開立方根、求素數的埃氏篩，線性方程組求解的高斯消元法。三國時代的劉徽給出求圓周率的演算法：劉徽割圓術。

自唐代以來，歷代更有許多專門論述 “演算法” 的專著：

唐代：《一位演算法》 一卷，《演算法》 一卷；
宋代：《演算法緒論》 一卷、《演算法秘訣》 一卷；最著名的是楊輝的《楊輝演算法》；
元代：《丁巨演算法》；
明代：程大位 《演算法統宗》
清代：《開平演算法》、《演算法一得》、《演算法全書》。

而英文名稱「algorithm」來自於 9 世紀波斯數學家花拉子米（比阿勒・霍瓦里松，波斯語：خوارزمی ，拉丁轉寫：al-Khwarizmi），因為比阿勒・霍瓦里松在數學上提出了演算法這個概念。「演算法」原為「algorism」，即 “al-Khwarizmi” 的音轉，意思是 “花剌子模的” 運演算法則，在 18 世紀演變為「algorithm」。

歐幾里得演算法被人們認為是史上第一個演算法。

第一次編寫程式是愛達・勒芙蕾絲（Ada Byron）於 1842 年為巴貝奇分析機編寫求解解伯努利微分方程的程式，因此愛達・勒芙蕾絲被大多數人認為是世界上第一位程式設計師。因為查爾斯・巴貝奇（Charles Babbage）未能完成他的巴貝奇分析機，這個演算法未能在巴貝奇分析機上執行。

因為「well-defined procedure」缺少數學上精確的定義，19 世紀和 20 世紀早期的數學家、邏輯學家在定義演算法上出現了困難。20 世紀的英國數學家圖靈提出了著名的圖靈論題，並提出一種假想的電腦的抽象模型，這個模型被稱為圖靈機。圖靈機的出現解決了演算法定義的難題，圖靈的思想對演算法的發展起到了重要的作用。

特徵

以下是高德納在他的著作《計算機程式設計藝術》裡對演演算法的特徵歸納：

輸入：一個演算法必須有零個或以上輸入量。
輸出：一個演算法應有一個或以上輸出量，輸出量是演算法計算的結果。
明確性：演算法的描述必須無歧義，以保證演算法的實際執行結果是精確地符合要求或期望，通常要求實際執行結果是確定的。
有限性：依據圖靈的定義，一個演演算法是能夠被任何圖靈完備系統模擬的一串運算，而圖靈機只有有限個狀態、有限個輸入符號和有限個轉移函式（指令）。而一些定義更規定演演算法必須在有限個步驟內完成任務。
有效性：又稱可行性。能夠實現，演算法中描述的操作都是可以透過已經實現的基本運算執行有限次來實現。

基本要素

演算法的核心是建立問題抽象的模型和明確求解目標，之後可以根據具體的問題選擇不同的模式和方法完成演算法的設計。

3.1 常用設計模式

完全遍歷法和不完全遍歷法：在問題的解是有限離散解空間，且可以驗證正確性和最優性時，最簡單的演算法就是把解空間的所有元素完全遍歷一遍，逐個檢測元素是否是我們要的解。這是最直接的演算法，實現往往最簡單。但是當解空間特別龐大時，這種演算法很可能導致工程上無法承受的計算量。這時候可以利用不完全遍歷方法 —— 例如各種搜尋法和規劃法 —— 來減少計算量。

分治法：把一個問題分割成互相獨立的多個部分分別求解的思路。這種求解思路帶來的好處之一是便於進行平行計算。

動態規劃法：當問題的整體最優解就是由區域性最優解組成的時候，經常採用的一種方法。貪婪演算法：常見的近似求解思路。當問題的整體最優解不是（或無法證明是）由區域性最優解組成，且對解的最優性沒有要求的時候，可以採用的一種方法。

線性規劃法：見條目。

簡併法：把一個問題透過邏輯或數學推理，簡化成與之等價或者近似的、相對簡單的模型，進而求解的方法。

3.2 常用實現方法

遞迴方法與迭代方法

順序計算、平行計算和分散式運算：順序計算就是把形式化演算法用程式設計語言進行單執行緒序列化後執行。

確定性演算法和非確定性演算法

精確求解和近似求解

形式化演算法

演算法是電腦處理資訊的本質，這是因為計算機程式本質上是一個用以告訴電腦確切的步驟並從中執行一個指定的任務的演算法，如計算職工的薪水或列印學生的成績單。一般而言，當演算法在處理資訊時，會從輸入裝置或資料的儲存地址讀取資料，將結果寫入輸出裝置或某個儲存地址供以後再呼叫。

複雜度

5.1 時間複雜度

演算法的時間複雜度是指演算法需要消耗的時間資源。一般來說，電腦演算法是問題規模的函式，演算法的時間複雜度也因此記做

：

演算法執行時間的增長率與的增長率正相關，稱作，簡稱時間複雜度。

常見的時間複雜度有：常數階，對數階，線性階，線性對數階，平方階，立方階，…，次方階，指數階。隨著問題規模的不斷增大，上述時間複雜度不斷增大，演算法的執行效率越低。

5.2 空間複雜度

演算法的空間複雜度是指演算法需要消耗的空間資源，其計算及表示方法與時間複雜度類似，一般會使用複雜度的漸近性表示。不過，與時間複雜度相比，空間複雜度的分析是簡單許多的。

實現

演算法不單可以依靠計算機程式的使用所實現，也可以在人工神經網路、電路或者機械裝置上實現。

示例

7.1 求最大值演演算法

這是演算法的一個簡單的例子。

即為，我們有一串隨機數列，我們的目的是找出這個數列中最大的數。而如果將數列中的每一個數位看成是一顆豆子的大小，可以將下面的演算法形象地稱為「撿豆子」：

首先將第一顆豆子放入口袋中。
從第二顆豆子開始檢查，如果正在檢查的豆子比口袋中的還大，則將它撿起放入口袋中，同時丟掉原先口袋中的豆子。反之則繼續下一顆豆子。直到最後一顆豆子。
最後口袋中的豆子就是所有的豆子中最大的一顆。以上演算法在中國大陸的教科書中通常被叫做 “打擂法” 或者 “迴圈打擂”：在一個 for 迴圈中，每輪迴圈都有新的挑戰者。若挑戰者勝的話，挑戰者做新擂主，否則擂主衛冕。for 迴圈結束後輸出最後的擂主。

下面是一個形式演算法，用 ANSI C 程式碼表示

int max（int *array, int size）

{

int mval = *array;

int i;

for （i = 1; i mval）

mval = arrayi;

return mval;

}

7.2 求最大公約數演演算法

求兩個自然數的最大公約數

設兩個變數和

如果，則交換和
除以，得到餘數
判斷，正確則即為 “最大公約數”，否則下一步
將賦值給，將賦值給，重做第一步。

用 ANSI C 程式碼表示

// 交換 2 數

void swapi（int x, int y）

{

int tmp = *x;

x = *y;
y = tmp;

}

int gcd（int m, int n）

{

int r;

do

{

if （m

利用 if 函式以及遞迴則能做出更為精簡的程式碼，更可省去交換的麻煩。（但是也因為遞迴呼叫，其空間複雜度提高）

int gcd（int a,int b）

{

if（a% b）

return gcd（b,a% b）;

return b;

}

分類

分類演算法有多種方法，每種方法都有自己的優點。

8.1 透過實施

分類演算法的一種方法是透過實現手段。

 遞迴

 遞迴演算法是一種重複呼叫（引用）自身的演算法，直到某個條件（也稱為終止條件）匹配，這是函數語言程式設計常用的方法。迭代演算法使用迴圈之類的重複構造，有時使用堆疊之類的附加資料結構來解決給定的問題。有些問題自然適合於這種或那種實現。例如，使用遞迴實現可以很好地理解河內的塔。每個遞迴版本都有一個等價的（但可能或多或少複雜）迭代版本，反之亦然。

 串聯的、平行的或分佈的

 演算法通常是在假設計算機一次執行一條演算法指令的情況下討論的。那些計算機有時被稱為序列計算機。針對這種環境設計的演算法稱為序列演算法，而不是並行演算法或分散式演算法。並行演算法是利用計算機體系結構的演算法，其中多個處理器可以同時處理一個問題。分散式演算法是使用與計算機網路連線的多臺機器的演算法。並行和分散式演算法將問題劃分為更加對稱或不對稱的子問題，並將結果收集在一起。例如，CPU 就是並行演算法的一個例子。這種演算法的資源消耗不僅是每個處理器上的處理器週期，而且是處理器之間的通訊開銷。有些排序演算法可以有效地並行化，但是它們的通訊開銷很大。迭代演算法通常是可並行的，但有些問題沒有並行演算法，稱為固有的序列問題。

 確定的或不確定的

 確定性演算法在演算法的每一步都用精確的決策來解決問題，而非確定性演算法透過猜測來解決問題，雖然透過啟發式使典型的猜測更加精確。

 精確的或近似的

 雖然許多演算法達到一個精確的解決方案，近似演算法尋求一個近似，更接近真正的解決方案。這種近似可以透過使用確定性策略或隨機策略來實現。這些演算法對許多難題都有實用價值。近似演算法的一個例子是揹包問題，其中有一組給定的專案。它的目標是包裝揹包，以獲得最大的總價值。每個物品都有一定的重量和價值。可攜帶的總重量不超過某個固定數字 X，因此，解決方案必須考慮物品的重量及其價值。

 量子演算法

 量子演算法執行在一個現實的量子計算模型上。這個術語通常用於那些本質上似乎是量子的演算法，或者使用量子計算的一些基本特性，如態疊加原理或量子糾纏。

8.2 透過設計範例

對演算法進行分類的另一種方法是透過它們的設計方法或範例。有一定數量的範例，每一個不同於其他。此外，這些類別中的每一個都包括許多不同型別的演算法。一些常見的範例是:

 暴力搜查或徹底搜查

 蠻力是一種解決問題的方法，包括系統地嘗試每一種可能的選擇，直到找到最佳解決方案。這種方法可能非常耗時，因為它需要遍歷所有可能的變數組合。但是，當其他方法不可用或過於複雜時，常常使用這種方法。蠻力可以用來解決各種問題，包括尋找兩點之間的最短路徑和破解密碼。

 各個擊破

 分而治之的演算法重複地將一個問題的例項減少為同一個問題的一個或多個更小的例項（通常是遞迴的） ，直到這些例項足夠小以便於解決。分而治之的一個例子是合併排序。在將資料分割成片段後，可以對每個片段進行排序，在征服階段透過合併片段可以對整個資料進行排序。一種更簡單的分而治之的演算法稱為減而治演算法，它解決一個相同的子問題，並使用這個子問題的解決方案來解決更大的問題。分治演算法將問題劃分為多個子問題，因此分治階段比減少分治演算法複雜。遞減和征服演算法的一個例子是二進位制搜尋演算法。

 搜尋和列舉

 許多問題（比如下棋）可以建模為圖形上的問題。圖探索演算法規定了在圖中移動的規則，對於這類問題非常有用。這一類別還包括搜尋演算法、分支和界列舉以及回溯。

 隨機演算法

 這樣的演算法隨機（或偽隨機）做出一些選擇。它們可以非常有用地找到近似解決方案的問題，找到精確的解決方案可能是不切實際的（見下面的啟發式方法）。對於其中的一些問題，我們知道最快的近似必須包含一些隨機性。 對於某些問題，具有多項式時間複雜度的隨機演算法能否成為最快的演算法，是一個被稱為 “P/NP 問題” 的懸而未決的問題。這種演算法有兩大類:

蒙特卡羅演算法以高機率返回正確答案。例如 RP 是這些執行在多項式時間的子類。
拉斯維加斯演算法總是返回正確的答案，但他們的執行時間只是機率約束，例如 ZPP。

 降低複雜性

 這種技術涉及到透過將一個困難問題轉化為一個更廣為人知的問題來解決它，我們（希望）已經有了漸近最優演算法。目標是找到一種複雜度不受所得到的簡化演算法控制的簡化演算法。例如，一種用於在未排序列表中查詢中值的選擇演算法首先對列表進行排序（代價較高的部分） ，然後取出排序列表中的中間元素（代價較低的部分）。這種技術也被稱為轉換和征服。

 反向追蹤

 在這種方法中，多個解決方案是逐步構建的，當確定它們不能生成有效的完整解決方案時，就會放棄這些解決方案。

8.3 最佳化問題

對於最最佳化問題，有一個更具體的演算法分類；這類問題的演算法可能屬於上述一個或多個一般類別，也可能屬於以下類別之一:

 線性規劃

 當搜尋受線性等式和不等式約束的線性函式的最優解時，該問題的約束可以直接用於產生最優解。有一些演算法可以解決這類問題，比如流行的單純形法。 線性規劃可以解決的問題包括有向圖的最大流問題。如果一個問題額外要求一個或多個未知數必須是一個整數，那麼它被分類為整數規劃。一個線性規劃演算法可以解決這樣的問題，如果它可以證明所有的限制整數值是表面的，即，解決方案滿足這些限制。在一般情況下，根據問題的難度，使用專門的演算法或找到近似解的演算法。

 動態程式設計

 當一個問題顯示出最優子結構ーー意味著一個問題的最優解可以從子問題的最優解構造出來ーー和重疊子問題，意味著同一個子問題可以用來解決許多不同的問題例項時，一種叫做動態規劃的快速方法可以避免重新計算已經計算出來的解。例如，Floyd-Warshall 演算法，在一個加權圖中，透過使用從所有相鄰頂點到達目標的最短路徑，可以找到從一個頂點到達目標的最短路徑。動態程式設計和製表一起使用。動態規劃與分治的主要區別在於子問題在分治中或多或少是獨立的，而子問題在動態規劃中是重疊的。動態程式設計和簡單遞迴的區別在於遞迴呼叫的快取或製表。當子問題是獨立的並且沒有重複時，製表不起作用；因此動態程式設計不是所有複雜問題的解決方案。透過使用製表法或維護已經解決的子問題表，動態規劃將許多問題的指數性質降低到多項式複雜度。

 貪婪的方法

 貪婪演算法類似於動態規劃演算法，它透過檢查子結構來工作，在這種情況下，不是檢查問題，而是檢查給定的解。這種演算法從某種解開始，這種解可能已經給出或已經以某種方式構造出來，然後透過小的修改對其進行改進。對於一些問題，他們可以找到最優解，而對於其他問題，他們停留在區域性最優，也就是說，在解決方案，不能改進的演算法，但不是最優的。貪婪演算法最常用的用途是尋找最小生成樹，在這種方法中可以找到最優解。霍夫曼樹，克魯斯卡爾，普里姆，Sollin 是貪婪的演算法，可以解決這個最佳化問題。

 啟發式方法

 在最佳化問題中，如果不能找到最優解，可以使用啟發式演算法來尋找接近最優解的解。這些演算法的工作原理是隨著它們的進展越來越接近最優解。原則上，如果執行無限長的時間，他們會找到最優解。它們的優點是能夠在相對較短的時間內找到一個非常接近最優解的解。這些演算法包括本地搜尋、禁忌搜尋、模擬退火搜尋和遺傳演算法。其中一些演算法，如模擬退火，是非確定性演算法，而其他的，如禁忌搜尋，是確定性的。當非最優解的誤差界限已知時，該演算法進一步被歸類為近似演演算法。

8.4 透過研究領域

每個科學領域都有自己的問題，需要高效的演算法。一個領域中的相關問題經常被一起研究。一些示例類是搜尋演算法、排序演算法、合併演算法、數值演算法、圖形演算法、字串演算法、計算幾何演算法、組合演算法、醫學演算法、機器學習、密碼學、資料壓縮演算法和解析技術。

欄位之間往往相互重疊，一個欄位的演算法進步可能會改進其他欄位的演算法，有時候這些欄位完全不相關。例如，動態規劃是為了最佳化工業中的資源消耗而發明的，但是現在被用於解決許多領域中的廣泛問題。

8.5 透過複雜性

演算法可以根據它們需要完成的時間和它們的輸入大小進行分類:

常數時間：如果演算法所需的時間相同，則不管輸入大小如何。例如，對陣列元素的訪問。
對數時間：如果時間是輸入大小的對數函式。二進位制搜尋演算法。
線性時間：如果時間與輸入大小成正比。列表的遍歷。
多項式時間：如果時間是輸入大小的冪次。例如，氣泡排序演算法具有二次時間複雜度。
EXPTIME: 如果時間是輸入大小的指數函式。例如暴力搜尋法。

一些問題可能有多個不同複雜度的演算法，而另一些問題可能沒有演算法或者沒有已知的有效演算法。還有從一些問題到其他問題的對映。由於這個原因，我們發現根據問題的最佳可能演算法的複雜性，將問題本身分類比將演算法分為等價類更為合適。

8.6 連續演算法

形容詞 “連續” 用於 “演算法” 一詞時，可以表示:

對錶示連續數量的資料進行操作的演算法，即使這些資料是由離散近似表示的ーー這種演算法是在數值分析中研究的；或
一種微分方程形式的演算法，在模擬計算機上執行，不斷地對資料進行操作。
基本演算法
列舉
搜尋
深度優先搜尋
廣度優先搜尋
啟發式搜尋
遺傳演算法
資料結構的演算法
數論與代數演算法
計算幾何的演算法
凸包演算法
圖論的演算法
哈夫曼編碼
樹的遍歷
最短路徑演算法
最小生成樹演算法
最小樹形圖
網路流演算法
匹配演算法
分團問題
動態規劃
其他
數值分析
加密演算法
排序演算法
檢索演算法
隨機化演算法
關於並行演算法，請參閱平行計算一文。

參閱

抽象機器
演算法作曲
高階綜合
垃圾進，垃圾出
《演算法導論》
計算理論
可計算性理論
計算複雜性理論