詳解時間複雜度和空間複雜度

1. 時間複雜度

1.1 定義

若存在函數 f(n)f(n),使得當 nn 趨向無窮大時,T(n)/f(n)T(n) / f(n) 的極限值爲不等於 0 的常數,則稱 f(n)f(n)T(n)T(n) 的同數量級函數,記作 T(n)=O(f(n))T(n)=O(f(n)),稱 O(f(n))O(f(n)) 爲算法的 漸進時間複雜度,簡稱 時間複雜度,用大 O 來表示,稱爲 大 O 表示法

1.2 推導時間複雜度的原則

  1. 若運行時間是常數量級,則用常數 1 表示
  2. 只保留時間函數中最高階項,如 O(n2+4n)O(n^2 + 4n),保留最高階項後,成爲 O(n2)O(n^2)
  3. 若最高階項存在,則省去最高階項前的係數,如 O(4n2)O(4n^2),省去最高階項的係數後,成爲 O(n2)O(n^2)

1.3 分析時間複雜度的方法

總結起來,對於如何分析一段代碼的時間複雜度,主要有如下 3 個實用方法:

  1. 只關注循環執行次數最多的一行代碼
  2. 加法原則:總複雜度等於量度最大的那段代碼的複雜度
  3. 乘法原則:嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積

1.4 常見的時間複雜度曲線

1.5 常見時間複雜度

O(1)O(1)

即無論執行多少行,都不會影響到其他區域,此時代碼的複雜度就是 O(1)O(1),如下面的代碼中,假設執行每行代碼時間都相同切爲 tt,則 2,3 行各需 1 個執行時間,即爲 $t + t = 2t。此時執行時間複雜度爲常數。

void sayHello(String name){
    System.out.prinln("Hello, " + String);
    System.out.prinln("歡迎關注我的公衆號:【村雨遙】");
}

O(logn)O(log n)

如下列二分查找代碼中,通過 while 循環,能夠成倍的縮減搜索範圍,假設需要 x 次才能跳出循環,則有 num * 2 * 2 * ... = n ,其中 num 是常數,有 n 個 2 相乘,則有 num2x=nnum * 2 ^x = n,從而推出 x=log2(n/num)x = log_2(n/num) ,因此時間複雜度用大 O 表示法表示爲 O(logn)O(log n)

int binarySearch(int[] arr, int target){
    int left = 0;
    int right = arr.length - 1;
    while(left <= right){
        int middle = left + (left - right) / 2;
        if(arr[middle] == target){
            return middle;
        }else if(arr[middle] > target){
            right = middle - 1;
        }else {
            left = middle + 1;
        }
    }
    
    return -1;
}

O(n)O(n)

如下面這段代碼中,for 循環中的代碼被執行了 arr.length 次,因此所需要的時間和數組長度成正比的,因此可以用 O(n)O(n) 來表示它的時間複雜度。利用上述推到原則和分析的方法,可以知道下面代碼中循環次數最多的是 4,5 行,總的執行時間是 O(2n)O(2n),拋去係數後,得到最終時間複雜度 O(n)O(n).

int sum(int[] arr){
    int total = 0;
    
    for(int i = 0; i < arr.length; i++){
        total += arr[i];
    }
    
    return total;
}

O(nlogn)O(n log n)

如果我們將一個複雜度爲 O(logn)O(logn) 的代碼重複執行 nn 次,那麼此時代碼的複雜度不就變成 O(nlogn)O(nlogn) 了嗎

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

O(n2)O(n^2)

假設我們將時間複雜度爲 O(n)O(n) 的代碼重複執行 nn 次,那麼此時的時間複雜度就是 nO(n)n*O(n),即可表示爲 O(n2)O(n^2),表現出來就是雙重循環的形式

void selectionSort(int[] arr, int n){
    for(int i = 0; i < n; i++){
        int min = i;
        for(int j = i + 1; j < n; j++){
            if(arr[j] < arr[min]){
                int tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

O(n3)O(n^3)

O(n2)O(n^2),類似,將時間複雜度爲 O(n2)O(n^2) 的代碼嵌套循環一次,此時複雜度就變成了 O(n3)O(n^3),表現出來就是三重循環嵌套的形式

void demo(int n){
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            for(int k = 0; k < n; k++){
                System.out.print("Hello, World");
            }
            System.out.print("------");
        }
        System.out.print("******");
    }
}

O(n!)O(n!)

雖然理論上存在時間複雜度爲 O(n!)O(n!) 的算法,但實踐中基本遇不到,所以這裏就不展開了

2. 空間複雜度

2.1 定義

空間複雜度是對一個算法在運行過程中臨時佔用存儲空間大小的一個量度(即除開原始序列大小的內存,在算法過程中用到的額外的存儲空間),反映的對內存佔用的趨勢,而不是具體內存,也叫作 漸進空間複雜度表示算法的存儲空間與數據規模間的增長關係,用 S(n)S(n) 來代替;

2.2 常用空間複雜度

O(1)O(1)

算法執行所需臨時空間不隨某一變量 n 的大小而變化,則該算法空間複雜度爲一個常量,表示爲 S(n)=O(1)S(n) = O(1)

int num1 = 1;
int num2 = 2;
int total = num1 + num2;

O(n)O(n)

數組佔用內存大小爲 n,而且後續未分配新的空間,因此該算法空間複雜度爲 S(n)=O(n)S(n) = O(n)

int[] arr = new int[n];

O(n2)O(n^2)

二維數組的情況;

int[][] arr = new int[n][n];

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章