# 空間複雜度
空間複雜度(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..