|
| 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