程式是用來解決問題的,是由多個步驟或過程組成的,這些步驟和過程就是解決問題的演算法。解決乙個問題有多種方法,也就有多種演算法。每一種演算法都可以達到解決問題的目的,但花費的成本和時間不盡相同,從節約成本和時間的角度考慮,需要找出最優演算法。那麼,如何衡量乙個演算法的好壞呢?顯然,選用的演算法應該是正確的(演算法的正確性不在此論述)。除此之外,通常有三個方面的考慮:
(1)演算法在執行過程中所消耗的時間;
(2)演算法在執行過程中所佔資源的大小,例如,占用記憶體空間的大小;
(3)演算法的易理解性、易實現性和易驗證性等等。
衡量乙個演算法的好壞,可以通過前面提出的三個方面進行綜合評估。從多個候選演算法中找出執行時間短、資源占用少、易理解、易實現的演算法。然而,現實情況卻不盡人意。往往是,乙個看起來很簡便的演算法,其執行時間要比乙個形式上覆雜的演算法慢的多;而乙個執行時間較短的演算法往往占用較多的資源。
因此,在不同情況下需要選擇不同的演算法。在實時系統中,對系統響應時間要求高,則盡量選用執行時間少的演算法;當資料處理量大,而儲存空間較少時,則盡量選用節省空間的演算法。
乙個演算法在執行過程中所消耗的時間取決於下面的因素:
(1)演算法所需資料輸入的時間;
(2)演算法編譯為可執行程式的時間;
(3)計算機執行每條指令所需的時間;
(4)演算法語句重複執行的次數。
其中(1)依賴於輸入裝置的效能,若是離線輸入,則輸入資料的時間可以忽略不計。(2)(3)取決於計算機本身執行的速度和編譯程式的效能。因此,習慣上將演算法語句重複執行的次數作為演算法的時間量度。
例如:
add函式僅包含一條語句,執行次數為1次;madd函式包含n次的單重for迴圈,每次迴圈都執行x=x+1語句,因此執行次數為n次;loopadd函式包含n次的雙重迴圈,在第二層迴圈執行x=x+1語句,因此執行次數為n*n次,即n^2次。
上面的例子並沒有把迴圈本身執行的次數算進去,下面給出2個執行次數計算較為精確的例子。
上面的程式段執行次數為2n+3次。
再看下面矩陣相加的例子。
矩陣相加的執行次數為2n^2+2n+2。
一般情況下,n為問題規模(大小)的量度,如陣列的長度、矩陣的階、圖中的頂點數等等。
對於前面add函式來說,問題規模量度為常數(1);對於陣列排序問題來說,問題規模量度為輸入陣列的長度(記為n);對於n階矩陣相加來說,問題規模量度為矩陣階數的平方(記為n^2)。
為了給出演算法通用的時間量度,用數學概念來描述演算法的執行次數,可以把乙個演算法中語句的執行次數稱為語句頻度或時間頻度,記為t(n)。當問題規模n不斷變化時,時間頻度t(n)也會不斷變化,我們需要評估當n不斷變化時,時間頻度t(n)的變化規律。
若有某個輔助函式f(n),當n趨向於無窮大時,如果t(n)/ f(n)的極限為不等於零的常數,則認為t(n)與f(n)是同量級的函式,記作:t(n) =o(f(n)),o(f(n))稱為演算法的漸進時間複雜度,簡稱時間複雜度。
漸進時間複雜度表示的意義是:
(1)在較複雜的演算法中,進行精確分析是非常複雜的;
(2)一般來說,我們並不關心t(n)的精確度量,而只是關心其量級。
t (n) = o(f (n)) 表示存在乙個常數c,當n趨於正無窮大時,總有t (n) ≤ c * f(n),其意義是t(n)在n趨於正無窮大時跟f(n)基本接近,因此完全可以用f(n)來表示t(n)。
o(f (n))通常取執行次數中最高次方或最大指數部分的項。例如:
(1)陣列元素相加為2n+3 = o(n)
(2)矩陣相加為2n^2+2n+1 = o(n^2)
(3)矩陣相乘為2n3+4n2+2n+2 = o(n^3)
在各種不同的演算法中,若演算法語句的執行次數為常數,則演算法的時間複雜度為o(1),按數量級遞增排列,常見的時間複雜度量有:
(1)o(1):常量階,執行時間為常量
(2)o(logn):對數階,如二分搜尋演算法
(3)o(n):線性階,如n個數內找最大值
(4)o(nlogn):對數階,如快速排序演算法
(5)o(n^2):平方階,如選擇排序,氣泡排序
(6)o(n^3):立方階,如兩個n階矩陣的乘法運算
(7)o(2^n):指數階,如n個元素集合的所有子集的演算法
(8)o(n!):階乘階,如n個元素全部排列的演算法
下圖給出了隨著n的變化,不同量級的時間複雜度變化曲線。
圖1 時間複雜度變化曲線圖
評估演算法時間複雜度的具體步驟是:
(1)找出演算法中重複執行次數最多的語句的頻度來估算演算法的時間複雜度;
(2)保留演算法的最高次冪,忽略所有低次冪和高次冪的係數;
(3)將演算法執行次數的數量級放入大ο記號中。
例如,下列三個簡單的程式段:
在程式段(a)中,語句x=x+1不在任何乙個迴圈體內,則它的時間頻度為1,其執行時間是個常量;而(b)中,同一語句被重複執行n次,其時間頻度為n;顯然在(c)中,該語句的頻度為n2。由此,這三個程式段的時間複雜度為o(1)、o(n)、o(n2)。分別為常量、線性階和平方階。
對於較為複雜的演算法,可以將它們分割成容易估算的幾個部分,然後利用o的求和原則得到整個演算法的時間複雜度。例如,若演算法的兩個部分的時間複雜度分別為t1(n)=o(f(n))和t2(n)=o(g(n)),則總的時間複雜度為:
t(n)= t1(n)+ t2(n)=o(max(f(n), g(n)))
然而,很多演算法的執行時間不僅依賴於問題的規模,也與處理的資料集有關。例如,有的排序演算法對某些原始資料(如自小至大有序),則其時間複雜度為o(n),而對另一些資料可達o(n^2)。因此,在估算演算法的時間複雜度時,均以資料集中最壞的情況來估算。
總結:
如果乙個演算法的執行次數是 t(n),那麼只保留最高次項,同時忽略最高項的係數後得到函式 f(n),此時演算法的時間複雜度就是 o(f(n))。
既然時間複雜度不是用來計算程式具體耗時的,那麼我也應該明白,空間複雜度也不是用來計算程式實際占用的空間的。
空間複雜度是對乙個演算法在執行過程中臨時占用儲存空間大小的乙個量度,同樣反映的是乙個趨勢,我們用 s(n) 來定義。
空間複雜度比較常用的有:o(1)、o(n)、o(n²),我們下面來看看:
空間複雜度 o(1)
如果演算法執行所需要的臨時空間不隨著某個變數n的大小而變化,即此演算法空間複雜度為乙個常量,可表示為 o(1)
int i =1;
int j =2;
++i;
j++;
int m = i + j;
**中的 i、j、m 所分配的空間都不隨著處理資料量變化,因此它的空間複雜度 s(n) = o(1)
空間複雜度 o(n)
這段**中,第一行new了乙個陣列出來,這個資料占用的大小為n,這段**的2-6行,雖然有迴圈,但沒有再分配新的空間,因此,這段**的空間複雜度主要看第一行即可,即 s(n) = o(n)
int[
] m =
newint
[n]for
(i=1
; i<=n;
++i)
演算法時間複雜度和空間複雜度
在進行演算法分析時,語句總的執行次數t n 是關於問題規模n的函式,進行分析t n 隨著n的變化情況並確定t n 的數量級。演算法的時間複雜度,也就是演算法的時間度量,記作 t n o f n 它表示隨著問題規模n的增大,演算法執行時間的增長率和f n 的增長率相同,稱作演算法的漸近時間複雜度,簡稱...
演算法時間複雜度和空間複雜度
執行演算法所需要的時間 空間複雜度 執行演算法所需要的記憶體空間 常見時間複雜度 例如 常數階o 1 線性階o n 平方階o n 2 立方階o n 3 對數階o log2n nlog2n階o nlog2n 指數階o n n 效率從大到小 o 1 o log2n o n o nlog2n o n 2 ...
演算法時間複雜度和空間複雜度
為了在函式之間建立一種相對的級別,我們通過比較其相對增長率有以下兩個定義 如果存在正常數c和n0使得當n n0時t n cf n 則記為t n o f n 即f n 增長率大於等於t n f n 是其上界 如果存在正常數c和n0使得當n n0時t n cf n 則記為t n f n 即f n 增長率...