聊聊算法的時間複雜度與空間複雜度

複雜度的概念

算法是一段執行的程序, 能夠理解成幾行代碼,或者一個方法; 算法的時間複雜度是指這段代碼須要消耗的時間資源;算法的空間複雜度是指這段代碼須要消耗的空間資源(空間資源一般是指佔用的內存)。java

大O複雜度表示法

一般咱們在討論一個算法時會說,這個算法時間複雜度是O(n​), 那個O(n^2​)。而這個O(n​)、O(n^2​)就是大O複雜度表示法。這個n​n^2​具體是怎麼來的呢,下面簡單舉個例子:算法

int cal(int n) { 
  int sum = 0;									(1)
  for(int i = 1;i <= n; ++i){ 	(2)
    sum = sum + i;							(3)
  }															(4)
  return sum;									
}  
複製代碼

上面的代碼是計算 1, 2, 3… n 的和一個方法,咱們假設每一行代碼cpu的時間都是相同的,每一行代碼執行時間爲run_time, 那這段代碼的總執行時間 T(n) 是多少 ? 第1行爲run_time, 第2行和第3行代碼循環n次,時間爲2 * n * run_time。全部總時間:數組

T(n) = (2 * n + 1)* run_time
複製代碼

根據規律,能夠總結成一個公式:bash

T(n) = O (f(n))
複製代碼

因此, 上面的O(n)、O(n^2)例子中,f(n) = n 以及 f(n) = n^2(2 * n + 1)* run_time 中run_time是常數, 當n足夠大, 公式中的低階、常量、係數均可以忽略不計,全部這段計算 1, 2, 3… n總和的代碼時間複雜度是O(n)。性能

常見時間複雜度有:O(1)、O(n)、O(logn )、O(nlogn ) 、O(n2) 、O(n^{3})​測試

下面在舉個空間複雜度的例子:spa

void print(int n){
  int i = 0;							 (1)
  int[] arr = new int[n];  (2)
  int j = 0;							 (3)
}
複製代碼

第1行和第3行代碼中都是分別申請了一個空間存儲變量, 都有數據規模n無關,全部能夠忽略,第2行中申請了數量爲n的數組空間。全部整段代碼的空間複雜度是O(n)​.net

常見空間複雜度有: O(1)、O(n)、O(n2 )​3d

掌握算法的時間複雜度與空間複雜度有什麼用

首先,對比幾個算法的好壞須要結合使用場景,好比數據量,內存,數據特徵等。一樣一段代碼,在不一樣機器上運行速度也是不一樣的。一般比較幾個算法的性能,就須要在同一個機器,同一批數據,同一個環境下去運行對比測試它們所須要的運行時間。code

可是,但開發者是使用或者選擇一個算法時,一般是沒辦法精準的對比算法的優劣,而且時間成本也不容許。全部咱們須要一個不用具體數據來測試, 就能夠粗略估算出算法的執行效率, 這就是算法的時間複雜度與空間複雜度做用啦。

最好、最壞狀況時間複雜度

什麼狀況下,一段代碼會出現有最好、最壞的狀況呢; 舉個例子, 好比在一個長度爲n的整數數組中去找一個數,找到就立馬返回數組的下表。在循環查找的過程當中,最好的狀況就是數組第一個就要找的,全部最好狀況時間複雜度爲O(1); 若是數組最後一個是要找的, 這時就是最壞的狀況,最壞狀況時間複雜度爲O(n)​

平均狀況時間複雜度

像上面一個例子,在一個長度爲n的整數數組中去找一個數, 有最好和最壞狀況,最好、最壞狀況時間複雜度沒法衡量一段代碼的執行效率,這時就須要用到平均狀況時間複雜度。平均=全部狀況總和/總次數。

那麼平均狀況時間複雜度怎麼算出來呢,要找的數要麼在數組裏,要麼不在,假設在與不在的機率是\frac{1}{2}​, 查找的數出如今0 ~ n-1 的位置機率是同樣的,爲\frac{1}{n}​, 全部查找的數在數組裏機率爲\frac{1}{2n}​。總過程爲:

T(n) = 1 * \frac{1}{2n} + 2 * \frac{1}{2n} + 3 * \frac{1}{2n} + .. + n * \frac{1}{2n} = \frac{1 + n }{4}

去掉常量,複雜度爲O(n)。

均攤時間複雜度

均攤時間複雜度就是一種特殊的平均時間複雜度,只能在特殊的場景才能是使用。很少說,舉個例子,會java的對容器arraylist不陌生吧,arraylist有個默認長度的數組,add方法裏,若是數組長度不夠,是須要擴容的。下面以一段add方法僞代碼爲例:(爲了代碼簡單易懂,以添加int爲例)

int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;

void add(int element){
  if(index >= len){  // 超出長度,數組須要擴容,並複製值到一個新數組裏
    final int newLen = len * 2;
    int new_arr[] = new int[newLen]; //申請2倍的數組
    for (int i = 0; i < len; ++i){
      new_arr[i] = arr[i];
		}
    arr = new_arr;
    len = newLen;
  }
  arr[index] = element;
  ++index;
}
複製代碼

當不擴容時, 這個add方法時間複雜度就是O(1), 當擴容時, if裏面複製到新數組須要遍歷一次,

int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;

void add(int element){
  if(index >= len){  // 超出長度,數組須要擴容,並複製值到一個新數組裏
    final int newLen = len * 2;
    int new_arr[] = new int[newLen]; //申請2倍的數組
    for (int i = 0; i < len; ++i){
      new_arr[i] = arr[i];
    }
    arr = new_arr;
    len = newLen;
  }
  arr[index] = element;
  ++index;
}
複製代碼

當不擴容時, 這個add方法時間複雜度就是, 當擴容時, if裏面複製到新數組須要遍歷一次,

int arr[] = new int[10]; //初始默認長度爲10
int len = 10;
int index = 0;

void add(int element){
  if(index >= len){  // 超出長度,數組須要擴容,並複製值到一個新數組裏
    final int newLen = len * 2;
    int new_arr[] = new int[newLen]; //申請2倍的數組
    for (int i = 0; i < len; ++i){
      new_arr[i] = arr[i];
    }
    arr = new_arr;
    len = newLen;
  }
  arr[index] = element;
  ++index;
}
複製代碼

當不擴容時, 這個add方法時間複雜度就是O(1), 當擴容時, if裏面複製到新數組須要遍歷一次,數組默認長度假設是n, 擴容時的時間複雜度是O(n)。平均狀況時間複雜度是多少呢,若是像上一個例子用機率論去計算也是能夠,可是麻煩。咱們假設須要添加11個數,添加第11個數時須要擴容,循環10次把前10個數複製到新的數組中而後把第11個數添加進去。添加前10個數時間複雜度都是O(1), 若是把擴容時循環10次分攤到添加前10個數操做中,那麼添加前10個數操做都只是都運行了一行代碼而已,仍是常量級別的,全部這個add方法的平均時間複雜度就是O(1)。 經過分攤分析時間複雜度就叫作均攤時間複雜度。

相關文章
相關標籤/搜索