1. 程式人生 > 其它 >演算法的時間複雜度和空間複雜度

演算法的時間複雜度和空間複雜度

技術標籤:演算法與資料結構演算法排序演算法java資料結構

文章目錄

一、演算法的時間複雜度

1.1 度量一個程式(演算法)執行時間的方法

度量一個程式執行時間通常有兩種方法:

  1. 事後統計的方法

    這種方法可行,但是有兩個問題:一是要想對設計的演算法的執行效能進行評測,需要實際執行該程式;二是所得時間的統計量依賴於計算機的硬體、軟體等環境因素。這種方式,要在同一臺計算機的相同狀態下執行,才能比較那個演算法速度更快

  2. 事前估算的方法

    通過分析某個演算法的時間複雜度來判斷哪個演算法更優。

在實際的應用中,我們通常使用事前估算的方法來度量一個演算法的執行時間。

1.2 時間頻度

一個演算法花費的時間與演算法中語句的執行次數成正比例, 哪個演算法中語句執行次數多, 它花費時間就多。一個演算法中的語句執行次數稱為語句頻度或時間頻度,記為 T(n)

例如:

下面有兩種演算法來計算 1 - 100 的所有數字之和,分別求出它們的時間頻度。

  • 演算法一:

    int sum = 0;
    int end = 100;
    // 使用 for 迴圈計算
    for(int i=0; i<=end; i++){
        sum += i;
    }

    對於演算法一,for 迴圈將會執行 101 次(包含最後一次的判斷,所以是 101 次),因此它的時間頻度就是 T(n)=n+1。

  • 演算法二:

    sum = (1 + end) * end / 2
    

    對於演算法二,它只需要執行一次,因此它的時間頻度就是 T(n)=1。

1.3 時間複雜度

1.3.1 什麼是時間複雜度

一般情況下,演算法中的基本操作語句的重複執行次數是問題規模 n 的某個函式,用 T(n)表示,若有某個輔助函式 f(n),使得當 n 趨近於無窮大時,T(n) / f(n) 的極限值為不等於零的常數,則稱 f(n) 是 T(n) 的同數量級函式。記作 T(n) =O( f(n) ), 稱O( f(n) ) 為演算法的漸進時間複雜度,簡稱時間複雜度。

T(n) 不同,但時間複雜度可能相同。例如:T(n)=n²+7n+6 與 T(n)=3n²+2n+2 它們的 T(n) 不同,但時間複雜度相同,都為 O(n²)。

1.3.2 計算時間複雜度的方法

從時間複雜度的定義我們可以得知:時間複雜度是由時間頻度簡化而來的

對於一個演算法的時間頻度是比較容易求出來的,那麼如何將時間頻度進行簡化得到時間複雜度呢?

很簡單:只保留時間頻度的最高階,並且將最高階的係數去除,最終得到的就是時間複雜度。

為什麼是這樣計算時間複雜度的呢?因為當 n 趨近於無窮的時候,時間頻度的大小實際上就是由最高階來決定的,因此只需要保留最高階即可。假設最高階的係數是一個常數 k,k 倍的最高階和 1 倍的最高階實際上是同數量級的數,因此也可以去除。

例如:某演算法的時間頻度是 T(n)=3n²+7n+6,它時間複雜度是多少?

首先只保留 T(n) 的最高階,得到的是 3n²,然後將最高階的係數去除,最後得到的是 n²,即該演算法的時間複雜度是 O(n²)。

1.3.3 常見的時間複雜度

常見的時間複雜度有下面幾種:

  1. 常數階 O(1)
  2. 對數階 O(log2n)
  3. 線性階 O(n)
  4. 線性對數階 O(nlog2n)
  5. 平方階 O(n^2)
  6. 立方階 O(n^3)
  7. k 次方階 O(n^k)
  8. 指數階 O(2^n)

他們的時間複雜度由小到大依次為:Ο (1)<Ο (log2n)<Ο (n)<Ο (nlog2n)<Ο (n2)<Ο (n3)< Ο (nk) <Ο (2n),隨著問題規模 n 的不斷增大,上述時間複雜度不斷增大,演算法的執行效率越低。

在我們實際的設計演算法過程中,應該儘量避免使用指數階的演算法

下面將分別簡單介紹一下這幾種常見的時間複雜度。

  • 常數階 O(1)

    無論程式碼執行了多少行,只要是沒有迴圈等複雜的結構,那麼這個程式的時間複雜度就是 O(1)。

    int i = 1;
    int j = 2;
    ++i;
    j++;
    int m = i + j;
    

    上述程式碼中執行的次數並不隨著某個變數的增長而增長,因此它的時間複雜度就是 O(1)。

  • 對數階 O(log2n)

    int i = 1;
    while(i < n){
        i = i * 2;
    }
    

    上述程式碼中,我們可以看到每次 while 迴圈,i 都會離 n 更近,那麼這個 i = i*2 的程式碼將會執行多少次呢?

    我們假設執行 m 次之後將會退出迴圈,即 1*2^m=n,則 m =log2n 。即該演算法的時間複雜度就是 O(log2n)。

  • 線性階 O(n)

    for(int i; i<=n; ++i){
        j = i;
        j++;
    }
    

    上述程式碼中,for 迴圈將會執行 n 次,因此這個演算法的時間複雜度就是 O(n)。

  • 線性對數階 O(nlog2n)

    for(m=1; m<n; m++){
        i = 1;
        while(i < n){
            i = i * 2;
        }
    }
    

    上述程式碼中,裡面的 while 迴圈將會執行 log2n 次,而外面的 for 迴圈將會執行 n 次,因此它的時間複雜度就是 O(nlog2n)。

  • 平方階 O(n^2)

    for(i=1; i<=n; i++){
        for(j=1; j<=n; j++){
            sum = j;
            sum += sum;
        }
    }
    

    上述程式碼中,兩層 for 迴圈總共會執行 n^2 次,因此這個演算法的時間複雜度就是 O(n^2)。

後面的更高次方階就不再一一使用程式碼來演示了,他們的原理和上面的是相同的。

1.4 平均時間複雜度和最壞時間複雜度

平均時間複雜度是指所有可能的輸入例項均以等概率出現的情況下,該演算法的執行時間。

最壞情況下的時間複雜度稱最壞時間複雜度。

一般討論的時間複雜度均是最壞情況下的時間複雜度。這樣做的原因是:最壞情況下的時間複雜度是演算法在任何輸入例項上執行時間的界限,這就保證了演算法的執行時間不會比最壞情況更長。

平均時間複雜度和最壞時間複雜度是否一致, 和演算法有關:

在這裡插入圖片描述

二、演算法的空間複雜度

類似於時間複雜度的討論, 一個演算法的空間複雜度(Space Complexity)定義為該演算法所耗費的儲存空間,它也是問題規模 n 的函式。

空間複雜度是對一個演算法在執行過程中臨時佔用儲存空間大小的量度。有的演算法需要佔用的臨時工作單元數與解決問題的規模 n 有關, 它隨著 n 的增大而增大, 當 n 較大時, 將佔用較多的儲存單元, 例如快速排序和歸併排序演算法, 基數排序就屬於這種情況。

在做演算法分析時,更看重時間複雜度。從使用者使用體驗上看,更看重的程式執行的速度。一些快取產品(如redis, memcache)和演算法(如基數排序)本質就是用空間換時間。