1. 程式人生 > 其它 >演算法與演算法分析

演算法與演算法分析

演算法

演算法和演算法分析

演算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每條指令表示一個或多個操作。

一個演算法具有下列5個重要特性:

  • 有窮性:一個演算法必須總是(對任何合法的輸入值)在執行有限步之後結束,且每一步都可在有限時間內完成。有窮的概念不是純數學的,而是在實際上是合理的,可接受的。
  • 確定性:演算法中每一條指令必須有確切的含義,不會產生二義性。
  • 可行性:一個演算法是能行的,即演算法中描述的操作都是可以通過已經實現的基本運算執行有限次來實現的。
  • 輸入性:一個演算法有零個或多個輸入。
  • 輸出性:一個演算法有一個或多個輸出。

演算法設計的要求

演算法設計應滿足以下幾條目標:

  • 正確性:演算法應當滿足具體問題的需求,這是最重要也是最基本的標準

“正確”一詞的含義,大體可分為以下4個層次:

a.程式不含語法錯誤;
b.程式對於幾組輸入資料能夠得出滿足規格說明要求的結果;
c.程式對於精心選擇的典型、苛刻而帶有刁難性的幾組輸入資料能夠得出滿足規格說明要求的結果;
d.程式對於一切合法的輸入資料都能產生滿足規格說明要求的結果

  • 可讀性:演算法主要是為了人的閱讀與交流,其次才是機器執行。
    可讀性好有助於人對演算法的理解;晦澀難懂的程式易於隱藏較多錯誤,難以除錯和修改。為了達到這個要求,演算法的邏輯必須是清晰的、簡單的和結構化的。

  • 健壯性:當輸入資料非法時, 演算法也能適當地做出反應或進行處理, 而不會產生莫明其妙的輸出結果。

  • 高效率與低儲存量需求
    通俗地說,效率指的是演算法執行的時間。對於同一個問題如果有多個演算法可以解決,執行時間短的演算法效率髙。儲存量需求指演算法執行過程中所需要的最大儲存空間。效率與低儲存量需求這兩者都與問題的規模有關

演算法效率的度量

計算機資源主要包括計算時間記憶體空間

  • 演算法分析是分析演算法佔用計算機資源的情況。所以演算法分析的兩個主要方面是分析演算法的時間複雜度和空間複雜度。

  • 演算法分析的目的不是分析演算法是否正確或是否容易閱讀,主要是考察演算法的時間和空間效率,以求改進演算法或對不同的演算法進行比較。

有兩種衡量演算法效率的方法

  1. 事後統計的方法
  2. 事前分析估算的方法

事後統計法存在的缺點:

一是必須先執行依據演算法編制的程式;
二是所得時間的統計量依賴於計算機的硬體、軟體等環境因素,有時容易掩蓋演算法本身的優劣。


因此人們常常採用另一種事前分析估算的方法:


事前分析估算的方法

一個用髙級程式語言編寫的程式在計算機上執行時所消耗的時間取決於下列因素:

① 依據的演算法選用何種策略;
② 問題的規模,例如求100以內還是1000以內的素數;
③ 書寫程式的語言,對於同一個演算法,實現語言的級別越高,執行效率就越低;
④ 編譯程式所產生的機器程式碼的質量;
⑤ 機器執行指令的速度。

這表明使用絕對的時間單位衡量演算法的效率是不合適的。

撇開這些與計算機硬體、軟體有關的因素,可以認為一個特定演算法“執行工作量”的大小,只依賴於問題的規模(通常用整數量n表示),或者說,它是問題規模的函式。

時間複雜度

一個演算法是由控制結構(順序、分支和迴圈3種)原操作(指固有資料型別的操作)構成的,演算法的執行時間取決於兩者的綜合效果。

演算法的執行時間主要與問題規模n有關。

例如,整數n的大小、陣列的元素個數、矩陣的階數等都可作為問題規模。

所謂一個語句的頻度,即指該語句在演算法中被重複執行的次數。
演算法中所有語句的頻度之和記做f(n),它是問題規模n的函式。
當問題規模n趨向無窮大時,f(n)的數量級(Order)稱為漸進時間複雜度,簡稱為時間複雜度,記作T(n)=O(f(n))。

演算法的時間量度記作:T(n)=O(f(n))
它表示隨問題規模n的增大,演算法執行時間的增長率和f(n)的增長率相同,稱做演算法的漸近時間複雜度(asymptotic time complexity),簡稱時間複雜度。

定理:

一個沒有迴圈的演算法中基本運算次數與問題規模n無 關,記作O(1),也稱作常量階
一個只有單迴圈的演算法中基本運算次數與問題規模n的增長呈線性增大關係,記作O(n),也稱線性階。
常用的還有平方階O(n2)、立方階O(n3)、對數階O(log2n)、指數階O(2n)等等。

  • 不同數量級對應的值存在著如下關係:

