--- comments: true --- # 2.4 Space complexity Space complexity is used to measure the growth trend of the memory space occupied by an algorithm as the amount of data increases. This concept is very similar to time complexity, except that "running time" is replaced with "occupied memory space". ## 2.4.1 Space related to algorithms The memory space used by an algorithm during its execution mainly includes the following types. - **Input space**: Used to store the input data of the algorithm. - **Temporary space**: Used to store variables, objects, function contexts, and other data during the algorithm's execution. - **Output space**: Used to store the output data of the algorithm. Generally, the scope of space complexity statistics includes both "Temporary Space" and "Output Space". Temporary space can be further divided into three parts. - **Temporary data**: Used to save various constants, variables, objects, etc., during the algorithm's execution. - **Stack frame space**: Used to save the context data of the called function. The system creates a stack frame at the top of the stack each time a function is called, and the stack frame space is released after the function returns. - **Instruction space**: Used to store compiled program instructions, which are usually negligible in actual statistics. When analyzing the space complexity of a program, **we typically count the Temporary Data, Stack Frame Space, and Output Data**, as shown in Figure 2-15. ![Space types used in algorithms](space_complexity.assets/space_types.png){ class="animation-figure" }
Figure 2-15 Space types used in algorithms
The relevant code is as follows: === "Python" ```python title="" class Node: """Classes""" def __init__(self, x: int): self.val: int = x # node value self.next: Node | None = None # reference to the next node def function() -> int: """Functions""" # Perform certain operations... return 0 def algorithm(n) -> int: # input data A = 0 # temporary data (constant, usually in uppercase) b = 0 # temporary data (variable) node = Node(0) # temporary data (object) c = function() # Stack frame space (call function) return A + b + c # output data ``` === "C++" ```cpp title="" /* Structures */ struct Node { int val; Node *next; Node(int x) : val(x), next(nullptr) {} }; /* Functions */ int func() { // Perform certain operations... return 0; } int algorithm(int n) { // input data const int a = 0; // temporary data (constant) int b = 0; // temporary data (variable) Node* node = new Node(0); // temporary data (object) int c = func(); // stack frame space (call function) return a + b + c; // output data } ``` === "Java" ```java title="" /* Classes */ class Node { int val; Node next; Node(int x) { val = x; } } /* Functions */ int function() { // Perform certain operations... return 0; } int algorithm(int n) { // input data final int a = 0; // temporary data (constant) int b = 0; // temporary data (variable) Node node = new Node(0); // temporary data (object) int c = function(); // stack frame space (call function) return a + b + c; // output data } ``` === "C#" ```csharp title="" /* Classes */ class Node { int val; Node next; Node(int x) { val = x; } } /* Functions */ int Function() { // Perform certain operations... return 0; } int Algorithm(int n) { // input data const int a = 0; // temporary data (constant) int b = 0; // temporary data (variable) Node node = new(0); // temporary data (object) int c = Function(); // stack frame space (call function) return a + b + c; // output data } ``` === "Go" ```go title="" /* Structures */ type node struct { val int next *node } /* Create node structure */ func newNode(val int) *node { return &node{val: val} } /* Functions */ func function() int { // Perform certain operations... return 0 } func algorithm(n int) int { // input data const a = 0 // temporary data (constant) b := 0 // temporary storage of data (variable) newNode(0) // temporary data (object) c := function() // stack frame space (call function) return a + b + c // output data } ``` === "Swift" ```swift title="" /* Classes */ class Node { var val: Int var next: Node? init(x: Int) { val = x } } /* Functions */ func function() -> Int { // Perform certain operations... return 0 } func algorithm(n: Int) -> Int { // input data let a = 0 // temporary data (constant) var b = 0 // temporary data (variable) let node = Node(x: 0) // temporary data (object) let c = function() // stack frame space (call function) return a + b + c // output data } ``` === "JS" ```javascript title="" /* Classes */ class Node { val; next; constructor(val) { this.val = val === undefined ? 0 : val; // node value this.next = null; // reference to the next node } } /* Functions */ function constFunc() { // Perform certain operations return 0; } function algorithm(n) { // input data const a = 0; // temporary data (constant) let b = 0; // temporary data (variable) const node = new Node(0); // temporary data (object) const c = constFunc(); // Stack frame space (calling function) return a + b + c; // output data } ``` === "TS" ```typescript title="" /* Classes */ class Node { val: number; next: Node | null; constructor(val?: number) { this.val = val === undefined ? 0 : val; // node value this.next = null; // reference to the next node } } /* Functions */ function constFunc(): number { // Perform certain operations return 0; } function algorithm(n: number): number { // input data const a = 0; // temporary data (constant) let b = 0; // temporary data (variable) const node = new Node(0); // temporary data (object) const c = constFunc(); // Stack frame space (calling function) return a + b + c; // output data } ``` === "Dart" ```dart title="" /* Classes */ class Node { int val; Node next; Node(this.val, [this.next]); } /* Functions */ int function() { // Perform certain operations... return 0; } int algorithm(int n) { // input data const int a = 0; // temporary data (constant) int b = 0; // temporary data (variable) Node node = Node(0); // temporary data (object) int c = function(); // stack frame space (call function) return a + b + c; // output data } ``` === "Rust" ```rust title="" use std::rc::Rc; use std::cell::RefCell; /* Structures */ struct Node { val: i32, next: OptionFigure 2-16 Common types of space complexity
### 1. Constant order $O(1)$ {data-toc-label="1. Constant order"} Constant order is common in constants, variables, objects that are independent of the size of input data $n$. Note that memory occupied by initializing variables or calling functions in a loop, which is released upon entering the next cycle, does not accumulate over space, thus the space complexity remains $O(1)$: === "Python" ```python title="space_complexity.py" def function() -> int: """Function""" # Perform some operations return 0 def constant(n: int): """Constant complexity""" # Constants, variables, objects occupy O(1) space a = 0 nums = [0] * 10000 node = ListNode(0) # Variables in a loop occupy O(1) space for _ in range(n): c = 0 # Functions in a loop occupy O(1) space for _ in range(n): function() ``` === "C++" ```cpp title="space_complexity.cpp" [class]{}-[func]{func} [class]{}-[func]{constant} ``` === "Java" ```java title="space_complexity.java" /* Function */ int function() { // Perform some operations return 0; } /* Constant complexity */ void constant(int n) { // Constants, variables, objects occupy O(1) space final int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new ListNode(0); // Variables in a loop occupy O(1) space for (int i = 0; i < n; i++) { int c = 0; } // Functions in a loop occupy O(1) space for (int i = 0; i < n; i++) { function(); } } ``` === "C#" ```csharp title="space_complexity.cs" [class]{space_complexity}-[func]{Function} [class]{space_complexity}-[func]{Constant} ``` === "Go" ```go title="space_complexity.go" [class]{}-[func]{function} [class]{}-[func]{spaceConstant} ``` === "Swift" ```swift title="space_complexity.swift" [class]{}-[func]{function} [class]{}-[func]{constant} ``` === "JS" ```javascript title="space_complexity.js" [class]{}-[func]{constFunc} [class]{}-[func]{constant} ``` === "TS" ```typescript title="space_complexity.ts" [class]{}-[func]{constFunc} [class]{}-[func]{constant} ``` === "Dart" ```dart title="space_complexity.dart" [class]{}-[func]{function} [class]{}-[func]{constant} ``` === "Rust" ```rust title="space_complexity.rs" [class]{}-[func]{function} [class]{}-[func]{constant} ``` === "C" ```c title="space_complexity.c" [class]{}-[func]{func} [class]{}-[func]{constant} ``` === "Kotlin" ```kotlin title="space_complexity.kt" [class]{}-[func]{function} [class]{}-[func]{constant} ``` === "Ruby" ```ruby title="space_complexity.rb" [class]{}-[func]{function} [class]{}-[func]{constant} ``` === "Zig" ```zig title="space_complexity.zig" [class]{}-[func]{function} [class]{}-[func]{constant} ``` ### 2. Linear order $O(n)$ {data-toc-label="2. Linear order"} Linear order is common in arrays, linked lists, stacks, queues, etc., where the number of elements is proportional to $n$: === "Python" ```python title="space_complexity.py" def linear(n: int): """Linear complexity""" # A list of length n occupies O(n) space nums = [0] * n # A hash table of length n occupies O(n) space hmap = dict[int, str]() for i in range(n): hmap[i] = str(i) ``` === "C++" ```cpp title="space_complexity.cpp" [class]{}-[func]{linear} ``` === "Java" ```java title="space_complexity.java" /* Linear complexity */ void linear(int n) { // Array of length n occupies O(n) space int[] nums = new int[n]; // A list of length n occupies O(n) space ListFigure 2-17 Recursive function generating linear order space complexity
### 3. Quadratic order $O(n^2)$ {data-toc-label="3. Quadratic order"} Quadratic order is common in matrices and graphs, where the number of elements is quadratic to $n$: === "Python" ```python title="space_complexity.py" def quadratic(n: int): """Quadratic complexity""" # A two-dimensional list occupies O(n^2) space num_matrix = [[0] * n for _ in range(n)] ``` === "C++" ```cpp title="space_complexity.cpp" [class]{}-[func]{quadratic} ``` === "Java" ```java title="space_complexity.java" /* Quadratic complexity */ void quadratic(int n) { // Matrix occupies O(n^2) space int[][] numMatrix = new int[n][n]; // A two-dimensional list occupies O(n^2) space ListFigure 2-18 Recursive function generating quadratic order space complexity
### 4. Exponential order $O(2^n)$ {data-toc-label="4. Exponential order"} Exponential order is common in binary trees. Observe Figure 2-19, a "full binary tree" with $n$ levels has $2^n - 1$ nodes, occupying $O(2^n)$ space: === "Python" ```python title="space_complexity.py" def build_tree(n: int) -> TreeNode | None: """Exponential complexity (building a full binary tree)""" if n == 0: return None root = TreeNode(0) root.left = build_tree(n - 1) root.right = build_tree(n - 1) return root ``` === "C++" ```cpp title="space_complexity.cpp" [class]{}-[func]{buildTree} ``` === "Java" ```java title="space_complexity.java" /* Exponential complexity (building a full binary tree) */ TreeNode buildTree(int n) { if (n == 0) return null; TreeNode root = new TreeNode(0); root.left = buildTree(n - 1); root.right = buildTree(n - 1); return root; } ``` === "C#" ```csharp title="space_complexity.cs" [class]{space_complexity}-[func]{BuildTree} ``` === "Go" ```go title="space_complexity.go" [class]{}-[func]{buildTree} ``` === "Swift" ```swift title="space_complexity.swift" [class]{}-[func]{buildTree} ``` === "JS" ```javascript title="space_complexity.js" [class]{}-[func]{buildTree} ``` === "TS" ```typescript title="space_complexity.ts" [class]{}-[func]{buildTree} ``` === "Dart" ```dart title="space_complexity.dart" [class]{}-[func]{buildTree} ``` === "Rust" ```rust title="space_complexity.rs" [class]{}-[func]{build_tree} ``` === "C" ```c title="space_complexity.c" [class]{}-[func]{buildTree} ``` === "Kotlin" ```kotlin title="space_complexity.kt" [class]{}-[func]{buildTree} ``` === "Ruby" ```ruby title="space_complexity.rb" [class]{}-[func]{build_tree} ``` === "Zig" ```zig title="space_complexity.zig" [class]{}-[func]{buildTree} ``` ![Full binary tree generating exponential order space complexity](space_complexity.assets/space_complexity_exponential.png){ class="animation-figure" }Figure 2-19 Full binary tree generating exponential order space complexity
### 5. Logarithmic order $O(\log n)$ {data-toc-label="5. Logarithmic order"} Logarithmic order is common in divide-and-conquer algorithms. For example, in merge sort, an array of length $n$ is recursively divided in half each round, forming a recursion tree of height $\log n$, using $O(\log n)$ stack frame space. Another example is converting a number to a string. Given a positive integer $n$, its number of digits is $\log_{10} n + 1$, corresponding to the length of the string, thus the space complexity is $O(\log_{10} n + 1) = O(\log n)$. ## 2.4.4 Balancing time and space Ideally, we aim for both time complexity and space complexity to be optimal. However, in practice, optimizing both simultaneously is often difficult. **Lowering time complexity usually comes at the cost of increased space complexity, and vice versa**. The approach of sacrificing memory space to improve algorithm speed is known as "space-time tradeoff"; the reverse is known as "time-space tradeoff". The choice depends on which aspect we value more. In most cases, time is more precious than space, so "space-time tradeoff" is often the more common strategy. Of course, controlling space complexity is also very important when dealing with large volumes of data.