# 空間複雜度 空間複雜度(space complexity)用於衡量演算法佔用記憶體空間隨著資料量變大時的增長趨勢。這個概念與時間複雜度非常類似,只需將“執行時間”替換為“佔用記憶體空間”。 ## 演算法相關空間 演算法在執行過程中使用的記憶體空間主要包括以下幾種。 - **輸入空間**:用於儲存演算法的輸入資料。 - **暫存空間**:用於儲存演算法在執行過程中的變數、物件、函式上下文等資料。 - **輸出空間**:用於儲存演算法的輸出資料。 一般情況下,空間複雜度的統計範圍是“暫存空間”加上“輸出空間”。 暫存空間可以進一步劃分為三個部分。 - **暫存資料**:用於儲存演算法執行過程中的各種常數、變數、物件等。 - **堆疊幀空間**:用於儲存呼叫函式的上下文資料。系統在每次呼叫函式時都會在堆疊頂部建立一個堆疊幀,函式返回後,堆疊幀空間會被釋放。 - **指令空間**:用於儲存編譯後的程式指令,在實際統計中通常忽略不計。 在分析一段程式的空間複雜度時,**我們通常統計暫存資料、堆疊幀空間和輸出資料三部分**,如下圖所示。 ![演算法使用的相關空間](space_complexity.assets/space_types.png) 相關程式碼如下: === "Python" ```python title="" class Node: """類別""" def __init__(self, x: int): self.val: int = x # 節點值 self.next: Node | None = None # 指向下一節點的引用 def function() -> int: """函式""" # 執行某些操作... return 0 def algorithm(n) -> int: # 輸入資料 A = 0 # 暫存資料(常數,一般用大寫字母表示) b = 0 # 暫存資料(變數) node = Node(0) # 暫存資料(物件) c = function() # 堆疊幀空間(呼叫函式) return A + b + c # 輸出資料 ``` === "C++" ```cpp title="" /* 結構體 */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* 函式 */ int func() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node* node = new Node(0); // 暫存資料(物件) int c = func(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Java" ```java title="" /* 類別 */ class Node { int val; Node next; Node(int x) { val = x; } } /* 函式 */ int function() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 final int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = new Node(0); // 暫存資料(物件) int c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "C#" ```csharp title="" /* 類別 */ class Node(int x) { int val = x; Node next; } /* 函式 */ int Function() { // 執行某些操作... return 0; } int Algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = new(0); // 暫存資料(物件) int c = Function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Go" ```go title="" /* 結構體 */ type node struct { val int next *node } /* 建立 node 結構體 */ func newNode(val int) *node { return &node{val: val} } /* 函式 */ func function() int { // 執行某些操作... return 0 } func algorithm(n int) int { // 輸入資料 const a = 0 // 暫存資料(常數) b := 0 // 暫存資料(變數) newNode(0) // 暫存資料(物件) c := function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "Swift" ```swift title="" /* 類別 */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* 函式 */ func function() -> Int { // 執行某些操作... return 0 } func algorithm(n: Int) -> Int { // 輸入資料 let a = 0 // 暫存資料(常數) var b = 0 // 暫存資料(變數) let node = Node(x: 0) // 暫存資料(物件) let c = function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "JS" ```javascript title="" /* 類別 */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // 節點值 this.next = null; // 指向下一節點的引用 } } /* 函式 */ function constFunc() { // 執行某些操作 return 0; } function algorithm(n) { // 輸入資料 const a = 0; // 暫存資料(常數) let b = 0; // 暫存資料(變數) const node = new Node(0); // 暫存資料(物件) const c = constFunc(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "TS" ```typescript title="" /* 類別 */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // 節點值 this.next = null; // 指向下一節點的引用 } } /* 函式 */ function constFunc(): number { // 執行某些操作 return 0; } function algorithm(n: number): number { // 輸入資料 const a = 0; // 暫存資料(常數) let b = 0; // 暫存資料(變數) const node = new Node(0); // 暫存資料(物件) const c = constFunc(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Dart" ```dart title="" /* 類別 */ class Node { int val; Node next; Node(this.val, [this.next]); } /* 函式 */ int function() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) Node node = Node(0); // 暫存資料(物件) int c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* 結構體 */ struct Node { val: i32, next: Option>>, } /* 建立 Node 結構體 */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } } } /* 函式 */ fn function() -> i32 { // 執行某些操作... return 0; } fn algorithm(n: i32) -> i32 { // 輸入資料 const a: i32 = 0; // 暫存資料(常數) let mut b = 0; // 暫存資料(變數) let node = Node::new(0); // 暫存資料(物件) let c = function(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "C" ```c title="" /* 函式 */ int func() { // 執行某些操作... return 0; } int algorithm(int n) { // 輸入資料 const int a = 0; // 暫存資料(常數) int b = 0; // 暫存資料(變數) int c = func(); // 堆疊幀空間(呼叫函式) return a + b + c; // 輸出資料 } ``` === "Kotlin" ```kotlin title="" /* 類別 */ class Node(var _val: Int) { var next: Node? = null } /* 函式 */ fun function(): Int { // 執行某些操作... return 0 } fun algorithm(n: Int): Int { // 輸入資料 val a = 0 // 暫存資料(常數) var b = 0 // 暫存資料(變數) val node = Node(0) // 暫存資料(物件) val c = function() // 堆疊幀空間(呼叫函式) return a + b + c // 輸出資料 } ``` === "Ruby" ```ruby title="" ### 類別 ### class Node attr_accessor :val # 節點值 attr_accessor :next # 指向下一節點的引用 def initialize(x) @val = x end end ### 函式 ### def function # 執行某些操作... 0 end ### 演算法 ### def algorithm(n) # 輸入資料 a = 0 # 暫存資料(常數) b = 0 # 暫存資料(變數) node = Node.new(0) # 暫存資料(物件) c = function # 堆疊幀空間(呼叫函式) a + b + c # 輸出資料 end ``` === "Zig" ```zig title="" ``` ## 推算方法 空間複雜度的推算方法與時間複雜度大致相同,只需將統計物件從“操作數量”轉為“使用空間大小”。 而與時間複雜度不同的是,**我們通常只關注最差空間複雜度**。這是因為記憶體空間是一項硬性要求,我們必須確保在所有輸入資料下都有足夠的記憶體空間預留。 觀察以下程式碼,最差空間複雜度中的“最差”有兩層含義。 1. **以最差輸入資料為準**:當 $n < 10$ 時,空間複雜度為 $O(1)$ ;但當 $n > 10$ 時,初始化的陣列 `nums` 佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 2. **以演算法執行中的峰值記憶體為準**:例如,程式在執行最後一行之前,佔用 $O(1)$ 空間;當初始化陣列 `nums` 時,程式佔用 $O(n)$ 空間,因此最差空間複雜度為 $O(n)$ 。 === "Python" ```python title="" def algorithm(n: int): a = 0 # O(1) b = [0] * 10000 # O(1) if n > 10: nums = [0] * n # O(n) ``` === "C++" ```cpp title="" void algorithm(int n) { int a = 0; // O(1) vector b(10000); // O(1) if (n > 10) vector nums(n); // O(n) } ``` === "Java" ```java title="" void algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) int[] nums = new int[n]; // O(n) } ``` === "C#" ```csharp title="" void Algorithm(int n) { int a = 0; // O(1) int[] b = new int[10000]; // O(1) if (n > 10) { int[] nums = new int[n]; // O(n) } } ``` === "Go" ```go title="" func algorithm(n int) { a := 0 // O(1) b := make([]int, 10000) // O(1) var nums []int if n > 10 { nums := make([]int, n) // O(n) } fmt.Println(a, b, nums) } ``` === "Swift" ```swift title="" func algorithm(n: Int) { let a = 0 // O(1) let b = Array(repeating: 0, count: 10000) // O(1) if n > 10 { let nums = Array(repeating: 0, count: n) // O(n) } } ``` === "JS" ```javascript title="" function algorithm(n) { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "TS" ```typescript title="" function algorithm(n: number): void { const a = 0; // O(1) const b = new Array(10000); // O(1) if (n > 10) { const nums = new Array(n); // O(n) } } ``` === "Dart" ```dart title="" void algorithm(int n) { int a = 0; // O(1) List b = List.filled(10000, 0); // O(1) if (n > 10) { List nums = List.filled(n, 0); // O(n) } } ``` === "Rust" ```rust title="" fn algorithm(n: i32) { let a = 0; // O(1) let b = [0; 10000]; // O(1) if n > 10 { let nums = vec![0; n as usize]; // O(n) } } ``` === "C" ```c title="" void algorithm(int n) { int a = 0; // O(1) int b[10000]; // O(1) if (n > 10) int nums[n] = {0}; // O(n) } ``` === "Kotlin" ```kotlin title="" fun algorithm(n: Int) { val a = 0 // O(1) val b = IntArray(10000) // O(1) if (n > 10) { val nums = IntArray(n) // O(n) } } ``` === "Ruby" ```ruby title="" def algorithm(n) a = 0 # O(1) b = Array.new(10000) # O(1) nums = Array.new(n) if n > 10 # O(n) end ``` === "Zig" ```zig title="" ``` **在遞迴函式中,需要注意統計堆疊幀空間**。觀察以下程式碼: === "Python" ```python title="" def function() -> int: # 執行某些操作 return 0 def loop(n: int): """迴圈的空間複雜度為 O(1)""" for _ in range(n): function() def recur(n: int): """遞迴的空間複雜度為 O(n)""" if n == 1: return return recur(n - 1) ``` === "C++" ```cpp title="" int func() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); } ``` === "Java" ```java title="" int function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); } ``` === "C#" ```csharp title="" int Function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } /* 遞迴的空間複雜度為 O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); } ``` === "Go" ```go title="" func function() int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } /* 遞迴的空間複雜度為 O(n) */ func recur(n int) { if n == 1 { return } recur(n - 1) } ``` === "Swift" ```swift title="" @discardableResult func function() -> Int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } /* 遞迴的空間複雜度為 O(n) */ func recur(n: Int) { if n == 1 { return } recur(n: n - 1) } ``` === "JS" ```javascript title="" function constFunc() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } /* 遞迴的空間複雜度為 O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); } ``` === "TS" ```typescript title="" function constFunc(): number { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } /* 遞迴的空間複雜度為 O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); } ``` === "Dart" ```dart title="" int function() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); } ``` === "Rust" ```rust title="" fn function() -> i32 { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } /* 遞迴的空間複雜度為 O(n) */ fn recur(n: i32) { if n == 1 { return; } recur(n - 1); } ``` === "C" ```c title="" int func() { // 執行某些操作 return 0; } /* 迴圈的空間複雜度為 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } /* 遞迴的空間複雜度為 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); } ``` === "Kotlin" ```kotlin title="" fun function(): Int { // 執行某些操作 return 0 } /* 迴圈的空間複雜度為 O(1) */ fun loop(n: Int) { for (i in 0..