空間複雜度

一個上機執行的程式除了需要儲存空間來寄存本身所用指令、常數、變數和輸入資料外,也需要一些對資料進行操作的工作單元和儲存一些為實現計算所需資訊的輔助空間,如形參所佔空間和區域性變數所佔空間等。

對演算法進行儲存空間分析時,只考察區域性變數所佔空間。

演算法的臨時空間一般也作為問題規模n的函式,以數量級形式給出,記作:S(n) = O(g(n)),其中“O”的含義與時間複雜度中的含義相同。

若演算法所需臨時空間相對於輸入資料量來說是常數,則稱此演算法為原地工作或就地工作

若所需臨時空間依賴於特定的輸入,則通常按最壞情況來考慮。

總結

“好”演算法的標準

解決一個問題的方法可能有很多,但能稱得上演算法的,首先它必須能徹底解決這個問題(稱為準確性),且根據其編寫出的程式在任何情況下都不能崩潰(稱為健壯性)。

注意,程式和演算法是完全不同的概念。演算法是解決某個問題的想法、思路;而程式是在根據演算法編寫出來的真正可以執行的程式碼。例如,要依次輸出一維陣列中的資料元素的值,首先想到的是使用迴圈結構,在這個演算法的基礎上,我們才開始編寫程式。

在滿足準確性和健壯性的基礎上,還有一個重要的篩選條件,即通過演算法所編寫出的程式的執行效率。程式的執行效率具體可以從 2 個方面衡量,分別為:

  • 程式的執行時間。
  • 程式執行所需記憶體空間的大小。

根據演算法編寫出的程式,執行時間更短,執行期間佔用的記憶體更少,該演算法的執行效率就更高,演算法也就更好。

時間複雜度

判斷一個演算法所程式設計序執行時間的多少,並不是將程式編寫出來,通過在計算機上執行所消耗的時間來度量。原因很簡單,一方面,解決一個問題的演算法可能有很多種,一一實現的工作量無疑是巨大的,得不償失;另一方面,不同計算機的軟、硬體環境不同,即便使用同一臺計算機,不同時間段其系統環境也不相同,程式的執行時間很可能會受影響,嚴重時甚至會導致誤判。

表示一個演算法所程式設計序執行時間的多少,用的並不是準確值(事實上也無法得出),而是根據合理方法得到的預估值。

也許很多讀者對於“使用無限大的思想”簡化頻度表示式,並不是很清楚。沒關係,這裡給大家總結一下,在資料結構中,頻度表示式可以這樣簡化:

  • 去掉頻度表示式中,所有的加法常數式子。例如 \(2n^{2}+2n+1\)簡化為\(2n^{2}+2n\)
  • 如果表示式有多項含有無限大變數的式子,只保留一個擁有指數最高的變數的式子。例如 \(2n^{2}+2*n\)簡化為 \(2n^{2}\)
  • 如果最高項存在係數,且不為 \(1\),直接去掉係數。例如 \(2n^{2}\) 係數為 \(2\),直接簡化為 \(n^{2}\)

事實上,對於一個演算法(或者一段程式)來說,其最簡頻度往往就是最深層次的迴圈結構中某一條語句的執行次數。例如 2n+1 最簡為 n,實際上就是 a++ 語句的執行次數;同樣 2n2+2n+1 簡化為 n2,實際上就是最內層迴圈中 num++ 語句的執行次數。

空間複雜度

要知道每一個演算法所編寫的程式,執行過程中都需要佔用大小不等的儲存空間,例如:

  • 程式程式碼本身所佔用的儲存空間;
  • 程式中如果需要輸入輸出資料,也會佔用一定的儲存空間;
  • 程式在執行過程中,可能還需要臨時申請更多的儲存空間。

所以,如果程式所佔用的儲存空間和輸入值無關,則該程式的空間複雜度就為 \(O(1)\);反之,如果有關,則需要進一步判斷它們之間的關係:

  • 如果隨著輸入值 \(n\) 的增大,程式申請的臨時空間成線性增長,則程式的空間複雜度用 \(O(n)\) 表示;
  • 如果隨著輸入值 \(n\) 的增大,程式申請的臨時空間成 \(n^{2}\) 關係增長,則程式的空間複雜度用 \(O(n^{2})\) 表示;
  • 如果隨著輸入值 \(n\) 的增大,程式申請的臨時空間成 \(n^{3}\) 關係增長,則程式的空間複雜度用 \(O(n^{3})\) 表示;
  • 等等。