算法時間復雜度、空間復雜度(大O表示法)


什么是算法?

計算機是人的大腦的延伸,它的存在主要是為了幫助我們解決問題。

而算法在計算機領域中就是為了解決問題而指定的一系列簡單的指令集合。不同的算法需要不同的資源,例如:執行時間或消耗內存。

如果一個算法執行時間需要好幾年或者需要占用非常大的內存,那么這算法幾乎毫無用處,即使有價值使用場景也非常有限。

因此,一般上我們討論一個算法的優劣的時候可以通過時間空間兩個維度來衡量,也就是常說的:

1、時間復雜度;

2、空間復雜度;

我們當然希望執行時間和消耗內存都越少越好,但很多時候其實我們無法同時兼顧,需要在時間和空間之間做一定的取舍達到平衡。

時間復雜度

一般上,如果我們要衡量一個程序片段的執行時間,我們會把程序運行一次並打印時間,這是最常見也是最簡單的方式。

這種方式存在一些問題:

1、不同的計算機會產生不同的執行時間,甚至於相同的計算機也會產生不同的時間,根據計算機當前的情況而定;

2、通常可能我們使用很小的數據量來測量,但一個算法隨着數據量的不同性能變化是不同的,所以小數據量衡量的時間不見得適用於大數據量;

3、甚至於有時候一個算法壓根無法直接通過運行來測試時間。

為了解決這些問題,引入了數學領域中的 “大O標記法”

大O標記法

 數學概念:如果存在正常數c和n,使得當N≥n的時候T(N)≤cf(N),則標記為T(N)=O(f(N))

數學概念看起來有些費解,我們可以把T(N)=1000N把f(N)=N2,當N=1000,c=1的時候,1000N=N2。而當N>1000的時候,N2>1000N。

也就是說,當N無限大的時候,N2的值將必定大於1000N的值,也就是說1000N這個函數的值不會超過N2,或者說N2是1000N的上界,它限定了1000N的最大值

如果我們考慮一個算法最糟糕的時候會執行多久,則大O標記法很輕易就能表示出來。

下面我來看一個例子,關於大O標記法怎么衡量算法時間:

1 int total = 0;
2 for (int i = 0; i < n ; i++ ) {
3     total += i;
4 }

我們計算一下代碼的執行時間:

1、第1行有一個賦值操作記1個單位時間;

2、第2行有一個賦值操作記1個單位時間,一個比較操作記n+1個單位時間,自增運算記n個單位時間,合計:2n+2個時間;

3、第3行在循環體中執行n次,我們記為2*n個單位時間;

以上代碼合計時間為:4n+3,也就是 T(n) = O(4n+3),O(4n+3)表示程序的運行時間上界。如果n無限大的時候,我們忽略倍數4和常數3,則T(n)的運行時間為O(n);

我們也可以說,以上代碼的時間復雜度為O(n)。

上面的例子中,我們分析了每一行的代碼運行時間,並最終得出時間復雜度為O(n),但一個算法的復雜度有時候讓我們難以像上面這樣每一步都去計算並合計時間,由此我們可以得出一些簡單的原則來計算時間

1、常數階O(1)

無論代碼執行多少行,只要代碼中沒有for等循環結構,那么復雜度就是O(1),如:

1 int i = 1;
2 int j = 2;
3 ++i;
4 j++;
5 int m = i + j;

無論程序中數據量有多少,都不影響代碼的運行時間,則為常數階。

2、線性階O(N)

如果存在一個循環體,那么循環n次,則復雜度為O(N),如:

1 for(i=1; i<=n; ++i)
2 {
3    j = i;
4    j++;
5 }

3、對數階O(logN)

線性階O(N)的情況是循環了N次,所以對數階的情況就是循環了logN次,如:

1 int i = 1;
2 while(i<n)
3 {
4     i = i * 2;
5 }

我們假設while循環體在循環了x次之后退出循環,那么也就是i*2x≥n,時間復雜度的上界也就是log2n=x,我們標記為O(logN)。

4、線性對數O(NlogN)

對數階是logN,那么nlogN即使把對數階循環n次,如:

1 for(m=1; m<n; m++)
2 {
3     i = 1;
4     while(i<n)
5     {
6         i = i * 2;
7     }
8 }

5、平方階O(N2)

平方階很容易理解,嵌套循環即是,如:

1 for(x=1; i<=n; x++)
2 {
3    for(i=1; i<=n; i++)
4     {
5        j = i;
6        j++;
7     }
8 }

根據平方階可以推出,立方階O(N3),k次方階O(Nk),或者O(n * m)

 

空間復雜度

時間復雜度粗略估計了執行時間的上界,空間復雜度也是類似的,我們看幾個常見的示例:

1、空間復雜度O(1)

和時間復雜度一樣,一個隨着數據量的變化內存消耗不變化的時候,我們認為空間復雜度為常數也就是O(1),如:

1 int i = 1;
2 int j = 2;
3 ++i;
4 j++;
5 int m = i + j;

2、空間復雜度O(N)

隨着數據量變化,內存消耗呈線性變化的時候,我們稱之為O(N),如:

1 int[] m = new int[n]
2 for(i=1; i<=n; ++i)
3 {
4    j = i;
5    j++;
6 }

這里的數組m隨着數據量n的變化線性增長。

3、空間復雜度O(N2)

隨着數據量的變化,內存消耗為平方變化,如:

1 int[][] arr = new int[n][n];
2 for (int i = 0; i < n; i++) {
3     for (int j = 0; j < n; j++) {
4         arr[i][j] = new Random().nextInt();
5     }
6 }

以上二維數組中,當n+1的時候,arr數組大小從n*n變為了(n+1)*(n+1),其它空間復雜度以此類推

總結

其實無論是時間復雜度還是空間復雜度,都是在考慮當n變化的時候時間或者空間會呈什么樣的變化,並最終確定時間和空間的上界問題。

考慮時間復雜度的時候,我們簡化為思考n+1的時候,循環次數如何變化,如果不變則O(1),線性則O(N),對數則log(N)...以此類推。也就是說把n當作問題規模,當n變化的時候,執行次數的變化呈現什么規律。

考慮空間復雜度的時候,我們簡化為思考n+1的時候,內存消耗的數量如何變化,如果不變則為O(1),線性則為O(N),平方則為O(N2)...以此類推。也就是說當n變化的時候,內存消耗的變化呈現什么規律。

本文討論的是在n不斷增長到無限大最糟糕的情況下時間與空間復雜度的問題,但我們程序中不是每個算法都需要考慮n無限大問題,也就是說如果我們的n是有限的且是很小的值我們甚至完全可以不考慮它的執行時間或者空間問題。

 

參考文章:https://blog.csdn.net/jsjwk/article/details/84315770

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM