首先了解一下幾個概念。一個是時間復雜度,一個是漸近時間復雜度。前者是某個算法的時間耗費,它是該算法所求解問題規模n的函數,而后者是指當問題規模趨向無窮大時,該算法時間復雜度的數量級。
當我們評價一個算法的時間性能時,主要標准就是算法的漸近時間復雜度,因此,在算法分析時,往往對兩者不予區分,經常是將漸近時間復雜度T(n)=O(f(n))簡稱為時間復雜度,其中的f(n)一般是算法中頻度最大的語句頻度。
此外,算法中語句的頻度不僅與問題規模有關,還與輸入實例中各元素的取值相關。但是我們總是考慮在最壞的情況下的時間復雜度。以保證算法的運行時間不會比它更長。
常見的時間復雜度,按數量級遞增排列依次為:常數階O(1)、對數階O(log2n)、線性階O(n)、線性對數階O(nlog2n)、平方階O(n^2)、立方階O(n^3)、k次方階O(n^k)、指數階O(2^n)。
1. 大O表示法
定義
設一個程序的時間復雜度用一個函數 T(n) 來表示,對於一個查找算法,如下:
int seqsearch( int a[], const int n, const int x)
{
int i = 0;
for (; a[i] != x && i < n ; i++ );
if ( i == n) return -1;
else return i;
}
這個程序是將輸入的數值順序地與數組中地元素逐個比較,找出與之相等地元素。
在第一個元素就找到需要比較一次,在第二個元素找到需要比較2次,…… ,在第n個元素找到需要比較n次。對於有n個元素的數組,如果每個元素被找到的概率相等,那么查找成功的平均比較次數為:
f(n) = 1/n (n + (n-1) + (n-2) + ... + 1) = (n+1)/2 = O(n)
這就是傳說中的大O函數的原始定義。
用大O來表述
要全面分析一個算法,需要考慮算法在最壞和最好的情況下的時間代價,和在平均情況下的時間代價。對於最壞情況,采用大O表示法的一般提法(注意,這里用的是“一般提法”)是:當且僅當存在正整數c和n0,使得 T(n) <= c*f(n)對於所有的n >= n0 都成立。則稱該算法的漸進時間復雜度為T(n) = O(f(n))。這個應該是高等數學里面的第一章極限里面的知識。這里f(n) = (n+1)/2, 那么c * f(n)也就是一個一次函數。就是在圖象上看就是如果這個函數在c*f(n)的下面,就是復雜度為T(n) = O(f(n))。
對於對數級,我們用大O記法記為O(log2N)就可以了。
規則
1) 加法規則
T(n,m) = T1(n) + T2(n) = O ( max (f(n), g(m) )
2) 乘法規則
T(n,m) = T1(n) * T2(m) = O (f(n) * g(m))
3)一個特例
在大O表示法里面有一個特例,如果T1(n) = O©, c是一個與n無關的任意常數,T2(n) = O ( f(n) ) 則有
T(n) = T1(n) * T2(n) = O ( c*f(n) ) = O( f(n) ).
也就是說,在大O表示法中,任何非0正常數都屬於同一數量級,記為O(1)。
4)一個經驗規則
有如下復雜度關系
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
其中c是一個常量,如果一個算法的復雜度為c 、 log2N 、n 、 n*log2N ,那么這個算法時間效率比較高 ,如果是 2^n , 3^n ,n!,那么稍微大一些的n就會令這個算法不能動了,居於中間的幾個則差強人意.
1)基本知識點:沒有循環的一段程序的復雜度是常數,一層循環的復雜度是O(n),兩層循環的復雜度是O(n^2)? (我用^2表示平方,同理 ^3表示立方);
2)二維矩陣的標准差,殘差,信息熵,fft2,dwt2,dct2的時間復雜度: 標准差和殘差可能O(n),FFT2是O(nlog(n)),DWT2可能也是O(nlog(n));信息熵要求概率,而dct的過程和jpeg一樣。因為和jpeg一樣,對二難矩陣處理了.Y=T*X*T',Z=Y.*Mask,這樣子,還有分成8*8子圖像了;
3)example:
1、設三個函數f,g,h分別為 f(n)=100n^3+n^2+1000 , g(n)=25n^3+5000n^2 , h(n)=n^1.5+5000nlgn
請判斷下列關系是否成立:
(1) f(n)=O(g(n))
(2) g(n)=O(f(n))
(3) h(n)=O(n^1.5)
(4) h(n)=O(nlgn)
這里我們復習一下漸近時間復雜度的表示法T(n)=O(f(n)),這里的"O"是數學符號,它的嚴格定義是"若T(n)和f(n)是定義在正整數集合上的兩個函數,則T(n)=O(f(n))表示存在正的常數C和n0 ,使得當n≥n0時都滿足0≤T(n)≤C?f(n)。"用容易理解的話說就是這兩個函數當整型自變量n趨向於無窮大時,兩者的比值是一個不等於0的常數。這么一來,就好計算了吧。
◆ (1)成立。題中由於兩個函數的最高次項都是n^3,因此當n→∞時,兩個函數的比值是一個常數,所以這個關系式是成立的。
◆ (2)成立。與上同理。
◆ (3)成立。與上同理。
◆ (4)不成立。由於當n→∞時n^1.5比nlgn遞增的快,所以h(n)與nlgn的比值不是常數,故不成立。
2、設n為正整數,利用大"O"記號,將下列程序段的執行時間表示為n的函數。
(1) i=1; k=0
while(i<n)
{ k=k+10*i;i++;
}
解答:T(n)=n-1, T(n)=O(n), 這個函數是按線性階遞增的。
(2) x=n; // n>1
while (x>=(y+1)*(y+1))
y++;
解答:T(n)=n1/2 ,T(n)=O(n1/2),最壞的情況是y=0,那么循環的次數是n1/2次,這是一個按平方根階遞增的函數。
(3) x=91; y=100;
while(y>0)
if(x>100)
{x=x-10;y--;}
else x++;
解答: T(n)=O(1),這個程序看起來有點嚇人,總共循環運行了1000次,但是我們看到n沒有? 沒。這段程序的運行是和n無關的,就算它再循環一萬年,我們也不管他,只是一個常數階的函數。
同一問題可用不同算法解決,而一個算法的質量優劣將影響到算法乃至程序的效率。算法分析的目的在於選擇合適算法和改進算法。一個算法的評價主要從時間復雜度和空間復雜度來考慮。
1、時間復雜度
(1)時間頻度
一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但我們不可能也沒有必要對每個算法都上機測試,只需知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。並且一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。
(2)時間復雜度
在剛才提到的時間頻度中,n稱為問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時我們想知道它變化時呈現什么規律。為此,我們引入時間復雜度概念。
一般情況下,算法中基本操作重復執行的次數是問題規模n的某個函數,用T(n)表示,若有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值為不等於零的常數,則稱f(n)是T(n)的同數量級函數。記作T(n)=O(f(n)),稱O(f(n)) 為算法的漸進時間復雜度,簡稱時間復雜度。
在各種不同算法中,若算法中語句執行次數為一個常數,則時間復雜度為O(1),另外,在時間頻度不相同時,時間復雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不同,但時間復雜度相同,都為O(n2)。
按數量級遞增排列,常見的時間復雜度有:
常數階O(1),對數階O(log2n),線性階O(n),
線性對數階O(nlog2n),平方階O(n2),立方階O(n3),...,
k次方階O(nk),指數階O(2n)。隨着問題規模n的不斷增大,上述時間復雜度不斷增大,算法的執行效率越低。
2、空間復雜度
與時間復雜度類似,空間復雜度是指算法在計算機內執行時所需存儲空間的度量。記作:
S(n)=O(f(n))
我們一般所討論的是除正常占用內存開銷外的輔助存儲單元規模。討論方法與時間復雜度類似,不再贅述。
(3)漸進時間復雜度評價算法時間性能
主要用算法時間復雜度的數量級(即算法的漸近時間復雜度)評價一個算法的時間性能。
【例3.7】有兩個算法A1和A2求解同一問題,時間復雜度分別是T1(n)=100n2,T2(n)=5n3。
(1)當輸入量n<20時,有T1(n)>T2(n),后者花費的時間較少。
(2)隨着問題規模n的增大,兩個算法的時間開銷之比5n3/100n2=n/20亦隨着增大。即當問題規模較大時,算法A1比算法A2要有效地多。
它們的漸近時間復雜度O(n2)和O(n3)從宏觀上評價了這兩個算法在時間方面的質量。在算法分析時,往往對算法的時間復雜度和漸近時間復雜度不予區分,而經常是將漸近時間復雜度T(n)=O(f(n))簡稱為時間復雜度,其中的f(n)一般是算法中頻度最大的語句頻度。
【例3.8】算法MatrixMultiply的時間復雜度一般為T(n)=O(n3),f(n)=n3是該算法中語句(5)的頻度。下面再舉例說明如何求算法的時間復雜度。
【例3.9】交換i和j的內容。
Temp=i;
i=j;
j=temp;
以上三條單個語句的頻度均為1,該程序段的執行時間是一個與問題規模n無關的常數。算法的時間復雜度為常數階,記作T(n)=O(1)。
如果算法的執行時間不隨着問題規模n的增加而增長,即使算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間復雜度是O(1)。
【例3.10】變量計數之一。
(1) x=0;y=0;
(2) for(k-1;k<=n;k++)
(3) x++;
(4) for(i=1;i<=n;i++)
(5) for(j=1;j<=n;j++)
(6) y++;
一般情況下,對步進循環語句只需考慮循環體中語句的執行次數,忽略該語句中步長加1、終值判別、控制轉移等成分。因此,以上程序段中頻度最大的語句是(6),其頻度為f(n)=n2,所以該程序段的時間復雜度為T(n)=O(n2)。
當有若干個循環語句時,算法的時間復雜度是由嵌套層數最多的循環語句中最內層語句的頻度f(n)決定的。
【例3.11】變量計數之二。
(1) x=1;
(2) for(i=1;i<=n;i++)
(3) for(j=1;j<=i;j++)
(4) for(k=1;k<=j;k++)
(5) x++;
該程序段中頻度最大的語句是(5),內循環的執行次數雖然與問題規模n沒有直接關系,但是卻與外層循環的變量取值有關,而最外層循環的次數直接與n有關,因此可以從內層循環向外層分析語句(5)的執行次數:
則該程序段的時間復雜度為T(n)=O(n3/6+低次項)=O(n3)。
(4)算法的時間復雜度不僅僅依賴於問題的規模,還與輸入實例的初始狀態有關。
【例3.12】在數值A[0..n-1]中查找給定值K的算法大致如下:
(1)i=n-1;
(2)while(i>=0&&(A[i]!=k))
(3) i--;
(4)return i;
此算法中的語句(3)的頻度不僅與問題規模n有關,還與輸入實例中A的各元素取值及K的取值有關:
①若A中沒有與K相等的元素,則語句(3)的頻度f(n)=n;
②若A的最后一個元素等於K,則語句(3)的頻度f(n)是常數0。
(5)最壞時間復雜度和平均時間復雜度
最壞情況下的時間復雜度稱最壞時間復雜度。一般不特別說明,討論的時間復雜度均是最壞情況下的時間復雜度。
這樣做的原因是:最壞情況下的時間復雜度是算法在任何輸入實例上運行時間的上界,這就保證了算法的運行時間不會比任何更長。
【例3.19】查找算法【例1·8】在最壞情況下的時間復雜度為T(n)=0(n),它表示對於任何輸入實例,該算法的運行時間不可能大於0(n)。
平均時間復雜度是指所有可能的輸入實例均以等概率出現的情況下,算法的期望運行時間。
常見的時間復雜度按數量級遞增排列依次為:常數0(1)、對數階0(log2n)、線形階0(n)、線形對數階0(nlog2n)、平方階0(n2)立方階0(n3)、…、k次方階0(nk)、指數階0(2n)。顯然,時間復雜度為指數階0(2n)的算法效率極低,當n值稍大時就無法應用。
類似於時間復雜度的討論,一個算法的空間復雜度(Space Complexity)S(n)定義為該算法所耗費的存儲空間,它也是問題規模n的函數。漸近空間復雜度也常常簡稱為空間復雜度。算法的時間復雜度和空間復雜度合稱為算法的復雜度。