原文連接:看動畫輕鬆理解時間複雜度(一)程序員
算法(Algorithm)是指用來操做數據、解決程序問題的一組方法。對於同一個問題,使用不一樣的算法,也許最終獲得的結果是同樣的,好比排序就有前面的十大經典排序和幾種奇葩排序,雖然結果相同,但在過程當中消耗的資源和時間卻會有很大的區別,好比快速排序與猴子排序:)。算法
那麼咱們應該如何去衡量不一樣算法之間的優劣呢?bash
主要仍是從算法所佔用的「時間」和「空間」兩個維度去考量。函數
時間維度:是指執行當前算法所消耗的時間,咱們一般用「時間複雜度」來描述。動畫
空間維度:是指執行當前算法須要佔用多少內存空間,咱們一般用「空間複雜度」來描述。ui
本小節將從「時間」的維度進行分析。spa
當看「時間」二字,咱們確定能夠想到將該算法程序運行一篇,經過運行的時間很容易就知道複雜度了。3d
這種方式能夠嗎?固然能夠,不過它也有不少弊端。code
好比程序員小吳的老式電腦處理10w數據使用冒泡排序要幾秒,但讀者的iMac Pro 可能只須要0.1s,這樣的結果偏差就很大了。更況且,有的算法運行時間要好久,根本沒辦法沒時間去完整的運行,仍是好比猴子排序:)。orm
那有什麼方法能夠嚴謹的進行算法的時間複雜度分析呢?
有的!
「 遠古 」的程序員大佬們提出了通用的方法:「 大O符號表示法 」,即 T(n) = O(f(n))。
其中 n 表示數據規模 ,O(f(n))表示運行算法所須要執行的指令數,和f(n)成正比。
上面公式中用到的 Landau符號是由德國數論學家保羅·巴赫曼(Paul Bachmann)在其1892年的著做《解析數論》首先引入,由另外一位德國數論學家艾德蒙·朗道(Edmund Landau)推廣。Landau符號的做用在於用簡單的函數來描述複雜函數行爲,給出一個上或下(確)界。在計算算法複雜度時通常只用到大O符號,Landau符號體系中的小o符號、Θ符號等等比較不經常使用。這裏的O,最初是用大寫希臘字母,但如今都用大寫英語字母O;小o符號也是用小寫英語字母o,Θ符號則維持大寫希臘字母Θ。
注:本文用到的算法中的界限指的是最低的上界。
咱們先從常見的時間複雜度量級進行大O的理解:
常數階O(1)
線性階O(n)
平方階O(n²)
對數階O(logn)
線性對數階O(nlogn)
不管代碼執行了多少行,其餘區域不會影響到操做,這個代碼的時間複雜度都是O(1)
void swapTwoInts(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
複製代碼
在下面這段代碼,for循環裏面的代碼會執行 n 遍,所以它消耗的時間是隨着 n 的變化而變化的,所以能夠用O(n)來表示它的時間複雜度。
int sum ( int n ){
int ret = 0;
for ( int i = 0 ; i <= n ; i ++){
ret += i;
}
return ret;
}
複製代碼
特別一提的是 c * O(n) 中的 c 可能小於 1 ,好比下面這段代碼:
void reverse ( string &s ) {
int n = s.size();
for (int i = 0 ; i < n/2 ; i++){
swap ( s[i] , s[n-1-i]);
}
}
複製代碼
void selectionSort(int arr[],int n){
for(int i = 0; i < n ; i++){
int minIndex = i;
for (int j = i + 1; j < n ; j++ )
if (arr[j] < arr[minIndex])
minIndex = j;
swap ( arr[i], arr[minIndex]);
}
}
複製代碼
這裏簡單的推導一下
不可貴到公式:
(n - 1) + (n - 2) + (n - 3) + ... + 0
= (0 + n - 1) * n / 2
= O (n ^2)
複製代碼
固然並非全部的雙重循環都是 O(n²),好比下面這段輸出 30n 次 Hello,五分鐘學算法:)
的代碼。
void printInformation (int n ){
for (int i = 1 ; i <= n ; i++)
for (int j = 1 ; j <= 30 ; j ++)
cout<< "Hello,五分鐘學算法:)"<< endl;
}
複製代碼
int binarySearch( int arr[], int n , int target){
int l = 0, r = n - 1;
while ( l <= r) {
int mid = l + (r - l) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] > target ) r = mid - 1;
else l = mid + 1;
}
return -1;
}
複製代碼
在二分查找法的代碼中,經過while循環,成 2 倍數的縮減搜索範圍,也就是說須要通過 log2^n 次便可跳出循環。
一樣的還有下面兩段代碼也是 O(logn) 級別的時間複雜度。
// 整形轉成字符串
string intToString ( int num ){
string s = "";
// n 通過幾回「除以10」的操做後,等於0
while (num ){
s += '0' + num%10;
num /= 10;
}
reverse(s)
return s;
}
複製代碼
void hello (int n ) {
// n 除以幾回 2 到 1
for ( int sz = 1; sz < n ; sz += sz)
for (int i = 1; i < n; i++)
cout<< "Hello,五分鐘學算法:)"<< endl;
}
複製代碼
將時間複雜度爲O(logn)的代碼循環N遍的話,那麼它的時間複雜度就是 n * O(logn),也就是了O(nlogn)。
void hello (){
for( m = 1 ; m < n ; m++){
i = 1;
while( i < n ){
i = i * 2;
}
}
}
複製代碼
下一節將深刻的對遞歸算法的複雜度進行分析,敬請期待:)
文章首發於公衆號:五分鐘學算法