一文搞定時間複雜度和空間複雜度

2021-10-08 10:32:08 字數 4349 閱讀 9089

寫在前面:博主是一位普普通通的19屆雙非軟工在讀生,平時最大的愛好就是聽聽歌,逛逛b站。博主很喜歡的一句話花開堪折直須折,莫待無花空折枝:博主的理解是頭一次為人,就應該做自己想做的事,做自己不後悔的事,做自己以後不會留有遺憾的事,做自己覺得有意義的事,不浪費這大好的青春年華。博主寫部落格目的是記錄所學到的知識並方便自己複習,在記錄知識的同時獲得部分瀏覽量,得到更多人的認可,滿足小小的成就感,同時在寫部落格的途中結交更多志同道合的朋友,讓自己在技術的路上並不孤單。

目錄:1.演算法的時間複雜度

演算法時間複雜度簡介

演算法的時間複雜度預估值

大o記法

時間複雜度案例分析

2.演算法的空間複雜度

空間複雜度概述

1.1演算法時間複雜度簡介

判斷乙個演算法所編程式執行時間的多少,並不是將程式編寫出來,而是通過在計算機上執行所消耗的時間來度量。原因很簡單,一方面,解決乙個問題的演算法可能有很多種,一一實現的工作量無疑是巨大的,得不償失;另一方面,不同計算機的軟、硬體環境不同,即便使用同一臺計算機,不同時間段其系統環境也不相同,程式的執行時間很可能會受影響,嚴重時甚至會導致誤判。

實際場景中,我們更喜歡用乙個估值來表示演算法所程式設計序的執行時間。所謂估值,即估計的、並不準確的值。注意,雖然估值無法準確的表示演算法所程式設計序的執行時間,但它的得來並非憑空揣測,需要經過縝密的計算後才能得出,也就是說:

表示乙個演算法所編程式執行時間的多少,用的並不是準確值(事實上也無法得出),而是根據合理方法得到的預估值。

1.2演算法的時間複雜度預估值
for

(int i =

0; i < n ; i++

)//

可以看到,這段程式中僅有 2 行**,其中:

for 迴圈從 i 的值為 0 一直逐增至 n(注意,迴圈退出的時候 i 值為 n),因此 for 迴圈語句執行了 n+1 次;而迴圈內部僅有一條語句,a++ 從 i 的值為 0 就開始執行,i 的值每增 1 該語句就執行一次,一直到 i 的值為 n-1,因此,a++ 語句一共執行了 n 次。因此,整段**中所有語句共執行了 (n+1)+n 次,即 2n+1 次。資料結構中,每條語句的執行次數,又被稱為該語句的頻度。整段**的總執行次數,即整段**的頻度。

再舉乙個例子:

for

(int i =

0; i < n ; i++

)// n+1

}

讀者可結合注釋,計算此段程式的頻度為:(n+1)+n*(m+1)+nm,簡化後得 2nm+2n+1。值得一提的是,不同程式的執行時間,更多場景中比較的是在最壞條件下程式的執行時間。以上面這段程式為例,最壞條件即指的是當 n、m 都為無限大時此段程式的執行時間。

要知道,當 n、m 都無限大時,我們完全就可以認為 n==m。在此基礎上,2nm+2n+1 又可以簡化為 2n2+2*n+1,這就是此段程式在最壞情況下的執行時間,也就是此段程式的頻度。

如果比較以上 2 段程式的執行時間,即比較 2n+1 和 2n2+2n+1 的大小,顯然當 n 無限大時,前者要遠遠小於後者(如圖 2 所示)。

顯然,第 1 段程式的執行時間更短,執行更快。

思考乙個問題,類似 2n+1、2n2+2n+1 這樣的頻度,還可以再簡化嗎?答案是肯定的。

以 2n+1 為例,當 n 無限大時,是否在 2n 的基礎上再做 +1 操作,並無關緊要,因為 2n 和 2n+1 當 n 無限大時,它們的值是無限接近的。甚至於我們還可以認為,當 n 無限大時,是否給 n 乘 2,也是無關緊要的,因為 n 是無限大,2*n 也是無限大。

再以無限大的思想來簡化 2n2+2n+1。當 n 無限大的:

首先,常數 1 是可以忽略不計的;

其次,對於指數級的 2n2 來說,是否在其基礎上加 2n,並無關緊要;

甚至於,對於是否給 n2 乘 2,也可以忽略。

因此,最終頻度 2n2+2n+1 可以簡化為 n2 。

事實上,對於乙個演算法(或者一段程式)來說,其最簡頻度往往就是最深層次的迴圈結構中某一條語句的執行次數。例如 2n+1 最簡為 n,實際上就是 a++ 語句的執行次數;同樣 2n2+2n+1 簡化為 n2,實際上就是最內層迴圈中 num++ 語句的執行次數。

