用於衡量算法佔用內存空間隨着數據量變大時的增長趨勢。這個概念與時 間複雜度非常類似,只需將“運行時間”替換爲“佔用內存空間”。
算法在運行過程中使用的內存空間主要包括以下幾種。
‧ 輸入空間:用於存儲算法的輸入數據。
‧ 暫存空間:用於存儲算法在運行過程中的變量、對象、函數上下文等數據。
暫存空間可以進一步劃分爲三個部分。
‧ 暫存數據:用於保存算法運行過程中的各種常量、變量、對象等。
‧ 棧幀空間:用於保存調用函數的上下文數據。系統在每次調用函數時都會在棧頂部創建一個棧幀,函數 返回後,棧幀空間會被釋放。
‧ 指令空間:用於保存編譯後的程序指令,在實際統計中通常忽略不計。
‧ 輸出空間:用於存儲算法的輸出數據。
一般情況下,空間複雜度的統計範圍是“暫存空間”加上“輸出空間”。在分析一段程序的空間複雜度時,我們通常統計暫存數據、棧幀空間和輸出數據三部分。
與時間複雜度不同的是,我們通常只關注最差空間複雜度。這是因爲內存空間是一項硬性要求,我們必須 確保在所有輸入數據下都有足夠的內存空間預留。
1. 以最差輸入數據爲準:
2. 以算法運行中的峯值內存爲準:
***在遞歸函數中,需要注意統計棧幀空間。***
遞歸和循環的區別:
循環空間複雜度:𝑂(1) ,在循環中每次調用的返回後,都釋放棧幀空間;
遞歸空間複雜度:𝑂(𝑛) ,在遞歸每次調用後同時存在多個未返回的遞歸函數,從而佔用棧幀空間。
設輸入數據大小爲 n,常見的空間複雜度類型(從低到高排列)。
數學表示:𝑂(1) < 𝑂(log 𝑛) < 𝑂(𝑛) < 𝑂(𝑛2 ) < 𝑂(2𝑛)
文字描述:常數階 < 對數階 < 線性階 < 平方階 < 指數階
常數階常見於數量與輸入數據大小 𝑛 無關的常量、變量、對象。 需要注意的是,在循環中初始化變量或調用函數而佔用的內存,在進入下一循環後就會被釋放,因此不會累 積佔用空間,空間複雜度仍爲 𝑂(1) 。
線性階常見於元素數量與 𝑛 成正比的數組、鏈表、棧、隊列等。
平方階常見於矩陣和圖,元素數量與 𝑛 成平方關係。
指數階常見於二叉樹。層數爲 𝑛 的“滿二叉樹”的節點數量爲 2 𝑛 − 1 ,佔用 𝑂(2𝑛) 空間。
對數階常見於分治算法。例如歸併排序,輸入長度爲 𝑛 的數組,每輪遞歸將數組從中點處劃分爲兩半,形成 高度爲 log 𝑛 的遞歸樹,使用 𝑂(log 𝑛) 棧幀空間。
理想情況下,我們希望算法的時間複雜度和空間複雜度都能達到最優。然而在實際情況中,同時優化時間復 雜度和空間複雜度通常非常困難。 降低時間複雜度通常需要以提升空間複雜度爲代價,反之亦然。我們將犧牲內存空間來提升算法運行速度的 思路稱爲“以空間換時間”;反之,則稱爲“以時間換空間”。 在大多數情況下,時間比空間更寶貴,因此“以空間換時間”通 常是更常用的策略。當然,在數據量很大的情況下,控制空間複雜度也非常重要。
「函數 function」可以被獨立執行,所有參數都以顯式傳遞。
「方法 method」與一個對象關聯,被隱式傳遞 給調用它的對象,能夠對類的實例中包含的數據進行操作。
下面以幾種常見的編程語言爲例來說明。
‧ C 語言是過程式編程語言,沒有面向對象的概念,所以只有函數。但我們可以通過創建結構體(struct) 來模擬面向對象編程,與結構體相關聯的函數就相當於其他編程語言中的方法。
‧ Java 和 C# 是面向對象的編程語言,代碼塊(方法)通常作爲某個類的一部分。靜態方法的行爲類似於 函數,因爲它被綁定在類上,不能訪問特定的實例變量。
‧ C++ 和 Python 既支持過程式編程(函數),也支持面向對象編程(方法)。
***複雜度,其反映的是增長趨勢***