算法
算法和算法分析
算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每條指令表示一個或多個操作。
一個算法具有下列5個重要特性:
- 有窮性:一個算法必須總是(對任何合法的輸入值)在執行有限步之后結束,且每一步都可在有限時間內完成。有窮的概念不是純數學的,而是在實際上是合理的,可接受的。
- 確定性:算法中每一條指令必須有確切的含義,不會產生二義性。
- 可行性:一個算法是能行的,即算法中描述的操作都是可以通過已經實現的基本運算執行有限次來實現的。
- 輸入性:一個算法有零個或多個輸入。
- 輸出性:一個算法有一個或多個輸出。
算法設計的要求
算法設計應滿足以下幾條目標:
- 正確性:算法應當滿足具體問題的需求,這是最重要也是最基本的標准。
“正確”一詞的含義,大體可分為以下4個層次:
a.程序不含語法錯誤;
b.程序對於幾組輸入數據能夠得出滿足規格說明要求的結果;
c.程序對於精心選擇的典型、苛刻而帶有刁難性的幾組輸入數據能夠得出滿足規格說明要求的結果;
d.程序對於一切合法的輸入數據都能產生滿足規格說明要求的結果
-
可讀性:算法主要是為了人的閱讀與交流,其次才是機器執行。
可讀性好有助於人對算法的理解;晦澀難懂的程序易於隱藏較多錯誤,難以調試和修改。為了達到這個要求,算法的邏輯必須是清晰的、簡單的和結構化的。 -
健壯性:當輸入數據非法時, 算法也能適當地做出反應或進行處理, 而不會產生莫明其妙的輸出結果。
-
高效率與低存儲量需求
通俗地說,效率指的是算法執行的時間。對於同一個問題如果有多個算法可以解決,執行時間短的算法效率髙。存儲量需求指算法執行過程中所需要的最大存儲空間。效率與低存儲量需求這兩者都與問題的規模有關
算法效率的度量
計算機資源主要包括計算時間
和內存空間
。
-
算法分析是分析算法
占用計算機資源的情況
。所以算法分析的兩個主要方面是分析算法的時間復雜度和空間復雜度。 -
算法分析的目的不是分析算法是否正確或是否容易閱讀,主要是考察算法的時間和空間效率,以求改進算法或對不同的算法進行比較。
有兩種衡量算法效率的方法:
- 事后統計的方法
- 事前分析估算的方法
事后統計法存在的缺點:
一是必須先運行依據算法編制的程序;
二是所得時間的統計量依賴於計算機的硬件、軟件等環境因素,有時容易掩蓋算法本身的優劣。
因此人們常常采用另一種事前分析估算的方法:
事前分析估算的方法
一個用髙級程序語言編寫的程序在計算機上運行時所消耗的時間取決於下列因素:
① 依據的算法選用何種策略;
② 問題的規模,例如求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})\) 表示;
- 等等。