1.3大o記法
得到最簡頻度的基礎上,為了避免人們隨意使用 a、b、c 等字元來表示執行時間,需要建立統一的規範。資料結構推出了大 o 記法(注意,是大寫的字母 o,不是數字 0)來表示演算法(程式)的執行時間。發展至今,此方法已為大多數人所採納。

大 o 記法的表示方法也很簡單,格式如下:

o

(頻度)

其中,這裡的頻度為最簡之後所得的頻度。

例如,用大 o 記法表示上面 2 段程式的執行時間,則上面第一段程式的時間複雜度為 o(n),第二段程式的時間複雜度為 o(n2)。

如下列舉了常用的幾種時間複雜度,以及它們之間的大小關係:

o(1)常數階 < o(logn)對數階 < o(n)線性階 < o(n2)平方階 < o(n3)(立方階) < o(2n) (指數階)

注意,這裡僅介紹了以最壞情況下的頻度作為時間複雜度,而在某些實際場景中,還可以用最好情況下的頻度和最壞情況下的頻度的平均值來作為演算法的時間複雜度。

1.4時間複雜度案例分析

1.對數階:

for

(int i=

0;ix++;

設迴圈體內部基本語句的頻度是f(n),那麼就有3f(n)

<=n

那麼就有f(n)<=log3n

2 .根號階

x=n;

//n>1y=0

;while

(x>=

(y+1)*

(y+1))

y++;

這個時間複雜度o(√n):

設基本語句y++執行次數為f(n),且x=n那麼n>=(f(n)+1)2

f(n)<=√n-1捨去常數項得到最終結果

2.1空間複雜度概述

與時間複雜度類似,乙個演算法的空間複雜度,也常用大 o 記法表示。

要知道每乙個演算法所編寫的程式,執行過程中都需要占用大小不等的儲存空間,例如:

1.程式**本身所占用的儲存空間;

2.程式中如果需要輸入輸出資料,也會占用一定的儲存空間;

3.程式在執行過程中,可能還需要臨時申請更多的儲存空間

首先,程式自身所占用的儲存空間取決於其包含的**量,如果要壓縮這部分儲存空間,就要求我們在實現功能的同時,盡可能編寫足夠短的**。

程式執行過程中輸入輸出的資料,往往由要解決的問題而定,即便所用演算法不同,程式輸入輸出所占用的儲存空間也是相近的。

事實上,對演算法的空間複雜度影響最大的,往往是程式執行過程中所申請的臨時儲存空間。不同的演算法所編寫出的程式,其執行時申請的臨時儲存空間通常會有較大不同。

舉個例子:

int n;

scanf

("%d"

,&n)

;int a[10]

;

通過分析不難看出,這段程式在執行時所申請的臨時空間,並不隨 n 的值而變化。而如果將第 3 行**改為:

int a[n]

;

此時,程式執行所申請的臨時空間,和 n 值有直接的關聯。

所以,如果程式所占用的儲存空間和輸入值無關,則該程式的空間複雜度就為 o(1);反之,如果有關,則需要進一步判斷它們之間的關係:

1.如果隨著輸入值 n 的增大,程式申請的臨時空間成線性增長,則程式的空間複雜度用 o(n) 表示;

2.如果隨著輸入值 n 的增大,程式申請的臨時空間成 n2 關係增長,則程式的空間複雜度用 o(n2) 表示;

3.如果隨著輸入值 n 的增大,程式申請的臨時空間成 n3 關係增長,則程式的空間複雜度用 o(n3) 表示;

等等。

在多數場景中,乙個好的演算法往往更注重的是時間複雜度的比較,而空間複雜度只要在乙個合理的範圍內就可以。

時間複雜度和空間複雜度(一)

如何度量演算法的效率?效率一般指演算法的執行時間。那麼如何度量演算法的執行時間?事後統計方法 可以把演算法跑若干次,用計時器計時 通過設計好的測試程式和資料,利用計算機計時器對不同演算法編制的程式的執行時間進行比較,從而確定演算法效率高低。缺陷 必須依據演算法,事先編制好測試程式,大量耗費時間精力。...

演算法複雜度 時間複雜度和空間複雜度

1 時間複雜度 1 時間頻度 乙個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。並且乙個演算法花費的時間與演算法中語句的執行次數成正比例,哪個演算法中語句執行次數...

演算法複雜度 時間複雜度和空間複雜度

演算法複雜度 時間複雜度和空間複雜度 關鍵字 演算法複雜度 時間複雜度 空間複雜度 1 時間複雜度 1 時間頻度 乙個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時 間多,哪個演算法花費的時間少就可以...