算法複雜度

算法和數據結構密不可分。算法依賴數據結構。算法

數據結構和算法解決是「如何讓計算機更快時間、更省空間的解決問題」數據結構

所以從  執行時間 和  資源佔用 兩個維度來評估數據結構和算法的性能框架

也就是咱們接下來說的複雜度的問題,數據結構和算法

時間維度 便是 時間複雜度;資源空間維度 就是 空間複雜度;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,平均狀況時間複雜度:

  • 全部發生的狀況爲 n +1 (n內找到+沒找到)
  • 機率,對於每一次查找 是的機率爲 1/2
  • 對於n+1種狀況來講,n內找到的機率爲 1/n

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         
View Code
 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();    
View Code

 

均攤狀況時間複雜度,實際上是一種更加特殊的平均狀況時間複雜度,

均攤也是存在複雜度級別的差距,可是級別較高的複雜度會被平攤到級別較低的複雜度執行中去。這樣來看算法複雜度進行了均攤。Redis不少場景都使用了這種思想。

平均狀況算法複雜度,代碼在不一樣的狀況下有不一樣的複雜度級別,參考相關發生的機率,進行平均計算。

至於平均和均攤的複雜度來講,哪一種狀況出現的可能性比較多,那麼他的複雜度越是趨近於這種複雜度。

以上的四個複雜度概念,平時見的很少,可是不少的流行的開源工具框架,不少都有這種思想在裏面。

相關文章
相關標籤/搜索