First version.

This commit is contained in:
krahets
2026-01-20 15:08:42 +08:00
parent 2213a59ff6
commit 8071daddaa
106 changed files with 11790 additions and 0 deletions

View File

@@ -0,0 +1,860 @@
# Пространственная сложность
<u>Пространственная сложность (space complexity)</u> используется для измерения роста объема памяти, занимаемого алгоритмом, по мере увеличения объема данных. Эта концепция очень похожа на временную сложность, только нужно заменить "время выполнения" на "занимаемый объем памяти".
## Пространство, связанное с алгоритмом
Память, используемая алгоритмом в процессе выполнения, в основном включает следующие типы.
- **Входное пространство**: используется для хранения входных данных алгоритма.
- **Временное пространство**: используется для хранения переменных, объектов, контекста функций и других данных в процессе выполнения алгоритма.
- **Выходное пространство**: используется для хранения выходных данных алгоритма.
В общем случае диапазон статистики пространственной сложности — это "временное пространство" плюс "выходное пространство".
Временное пространство можно дополнительно разделить на три части.
- **Временные данные**: используются для сохранения различных констант, переменных, объектов и т. д. в процессе выполнения алгоритма.
- **Пространство стекового кадра**: используется для сохранения контекстных данных вызываемой функции. Система создает стековый кадр в верхней части стека при каждом вызове функции, и пространство стекового кадра освобождается после возврата функции.
- **Пространство инструкций**: используется для сохранения скомпилированных инструкций программы, обычно игнорируется при фактической статистике.
При анализе пространственной сложности фрагмента программы **мы обычно учитываем три части: временные данные, пространство стекового кадра и выходные данные**, как показано на рисунке ниже.
![Пространство, связанное с использованием алгоритма](../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<Rc<RefCell<Node>>>,
}
/* Создание структуры 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
```
## Метод расчета
Метод расчета пространственной сложности в целом аналогичен временной сложности, только нужно изменить объект статистики с "количества операций" на "размер используемого пространства".
В отличие от временной сложности, **мы обычно обращаем внимание только на наихудшую пространственную сложность**. Это связано с тем, что память является жестким требованием, и мы должны обеспечить достаточный резерв памяти для всех входных данных.
Рассмотрим следующий код, "наихудший" в наихудшей пространственной сложности имеет два значения.
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<int> b(10000); // O(1)
if (n > 10)
vector<int> 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<int> b = List.filled(10000, 0); // O(1)
if (n > 10) {
List<int> 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
```
**В рекурсивных функциях необходимо учитывать пространство стекового кадра**. Рассмотрим следующий код:
=== "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;
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;
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;
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;
recur(n - 1);
}
```
=== "Kotlin"
```kotlin title=""
fun function(): Int {
// выполнение некоторых операций
return 0
}
/* Пространственная сложность цикла O(1) */
fun loop(n: Int) {
for (i in 0..<n) {
function()
}
}
/* Пространственная сложность рекурсии O(n) */
fun recur(n: Int) {
if (n == 1) return
return recur(n - 1)
}
```
=== "Ruby"
```ruby title=""
def function
# выполнение некоторых операций
0
end
### Пространственная сложность цикла O(1) ###
def loop(n)
(0...n).each { function }
end
### Пространственная сложность рекурсии O(n) ###
def recur(n)
return if n == 1
recur(n - 1)
end
```
Функции `loop()` и `recur()` имеют временную сложность $O(n)$, но пространственная сложность различается.
- Функция `loop()` вызывает `function()` $n$ раз в цикле, на каждой итерации `function()` возвращается и освобождает пространство стекового кадра, поэтому пространственная сложность остается $O(1)$.
- Рекурсивная функция `recur()` в процессе выполнения будет иметь одновременно $n$ невозвращенных вызовов `recur()`, занимая $O(n)$ пространства стекового кадра.
## Распространенные типы
При размере входных данных $n$ на рисунке ниже показаны распространенные типы пространственной сложности (от низкой к высокой).
$$
\begin{aligned}
O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
\text{константный порядок} < \text{логарифмический порядок} < \text{линейный порядок} < \text{квадратичный порядок} < \text{экспоненциальный порядок}
\end{aligned}
$$
![Распространенные типы пространственной сложности](../assets/space_complexity_common_types.png)
### Константный порядок $O(1)$
Константный порядок часто встречается в константах, переменных, объектах, количество которых не зависит от размера входных данных $n$.
Следует отметить, что память, занимаемая при инициализации переменных или вызове функций в цикле, освобождается при переходе к следующей итерации, поэтому не накапливается, и пространственная сложность остается $O(1)$:
```src
[file]{space_complexity}-[class]{}-[func]{constant}
```
### Линейный порядок $O(n)$
Линейный порядок часто встречается в массивах, связанных списках, стеках, очередях и т. д., где количество элементов пропорционально $n$:
```src
[file]{space_complexity}-[class]{}-[func]{linear}
```
Как показано на рисунке ниже, глубина рекурсии этой функции равна $n$, т. е. одновременно существует $n$ невозвращенных функций `linear_recur()`, использующих $O(n)$ пространства стекового кадра:
```src
[file]{space_complexity}-[class]{}-[func]{linear_recur}
```
![Линейная пространственная сложность, создаваемая рекурсивной функцией](../assets/space_complexity_recursive_linear.png)
### Квадратичный порядок $O(n^2)$
Квадратичный порядок часто встречается в матрицах и графах, где количество элементов имеет квадратичную зависимость от $n$:
```src
[file]{space_complexity}-[class]{}-[func]{quadratic}
```
Как показано на рисунке ниже, глубина рекурсии этой функции равна $n$, и в каждой рекурсивной функции инициализируется массив длиной $n$, $n-1$, $\dots$, $2$, $1$ соответственно, средняя длина составляет $n / 2$, поэтому общее занимаемое пространство составляет $O(n^2)$:
```src
[file]{space_complexity}-[class]{}-[func]{quadratic_recur}
```
![Квадратичная пространственная сложность, создаваемая рекурсивной функцией](../assets/space_complexity_recursive_quadratic.png)
### Экспоненциальный порядок $O(2^n)$
Экспоненциальный порядок часто встречается в двоичных деревьях. Как показано на рисунке ниже, "полное двоичное дерево" с $n$ уровнями име