算法的復雜性是算法運行所以需要的計算機資源的量,需要時間資源的量稱為時間復雜性,需要空間資源的量稱為空間復雜性。此量應只依賴算法要解決問題的規模、算法的輸入和算法本身,分別用N,I和A表示,用C表示復雜性,應有C=F(N,I,A),若將時間和空間分開,分別用T和S表示,且A通常隱含在復雜性函數名中,因而將T和S簡寫為T=T(N,I)和S=(N,I)。
時間復雜度
復雜函數具象化。
根據T(N,I)概念,它應該是算法在一台抽象計算機上運行所需時間。
設此計算機提供元運算共k種,它們分別記為O1,O2,...,Ok。
又設每執行一次這些元運算所需時間分別為t1,t2,...,tk。
對於給定算法A,設經統計用到元運算Oi的次數為ei,i=1,2,...,k。
因此,對於每個i,1<=i<=k,ei是N和I的函數,即ei=ei(N,I)。因此,T(N,I)=∑ti*ei(N,I)。其中,ti(i=1,2,3,..,k)是與N和I無關的常數。
不可能對規模N的每個合法輸入I都統計ei(N,I),i=1,2,...,k。因此,T(N,I)還需進步簡化。或者說,只能對N的某些或某類代表性合法輸入統計相應ei來評價時間復雜度。如下分別為最壞、最好、平均時間復雜度:
其中DN是規模為N的合法輸入集合;I*是式一合法輸入;I~是式二合法輸入;而P(N)是算法的應用中出現輸入I的概率。其中最有價值的是最壞時間復雜度。
設T(N)為關於算法A的復雜性函數。若有N->∞時有(T(N)-T'(N))/T'(N) -> 0,那么,就說T'(N)是T(N)當N->∞時的漸進性態。即T'(N)是T(N)當N->∞時的漸進表達式,也就是其略去低階項所留下的主項,更加簡單,即對其的簡化。
進一步簡化。常常比較階來判斷算法復雜度效率高低,所以常常假設算法中用到的所有不同的元運算各執行一次所需要的時間都是一個單位時間。
如下引入漸進意義下的記號:O,Ω,θ,ο
若存在正常數C和自然數N0,使當N>=N0時有f(N)<=Cg(N),則稱函數f(N)當N充分大時上有界,且g(N)是它一個上屆,記為f(N)=O(g(N)),且說f(N)階不高於g(N)階。舉例:
1、對所有N>=1時有3N<=4N,有3N=O(N)
2、當N>=1時有N+1024<=1025N,有N+1024=O(N)
3、當N>=10時有2N^2+11N-10<=3N^2,有2N^2+11N-10=O(N^2)
根據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)
則Ω為下屆,θ為同階,f(N)=o(g(N))則表示f(N)當N充分大時階比g(N)低。
常見復雜度函數比較
masterd定理
假設有遞推關系式,其中n 為問題規模,
為遞推的子問題數量,
為每個子問題的規模(假設每個子問題的規模基本一樣),
為遞推以外進行的計算工作。a≥1,b>1為常數,f(n) 為函數,T(n) 為非負整數。則有以下結果(分類討論):




