時間複雜度與空間複雜度完全解析

2021-07-23 09:01:53 字數 3723 閱讀 3544

本文出自【何嘉龍的部落格】

國慶小長假到了,不知道大家的國慶長假過得怎麼樣?在空間裡面看到同學都外出浪了,我表示很嫉妒啊。哎,作為乙個死宅男,還是安安靜靜的在宿舍寫我的部落格還有刷劇吧。我是自動化專業,由於是非科班出生,又聽說大公司都比較重基礎,於是乎,我準備惡補下資料結構與演算法分析,我看的是《大話資料結構》,希望把自己的基礎功打牢一點,進自己想進的公司吧。我也希望大家國慶能過得愉快!

在進行演算法分析時,語句總的執行次數t(n)是關於問題規模n的函式,進而分析t(n)隨n的變化情況並確定t(n)的數量級。演算法的時間複雜度,也就是演算法的時間複雜量度,記住:t(n)=o(f(n))。它表示隨問題規模n的增大,演算法執行時間的增長率和f(n)的增長率相同,稱作演算法的漸近時間複雜度,簡稱為時間複雜度。其中f(n)是問題規模n的某個函式。

這樣用大寫o()來體現演算法時間複雜度的記法,我們稱之為大o記法。一般情況下,隨著n的增大,t(n)增長最慢的演算法為最優演算法。

顯然,由此演算法時間複雜度的定義可知,我們的三個求和演算法的時間複雜度分別為o(n),o(1),o(n²)。我們分別給它們取了非官方的名稱,o(1)叫常數階、o(n)叫做線性階,o(n²)叫平方階,當然,還有其他的一些階,我們之後會介紹。

那麼如何分析乙個演算法的時間複雜度呢?即如何推導大o階呢?我們給出了下面的推導方法,基本上,這也就是總結前面我們舉的例子。

推導大o階:

1.用常數1取代執行時間後的所有加法常數。

2.在修改後的執行次數函式中,只保留最高端項。

3.如果最高端存在且不是1,則去除與這個項相乘的常數。得到的結果就是大o階。

哈,彷彿是得到了遊戲攻略一樣,我們好像已經得到了乙個推導演算法時間複雜度的萬能公式。可事實上,分析乙個演算法的時間複雜度,沒有這麼簡單,我們還需要多看幾個例子。

首先順序結構的時間複雜度。下面這個演算法,也就是高斯演算法,為什麼時間複雜度不是o(3),而是o(1)。

int

sum = 0, n = 100; //執行1次

sum = (1 + n) * n / 2; //執行1次

printf("%d", sum); //執行1次

這個演算法的執行次數函式是f(n) = 3.根據我們推導的大o階的方法,第一步就是把常數項3改為1,。在保留最高端項時發現,它根本沒有最高端項,所以這個演算法的時間複雜度為o(1)。

另外,我們試想一下,如果這個演算法當中的語句sum = (1 + n) * n / 2有10句,即:

int

sum = 0, n = 100; //執行1次

sum = (1 + n) * n / 2; //執行第1次

sum = (1 + n) * n / 2; //執行第2次

sum = (1 + n) * n / 2; //執行第3次

sum = (1 + n) * n / 2; //執行第4次

sum = (1 + n) * n / 2; //執行第5次

sum = (1 + n) * n / 2; //執行第6次

sum = (1 + n) * n / 2; //執行第7次

sum = (1 + n) * n / 2; //執行第8次

sum = (1 + n) * n / 2; //執行第9次

sum = (1 + n) * n / 2; //執行第10次

printf("%d", sum); //執行1次

事實上無論n為多少,上面的兩段**就是3次和12次執行的差異。這種與問題的大小無關(n的多少),執行時間恆定的演算法,我們稱之為具有o(1)的時間複雜度,又叫常數階。

注意:不管這個常數是多少,我們都記作o(1),而不能是o(3)、o(12)等其他任何數字,這是初學者常犯的錯誤。

對於分支結構而言,無論是真,還是假,執行的次數都是恆定的,不會隨著n的變大而發生變化,所以單純的分支結構(不包含在迴圈結構裡),其時間複雜度也是o(1)。

線性階的迴圈結構會複雜很多。要確定某個演算法的階次,我們常常需要確定某個特定語句或某個語句集執行的次數。因此,我們要分析演算法的複雜度,關鍵就是要分析迴圈結構的運**況。

