본문으로 건너뛰기

연결 자료구조와 연결 리스트

간단 요약

순차 자료구조의 한계를 극복하기 위한 대안으로서 연결 자료구조의 핵심 개념, 유형, 그리고 주요 연산 방식을 분석합니다. 배열 기반의 순차 자료구조는 데이터 삽입 및 삭제 시 발생하는 오버헤드와 고정된 크기로 인한 메모리 비효율성 문제를 내재하고 있습니다. 연결 자료구조는 각 데이터 요소를 노드(Node) 단위로 관리하고, 각 노드가 다음 노드의 위치를 가리키는 포인터(Pointer) 를 포함하는 방식으로 이 문제를 해결합니다.

이를 통해 데이터의 논리적 순서와 물리적 저장 위치를 분리하여 동적 데이터 관리에 필요한 유연성을 확보합니다.

이 개념은 기차놀이에 비유할 수 있습니다. 각 어린이는 데이터를 담은 노드이며, 다음 순서의 아이를 명시적으로 가리키는 행위(이름표 들기)가 링크(포인터) 역할을 합니다. 아이들이 운동장 어디에 흩어져 있든, 이 논리적 연결만 따라가면 전체 순서가 유지되는 것과 같은 원리입니다. 가장 대표적인 연결 자료구조 형태인 단순 연결 리스트와 그 변형인 원형 연결 리스트를 중심으로, 각각의 구조에서 데이터 삽입 및 삭제 연산이 어떻게 이루어지는지 심도 있게 분석할 것입니다.


1. 순차 자료구조의 한계와 연결 자료구조의 필요성

효율적인 데이터 관리는 소프트웨어 성능의 핵심이며, 이를 위해 어떤 자료구조를 선택하는가는 매우 중요한 아키텍처 결정입니다. 배열이 대표적인 예시이며, 구조가 단순하고 인덱스를 통한 O(1) 접근이 가능하다는 명확한 장점이 있습니다.

가장 기본적인 자료구조 중 하나인 순차 자료구조는 데이터를 메모리상에 연속적으로 저장하는 방식을 사용합니다.

하지만 이러한 구조적 특성은 특정 시나리오에서 명백한 한계로 작용합니다.

순차 자료구조가 직면하는 핵심적인 문제점

1. 메모리 사용의 비효율성

배열 기반 구현은 초기에 필요한 전체 메모리 크기를 예측하여 할당해야 합니다. 할당된 공간보다 데이터가 적으면 메모리가 낭비되고, 공간이 부족하면 더 큰 배열을 새로 만들어 기존 데이터를 모두 복사해야 하는 비효율이 발생합니다.

2. 삽입 및 삭제 연산의 오버헤드

데이터의 논리적 순서와 물리적 저장 순서가 일치해야 하므로, 중간에 데이터를 삽입하거나 삭제할 경우 그 뒤의 모든 원소를 한 칸씩 이동시켜야 합니다.

중간 삽입/삭제 시 → 뒤의 모든 원소를 이동

이러한 이동 작업은 데이터의 양이 많고 삽입·삭제가 빈번한 환경에서 심각한 성능 저하(오버헤드)를 유발합니다.

이러한 문제들은 데이터의 양이 유동적으로 변하는 동적 환경에서 특히 두드러집니다.

이 오버헤드는 텍스트 편집기나 메모리 관리 서브시스템과 같이 실시간 제약 조건이 있는 애플리케이션에서 대규모 데이터 블록을 이동시켜야 할 때 눈에 띄는 지연 시간 급증(latency spike)으로 나타날 수 있습니다.

이러한 한계를 극복하기 위해, 논리적 순서와 물리적 순서를 분리하는 새로운 접근법이 필요했고, 그 결과로 연결 자료구조가 등장하게 되었습니다.

2. 연결 자료구조의 핵심 개념

연결 자료구조는 순차 자료구조의 근본적인 문제, 즉 물리적 연속성 제약을 해결하기 위한 혁신적인 접근 방식을 취합니다.

핵심: 데이터의 논리적 순서와 메모리상의 물리적 순서를 분리하는 것입니다.
이를 통해 데이터 관리의 유연성과 효율성을 극대화합니다.

