算法和數據結構密不可分。算法依賴數據結構。算法
數據結構和算法解決是「如何讓計算機更快時間、更省空間的解決問題」數據結構
所以從 執行時間 和 資源佔用 兩個維度來評估數據結構和算法的性能框架
也就是咱們接下來說的複雜度的問題,數據結構和算法
時間維度 便是 時間複雜度;資源空間維度 就是 空間複雜度;ide
複雜度是描述了 執行時間或者資源空間與數據規模的關係函數
好比一段程序,咱們如何估算這段代碼的執行時間?工具
在這裏,咱們能夠約定每一行代碼的執行時間是同樣的,咱們約定爲 單位時間 unit_time。性能
這樣問題就能夠轉換爲:程序總共執行了多少個單位時間。spa
先看下面這段代碼,總共執行了多少個單位時間:3d
1 public static void F(int n) 2 { 3 int i_index = 0; 4 int j_todo = 0; 5 for (; i_index < n; i_index++) 6 { 7 j_todo = i_index; 8 Console.WriteLine($"業務調用次數 :{j_todo + 1}"); 9 Console.Write($"循環次數 :{i_index + 1}"); 10 } 11 }
第 3 行,一個單位是時間 =1 unit_time
第 4 行,也是一個執行單位時間 =1 unit_time
第 5 行,根據n的大小,因此會執行 n次 =n unit_time
一樣第 7 ,8 , 9 這三行都會執行 n次 = ( n + n + n ) unit_time
因此 以上這個方法內部的算法執行時間是:T=1+1+n+3n = 4n+2;
T = 4n+2 是算法的執行時間,因此咱們能夠看出T和n成正比。
咱們再來看一下一個嵌套循環的時間複雜度:
1 public static void F2(int n) 2 { 3 int i_index = 0; 4 for (; i_index < n; i_index++) 5 { 6 int j_todo = 0; 7 for (; j_todo < n; j_todo++) 8 { 9 Console.WriteLine($"業務調用次數 :{j_todo + 1}"); 10 } 11 Console.Write($"循環次數 :{i_index + 1}"); 12 } 13 }
第3行 = 1 unit
第4行 = n unit
第6行 = n unit
第7行 = n * n
第9行 = n * n
第10行= n
T = 3n + 2*n^2
其實咱們也已經看到,執行時間T 和 n 執行次數的數量級成正比,
這裏就引出了一個公式:T(n) = O(f(n))
T(n) 代碼在數量級n規模下的執行時間;
f(n) 每行代碼執行次數總和
O 即爲 T(n) 和 f(n) 成正比關係
上面的例1:T = 4n+2 ,大O表示法爲 T(n) = O(4n+2)
例2:T = 3n + 2*n^2 , 大O表示法爲 T(n) = O(3n + 2*n^2)
經過以上例子咱們能夠知道,當n很大時,常量,係數,低階這些對趨勢影響極小,也就是咱們找到影響面最大的因子
對案例1來講,去除常量和係數,影響最大因子就是n,便可表示爲 T(n)=O(n)
案例2來講,一樣去除影響面較小的因子,可表示爲 T(n)=O(n^2)
咱們能夠總結爲:去除影響趨勢較小的因子,保留影響趨勢最大的因子
1)單段代碼看高頻:好比循環。
2)嵌套代碼求乘積:好比遞歸、多重循環等
3)多段代碼取最大:好比一段代碼中有單循環和多重循環,那麼取多重循環的複雜度。
4)多個規模求加法:好比方法有兩個參數控制兩個循環的次數,那麼這時就取兩者複雜度相加。
咱們常見的或者常聽到的複雜度級別好比:
O(1) 常量階
1不是表明的代碼行數,這是對執行次數爲固定常量的統稱。
如下FormatWriteLineMsg函數只是一個格式化展現的函數
1 public static void N1(int n) 2 { 3 int i_index = 0; 4 Console.WriteLine($"時間複雜度-常量階-i :{i_index + 1}"); 5 }
1 int n = 10; 2 3 FormatWriteLineMsg("時間複雜度-常量階: O(1)"); 4 N1(n);
O(n) 線性階
咱們剛纔案例1已經提到過,成正比關係。
1 public static void N(int n) 2 { 3 int i_index = 0; 4 5 for (; i_index < n; i_index++) 6 { 7 Console.WriteLine($"時間複雜度-線性階-i :{i_index + 1}"); 8 } 9 }
1 int n = 10; 2 3 FormatWriteLineMsg("時間複雜度-線性階: O(n)"); 4 N(n);
O(n^k) 次方階
案例2也是平方階,固然還有給根據嵌套的層數對應的k的增長。
1 public static void NxN(int n) 2 { 3 int i_index = 0; 4 for (; i_index < n; i_index++) 5 { 6 int j_todo = 0; 7 for (; j_todo < n; j_todo++) 8 { 9 Console.WriteLine($"時間複雜度-X方階 :i*j ={i_index + 1} *{j_todo + 1}"); 10 } 11 } 12 }
1 int n = 10; 2 3 FormatWriteLineMsg("時間複雜度-X方階: O(n^x)"); 4 NxN(n);
......
O(m+n)、O(m*n)
複雜度與兩個數據資源有關
O(log n) 對數階
1 public static void LogN(int n) 2 { 3 double flag = 1; 4 double step = 2; 5 int forCount = 0; 6 while (flag <= n) 7 { 8 forCount++; 9 flag = flag * step; 10 Console.WriteLine($"時間複雜度-對數階:{forCount} ==> {step}^{Math.Log(flag, step)}"); 11 } 12 }
1 int n = 10; 2 3 FormatWriteLineMsg("時間複雜度-對數階: O(log n)"); 4 LogN(n);
循環最多的代碼是 第六、八、九、10行,咱們看一下它們執行了多少次:
次數 => 1 2 3 4
flag => 1*2 2*2 4*2 8*2
n => 2^1 2^2 2^3 2^4
因此 對於n來講,n=2^x,執行了x次。x=log2n,時間複雜度爲 O( log2n ).
我i什麼會將這些對數贊成規整爲 logn?--忽略對數的底,即爲 O( logn )
空間複雜度
算法的存儲空間與數據規模之間的增加關係
一般空間複雜度級別:O(1) O(n) O(n^2)
通常分析到這已經能夠應對咱們平時的開發需求,由於下面的不少的概念思想能夠借鑑。也是不少的開源工具框架運用了其中的一些算法的精髓。
因此咱們有必要再對算法複雜度深刻一下,那麼接下來咱們再進一步瞭解一下幾個概念:
最壞狀況時間複雜度、最好狀況時間複雜度 、平均狀況時間複雜度、均攤時間複雜度
來看一下下面這一段代碼:查找某個元素,找到就結束返回索引,沒找到爲-1。
1 string source = "8,4,5,6,2,3,1,9,0,7"; 2 string[] data = source.Split(',').ToArray(); 3 Console.WriteLine($"Source ={source} | find =>{search}"); 4 Console.WriteLine(); 5
6 int count = data.Count(); 7 int search_index = -1; 8
9 int i_index = 0; 10 for (; i_index < count; i_index++) 11 { 12 Console.WriteLine($"todo:{i_index + 1}"); 13 if (search == data[i_index]) 14 { 15 search_index = i_index; 16 break; 17 } 18 }
這就會出現多種狀況:
一、好比上來第一個就找到了,那麼時間複雜度爲O(1),由於只循環了一次就找到了;
二、運氣較差最有一個找到了,時間複雜度爲O(n);
三、那麼在第二個,第三個,第n個找到呢 ?
四、找遍全部仍是沒有找到,那麼這個時間複雜度仍爲 O(n);
第一、二、4 只是一種特殊狀況,咱們不少狀況會是3.
固然,第1就是咱們上面提到的 最好狀況時間複雜度=O(1);
第二、4 就是最壞狀況時間複雜度 = O(n);
咱們接下來所分析的就是 第3,平均狀況時間複雜度:
1+2+3...+n+n / n+1 => + 機率 => 1*(1/2n)+2*(1/2n)+3*(1/2n)...+n*(1/2n)+n*1/2 => 3n+1 / 4
因此 平均時間複雜度爲 O(n)
Demo
1 public static void Best_Worst_Avg_CaseTime(string search) 2 { 3 string source = "8,4,5,6,2,3,1,9,0,7"; 4 string[] data = source.Split(',').ToArray(); 5 Console.WriteLine($"Source ={source} | find =>{search}"); 6 Console.WriteLine(); 7
8 int count = data.Count(); 9 int search_index = -1; 10
11 int i_index = 0; 12 for (; i_index < count; i_index++) 13 { 14 Console.WriteLine($"todo:{i_index + 1}"); 15 if (search == data[i_index]) 16 { 17 search_index = i_index; 18 break; 19 } 20 } 21
22 if (i_index == 0) 23 { 24 Console.WriteLine($">>>>>>>>>>>>>>>>>Best:O(1)"); 25 Console.WriteLine(); 26 } 27 else
28 { 29 if (search_index == -1) 30 { 31 Console.WriteLine($">>>>>>>>>>>>>>>>>Worst:O(n)"); 32 Console.WriteLine(); 33 } 34 else
35 { 36 Console.WriteLine($@">>>>>>>>>>>>>>>>>Other: 37 => O( 1+2+...+n+n / n+1 ) 38 => O( 2n / n+1 ) 39 => O(n)"); 40
41 Console.WriteLine($@">>>>>>>>>>>>>>>>>Avg: 42 =>O( 1*(1/n)*(1/2)+...n*(1/2n)+n*(1/2) ) 43 => O( 3n/4 ) 44 => O(n)"); 45 Console.WriteLine(); 46 } 47 } 48
49 } 50
1 FormatWriteLineMsg("最好狀況時間複雜度"); 2 string best = "8"; 3 Best_Worst_Avg_CaseTime(best); 4
5 FormatWriteLineMsg("最壞狀況時間複雜度"); 6 string worst = "10"; 7 Best_Worst_Avg_CaseTime(worst); 8
9 FormatWriteLineMsg("平均狀況時間複雜度"); 10 string avg = "6"; 11 Best_Worst_Avg_CaseTime(avg); 12
13 Console.ReadKey();
均攤狀況時間複雜度,實際上是一種更加特殊的平均狀況時間複雜度,
均攤也是存在複雜度級別的差距,可是級別較高的複雜度會被平攤到級別較低的複雜度執行中去。這樣來看算法複雜度進行了均攤。Redis不少場景都使用了這種思想。
平均狀況算法複雜度,代碼在不一樣的狀況下有不一樣的複雜度級別,參考相關發生的機率,進行平均計算。
至於平均和均攤的複雜度來講,哪一種狀況出現的可能性比較多,那麼他的複雜度越是趨近於這種複雜度。
以上的四個複雜度概念,平時見的很少,可是不少的流行的開源工具框架,不少都有這種思想在裏面。