mirror of
http://bgp.hk.skcks.cn:10086/https://github.com/krahets/hello-algo
synced 2026-04-20 21:00:58 +08:00
* docs(ru): replace prose quotes with guillemets * docs(ru): replace prose semicolons with periods * docs(ru): align animation title forms * docs(ru): align figure and table references
881 lines
33 KiB
Markdown
881 lines
33 KiB
Markdown
# Пространственная сложность
|
||
|
||
<u>Пространственная сложность (space complexity)</u> служит для оценки того, как меняется объем памяти, требуемой алгоритму, по мере роста объема данных. Это понятие очень похоже на временную сложность, только вместо времени выполнения рассматривается объем используемой памяти.
|
||
|
||
## Пространство, связанное с алгоритмом
|
||
|
||
Память, которую использует алгоритм во время работы, в основном делится на следующие части.
|
||
|
||
- **Входное пространство**: используется для хранения входных данных алгоритма.
|
||
- **Временное пространство**: используется для хранения переменных, объектов, контекста функций и других данных, возникающих во время выполнения алгоритма.
|
||
- **Выходное пространство**: используется для хранения выходных данных алгоритма.
|
||
|
||
Как правило, при анализе пространственной сложности в расчет включают временное пространство и выходное пространство.
|
||
|
||
Временное пространство можно дополнительно разделить на три части.
|
||
|
||
- **Временные данные**: используются для хранения различных констант, переменных, объектов и т.д., возникающих во время выполнения алгоритма.
|
||
- **Пространство кадров стека**: используется для хранения контекстных данных вызываемых функций. При каждом вызове функции система создает на вершине стека новый кадр. После возврата функции пространство этого кадра освобождается.
|
||
- **Пространство инструкций**: используется для хранения скомпилированных инструкций программы и в реальном подсчете обычно не учитывается.
|
||
|
||
При анализе пространственной сложности программы **обычно учитываются временные данные, пространство стека и выходные данные**, как показано на рисунке ниже.
|
||
|
||

|
||
|
||
Ниже приведен соответствующий код:
|
||
|
||
=== "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}
|
||
$$
|
||
|
||

|
||
|
||
### Постоянная сложность $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}
|
||
```
|
||
|
||

|
||
|
||
### Квадратичная сложность $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}
|
||
```
|
||
|
||

|
||
|
||
### Экспоненциальная сложность $O(2^n)$
|
||
|
||
Экспоненциальная сложность часто встречается у бинарных деревьев. Как видно на рисунке ниже, полное бинарное дерево с $n$ уровнями содержит $2^n - 1$ узлов и занимает $O(2^n)$ пространства:
|
||
|
||
```src
|
||
[file]{space_complexity}-[class]{}-[func]{build_tree}
|
||
```
|
||
|
||

|
||
|
||
### Логарифмическая сложность $O(\log n)$
|
||
|
||
Логарифмическая сложность часто встречается в алгоритмах «разделяй и властвуй». Например, при сортировке слиянием входной массив длины $n$ на каждом шаге рекурсии делится пополам, образуя рекурсивное дерево высоты $\log n$ и используя $O(\log n)$ пространства кадров стека.
|
||
|
||
Еще один пример - преобразование числа в строку. Если задано положительное целое число $n$ , то количество его цифр равно $\lfloor \log_{10} n \rfloor + 1$ , то есть длина соответствующей строки тоже равна $\lfloor \log_{10} n \rfloor + 1$ , следовательно, пространственная сложность составляет $O(\log_{10} n + 1) = O(\log n)$ .
|
||
|
||
## Компромисс между временем и пространством
|
||
|
||
В идеальных условиях хотелось бы, чтобы и временная, и пространственная сложность алгоритма были оптимальными. Однако на практике одновременно оптимизировать и время, и память обычно очень трудно.
|
||
|
||
**Снижение временной сложности обычно достигается ценой увеличения пространственной сложности, и наоборот**. Подход, при котором жертвуют памятью ради ускорения работы алгоритма, называется обменом пространства на время. Обратный подход называется обменом времени на пространство.
|
||
|
||
Выбор между этими двумя идеями зависит от того, что важнее в конкретной задаче. В большинстве случаев время ценнее памяти, поэтому стратегия обмена пространства на время используется чаще. Но при очень больших объемах данных контроль пространственной сложности тоже становится крайне важным.
|