算法的時間複雜度

[轉自:http://blog.sciencenet.cn/blog-340399-861142.html ]html

其實說那麼多,只要記住一句話: 109 的數據能夠在一秒內跑出(能夠由此來判斷本身的算法是否科學)算法

簡介:算法時間複雜度的研究,以所謂的多項式時間作爲最低複雜度,由此認定只有多項式類型的算法程序執行最快,實在有些不靠譜。實際上計算機程序執行時間能夠用其編譯以後的機器語言程序來計算。由於每一機器指令(彙編指令)的指令週期都是肯定的,故計算程序執行的時間,可先計算出每條機器指令重複執行次數,而後與指令週期相乘求和,最終得到準確時間。本文給出了的算法程序執行中指令重複執行次數的二元函數基本計算公式,依據該計算公式能夠實際計算出程序運行的時間消耗,又提出了程序設計方法轉換爲一元函數以後,如何斷定在一樣的循環重複數之下,斷定不一樣算法指令重複執行次數增長的快慢,進而斷定各類算法程序執行時間的變化。編程

  1. 背景

爲了研究算法程序完成任務運行時間長短,人們引入了算法時間複雜度的概念。函數

在維基百科中,算法時間複雜度被定義爲:在計算機科學中,算法的時間複雜度是一個函數,它定量描述了該算法的運行時間。這是一個關於表明算法輸入值的字符串的長度的函數。時間複雜度經常使用大O符號表述,不包括這個函數的低階項和首項係數。atom

在百度文庫中有計算方法解釋:通常狀況下,算法的基本操做重複執行的次數是模塊n的某一個函數f(n),所以,算法的時間複雜度記作:T(n)=O(f(n))。若 T(n)/f(n) 求極限可獲得一常數c,則時間複雜度T(n) = O(f(n))。spa

不論維基百科仍是百度的解釋,都稱算法時間複雜度是程序運行時間的定量描述。實際上,咱們見到的大O符號表述只能算定性描述,根本談不上定量。設計

爲了可以確實對算法程序運行時間進行定量分析,本文從單處理器計算機指令設計與程序執行的原理出發,依據程序設計的基本結構,提出了一種比較切合實際的程序執行時間計算方法。結果會見到有限時間的程序運行計算,均可以表達成一種二元的多項式形式。htm

  1. 指令重複執行次數

咱們知道,不論任何形式的計算機設計語言程序都要轉化成機器語言程序纔可以執行。所以,用與機器語言一一對應的彙編語言來討論程序執行時間才應該是最準確的。不過因爲各類語言程序在執行時間分析上具備必定的一致性,故而做定性分析時,也能夠替代彙編語言。blog

不論何種計算機程序設計語言,就程序設計的基本結構來講都是一致的。程序的基本結構分爲:順序、分支、循環和子程序調用。任何計算機程序的執行均可以說是這幾種程序結構的重複,因此計算程序執行過程所消耗的時間,都離不開對程序基本結構的分析。ip

程序執行的過程是由每一個指令執行的過程組成的,於是程序執行的時間就是指令執行時間的累加。在彙編程序語言中,每條指令執行的時間消耗是肯定的。特別是那些指令週期相等的計算機系統設計,用單一指令執行的時間乘以所有指令重複執行的次數,就可以計算出程序執行所須要的時間。若是計算機的指令週期不相同,則能夠用每條指令的指令週期作爲係數,進行所謂的加權求和來計算程序執行的時間。所以,算法程序執行時間的消耗,最關鍵的是計算出每條指令重複執行的次數。

  1. 指令重複執行次數公式

咱們將彙編程序不在執行狀態的每條指令叫「寫出的指令」,計算指令重複執行次數,就與這種寫出的指令有關。

指令重複執行次數是由程序的基本結構所決定的。在順序程序結構中寫出的指令重複執行次數是1。而在循環結構中,寫出的指令重複執行次數與循環的次數一致。程序執行中消耗時間最長的就是循環程序結構。直觀地說,循環的次數越多,處在循環體內的寫出指令重複執行次數越多,於是累計耗時就越長。在此種觀點之下,整個程序就能夠當作是多層循環結構,能夠按照多層循環結構來計算程序執行時間。固然,子程序能夠單獨計算子程序執行的時間。因爲分支程序結構的程序分支選擇老是唯一的,故時間計算基本上等同於順序結構。

咱們將順序與分支都認定爲0層循環,那麼程序運行的耗時計算,能夠創建在多層循環結構的耗時計算上,也就是循環結構的寫出指令重複執行次數計算上。若是用函數來描述,那麼一個程序的指令重複執行次數T應爲層內循環次數n與循環層數k的二元函數,即T=f(n,k)。爲說明問題簡單通俗,咱們經過c程序的例子加以解釋。

例1,假設k=n,那麼冪nn結構的n重循環用c語言描述以下。

for( i1 =1; i0 <=n; i1 ++){

for( i2 =1; i1 <=n; i2 ++){

……

for( in =1; in <=n; in ++){

no1 += 1;

no2 += no1 ;

}}…}

for語句條件表達式的初值語句在每層只執行一次,而定界和步長語句都要執行n次。顯然最內層每一個語句重複循環次數是nn,向外各層依次爲 nn1 nn2 、…、 n2 、n。那麼總的語句重複執行次數爲1+3n+ 3n2 + 3n3 +…+ 3nn2 + 3nn1 + 4nn

對於一個程序,在順序結構中,指令重複執行次數是1;在單層n次循環結構中,指令重複執行的次數是n;在k層n次循環當中寫出的指令,其重複執行的次數是 nk 。因而各層指令重複執行的次數可用公式

f(n,k) = a0 + a1n + a2n2 + ... + ak1nk1 + aknk 來計算,其中 ai 是各循環層的寫出指令數,i=0,1,2,…,k。

若是將公式(1)的k認爲是常量,那麼(1)式不就是一個多項式嗎?咱們能說這種形式的算法複雜度程序執行最快嗎?

  1. 計算類型實例

公式(1)中,若冪指數最高固定爲常數k,則獲得k次多項式;若冪底數固定爲常數k,則獲得指數多項式;若1層循環次數從1開始,逐層循環次數加1遞增,則獲得階乘多項式。如此變化循環層數或循環次數,則能夠獲得各類重複執行計算的實際公式。爲了說明這方面的演變,咱們再舉兩個實例。

例2,將(1)式的冪底數(循環次數)設定爲常數2,則可以獲得指數型指令重複執行的多項式表達式 a0 + a12 + a222 +…+ an12n1 + an2n

例3,設定冪底數是常數k,下面的循環結構能夠實現nlogkn型的指令重複執行次數。

for( i=0; i < n; i++){

for( j=k; j<=n; j*=k){

no1 += 1;

no2 += no1 ;

}}

由j*=k知j的值循環變化序列爲k, k2 , k3 ,…, kt =n,因而內層循環次數t= logk n。

因爲這個程序的外層循環次數是n,內層循環次數是logkn,於是程序指令重複執行次數總共是1+2n+n+4n logk n=1+3n+4n logk n。

  1. 程序執行時間比較

關於算法程序時間複雜度,一種流行的觀點認爲多項式類型的算法複雜度較低,並且認爲多項式類型算法程序運行耗時最少。其實在大多數狀況下這是一種錯覺。

從公式(1)咱們知道,決定算法程序執行時間長短的有兩個因素,一個是多項式的冪指數k,另外一個是多項式的冪底數n。當冪指數k較大的時候,雖然它是一個常數,咱們也不可以認爲多項式時間類型算法程序運行時間消耗會最少。特別在n < k時,從(1)式當即知道, nk 多項式型算法程序時間的消耗會大於冪 nn 型算法程序的耗時。這說明在較大的循環層數的範圍內,不可以就認爲多項式時間具備較低的複雜度,固然也不可以認爲這種狀況的算法程序耗時最短。咱們不妨僅就一項來進行比較。例如設n=5,k=7,那麼 nk = 57 =78125;而 nn = 55 =3125。因而可知用多項式時間複雜度來講明算法程序耗時長短是不太靠譜的事情。有人也許會說,n趨於無窮纔對。想象一下,n趨於無窮大對程序執行的時間計算的意義有多大?

  1. 算法指令增速

在前面咱們已經指出,程序執行時間要用指令重複執行次數爲計算單位。並給出了二元多項式型計算公式。這天然會引出什麼樣的算法程序耗時較多,什麼樣的算法程序耗時較少的問題。因爲一樣的問題或任務能夠採用不一樣形式的算法解決,故而研究不一樣算法程序執行耗時的多少具備現實意義。

6.1 指令增速

將(1)這個公式進行一元函數變換,即或者固定比較次數,或者固定循環層數,能夠獲得各類與實際問題有關的一元函數。例如咱們用c表示常數,n表示變量,(1)式能夠演化出 nc cn nn 、n logc n等多種類型程序指令重複執行次數的計算公式。不一樣的類型公式直接與程序執行的耗時有關,於是咱們有興趣去研究,那些可以斷定出採用何種類型算法更可以耗時較少的方法。

爲了統一且不失通常性,咱們讓循環各層參與執行的指令都儘量的少些,好比各層執行的指令只有一個。那麼由(1)式能夠獲得下面一些類型的指令重複次數的一元函數表達式。這個函數咱們用F(n)表示。

F(n)= 1c + 2c + 3c +…+ nc (2)

F(n)= 20 + 21 + 22 +…+ 2n1 + 2n (3)

F(n)=1!+2!+3!+…+(n-1)!+n! (4)

F(n)= lg1+2lg2+3lg3+…+(n-1)lg(n-1)+nlgn (5)

F(n)= 11 + 22 + 33 +…+ (n1)n1 + nn (6)

這些表達式中的變量n或者表示循環中指令重複執行次數,或者表示程序循環結構的層數,也能夠具備二者同步的屬性。爲了研究各類算法程序執行過程當中F(n)的增加快慢,咱們引入F(n)的指令增加速度概念。

定義1:F(n)中變量n增長一個單位的指令重複次數增加量,稱爲指令增速。

由定義可知指令增速是與n有關的函數。將定義1用表達式寫出,並用υ(n)來記,則有:υ(n)=F(n) - F(n-1)

因而,表達式(2)的指令n點增速υ(n)= nc

表達式(3)的指令增速υ(n)= 2n

表達式(4)的指令增速υ(n)=n!;

表達式(5)的指令增速υ(n)= nlgn;

表達式(6)的指令增速υ(n)= nn

例,n=10,求上列各算法指令增速。

表達式(2)的指令增速是υ(n)= nc = 10c .

表達式(3)的指令增速是υ(n)= 2n = 210 =1024.

表達式(4)的指令增速是υ(n)=n!=10!=3628800.

表達式(5)的指令增速是υ(n)= nlgn=10 lg10=10.

表達式(6)的指令增速是υ(n)= nn = 1010 =10000000000.

指令增速不是表示算法程序執行時間縮短的指標,而是在算法中指令重複執行的次數增長的快慢指標。若是指令增速較大,則說明在某種程序設計之下指令重複執行的次數增長很快。例如在n=9到n=10的變化中,上面各類算法程序指令增速最快的是冪 nn 型,指令增速最慢的是nlgn型。所謂的多項式型由指數常量肯定,c=1時指令增速最慢,如果c的值超過10,那麼在這個地方,會成爲指令增速最快的算法形式。於是咱們說所謂的O( nk )表示的多項式時間程序執行最快是不靠譜的。

公式(1)的每一種肯定的變化形式都表明一種算法。指令增速無疑可以表示在肯定的循環次數或循環層數處指令重複執行次數的增加快慢。這天然能夠大體描述n值附近的程序執行耗時的變化。例如n=10,咱們當即能夠計算出由n=9變到n=10的指令增速,從而斷定出那種算法在此處耗時增長快慢。

6.2 指令相對變化率

依據定義1咱們可以計算出指令增速表達式,可是因爲在n的值變更的狀況下,咱們仍然不能直接比較出各類形式的表達式指令增速的快慢,爲了可以在統一的範圍內進行比較,咱們引入單位指令重複執行次數下的指令增速,即指令相對變化率的概念。

定義2:指令增速與指令重複執行次數的比,稱爲指令相對變化率。

用μ表示指令相對變化率,即有μ=υ(n)/ F(n)。由指令相對變化率的大小就能夠知道,不一樣類型算法的指令增速對程序執行時間長短的影響程度。

這樣,表達式(2)的指令相對變化率

μ=υ(n)/ F(n)= nc /( 1c + 2c + 3c +…+ nc );

表達式(3)的指令相對變化率

μ=υ(n)/ F(n)= 2n /( 20 + 21 + 22 +…+ 2n1 + 2n );

表達式(4)的指令相對變化率

μ=υ(n)/ F(n)= n! /(1!+2!+3!+…+(n-1)!+n!);

表達式(5)的指令相對變化率

μ=υ(n)/ F(n)= nlgn /( lg1+2lg2+3lg3+…+(n-1)lg(n-1)+nlgn);

表達式(6)的指令相對變化率

μ=υ(n)/ F(n)= nn /( 11 + 22 + 33 +…+ (n1)n1 + nn )。

在這些公式中只要肯定n值,就能夠求出指令相對變化率。

6.3 算法指令增速

作爲一種算法,通常都要研究公式(1)中k或n的全局變化狀況。這種變化會隨着k與n的值超大變化帶來諸多的未知情況,爲此對k或n向着無窮大方向的變化,就體現了與算法相關的指令執行重複數的總體變化規律。對於一元函數的狀況,咱們引入算法指令增速的概念。

定義3:指令相對變化率的極限稱爲算法指令增速。

咱們但願在n較大的時候,經過算法一元函數全局的變化,來尋找算法類型對指令執行重複數的增加規律。如此咱們對上面的幾種類型指令相對變化率求極限。

表達式(2)的算法指令增速爲

nc /( 1c + 2c + 3c +…+ n1c + nc )=0;

表達式(3)的算法指令增速爲 lim 2n /( 20 + 21 + 22 +…+ 2n1 + 2n =1/( 2n + 2n+1 +…+ 21 +1)=1/2;這是一個等比序列,若是冪底數是常數c,那麼結果將是(c-1)/c。

表達式(4)的算法指令增速爲 lim n! /(1!+2!+3!+…+(n-1)!+n!)=1

表達式(5)的算法指令增速爲 lim nlgn /( lg1+2lg2+3lg3+…+(n-1)lg(n-1)+nlgn)

這個極限是多少?我沒能求出,依指令增速判斷應該是0。請有興趣的讀者幫我補充一下。

表達式(6)的算法指令增速爲 lim nn /( 11 + 22 + 33 +…+ (n1)n1 + nn )=1

(上面各極限n->∞)

由指令增速與指令重複執行次數的定義可知,μ=υ(n)/ F(n)是一個0與1之間的數。於是0是最小的算法指令增速,1是最大的算法指令增速。由此知從算法全局來看,多項式型算法的指令增速最小,階乘型或冪指型增速最快,而指數型介於它們之間。

須要說明,算法指令增速是對算法全局的一種屬性描述,它並不可以表明算法程序執行的實際時間。這是由於不論何種算法,只要n趨向無窮,那麼算法程序執行就不會有完結的時候,於是也就無所謂程序執行時間快慢問題了。

算法指令增速很像算法時間複雜度,但算法指令增速是真實依據時間計算得到的概念,就此一點就說明比沒有確切定義的算法時間複雜度更加實用。

  1. 結論

計算程序執行時間,要經過程序寫出的指令重複執行次數進行,指令重複執行次數的多少,取決於程序循環結構的循環次數和循環層數這兩個變量。通俗地講,循環層數和循環次數越大,程序運行得出結果所消耗的時間越長。

任何在計算機上完成任務的程序運行,都須要在有限時間內解決問題,否則咱們編寫的程序就失去了意義。於是研究程序運行時間的有限性,遠比研究程序執行時間的無限性更爲重要。可是爲了定性地比較算法的效率,咱們引入了算法指令增速的概念,做者相信,依據算法指令增速的概念能夠推動算法的深刻研究,也或許對P與NP的討論會有所幫助。

【學了多年信競卻對時間複雜度不明因此的窩,真是弱爆辣!//憂愁】

相關文章
相關標籤/搜索