# 공간 복잡도
공간 복잡도(space complexity)는 데이터 양이 많아질 때 알고리즘이 차지하는 메모리 공간이 증가하는 추세를 측정한다. 시간 복잡도와 매우 비슷한 개념으로, ‘실행 시간’을 ‘차지하는 메모리 공간’으로 바꾸면 된다.
## 알고리즘과 관련된 공간
알고리즘이 실행 중 사용하는 메모리 공간에는 주로 다음과 같은 종류가 있다.
- **입력 공간**: 알고리즘의 입력 데이터를 저장한다.
- **임시 공간**: 실행 중의 변수, 객체, 함수 문맥 등의 데이터를 저장한다.
- **출력 공간**: 알고리즘의 출력 데이터를 저장한다.
일반적으로 공간 복잡도에 포함하는 범위는 ‘임시 공간’과 ‘출력 공간’의 합이다.
임시 공간은 다시 세 부분으로 나눌 수 있다.
- **임시 데이터**: 실행 중의 각종 상수, 변수, 객체 등을 저장한다.
- **스택 프레임 공간**: 호출한 함수의 문맥 데이터를 저장한다. 시스템은 함수가 호출될 때마다 스택 맨 위에 스택 프레임을 만들며, 함수가 반환되면 이 공간을 해제한다.
- **명령어 공간**: 컴파일된 프로그램 명령어를 저장하며, 실제 계산에서는 보통 무시한다.
프로그램의 공간 복잡도를 분석할 때는 아래 그림과 같이 **보통 임시 데이터, 스택 프레임 공간, 출력 데이터의 세 부분을 계산한다**.

관련 코드는 다음과 같다.
=== "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
```
## 계산 방법
공간 복잡도를 구하는 방법은 시간 복잡도와 대체로 같다. 계산 대상을 ‘연산 횟수’에서 ‘사용 공간의 크기’로 바꾸면 된다.
시간 복잡도와 달리 **보통 최악 공간 복잡도에만 관심을 둔다**. 메모리 공간은 반드시 충족해야 하는 요구 사항이므로, 어떤 입력 데이터에서도 충분한 메모리 공간이 확보되어 있어야 하기 때문이다.
다음 코드를 보면 최악 공간 복잡도에서 ‘최악’은 두 가지 의미를 지닌다.
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
```
**재귀 함수에서는 스택 프레임 공간을 계산하는 데 주의해야 한다**. 다음 코드를 살펴보자.
=== "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..