複雜度O(n)計算

https://blog.csdn.net/daijin888888/article/details/66970902算法

1、算法的時間複雜度定義數組

    在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化狀況並肯定T(n)的數量級。算法的時間複雜度,也就是算法的時間量度。記做:T(n)=O(f(n))。它表示隨問題n的增大,算法執行時間的增加率和f(n)的增加率相同,稱做算法的漸進時間複雜度,簡稱爲時間複雜度。其中,f(n)是問題規模n的某個函數。函數

    這樣用大寫O()來體現算法時間複雜度的記法,咱們稱之爲大0記法。.net

2、推導大O階方法blog

一、用常數1取代運行時間中的全部加法常數。內存

二、在修改後的運行次數函數中,只保留最高階項。數學

三、若是最高階項存在且不是1,則去除與這個項目相乘的常數。獲得的結果就是大O階。class

3、推導示例效率

一、常數階變量

     首先順序結構的時間複雜度。下面這個算法,是利用高斯定理計算1,2,……n個數的和。


int sum = 0, n = 100; /*執行一次*/
sum = (1 + n) * n / 2; /*執行一次*/
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)。
二、線性階

    線性階的循環結構會複雜不少。要肯定某個算法的階次,咱們經常須要肯定某個特定語句或某個語句集運行的次數。所以,咱們要分析算法的複雜度,關鍵就是要分析循環結構的運行狀況。

    下面這段代碼,它的循環的時間複雜度爲O(n), 由於循環體中的代碼需要執行n次。


int i;
for(i = 0; i < n; i++){
/*時間複雜度爲O(1)的程序步驟序列*/
}
三、對數階

    以下代碼:


int count = 1;
while (count < n){
count = count * 2;
  /*時間複雜度爲O(1)的程序步驟序列*/
}
    因爲每次count乘以2以後,就距離n更近了一分。 也就是說,有多少個2相乘後大於n,則會退出循環。 由2^x=n 獲得x=logn。 因此這個循環的時間複雜度爲O(logn)。
四、平方階

    下面例子是一個循環嵌套,它的內循環剛纔咱們已經分析過,時間複雜度爲O(n)。


int i, j;
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
/*時間複雜度爲O(1)的程序步驟序列*/
}
}
    而對於外層的循環,不過是內部這個時間複雜度爲O(n)的語句,再循環n次。 因此這段代碼的時間複雜度爲O(n^2)。
    若是外循環的循環次數改成了m,時間複雜度就變爲O(mXn)。

    因此咱們能夠總結得出,循環的時間複雜度等於循環體的複雜度乘以該循環運行的次數。
    那麼下面這個循環嵌套,它的時間複雜度是多少呢?


int i, j;
for(i = 0; i < n; i++){
for(j = i; j < n; j++){ /*注意j = i而不是0*/
/*時間複雜度爲O(1)的程序步驟序列*/
}
}
    因爲當i=0時,內循環執行了n次,當i = 1時,執行了n-1次,……當i=n-1時,執行了1次。因此總的執行次數爲:


    用咱們推導大O階的方法,第一條,沒有加法常數不予考慮;第二條,只保留最高階項,所以保留時(n^2)/2; 第三條,去除這個項相乘的常數,也就是去除1/2,最終這段代碼的時間複雜度爲O(n2)。

    從這個例子,咱們也能夠獲得一個經驗,其實理解大0推導不算難,難的是對數列的一些相關運算,這更多的是考察你的數學知識和能力。

五、立方階

    下面例子是一個三重循環嵌套。


int i, j;
for(i = 1; i < n; i++)
for(j = 1; j < n; j++)
for(j = 1; j < n; j++){
/*時間複雜度爲O(1)的程序步驟序列*/

}
    這裏循環了(1^2+2^2+3^2+……+n^2) = n(n+1)(2n+1)/6次,按照上述大O階推導方法,時間複雜度爲O(n^3)。
