時間複雜度 空間複雜度

2021-10-06 08:46:18 字數 3975 閱讀 8406

總結當你在大資料環境中開發**時,你一定遇到過程式執行好幾個小時、甚至好幾天的情況,或者是執行過程中電腦幾乎宕機的情況:

複雜度是衡量**執行效率的重要的度量因素。在介紹複雜度之前,有必要先看一下複雜度和計算機實際任務處理效率的關係,從而了解降低複雜度的必要性。

計算機通過乙個個程式去執行計算任務,也就是對輸入資料進行加工處理,並最終得到結果的過程。每個程式都是由**構成的。可見,編寫**的核心就是要完成計算。但對於同乙個計算任務,不同計算方法得到結果的過程複雜程度是不一樣的,這對你實際的任務處理效率就有了非常大的影響。

那提到降低複雜度,我們首先需要知道怎麼衡量複雜度。而在實際衡量時,我們通常會圍繞以下2 個維度進行。首先,這段**消耗的資源是什麼。一般而言,**執行過程中會消耗計算時間和計算空間,那需要衡量的就是時間複雜度和空間複雜度。

我舉乙個實際生活中的例子。某個十字路口沒有建立立交橋時,所有車輛通過紅綠燈分批次行駛通過。當大量汽車同時過路口的時候,就會分別消耗大家的時間。但建了立交橋之後,所有車輛都可以同時通過了,因為立交橋的存在,等於是消耗了空間資源,來換取了時間資源。

其次,這段**對於資源的消耗是多少。我們不會關注這段**對於資源消耗的絕對量,因為不管是時間還是空間,它們的消耗程度都與輸入的資料量高度相關,輸入資料少時消耗自然就少。為了更客觀地衡量消耗程度,我們通常會關注時間或者空間消耗量與輸入資料量之間的關係。

好,現在我們已經了解了衡量複雜度的兩個緯度,那應該如何去計算複雜度呢?

複雜度是乙個關於輸入資料量 n 的函式。假設你的**複雜度是 f(n),那麼就用個大寫字母 o 和括號,把 f(n) 括起來就可以了,即 o(f(n))。例如,o(n) 表示的是,複雜度與計算例項的個數 n 線性相關;o(logn) 表示的是,複雜度與計算例項的個數 n 對數相關。

通常,複雜度的計算方法遵循以下幾個原則:

值得一提的是,o(1) 也是表示乙個特殊複雜度,含義為某個任務通過有限可數的資源即可完成。此處有限可數的具體意義是,與輸入資料量 n 無關。

例如,你的**處理 10 條資料需要消耗 5 個單位的時間資源,3 個單位的空間資源。處理 1000 條資料,還是只需要消耗 5 個單位的時間資源,3 個單位的空間資源。那麼就能發現資源消耗與輸入資料量無關,就是 o(1) 的複雜度。

為了方便你理解不同計算方法對複雜度的影響,我們來看乙個**任務:對於輸入的陣列,輸出與之逆序的陣列。例如,輸入 a=[1,2,3,4,5],輸出 [5,4,3,2,1]。

先看方法一,建立並初始化陣列 b,得到乙個與輸入陣列等長的全零陣列。通過乙個 for 迴圈,從左到右將 a 陣列的元素,從右到左地賦值到 b 陣列中,最後輸出陣列 b 得到結果。

**如下:

public

void

s1_1()

;int b=

newint[5

];for(

int i =

0; i < a.length; i++

)for

(int i =

0; i < a.length; i++

) system.out.

println

(arrays.

tostring

(b))

;}

這段**的輸入資料是 a,資料量就等於陣列 a 的長度。**中有兩個 for 迴圈,作用分別是給b 陣列初始化和賦值,其執行次數都與輸入資料量相等。因此,**的時間複雜度就是 o(n)+o(n),也就是 o(n)。

空間方面主要體現在計算過程中,對於儲存資源的消耗情況。上面這段**中,我們定義了乙個新的陣列 b,它與輸入陣列 a 的長度相等。因此,空間複雜度就是 o(n)。

接著我們看一下第二種編碼方法,它定義了快取變數 tmp,接著通過乙個 for 迴圈,從 0 遍歷到a 陣列長度的一半(即 len(a)/2)。每次遍歷執行的是什麼內容?就是交換首尾對應的元素。最後列印陣列 a,得到結果。

**如下:

public

void

s1_2()

;int tmp =0;

for(

int i =

0; i <

(a.length /2)

; i++

) system.out.

println

(arrays.

tostring

(a))

;}

這段**包含了乙個 for 迴圈,執行的次數是陣列長度的一半,時間複雜度變成了 o(n/2)。根據複雜度與具體的常係數無關的性質,這段**的時間複雜度也就是 o(n)。