연결 자료구조의 기본 원리

1. 논리적 vs 물리적 순서의 분리

  • 연결 자료구조에서는 각 데이터 원소(노드)가 메모리상에 흩어져 저장될 수 있습니다.
  • 순서는 각 원소가 다음 원소의 메모리 주소를 직접 가리키는 방식으로 정의됩니다.
  • 물리적 위치를 맞추기 위한 데이터 이동 오버헤드가 원천적으로 발생하지 않습니다.

2. 유연한 메모리 관리

큰 메모리 덩어리를 한 번에 할당하는 대신, 필요할 때마다 작은 노드 단위로 메모리 공간을 할당하고 이를 연결하여 전체 구조를 형성합니다.

필요할 때마다 작은 노드 단위로 할당 → 전체 구조 형성

이는 C의 malloc이나 C++의 new와 같이 필요에 따라 힙(heap) 영역에서 비연속적인 청크로 메모리를 획득하는 동적 메모리 할당과 유사하며, 스택(stack)에 정적 배열을 선언하는 것과 같은 경직된 사전 할당을 피하게 해줍니다.

비유: '퀼트 이불'이나 '줄줄이 소시지'처럼 각 조각을 연결

노드(Node)의 구조

연결 리스트를 구성하는 기본 단위는 노드(Node) 이며, 이는 두 개의 핵심 필드로 구성됩니다.

필드설명역할
데이터 필드 (Data Field)원소의 실제 값(숫자, 문자열 등)데이터 저장
링크 필드 (Link Field)다음 노드의 메모리 주소(포인터)논리적 연결 유지

순차 자료구조와의 비교

두 자료구조의 핵심적인 차이점을 정리하면 다음과 같습니다.

구분순차 자료구조연결 자료구조
메모리 저장 방식연속적으로 할당, 시작 위치부터 빈자리 없이 순서대로 저장비연속적으로 할당, 각 노드의 링크 필드가 다음 자료의 주소를 저장하여 순서 유지
연산 특징논리적 순서와 물리적 순서를 일치시키기 위해 데이터 이동 발생링크 정보만 변경될 뿐, 물리적 위치는 그대로 유지
임의 접근O(1) - 인덱스로 바로 접근O(N) - 리스트의 헤드부터 순회 필요
프로그램 기법배열(Array) 이용포인터(Pointer) 이용

이러한 유연성에는 비용이 따릅니다. 바로 임의 접근(random access) 입니다.

N번째 원소에 접근하기 위해서는 리스트의 헤드부터 시작하여 N번 순회해야 하므로 O(N)의 시간 복잡도를 가집니다.

효율적인 수정과 효율적인 접근 사이의 이러한 트레이드오프는 연결 구조를 선택할 때 가장 핵심적인 설계 고려사항입니다.

이제 이러한 기본 개념을 바탕으로, 가장 대표적인 형태인 단순 연결 리스트의 구체적인 연산 방식을 살펴보겠습니다.

3. 단순 연결 리스트: 구조와 연산

단순 연결 리스트(Singly Linked List)는 가장 기본적이면서도 널리 사용되는 연결 리스트 형태입니다. 각 노드가 단 하나의 링크 필드를 통해 다음 노드를 가리키는 단방향 선형 구조를 가집니다.

이 구조에서 데이터의 삽입과 삭제가 어떻게 효율적으로 이루어지는지 분석하는 것이 이 섹션의 목표입니다.

삽입 연산 분석

단순 연결 리스트의 노드 삽입은 위치에 따라 세 가지 시나리오로 나눌 수 있으며, 핵심은 링크(포인터)의 연결을 정확한 순서로 재설정하는 것입니다.

첫 번째 노드로 삽입

1. 새로운 노드를 생성하고 데이터를 저장합니다.
2. 새로운 노드의 링크가 기존의 첫 번째 노드를 가리키도록 설정합니다.
new.link ← L
3. 리스트의 시작을 가리키는 헤드 포인터(L)가 새로운 노드를 가리키도록 업데이트합니다.
L ← new

중간 노드로 삽입