下面這段**,它的迴圈的時間複雜度為o(n),因為迴圈體中的**必須要執行n次。

for(int i = 0; i

< n; i++)

下面這段**,時間複雜度又是多少呢?

int

count = 1;

while(count

< n)

由於每次count乘以2之後,就距離n更近了一分。也就是說,有多少個2相乘後大於n,則會退出迴圈。由2的x次方=n,得到x=log₂n。所以這個迴圈的時間複雜度為o(logn)。

下面例子是乙個迴圈巢狀,它的內迴圈剛才我們分析過了,時間複雜度為o(n)。

for(int i = 0; i < n; i++) 

}

而對於外層的迴圈,不過是內部這個時間複雜度為o(n)的語句,再迴圈n次。所以這段**的時間複雜度為o(n²)。

如果外迴圈的迴圈次數改為了m,時間複雜度就為o(m × n)。

for(int i = 0; i < m; i++) 

}

所以,我們可以總結出,迴圈的時間複雜度等於迴圈體的複雜度乘以該迴圈執行的次數。

那麼下面這個迴圈巢狀,他的時間複雜度又是多少呢?

for(int i = 0; i < n; i++) 

}

當i = 0時,內迴圈執行了n次,當i = 1時,執行了n - 1次,依次類推,當i = n - 1時,執行了一次。所以總的執行次數為:

用我們推導大o階的方法,第一條,沒有加法常數不予考慮;第二條,只保留最高端項,因此保留n²/2;第三條,去除這個項相乘的常數,也就是去除1/2,最終**的時間複雜度為o(n²)。

常見的時間複雜度如表所示:

所耗費的時間從小到大依次為:

我們在寫**的時候,完全可以用空間來換取時間,比如說,要判斷某某年是不是閏年,你可能會花一點心思寫了乙個演算法,而且由於是乙個演算法,也就意味著,每次給乙個年份,都是要通過計算得到是否是閏年的結果,還有另外乙個辦法就是,事先建立乙個有2050個元素的陣列(年數略比現實多一點),然後把所有的年份按下標的數字對應,如果是閏年,此陣列項就是1,否則為0。這樣,所謂的判斷一年是否為閏年,就變成了查詢陣列的某一項值是多少的問題。此時,我們的運算已經是最小化了,但是硬碟上或者記憶體中需要儲存這2050個0和1。

這是一筆空間上的開銷來換取計算時間的小技巧。到底哪乙個好,要看你用在什麼地方。

演算法的空間複雜度通過計算演算法所需的儲存控制項實現,演算法空間複雜度的計算公式為:s(n) = o(f(n)),其中,n為問題的規模,f(n)為語句關於n所佔儲存空間的函式。

一般情況下,乙個程式在機器上執行時,除了需要儲存程式本身的指令、常數、變數和輸入資料外,還需要儲存對資料操作的儲存單元。當不限定詞的使用「複雜度」的時候,通常指時間複雜度。

時間複雜度與空間複雜度

空間複雜度 space complexity 是對乙個演算法在執行過程中臨時占用儲存空間大小的量度,記做s n o f n 比如直接 插入排序 的時間複雜度 是o n 2 空間複雜度是o 1 而一般的 遞迴演算法就要有o n 的空間複雜度了,因為每次遞迴都要儲存返回資訊。乙個演算法的優劣主要從演算法...

時間複雜度與空間複雜度

本文是對時間複雜度以及空間複雜度的乙個理解 時間複雜度 由於環境的不同,同樣的 執行所需要的時間是不同的,所以是不能拿來比較的 而函式中執行的次數確實一樣的 所以時間複雜度就是 程式每個迴圈中的語句總共會執行的次數 時間複雜度的表示方法 大o漸進表示法 o f n 這裡的f n 是什麼呢?void ...

時間複雜度與空間複雜度

本文部分取自搜狗百科 在求演算法效率時,通常有事前分析和事後分析兩種方法,事後分析因為必須實際檢驗過後才能得出答案,且可能由於硬體方面等外部原因影響結果而不被推廣,事前分析的主要就是在考量乙個演算法的基本執行次數,這就是時間複雜度。時間複雜度 一般情況下,演算法中基本操作重複執行的次數是問題規模n的...