Skip to content

Commit ee4372c

Browse files
committed
docs: 자료구조 5
- 순차 자료구조의 한계를 극복하기 위한 대안으로서 연결 자료구조의 핵심 개념, 유형, 그리고 주요 연산 방식을 분석
1 parent 96f478d commit ee4372c

File tree

1 file changed

+278
-0
lines changed

1 file changed

+278
-0
lines changed

docs/자료구조/05.md

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
---
2+
sidebar_position: 5
3+
title: 연결 자료구조와 연결 리스트
4+
description: 순차 자료구조의 한계를 극복하기 위한 대안으로서 연결 자료구조의 핵심 개념, 유형, 그리고 주요 연산 방식을 분석
5+
---
6+
7+
## 간단 요약
8+
9+
순차 자료구조의 한계를 극복하기 위한 대안으로서 연결 자료구조의 핵심 개념, 유형, 그리고 주요 연산 방식을 분석합니다.
10+
배열 기반의 순차 자료구조는 데이터 삽입 및 삭제 시 발생하는 오버헤드와 고정된 크기로 인한 메모리 비효율성 문제를 내재하고 있습니다.
11+
연결 자료구조는 각 데이터 요소를 **노드(Node)** 단위로 관리하고, 각 노드가 다음 노드의 위치를 가리키는 **포인터(Pointer)** 를 포함하는 방식으로 이 문제를 해결합니다.
12+
13+
이를 통해 데이터의 논리적 순서와 물리적 저장 위치를 분리하여 동적 데이터 관리에 필요한 유연성을 확보합니다.
14+
15+
이 개념은 **기차놀이**에 비유할 수 있습니다. 각 어린이는 데이터를 담은 **노드**이며, 다음 순서의 아이를 명시적으로 가리키는 행위(이름표 들기)가 **링크(포인터)** 역할을 합니다.
16+
아이들이 운동장 어디에 흩어져 있든, 이 논리적 연결만 따라가면 전체 순서가 유지되는 것과 같은 원리입니다.
17+
가장 대표적인 연결 자료구조 형태인 **단순 연결 리스트**와 그 변형인 **원형 연결 리스트**를 중심으로, 각각의 구조에서 데이터 삽입 및 삭제 연산이 어떻게 이루어지는지 심도 있게 분석할 것입니다.
18+
19+
---
20+
21+
## 1. 순차 자료구조의 한계와 연결 자료구조의 필요성
22+
23+
효율적인 데이터 관리는 소프트웨어 성능의 핵심이며, 이를 위해 어떤 자료구조를 선택하는가는 매우 중요한 아키텍처 결정입니다. 배열이 대표적인 예시이며, 구조가 단순하고 인덱스를 통한 O(1) 접근이 가능하다는 명확한 장점이 있습니다.
24+
25+
가장 기본적인 자료구조 중 하나인 **순차 자료구조**는 데이터를 메모리상에 연속적으로 저장하는 방식을 사용합니다.
26+
27+
하지만 이러한 구조적 특성은 특정 시나리오에서 명백한 한계로 작용합니다.
28+
29+
### 순차 자료구조가 직면하는 핵심적인 문제점
30+
31+
#### 1. 메모리 사용의 비효율성
32+
33+
배열 기반 구현은 초기에 필요한 전체 메모리 크기를 예측하여 할당해야 합니다. 할당된 공간보다 데이터가 적으면 메모리가 낭비되고, 공간이 부족하면 더 큰 배열을 새로 만들어 기존 데이터를 모두 복사해야 하는 비효율이 발생합니다.
34+
35+
#### 2. 삽입 및 삭제 연산의 오버헤드
36+
37+
데이터의 논리적 순서와 물리적 저장 순서가 일치해야 하므로, 중간에 데이터를 삽입하거나 삭제할 경우 그 뒤의 모든 원소를 한 칸씩 이동시켜야 합니다.
38+
39+
```text
40+
중간 삽입/삭제 시 → 뒤의 모든 원소를 이동
41+
```
42+
43+
이러한 이동 작업은 데이터의 양이 많고 삽입·삭제가 빈번한 환경에서 심각한 성능 저하(오버헤드)를 유발합니다.
44+
45+
이러한 문제들은 데이터의 양이 유동적으로 변하는 동적 환경에서 특히 두드러집니다.
46+
47+
이 오버헤드는 텍스트 편집기나 메모리 관리 서브시스템과 같이 실시간 제약 조건이 있는 애플리케이션에서 대규모 데이터 블록을 이동시켜야 할 때 눈에 띄는 지연 시간 급증(**latency spike**)으로 나타날 수 있습니다.
48+
49+
이러한 한계를 극복하기 위해, 논리적 순서와 물리적 순서를 분리하는 새로운 접근법이 필요했고, 그 결과로 연결 자료구조가 등장하게 되었습니다.
50+
51+
## 2. 연결 자료구조의 핵심 개념
52+
53+
연결 자료구조는 순차 자료구조의 근본적인 문제, 즉 **물리적 연속성** 제약을 해결하기 위한 혁신적인 접근 방식을 취합니다.
54+
55+
> **핵심**: 데이터의 논리적 순서와 메모리상의 물리적 순서를 분리하는 것입니다.<br/>
56+
> 이를 통해 데이터 관리의 유연성과 효율성을 극대화합니다.
57+
58+
### 연결 자료구조의 기본 원리
59+
60+
#### 1. 논리적 vs 물리적 순서의 분리
61+
62+
- 연결 자료구조에서는 각 데이터 원소(**노드**)가 메모리상에 흩어져 저장될 수 있습니다.
63+
- 순서는 각 원소가 다음 원소의 메모리 주소를 직접 가리키는 방식으로 정의됩니다.
64+
- 물리적 위치를 맞추기 위한 데이터 이동 오버헤드가 원천적으로 발생하지 않습니다.
65+
66+
#### 2. 유연한 메모리 관리
67+
68+
큰 메모리 덩어리를 한 번에 할당하는 대신, 필요할 때마다 작은 노드 단위로 메모리 공간을 할당하고 이를 연결하여 전체 구조를 형성합니다.
69+
70+
```text
71+
필요할 때마다 작은 노드 단위로 할당 → 전체 구조 형성
72+
```
73+
74+
이는 C의 `malloc`이나 C++의 `new`와 같이 필요에 따라 **힙(heap) 영역**에서 비연속적인 청크로 메모리를 획득하는 **동적 메모리 할당**과 유사하며, 스택(stack)에 정적 배열을 선언하는 것과 같은 경직된 사전 할당을 피하게 해줍니다.
75+
76+
> **비유**: '퀼트 이불'이나 '줄줄이 소시지'처럼 각 조각을 연결
77+
78+
### 노드(Node)의 구조
79+
80+
연결 리스트를 구성하는 기본 단위는 **노드(Node)** 이며, 이는 두 개의 핵심 필드로 구성됩니다.
81+
82+
| 필드 | 설명 | 역할 |
83+
| ---------------------------- | ------------------------------- | ---------------- |
84+
| **데이터 필드** (Data Field) | 원소의 실제 값(숫자, 문자열 등) | 데이터 저장 |
85+
| **링크 필드** (Link Field) | 다음 노드의 메모리 주소(포인터) | 논리적 연결 유지 |
86+
87+
### 순차 자료구조와의 비교
88+
89+
두 자료구조의 핵심적인 차이점을 정리하면 다음과 같습니다.
90+
91+
| 구분 | 순차 자료구조 | 연결 자료구조 |
92+
| -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ |
93+
| **메모리 저장 방식** | 연속적으로 할당, 시작 위치부터 빈자리 없이 순서대로 저장 | 비연속적으로 할당, 각 노드의 링크 필드가 다음 자료의 주소를 저장하여 순서 유지 |
94+
| **연산 특징** | 논리적 순서와 물리적 순서를 일치시키기 위해 데이터 이동 발생 | 링크 정보만 변경될 뿐, 물리적 위치는 그대로 유지 |
95+
| **임의 접근** | `O(1)` - 인덱스로 바로 접근 | `O(N)` - 리스트의 헤드부터 순회 필요 |
96+
| **프로그램 기법** | 배열(Array) 이용 | 포인터(Pointer) 이용 |
97+
98+
이러한 유연성에는 비용이 따릅니다. 바로 **임의 접근(random access)** 입니다.
99+
100+
N번째 원소에 접근하기 위해서는 리스트의 헤드부터 시작하여 N번 순회해야 하므로 O(N)의 시간 복잡도를 가집니다.
101+
102+
효율적인 수정과 효율적인 접근 사이의 이러한 트레이드오프는 연결 구조를 선택할 때 가장 핵심적인 설계 고려사항입니다.
103+
104+
이제 이러한 기본 개념을 바탕으로, 가장 대표적인 형태인 **단순 연결 리스트**의 구체적인 연산 방식을 살펴보겠습니다.
105+
106+
## 3. 단순 연결 리스트: 구조와 연산
107+
108+
단순 연결 리스트(Singly Linked List)는 가장 기본적이면서도 널리 사용되는 연결 리스트 형태입니다. 각 노드가 단 하나의 링크 필드를 통해 다음 노드를 가리키는 단방향 선형 구조를 가집니다.
109+
110+
이 구조에서 데이터의 삽입과 삭제가 어떻게 효율적으로 이루어지는지 분석하는 것이 이 섹션의 목표입니다.
111+
112+
### 삽입 연산 분석
113+
114+
단순 연결 리스트의 노드 삽입은 위치에 따라 세 가지 시나리오로 나눌 수 있으며, 핵심은 링크(포인터)의 연결을 정확한 순서로 재설정하는 것입니다.
115+
116+
#### 첫 번째 노드로 삽입
117+
118+
```pseudocode
119+
1. 새로운 노드를 생성하고 데이터를 저장합니다.
120+
2. 새로운 노드의 링크가 기존의 첫 번째 노드를 가리키도록 설정합니다.
121+
new.link ← L
122+
3. 리스트의 시작을 가리키는 헤드 포인터(L)가 새로운 노드를 가리키도록 업데이트합니다.
123+
L ← new
124+
```
125+
126+
#### 중간 노드로 삽입
127+
128+
```pseudocode
129+
1. 삽입할 위치의 바로 이전 노드(pre)를 찾습니다.
130+
2. 핵심은 새 노드를 리스트의 나머지 부분에 먼저 연결하는 것입니다:
131+
new.link ← pre.link
132+
(이 단계를 먼저 수행하지 않으면 리스트의 나머지 부분에 대한 참조가 유실됨)
133+
3. 그 후에야 이전 노드의 링크를 재설정합니다:
134+
pre.link ← new
135+
```
136+
137+
> **경고**: 순서가 중요합니다.
138+
> 새 노드를 먼저 리스트의 나머지 부분에 연결해야 참조가 유실되지 않습니다.
139+
140+
#### 마지막 노드로 삽입
141+
142+
```pseudocode
143+
1. 리스트의 첫 노드부터 순회하여 마지막 노드(링크 필드가 NULL인 노드)를 찾습니다.
144+
2. 새로운 노드를 생성하고, 데이터 저장 후 링크 필드를 NULL로 설정합니다.
145+
3. 찾아낸 기존 마지막 노드의 링크가 새로운 노드를 가리키도록 변경합니다.
146+
```
147+
148+
### 삭제 연산 분석
149+
150+
노드 삭제는 삭제할 노드를 기준으로 앞뒤 노드를 직접 연결하여 논리적 연결에서 제외하는 과정입니다.
151+
152+
```pseudocode
153+
1. 삭제할 노드의 선행자(pre) 탐색
154+
삭제할 노드의 바로 앞 노드를 찾습니다.
155+
156+
2. 링크 재연결
157+
pre.link ← old.link
158+
(한 번의 포인터 변경으로 삭제될 노드는 리스트의 연결에서 논리적으로 완벽히 제외)
159+
160+
3. 메모리 반환
161+
삭제된 노드가 차지하던 메모리 공간을 시스템에 반환하여 메모리 누수를 방지합니다.
162+
```
163+
164+
단순 연결 리스트는 이처럼 구조가 간단하고 삽입·삭제 구현이 용이합니다.
165+
166+
하지만 마지막 노드에서 첫 노드로 즉시 접근할 수 없으며, 특정 노드에서 이전 노드를 탐색하는 것이 비효율적이라는 한계를 가집니다.
167+
168+
이러한 단점을 보완하기 위해 고안된 것이 바로 **원형 연결 리스트**입니다.
169+
170+
## 4. 원형 연결 리스트: 순환 구조의 활용
171+
172+
원형 연결 리스트(Circular Linked List)는 단순 연결 리스트의 마지막 노드가 첫 번째 노드를 가리키게 하여 리스트 전체를 **'원형' 또는 '순환' 구조**로 만든 형태입니다.
173+
174+
> **컨벤션**: 본 섹션에서는 리스트를 가리키는 주 포인터(이하 **CL**)가 리스트의 **마지막 노드**를 가리키는 컨벤션을 채택합니다.
175+
>
176+
> 이 컨벤션은 마지막 노드(**CL**)와 첫 번째 노드(**CL.link**) 모두에 O(1) 시간 복잡도로 접근할 수 있게 해주는 실용적인 구현 방식입니다.
177+
178+
### 핵심 개념 및 장점
179+
180+
| 구분 | 설명 |
181+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
182+
| **구조적 특징** | 가장 큰 차이점은 마지막 노드의 링크 필드가 `NULL`이 아닌, 리스트의 첫 번째 노드 주소를 저장한다는 점입니다. 이로 인해 리스트의 끝과 시작이 논리적으로 연결됩니다. |
183+
| **주요 장점** | 모든 노드가 순환적으로 연결되어 있어 리스트의 어떤 노드에서든 출발하여 다른 모든 노드로 순회할 수 있습니다. 특히 **CL** 컨벤션을 사용하면 마지막 노드와 첫 번째 노드 양쪽에 대한 접근이 즉시 이루어져 매우 효율적입니다. |
184+
185+
### 원형 연결 리스트의 삽입 연산
186+
187+
원형 연결 리스트의 삽입 연산은 리스트의 순환 구조를 유지하기 위해 링크를 신중하게 조정해야 합니다.
188+
189+
#### 리스트 마지막에 노드 삽입
190+
191+
```pseudocode
192+
1. 새로운 노드를 생성하고 데이터를 저장합니다.
193+
2. 새로운 노드의 링크가 현재 첫 번째 노드(CL.link)를 가리키도록 설정합니다.
194+
3. 현재 마지막 노드(CL)의 링크가 새로운 노드를 가리키도록 변경합니다.
195+
4. 마지막으로, 리스트 포인터 CL이 새로 삽입된 마지막 노드를 가리키도록 업데이트합니다.
196+
```
197+
198+
#### 원형 리스트의 중간 노드로 삽입
199+
200+
이 과정은 단순 연결 리스트와 동일한 로직을 따릅니다.
201+
202+
```pseudocode
203+
1. 삽입할 위치의 이전 노드(pre)를 찾습니다.
204+
2. new.link ← pre.link
205+
3. pre.link ← new
206+
```
207+
208+
삽입할 위치의 이전 노드(pre)와 그 다음 노드 사이에 새 노드를 삽입하고 pre.link와 new.link를 순서에 맞게 재설정합니다.
209+
210+
### 원형 연결 리스트의 삭제 연산
211+
212+
노드 삭제 시에도 원형 구조가 깨지지 않도록 링크를 조정하는 것이 중요하며, 특히 첫 번째 노드를 삭제하는 경우 **CL** 포인터와의 상호작용을 고려해야 합니다.
213+
214+
#### 중간 노드 삭제
215+
216+
단순 연결 리스트와 동일하게, 삭제할 노드의 선행자(pre)가 삭제할 노드의 다음 노드(old.link)를 직접 가리키도록 링크를 변경합니다.
217+
218+
```pseudocode
219+
pre.link ← old.link
220+
```
221+
222+
#### 첫 번째 노드 삭제
223+
224+
```pseudocode
225+
1. 삭제할 노드는 첫 번째 노드, 즉 CL.link입니다.
226+
2. 핵심은 마지막 노드의 링크를 재설정하여 삭제될 노드를 건너뛰게 하는 것입니다:
227+
CL.link ← CL.link.link
228+
(마지막 노드가 기존의 두 번째 노드를 직접 가리키도록 변경)
229+
3. 삭제된 첫 번째 노드의 메모리를 시스템에 반환합니다.
230+
4. 만약 리스트에 노드가 하나뿐이었다면, 삭제 후 리스트가 비게 되므로 CL 포인터를 NULL로 설정해야 합니다.
231+
```
232+
233+
원형 연결 리스트는 이처럼 리스트를 순환적으로 처리해야 하는 작업(예: 라운드 로빈 스케줄링)이나, 리스트의 양 끝단에 대한 접근이 빈번한 애플리케이션에서 매우 유용합니다.
234+
235+
## 5. 결론: 자료구조의 전략적 선택
236+
237+
본 문서는 순차 자료구조의 한계와 이를 극복하기 위한 연결 자료구조의 핵심 원리를 분석했습니다.
238+
239+
### 근본적인 차이
240+
241+
두 구조의 근본적인 차이는 메모리 연속성 대 포인터 기반 연결이라는 대비에서 비롯됩니다.
242+
243+
| 측면 | 순차 자료구조 | 연결 자료구조 |
244+
| --------------- | --------------------------- | ------------------------------ |
245+
| **메모리 구조** | 연속된 메모리 할당 | 비연속적 노드 연결 |
246+
| **접근 속도** | `O(1)` - 인덱스로 빠른 접근 | `O(N)` - 순차 탐색 필요 |
247+
| **데이터 수정** | 유연성 떨어짐 (이동 필요) | 유연하고 효율적 (링크만 변경) |
248+
| **캐시 성능** | 캐시 친화적 | 포인터 추적으로 캐시 미스 위험 |
249+
250+
순차 자료구조는 물리적으로 연속된 메모리를 기반으로 하여 인덱스를 통한 빠른 접근을 보장하지만, 데이터 변경 시 유연성이 떨어집니다.
251+
252+
반면, 연결 자료구조는 포인터를 통해 노드를 논리적으로 연결함으로써 데이터의 삽입과 삭제를 유연하고 효율적으로 처리합니다.
253+
254+
### 자료구조 선택 가이드라인
255+
256+
이러한 특성에 기반하여, 어떤 자료구조를 선택할지에 대한 전략적 가이드라인은 다음과 같이 제시할 수 있습니다.
257+
258+
#### 순차 자료구조가 유리한 경우
259+
260+
- 저장할 데이터의 크기가 사전에 예측 가능하고 거의 변하지 않을 때
261+
- 데이터의 삽입·삭제보다 특정 위치의 데이터를 빠르게 조회(**random access**)하는 연산이 훨씬 중요할 때
262+
263+
#### 연결 자료구조가 유리한 경우
264+
265+
- 데이터의 크기가 가변적이고 예측하기 어려울 때
266+
- 데이터의 삽입과 삭제가 빈번하게 발생하여 동적인 데이터 관리가 필요할 때
267+
268+
### 고급 고려사항
269+
270+
궁극적으로 배열과 연결 리스트 사이의 선택은 시공간 복잡도 트레이드오프(**space-time tradeoff**)의 전형적인 사례이며, 캐시 성능에 영향을 미치는 근본적인 결정입니다.
271+
272+
배열의 연속적인 메모리 구조는 캐시에 매우 친화적(**cache-friendly**)이어서 순회 및 접근 연산에서 뛰어난 성능을 보입니다.
273+
274+
반면, 연결 리스트의 유연성은 포인터 추적(**pointer chasing**)으로 인한 잠재적인 캐시 미스(**cache miss**)를 대가로 합니다.
275+
276+
> **최종 통찰**: 숙련된 엔지니어는 고성능 시스템을 설계할 때 이러한 저수준 하드웨어의 영향까지 고려하여 자료구조를 선택합니다.
277+
>
278+
> 이처럼 자료구조에 대한 깊이 있는 이해는 효율적이고 확장 가능한 소프트웨어를 설계하는 핵심 역량입니다.

0 commit comments

Comments
 (0)