1. 삽입할 위치의 바로 이전 노드(pre)를 찾습니다.
2. 핵심은 새 노드를 리스트의 나머지 부분에 먼저 연결하는 것입니다:
new.link ← pre.link
(이 단계를 먼저 수행하지 않으면 리스트의 나머지 부분에 대한 참조가 유실됨)
3. 그 후에야 이전 노드의 링크를 재설정합니다:
pre.link ← new

경고: 순서가 중요합니다. 새 노드를 먼저 리스트의 나머지 부분에 연결해야 참조가 유실되지 않습니다.

마지막 노드로 삽입

1. 리스트의 첫 노드부터 순회하여 마지막 노드(링크 필드가 NULL인 노드)를 찾습니다.
2. 새로운 노드를 생성하고, 데이터 저장 후 링크 필드를 NULL로 설정합니다.
3. 찾아낸 기존 마지막 노드의 링크가 새로운 노드를 가리키도록 변경합니다.

삭제 연산 분석

노드 삭제는 삭제할 노드를 기준으로 앞뒤 노드를 직접 연결하여 논리적 연결에서 제외하는 과정입니다.

1. 삭제할 노드의 선행자(pre) 탐색
삭제할 노드의 바로 앞 노드를 찾습니다.

2. 링크 재연결
pre.link ← old.link
(한 번의 포인터 변경으로 삭제될 노드는 리스트의 연결에서 논리적으로 완벽히 제외)

3. 메모리 반환
삭제된 노드가 차지하던 메모리 공간을 시스템에 반환하여 메모리 누수를 방지합니다.

단순 연결 리스트는 이처럼 구조가 간단하고 삽입·삭제 구현이 용이합니다.

하지만 마지막 노드에서 첫 노드로 즉시 접근할 수 없으며, 특정 노드에서 이전 노드를 탐색하는 것이 비효율적이라는 한계를 가집니다.

이러한 단점을 보완하기 위해 고안된 것이 바로 원형 연결 리스트입니다.

4. 원형 연결 리스트: 순환 구조의 활용

원형 연결 리스트(Circular Linked List)는 단순 연결 리스트의 마지막 노드가 첫 번째 노드를 가리키게 하여 리스트 전체를 '원형' 또는 '순환' 구조로 만든 형태입니다.

컨벤션: 본 섹션에서는 리스트를 가리키는 주 포인터(이하 CL)가 리스트의 마지막 노드를 가리키는 컨벤션을 채택합니다.

이 컨벤션은 마지막 노드(CL)와 첫 번째 노드(CL.link) 모두에 O(1) 시간 복잡도로 접근할 수 있게 해주는 실용적인 구현 방식입니다.

핵심 개념 및 장점

구분설명
구조적 특징가장 큰 차이점은 마지막 노드의 링크 필드가 NULL이 아닌, 리스트의 첫 번째 노드 주소를 저장한다는 점입니다. 이로 인해 리스트의 끝과 시작이 논리적으로 연결됩니다.
주요 장점모든 노드가 순환적으로 연결되어 있어 리스트의 어떤 노드에서든 출발하여 다른 모든 노드로 순회할 수 있습니다. 특히 CL 컨벤션을 사용하면 마지막 노드와 첫 번째 노드 양쪽에 대한 접근이 즉시 이루어져 매우 효율적입니다.

원형 연결 리스트의 삽입 연산

원형 연결 리스트의 삽입 연산은 리스트의 순환 구조를 유지하기 위해 링크를 신중하게 조정해야 합니다.

리스트 마지막에 노드 삽입

1. 새로운 노드를 생성하고 데이터를 저장합니다.
2. 새로운 노드의 링크가 현재 첫 번째 노드(CL.link)를 가리키도록 설정합니다.
3. 현재 마지막 노드(CL)의 링크가 새로운 노드를 가리키도록 변경합니다.
4. 마지막으로, 리스트 포인터 CL이 새로 삽입된 마지막 노드를 가리키도록 업데이트합니다.

원형 리스트의 중간 노드로 삽입

이 과정은 단순 연결 리스트와 동일한 로직을 따릅니다.

1. 삽입할 위치의 이전 노드(pre)를 찾습니다.
2. new.link ← pre.link
3. pre.link ← new

삽입할 위치의 이전 노드(pre)와 그 다음 노드 사이에 새 노드를 삽입하고 pre.link와 new.link를 순서에 맞게 재설정합니다.

