演算法複雜性分析

epluguo發表於2013-09-25

演算法複雜性,這個東西,以前一直搞不大懂,很是苦惱。網上的資料不是很粗淺就是看不懂。大三了,看了門演算法的課,根據老師的描述以及自己查證的資料,終於對這複雜度有了一定的瞭解。

演算法複雜度主要體現在執行該演算法所需要的計算機資源的多少上。計算機的資源最重要的是時間和空間(儲存器)資源,所以,演算法的複雜性有時間複雜度以及空間複雜度之分。這兩個量集中反映了演算法的效率,而從具體執行該演算法的計算機中抽象出來。毫無疑問,一個好的演算法,追求的是更少的資源,出色的完成問題。換言之,就是時間複雜度儘可能低,空間複雜度也儘可能低。但這兩個傢伙往往是唱對臺的,時間複雜度低的代價一般是空間複雜度高,反之亦然。不過,在儲存硬體白菜價的這個年代,我們更多地追求時間複雜度。當然,具體情況具體考慮,但一般都會優先考慮時間複雜度。時間複雜性與空間複雜性概念雷同,計量方法相似,這裡重點討論時間複雜度。

在介紹時間複雜度之前,我們先引入幾個概念。

首先,設f(N)和g(N)是定義在正整數上的正函式。

如果存在正的常數C和自然數N。,使得當N≧N。時有f(N) ≦Cg(N),則稱函式f(N)當N充分大時上有界,且g(N)是它的一個上界,記為f(N)=O(g(N))。這時還說f(N)的階不高於g(N)的階。

如果存在正的常數C和自然數N。,使得當N≥N。時,有f(N) ≧Cg(N),則稱函式f(N)當N充分大時下有界,且g(N)是它的一個下界,記為f(N)= Ω(g(N))。這時還說f(N)的階不低於g(N)的階。顯然上界與下界的概念是相對的。

再來看下同階的概念。當f(N)=O(g(N))且f(N)= Ω(g(N))時,我們稱f(N)與g(N)同階。記為f(N)= θ(g(N))

沒打錯吧,數學概念的東西要嚴謹。學過高等數學的應該能很好理解上面的概念吧,學過數學分析的就更輕鬆了。額,覺得困難的只要理解第一條上界就好。

時間複雜度一般指考慮三種,即最壞情況,最好情況和平均情況。這三種情況都從某種角度反映出演算法的效率,各有各的好處,但同時也有各自的侷限性。實踐證明,可操作性最好且最有價值的是最壞情況下的時間複雜性。而上界的概念跟最壞情況下的時間複雜性有著很大的關聯,聰明的你應該可以想到吧。唔,當然,這個上界的階越低則評估越準確,結果就越有價值。而下界嘛,也可以用來評估演算法的時間複雜性,常常與符號O配合以證明某問題的一個特定演算法是該問題的最優演算法或該問題的某演算法類中的最優演算法。類似地,這個下界的階越高,則評估越準確,結果就越有價值。

好吧,將上界的概念移到了前面是為了更早一步接觸大O,但是想要更進一步理解大O演算法,這裡還需要再引入複雜性漸近形態的概念:

設T(N)是演算法A的複雜性函式。一般來說,當N單調增加且趨於∞時,T(N)也將單調增加趨於∞。對於T(N),如果存在使得當N->∞時,有那麼就說是T(N)當N->∞時的漸近性態,或者稱為演算法A當N->∞時的漸近複雜性而與T(N)相區別。

直觀上來說吧,是T(N)略去低階項所留下的主項,因為在趨於無窮時,高階變化的速度遠遠大於低階,所以可以將低階的略去。唔!從直觀上看來,它的確是比T(N)要簡單得多。

由於當N->∞時,T(N)漸近於,有理由用來代替T(N)作為演算法A在N->∞時的複雜性的度量。而且比T(N)明顯簡化得多,所以這種代替是對演算法複雜性分析的一種簡化。進一步吧,考慮到分析演算法複雜度的目的在於比較求解同一個問題的兩個不同演算法的效率。而比較的兩個演算法的漸近複雜性的階不相同時,只要能確定出各自的階,就可以判定哪一個演算法的效率高了。換句話說,這時的漸進複雜性分析只要關心的階就夠了,不必關心包含在中的常數因子。所以,常常又對

的分析進一步簡化,即假設演算法中用到的所有不同的元運算各執行一次所需要的時間都是一個單位時間。