4、常見的時間複雜度

    常見的時問複雜度如表所示。

    經常使用的時間複雜度所耗費的時間從小到大依次是:

    咱們前面已經談到了。O(1)常數階、O(logn)對數階、O(n)線性階、 O(n^2)平方階等,像O(n^3),過大的n都會使得結果變得不現實。一樣指數階O(2^n)和階乘階O(n!)等除非是很小的n值,不然哪怕n 只是100,都是噩夢般的運行時間。因此這種不切實際的算法時間複雜度,通常咱們都不去討論。

5、最壞狀況與平均狀況

    咱們查找一個有n 個隨機數字數組中的某個數字,最好的狀況是第一個數字就是,那麼算法的時間複雜度爲O(1),但也有可能這個數字就在最後一個位置上待着,那麼算法的時間複雜度就是O(n),這是最壞的一種狀況了。
    最壞狀況運行時間是一種保證,那就是運行時間將不會再壞了。 在應用中,這是一種最重要的需求, 一般, 除非特別指定, 咱們提到的運行時間都是最壞狀況的運行時間。
    而平均運行時間也就是從機率的角度看, 這個數字在每個位置的可能性是相同的,因此平均的查找時間爲n/2次後發現這個目標元素。平均運行時間是全部狀況中最有意義的,由於它是指望的運行時間。也就是說,咱們運行一段程序代碼時,是但願看到平均運行時間的。可現實中,平均運行時間很難經過分析獲得,通常都是經過運行必定數量的實驗數據後估算出來的。通常在沒有特殊說明的狀況下,都是指最壞時間複雜度。

6、算法空間複雜度

    咱們在寫代碼時,徹底能夠用空間來換取時間,好比說,要判斷某某年是否是閏年,你可能會花一點心思寫了一個算法,並且因爲是一個算法,也就意味着,每次給一個年份,都是要經過計算獲得是不是閏年的結果。 還有另外一個辦法就是,事先創建一個有2050個元素的數組(年數略比現實多一點),而後把全部的年份按下標的數字對應,若是是閏年,此數組項的值就是1,若是不是值爲0。這樣,所謂的判斷某一年是不是閏年,就變成了查找這個數組的某一項的值是多少的問題。此時,咱們的運算是最小化了,可是硬盤上或者內存中須要存儲這2050個0和1。這是經過一筆空間上的開銷來換取計算時間的小技巧。到底哪個好,其實要看你用在什麼地方。
    算法的空間複雜度經過計算算法所需的存儲空間實現,算法空間複雜度的計算公式記做:S(n)= O(f(n)),其中,n爲問題的規模,f(n)爲語句關於n所佔存儲空間的函數。
    通常狀況下,一個程序在機器上執行時,除了須要存儲程序自己的指令、常數、變量和輸入數據外,還須要存儲對數據操做的存儲單元,若輸入數據所佔空間只取決於問題自己,和算法無關,這樣只須要分析該算法在實現時所需的輔助單元便可。若算法執行時所需的輔助空間相對於輸入數據量而言是個常數,則稱此算法爲原地工做,空間複雜度爲0(1)。
     一般, 咱們都使用"時間複雜度"來指運行時間的需求,使用"空間複雜度"指空間需求。當不用限定詞地使用"複雜度'時,一般都是指時間複雜度。

7、一些計算的規則

一、加法規則

     T(n,m) = T1(n) + T2(m) = O(max{f(n), g(m)})

二、乘法規則

     T(n,m) = T1(n) * T2(m) = O(max{f(n)*g(m)})

三、一個經驗

     複雜度與時間效率的關係:
    c(常數) < logn < n < n*logn < n^2 < n^3 < 2^n < 3^n < n!
    l------------------------------l--------------------------l--------------l
                   較好                          通常                    較差

8、經常使用算法的時間複雜度和空間複雜度

相關文章
相關標籤/搜索