원형 연결 리스트의 삭제 연산

노드 삭제 시에도 원형 구조가 깨지지 않도록 링크를 조정하는 것이 중요하며, 특히 첫 번째 노드를 삭제하는 경우 CL 포인터와의 상호작용을 고려해야 합니다.

중간 노드 삭제

단순 연결 리스트와 동일하게, 삭제할 노드의 선행자(pre)가 삭제할 노드의 다음 노드(old.link)를 직접 가리키도록 링크를 변경합니다.

pre.link ← old.link

첫 번째 노드 삭제

1. 삭제할 노드는 첫 번째 노드, 즉 CL.link입니다.
2. 핵심은 마지막 노드의 링크를 재설정하여 삭제될 노드를 건너뛰게 하는 것입니다:
CL.link ← CL.link.link
(마지막 노드가 기존의 두 번째 노드를 직접 가리키도록 변경)
3. 삭제된 첫 번째 노드의 메모리를 시스템에 반환합니다.
4. 만약 리스트에 노드가 하나뿐이었다면, 삭제 후 리스트가 비게 되므로 CL 포인터를 NULL로 설정해야 합니다.

원형 연결 리스트는 이처럼 리스트를 순환적으로 처리해야 하는 작업(예: 라운드 로빈 스케줄링)이나, 리스트의 양 끝단에 대한 접근이 빈번한 애플리케이션에서 매우 유용합니다.

5. 결론: 자료구조의 전략적 선택

본 문서는 순차 자료구조의 한계와 이를 극복하기 위한 연결 자료구조의 핵심 원리를 분석했습니다.

근본적인 차이

두 구조의 근본적인 차이는 메모리 연속성 대 포인터 기반 연결이라는 대비에서 비롯됩니다.

측면순차 자료구조연결 자료구조
메모리 구조연속된 메모리 할당비연속적 노드 연결
접근 속도O(1) - 인덱스로 빠른 접근O(N) - 순차 탐색 필요
데이터 수정유연성 떨어짐 (이동 필요)유연하고 효율적 (링크만 변경)
캐시 성능캐시 친화적포인터 추적으로 캐시 미스 위험

순차 자료구조는 물리적으로 연속된 메모리를 기반으로 하여 인덱스를 통한 빠른 접근을 보장하지만, 데이터 변경 시 유연성이 떨어집니다.

반면, 연결 자료구조는 포인터를 통해 노드를 논리적으로 연결함으로써 데이터의 삽입과 삭제를 유연하고 효율적으로 처리합니다.

자료구조 선택 가이드라인

이러한 특성에 기반하여, 어떤 자료구조를 선택할지에 대한 전략적 가이드라인은 다음과 같이 제시할 수 있습니다.

순차 자료구조가 유리한 경우

  • 저장할 데이터의 크기가 사전에 예측 가능하고 거의 변하지 않을 때
  • 데이터의 삽입·삭제보다 특정 위치의 데이터를 빠르게 조회(random access)하는 연산이 훨씬 중요할 때

연결 자료구조가 유리한 경우

  • 데이터의 크기가 가변적이고 예측하기 어려울 때
  • 데이터의 삽입과 삭제가 빈번하게 발생하여 동적인 데이터 관리가 필요할 때

고급 고려사항

궁극적으로 배열과 연결 리스트 사이의 선택은 시공간 복잡도 트레이드오프(space-time tradeoff)의 전형적인 사례이며, 캐시 성능에 영향을 미치는 근본적인 결정입니다.

배열의 연속적인 메모리 구조는 캐시에 매우 친화적(cache-friendly)이어서 순회 및 접근 연산에서 뛰어난 성능을 보입니다.

반면, 연결 리스트의 유연성은 포인터 추적(pointer chasing)으로 인한 잠재적인 캐시 미스(cache miss)를 대가로 합니다.

최종 통찰: 숙련된 엔지니어는 고성능 시스템을 설계할 때 이러한 저수준 하드웨어의 영향까지 고려하여 자료구조를 선택합니다.

이처럼 자료구조에 대한 깊이 있는 이해는 효율적이고 확장 가능한 소프트웨어를 설계하는 핵심 역량입니다.