概述
尷尬,學妹問我“冒泡排序、二分查找、希爾排序、快速排序方法”算法的『時間複雜度』,我只能使用百度查詢答案進行了回答,但這不符合我的人設,我必須要弄懂這個東西。
作爲一個「不稱職的攻城獅」,對複雜度的概念是很模糊的,更不要說去計算複雜度了。
但是在開發中對於代碼快的執行,做到“提高代碼執行率、降低內存佔用率”,巧了,這和我百度瞭解到的『複雜度』相似,然後查詢了相關資料,進行一個複習歸納。
簡單來說,就是同一個功能:
- 別人寫出來運行,內存佔用100M,運行時間需要1秒;
- 自己寫出來運行,內存佔用500M,運行時間需要5秒。
這你能忍?但是在代碼開發中是沒辦法計算內存佔用和運行時間情況的,怎麼樣才能預估代碼塊達到了較優呢?而且在不同的設備上執行的效果也不同,
比如你新買的手機和用了5年的手機,打開同一個軟件所需要的時間肯定是不同的。
但是我們基本的代碼塊運行次數是可以計算的,這就是我們今天的主角『複雜度』。
PS:衡量不同算法之間的優劣就要用到時間複雜度和空間複雜度。
時間複雜度
什麼是時間複雜度?舉個例子:
public string attack(int n) { for (int i = 0; i < n; i++) { Console.WriteLine("被攻擊"); } return "快來救我"; }
上面代碼執行如下:
i = 0 : 執行 1 次 i < n : 執行 n+1 次 i++ : 執行 n+1 次 Console.WriteLine("被攻擊"); : 執行 n 次 return "快來救我" : 執行 1 次
這個方法的總執行次數就是 3n + 4 次,
但是開發過程中不可能都這樣去數,所以根據代碼執行時間推導過程就有一個規律,這就是所有代碼執行時間 T(n)和代碼的執行次數 f(n) ,這個是成正比的,而這個規律有一個公式(大歐表示法):
T(n) = O(f(n))
n 是輸入數據的大小或者輸入數據的數量
T(n) 表示一段代碼的總執行時間
f(n) 表示一段代碼的總執行次數
O 表示代碼的執行時間 T(n) 和 執行次數f(n) 成正比
套用公式可以得到上面方法的複雜度就是:O(3n+4),簡化爲O(n)。
爲什麼可以簡化爲O(n)呢,我們看下簡化過程。
- 如果只是常數直接估算爲1,O(3) 的時間複雜度就是 O
(1)
,不是說只執行了1次,而是對常量級時間複雜度的一種表示法。一般情況下,只要算法裏沒有循環和遞歸,就算有上萬行代碼,時間複雜度也是O(1) - O(3n+4) 裏常數4對於總執行次數的幾乎沒有影響,直接忽略不計,係數 3 影響也不大,因爲3n和n都是一個量級的,所以作爲係數的常數3也估算爲1或者可以理解爲去掉係數,所以 O(3n+4) 的時間複雜度爲 O
(n)
- 如果是多項式,只需要保留n的最高次項,O
( 666n³ + 666n² + n )
,這個複雜度裏面的最高次項是n的3次方。因爲隨着n的增大,後面的項的增長遠遠不及n的最高次項大,所以低於這個次項的直接忽略不計,常數也忽略不計,簡化後的時間複雜度爲 O(n³)
是不是很簡單,我們看下常用的時間複雜度。
1、常數階 O(1)
通過上面的簡化過程知道,一般情況下,只要算法裏沒有循環和遞歸,就算有上萬行代碼,時間複雜度也是 O(1)
,因爲它的執行次數不會隨着任何一個變量的增大而變長,比如下面這樣
public string attack(int n) { int power = 100; int speed = 65; Console.WriteLine("力量是:", power); Console.WriteLine("速度是:", speed); if (power == 200) Console.WriteLine("快來救我"); ..... return "我沒事,不用擔心"; }
2、線性階 O(n)
只有一層循環或者遞歸等,時間複雜度就是 O(n)
,這樣消耗時間隨着N變化而變化,比如下面這種
public string attack(int n) { for (int i = 0; i < n; i++) { Console.WriteLine("我沒事"); } return "我很好"; } public string runaway(int n) { while(--n>0) Console.WriteLine("跑跑跑"); return "我跑的很快"; } public string gameover(int n) { Console.WriteLine("hhh"); if (--n > 0) { Console.WriteLine(gameover(n)); } return "結束"; }
3、平方階 O(n²)
就是雙重for循環,如果外層的是n,內層的是m,那麼複雜度就是O(m*n),比如下面這種
public string attack(int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { Console.WriteLine("我沒事"); } } return "我很好"; }
還記得上面的簡化不?這樣的,總執行次數爲 n + n²,如果是多項式,取最高次項,所以這個時間複雜度也是 O(n²)
public string attack(int n) { for (int i = 0; i < n; i++) { Console.WriteLine("我沒事"); } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { Console.WriteLine("我沒事"); } } return "我很好"; }
4、對數階 O(logN)
下面的循環就按i*2何時=N,就是2的多少次方是N,即log2^N,讀作以2爲底N的對數。對數公式[1] log不瞭解的可以點擊查看一下,這裏不做過多講解。
int i = 1; while (i < n) i = i * 2;
5、線性對數階 O(nlogN)
就是for循環套while,如下nlog2^N,讀作n倍以2爲底N的對數。。對數公式[1] log不瞭解的可以點擊查看一下,這裏不做過多講解。
for (int j = 0; j < n; j++) { int i = 1; while (i < n) i = i * 2; }
6、更多...
其他還有一些時間複雜度的運用,下面由快到慢排列了一下:
複雜度 | 名稱 |
---|---|
O(1) |
常數複雜度 |
O(logn) |
對數複雜度 |
O(n) |
線性時間複雜度 |
O(nlogn) |
線性對數複雜度 |
O(n²) |
平方 |
O(n³) |
立方 |
O(2^ n ) |
指數,一點數據量就卡的不行 |
O(n!) |
階乘,就更慢了 |
這些時間複雜度有什麼區別呢,看張圖
隨着數據量或者 n 的增大,時間複雜度也隨之增加,也就是執行時間的增加,會越來越慢,越來越卡。
總的來說時間複雜度就是執行時間增長的趨勢,那麼空間複雜度就是存儲空間增長的趨勢。
空間複雜度
空間複雜度就是算法需要多少內存,佔用了多少空間。
常用的空間複雜度有 O(1)
、O(n)
、O(n²)。
O(1) 就是算法執行臨時空間不會隨着N發生變化 都算成一個常量,如下。
public string attack(int n) { int j = 0; for (int i = 0; i < n; i++) { j = i; j++; } return "我很好"; }
O(n)就是一開始就開闢的內存,不會在改變,如下new出來了N個,但是N下面循環並沒有再開臨時變量
public string attack(int n) { int[] m = new int[n]; int j = 0; for (int i = 1; i < n; i++) { j = i; j++; } return "我很好"; }
『空間複雜度』和『時間複雜度』判斷標準差不多,主要是看開了多少個臨時變量,是否跟N有一定的線性關係。
這都是一些簡單的,如果是複雜的怎麼計算呢, 下面都計算時間複雜度爲例子:
- T(n) = n + 29 一般說是O(n)因爲常數項影響函數增長很小
- T(n) = n^3 + n^2 + 29 一般說爲O(n3),n3 的增長速度是遠超 n^2 的,同時 n^2 的增長速度是遠超 n 的,所以有了高次項可以忽略低次項
- T(n) = 3n^3 一般說爲O(n^3)因爲階數比乘數影響增長速度最顯著我們通常忽略
總結
重新梳理了一遍以後,現在寫代碼眼裏浮現的都是一堆O(1)、O(n)、O(n²)、logN,感覺要瘋了。
參考資料
2.如何理解時間複雜度和空間複雜度:https://www.cnblogs.com/magicg/p/15178750.html
3.C#算法的時間複雜度和空間複雜度:https://blog.csdn.net/q_17600689511/article/details/100189543
4.c#算法時間複雜度 面試必看:https://blog.csdn.net/weixin_44370124/article/details/119321322
歡迎關注訂閱微信公衆號【熊澤有話說】,更多好玩易學知識等你來取
作者:熊澤-學習中的苦與樂 公衆號:熊澤有話說 出處: https://www.cnblogs.com/xiongze520/p/15666448.html 您可以隨意轉載、摘錄,但請在文章內註明作者和原文鏈接。
|