空間方面,我們定義了乙個 tmp 變數,它與陣列長度無關。也就是說,輸入是 5 個元素的陣列,需要乙個 tmp 變數;輸入是 50 個元素的陣列,依然只需要乙個 tmp 變數。因此,空間複雜度與輸入陣列長度無關,即 o(1)。

可見,對於同乙個問題,採用不同的編碼方法,對時間和空間的消耗是有可能不一樣的。因此,工程師在寫**的時候,一方面要完成任務目標;另一方面,也需要考慮時間複雜度和空間複雜度,以求用盡可能少的時間損耗和盡可能少的空間損耗去完成任務。

好了,通過前面的內容,相信你已經對時間複雜度和空間複雜度有了很好的理解。從本質來看,時間複雜度與**的結構有著非常緊密的關係;而空間複雜度與資料結構的設計有關,關於這一點我們會在下一講進行詳細闡述。接下來我先來系統地講一下時間複雜度和**結構的關係。

**的時間複雜度,與**的結構有非常強的關係,我們一起來看一些具體的例子。

例 1,定義了乙個陣列 a = [1, 4, 3],查詢陣列 a 中的最大值,**如下:

public

void

s1_3()

;int max_val =-1

;int max_inx =-1

;for

(int i =

0; i < a.length; i++)}

system.out.

println

(max_val)

;}

這個例子比較簡單,實現方法就是,暫存當前最大值並把所有元素遍歷一遍即可。因為**的結構上需要使用乙個 for 迴圈,對陣列所有元素處理一遍,所以時間複雜度為 o(n)。

例2,下面的**定義了乙個陣列 a = [1, 3, 4, 3, 4, 1, 3],並會在這個陣列中查詢出現次數最多的那個數字:

public

void

s1_4()

;int val_max =-1

;int time_max =0;

int time_tmp =0;

for(

int i =

0; i < a.length; i++)if

(time_tmp > time_max)}}

system.out.

println

(val_max)

;}

這段**中,我們採用了雙層迴圈的方式計算:第一層迴圈,我們對陣列中的每個元素進行遍歷;第二層迴圈,對於每個元素計算出現的次數,並且通過當前元素次數 time_tmp 和全域性最大次數變數 time_max 的大小關係,持續儲存出現次數最多的那個元素及其出現次數。由於是雙層迴圈,這段**在時間方面的消耗就是 n*n 的複雜度,也就是 o(n²)。

在這裡,我們給出一些經驗性的結論:

有了這些基本的結論,再去分析**的時間複雜度將會輕而易舉。

為了更好理解,我們來看一些資料。假設某個計算任務需要處理 10萬 條資料。你編寫的**:

數字是不是一下子變得很懸殊?通常在小資料集上,時間複雜度的降低在絕對處理時間上沒有太多體現。但在當今的大資料環境下,時間複雜度的優化將會帶來巨大的系統收益。而這是優秀工程師必須具備的工程開發基本意識。

複雜度通常包括時間複雜度和空間複雜度。在具體計算複雜度時需要注意以下幾點。

複雜度細分為時間複雜度和空間複雜度,其中時間複雜度與**的結構設計高度相關;空間複雜度與**中資料結構的選擇高度相關。會計算一段**的時間複雜度和空間複雜度,是工程師的基本功。這項技能你在實際工作中一定會用到,甚至在參加網際網路公司面試的時候,也是面試中的必考內容。

時間複雜度 空間複雜度

時間複雜度 在電腦科學中,演算法的時間複雜度是乙個函式,它定性描述了該演算法的執行時間。這是乙個關於代表演算法輸入值的字串 的長度的函式。時間複雜度常用大o符號 表述,不包括這個函式的低階項和首項係數。計算時間複雜度的方法 1 只保留高階項,低階項直接丟棄 2 係數不要 3 執行次數是常數是為o 1...

時間複雜度 空間複雜度

演算法複雜度分為時間複雜度和空間複雜度。其作用 時間複雜度是指執行演算法所需要的計算工作量 而空間複雜度是指執行這個演算法所需要的記憶體空間。一 時間複雜度 時間頻度 乙個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道...

時間複雜度 空間複雜度

一 時間複雜度 實際是指程式執行次數,而不是程式執行時間 1.我們一般討論的是最壞時間複雜度,這樣做的原因是 最壞情況下的時間複雜度是演算法在任何輸入例項上執行時間的上限,以最壞代表最全。2.時間複雜度的書寫規則 忽略常數項,用o 1 表示 選取最壞時間複雜度即選取增長最快的項 遞迴的時間複雜度 遞...