這裡我們再來看看符號O的運算規則:

(1) O(f) + O(g) = O(max(f,g));

(2) O(f) + O(g) = O(f + g);

(3) O(f)O(g) = O(fg);

(4)如果g(N) = O(f(N)),則O(f) + O(g)= O(f);

(5) O(Cf(N)) = O(f(N)),其中C是一個正的常數;

(6) f=O(f)

 

我覺得精華是上面這個演繹,知其然不知其所以然,另外還得說一下,按照上面計算出來的是漸近時間複雜度,簡稱時間複雜度,當然我們一般求的也就是這個了。以前見到一些例如O(n),O(nlogn)之類的演算法複雜度,之所以不明白,就是因為不知道為什麼沒看過上面的過程,恨之晚矣。好了,其實都是有依據的,以前見到一個迴圈就是O(n),兩個巢狀就是平分階……其實都是可以算的。

好吧,我承認我列這麼多東西在這裡大大地加深了大家的迷惑,但我覺得上面的真的很有必要去理解下,我在很多書裡面都沒有看到上面的解析的,讓我大感鬱悶。下面我們來簡略的再次整理下吧,上面的看不懂?沒關係,按照下面的做就得了,改天回頭再看看前面的。下面的部分資訊百度出來的,懶得寫了。

根據各種概念定義,可以歸納出基本的計算步驟: 

1. 計算出基本操作的執行次數T(n) 
    基本操作即演算法中的每條語句(以;號作為分割),語句的執行次數也叫做語句的頻度。在做演算法分析時,一般預設為考慮最壞的情況。

2. 計算出T(n)的數量級 
    求T(n)的數量級,只要將T(n)進行如下一些操作:
    忽略常量、低次冪和最高次冪的係數

    令f(n)=T(n)的數量級。

3. 用大O來表示時間複雜度 
    當n趨近於無窮大時,如果lim(T(n)/f(n))的值為不等於0的常數,則稱f(n)是T(n)的同數量級函式。記作T(n)=O(f(n))。

 

下面我們簡單地來看看例項分析吧。

例1: 

int num1, num2;
for(int i=0; i<n; i++)
{ 
     num1 += 1;
     for(int j=1; j<=n; j*=2)
          { 
         num2 += num1;
     }
 } 

分析:
1.
語句int num1, num2;的頻度為1;
語句i=0;的頻度為1;
語句i<n; i++; num1+=1; j=1; 的頻度為n;
語句j<=n; j*=2; num2+=num1;的頻度為n*log2n;
T(n) = 2 + 4n + 3n*log2n

2.
忽略掉T(n)中的常量、低次冪和最高次冪的係數
f(n) = n*log2n

3.
lim(T(n)/f(n)) = (2+4n+3n*log2n) / (n*log2n)
                    = 2*(1/n)*(1/log2n) + 4*(1/log2n) + 3

當n趨向於無窮大,1/n趨向於0,1/log2n趨向於0
所以極限等於3。

T(n) = O(n*log2n)

簡化的計算步驟 

再來分析一下,可以看出,決定演算法複雜度的是執行次數最多的語句,這裡是num2 += num1,一般也是最內迴圈的語句。

並且,通常將求解極限是否為常量也省略掉?

於是,以上步驟可以簡化為: 

1. 找到執行次數最多的語句 
2. 計算語句執行次數的數量級
3.
用大O來表示結果 

繼續以上述演算法為例,進行分析:
1.
執行次數最多的語句為num2 += num1

2.
T(n) = n*log2n
f(n) = n*log2n

3.
// lim(T(n)/f(n)) = 1
T(n) = O(n*log2n)

 

 

 

最後,列出幾個比較有用的遞迴分治的時間複雜度計算公式吧。





常用的演算法的時間複雜度和空間複雜度:

排序法

最差時間分析

平均時間複雜度

穩定度

空間複雜度

氣泡排序

O(n2)

O(n2)

穩定

O(1)

快速排序

O(n2)

O(n*log2n)

不穩定

O(log2n)~O(n)

選擇排序

O(n2)

O(n2)

穩定

O(1)

二叉樹排序

O(n2)

O(n*log2n)

不一頂

O(n)

插入排序

O(n2)

O(n2)

穩定

O(1)

堆排序

O(n*log2n)

O(n*log2n)

不穩定

O(1)

希爾排序

O

O

不穩定

O(1)

 


相關文章