如下轉自:https://www.cnblogs.com/silence-x/p/10544072.html
一、算法的時間復雜度定義
在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化情況並確定T(n)的數量級。算法的時間復雜度,也就是算法的時間量度。記作:T(n)=O(f(n))。它表示隨問題n的增大,算法執行時間的增長率和f(n)的增長率相同,稱作算法的漸進時間復雜度,簡稱為時間復雜度。其中,f(n)是問題規模n的某個函數。
這樣用大寫O()來體現算法時間復雜度的記法,我們稱之為大0記法。
二、推導大O階方法
1、用常數1取代運行時間中的所有加法常數。
2、在修改后的運行次數函數中,只保留最高階項。
3、如果最高階項存在且不是1,則去除與這個項目相乘的常數。得到的結果就是大O階。
三、推導示例
1、常數階
首先順序結構的時間復雜度。下面這個算法,是利用高斯定理計算1,2,……n個數的和。
1 int sum = 0, n = 100; /*執行一次*/ 2 3 sum = (1 + n) * n / 2; /*執行一次*/ 4 5 printf("%d",sum); /*執行一次*/
這個算法的運行次數函數是f (n) =3。 根據我們推導大0階的方法,第一步就是把常數項3 改為1。在保留最高階項時發現,它根本沒有最高階項,所以這個算法的時間復雜度為0(1)。
另外,我們試想一下,如果這個算法當中的語句 sum = (1+n)*n/2; 有10 句,則與示例給出的代碼就是3次和12次的差異。這種與問題的大小無關(n的多少),執行時間恆定的算法,我們稱之為具有O(1)的時間復雜度,又叫常數階。對於分支結構而言,無論是真,還是假,執行的次數都是恆定的,不會隨着n 的變大而發生變化,所以單純的分支結構(不包含在循環結構中),其時間復雜度也是0(1)。
2、線性階
線性階的循環結構會復雜很多。要確定某個算法的階次,我們常常需要確定某個特定語句或某個語句集運行的次數。因此,我們要分析算法的復雜度,關鍵就是要分析循環結構的運行情況。
下面這段代碼,它的循環的時間復雜度為O(n), 因為循環體中的代碼須要執行n次。
1 int i; 2 3 for(i = 0; i < n; i++){ 4 5 /*時間復雜度為O(1)的程序步驟序列*/ 6 7 }
3、對數階
如下代碼:
1 int count = 1; 2 3 while (count < n){ 4 5 count = count * 2; 6 7 /*時間復雜度為O(1)的程序步驟序列*/ 8 9 }
由於每次count乘以2之后,就距離n更近了一分。 也就是說,有多少個2相乘后大於n,則會退出循環。 由2^x=n 得到x=logn。 所以這個循環的時間復雜度為O(logn)。
4、平方階
下面例子是一個循環嵌套,它的內循環剛才我們已經分析過,時間復雜度為O(n)。
1 int i, j; 2 3 for(i = 0; i < n; i++){ 4 5 for(j = 0; j < n; j++){ 6 7 /*時間復雜度為O(1)的程序步驟序列*/ 8 9 } 10 11 }
而對於外層的循環,不過是內部這個時間復雜度為O(n)的語句,再循環n次。 所以這段代碼的時間復雜度為O(n^2)。
如果外循環的循環次數改為了m,時間復雜度就變為O(mXn)。
所以我們可以總結得出,循環的時間復雜度等於循環體的復雜度乘以該循環運行的次數。
那么下面這個循環嵌套,它的時間復雜度是多少呢?
1 int i, j; 2 3 for(i = 0; i < n; i++){ 4 5 for(j = i; j < n; j++){ /*注意j = i而不是0*/ 6 7 /*時間復雜度為O(1)的程序步驟序列*/ 8 9 } 10 11 }
由於當i=0時,內循環執行了n次,當i = 1時,執行了n-1次,……當i=n-1時,執行了1次。所以總的執行次數為:
用我們推導大O階的方法,第一條,沒有加法常數不予考慮;第二條,只保留最高階項,因此保留時(n^2)/2; 第三條,去除這個項相乘的常數,也就是去除1/2,最終這段代碼的時間復雜度為O(n2)。
從這個例子,我們也可以得到一個經驗,其實理解大0推導不算難,難的是對數列的一些相關運算,這更多的是考察你的數學知識和能力。
5、立方階
下面例子是一個三重循環嵌套。
1 int i, j; 2 3 for(i = 1; i < n; i++) 4 5 for(j = 1; j < n; j++) 6 7 for(j = 1; j < n; j++){ 8 9 /*時間復雜度為O(1)的程序步驟序列*/ 10 11 12 13 }
這里循環了(1^2+2^2+3^2+……+n^2) = n(n+1)(2n+1)/6次,按照上述大O階推導方法,時間復雜度為O(n^3)。
四、常見的時間復雜度
常見的時問復雜度如表所示。
常用的時間復雜度所耗費的時間從小到大依次是:
我們前面已經談到了。O(1)常數階、O(logn)對數階、O(n)線性階、 O(n^2)平方階等,像O(n^3),過大的n都會使得結果變得不現實。同樣指數階O(2^n)和階乘階O(n!)等除非是很小的n值,否則哪怕n 只是100,都是噩夢般的運行時間。所以這種不切實際的算法時間復雜度,一般我們都不去討論。
五、最壞情況與平均情況
我們查找一個有n 個隨機數字數組中的某個數字,最好的情況是第一個數字就是,那么算法的時間復雜度為O(1),但也有可能這個數字就在最后一個位置上待着,那么算法的時間復雜度就是O(n),這是最壞的一種情況了。
最壞情況運行時間是一種保證,那就是運行時間將不會再壞了。 在應用中,這是一種最重要的需求, 通常, 除非特別指定, 我們提到的運行時間都是最壞情況的運行時間。
而平均運行時間也就是從概率的角度看, 這個數字在每一個位置的可能性是相同的,所以平均的查找時間為n/2次后發現這個目標元素。平均運行時間是所有情況中最有意義的,因為它是期望的運行時間。也就是說,我們運行一段程序代碼時,是希望看到平均運行時間的。可現實中,平均運行時間很難通過分析得到,一般都是通過運行一定數量的實驗數據后估算出來的。一般在沒有特殊說明的情況下,都是指最壞時間復雜度。
六、算法空間復雜度
我們在寫代碼時,完全可以用空間來換取時間,比如說,要判斷某某年是不是閏年,你可能會花一點心思寫了一個算法,而且由於是一個算法,也就意味着,每次給一個年份,都是要通過計算得到是否是閏年的結果。 還有另一個辦法就是,事先建立一個有2050個元素的數組(年數略比現實多一點),然后把所有的年份按下標的數字對應,如果是閏年,此數組項的值就是1,如果不是值為0。這樣,所謂的判斷某一年是否是閏年,就變成了查找這個數組的某一項的值是多少的問題。此時,我們的運算是最小化了,但是硬盤上或者內存中需要存儲這2050個0和1。這是通過一筆空間上的開銷來換取計算時間的小技巧。到底哪一個好,其實要看你用在什么地方。
算法的空間復雜度通過計算算法所需的存儲空間實現,算法空間復雜度的計算公式記作:S(n)= O(f(n)),其中,n為問題的規模,f(n)為語句關於n所占存儲空間的函數。
一般情況下,一個程序在機器上執行時,除了需要存儲程序本身的指令、常數、變量和輸入數據外,還需要存儲對數據操作的存儲單元,若輸入數據所占空間只取決於問題本身,和算法無關,這樣只需要分析該算法在實現時所需的輔助單元即可。若算法執行時所需的輔助空間相對於輸入數據量而言是個常數,則稱此算法為原地工作,空間復雜度為0(1)。
通常, 我們都使用"時間復雜度"來指運行時間的需求,使用"空間復雜度"指空間需求。當不用限定詞地使用"復雜度'時,通常都是指時間復雜度。
七、一些計算的規則
1、加法規則
T(n,m) = T1(n) + T2(m) = O(max{f(n), g(m)})
2、乘法規則
T(n,m) = T1(n) * T2(m) = O(max{f(n)*g(m)})
3、一個經驗
復雜度與時間效率的關系:
c(常數) < logn < n < n*logn < n^2 < n^3 < 2^n < 3^n < n!
l------------------------------l--------------------------l--------------l
較好 一般 較差