자료구조와 알고리즘 Chapter 11 ← 10 재귀 알고리즘12 이진 트리 →
제 11 장 · Node-Based Data Structures

노드 기반 자료구조

배열은 메모리에 연속으로 놓인다. 연결 리스트는 다르다 — 메모리 전역에 흩어진 노드들을, 다음 주소를 가리키는 링크가 잇는다. 그 차이가 만드는 효율성의 거울상.

연결 리스트 노드 링크 이중 연결 리스트

§ 1 · 흩어진 노드연결 리스트란

연결 리스트(linked list)는 배열처럼 항목들의 목록이다. 그러나 내부 구현이 다르다. 배열은 메모리에 연속된 셀로 저장되지만, 연결 리스트의 셀들—노드(node)—은 메모리 전역에 흩어질 수 있다.

흩어진 노드들이 어떻게 같은 리스트로 묶일까? 핵심은 이것이다 — 각 노드는 자기 데이터 외에 다음 노드의 메모리 주소를 저장한다. 이 추가 데이터를 링크(link)라 한다.

노드와 연결 리스트 — Ruby
class Node
  attr_accessor :data, :next_node
  def initialize(data)
    @data = data
  end
end

class LinkedList
  attr_accessor :first_node
  def initialize(first_node)
    @first_node = first_node
  end
end
핵심 프로그램이 알아야 할 것은 첫 번째 노드의 주소뿐이다. 각 노드가 다음 노드를 가리키므로, 첫 노드만 알면 링크를 따라 리스트 전체를 파악할 수 있다. 마지막 노드의 링크는 null이다.

§ 2 · 링크를 따라읽기와 검색

배열에서 인덱스 2 읽기는 O(1)이었다. 연결 리스트에서는 그렇지 않다. 노드들이 메모리 어디에 있을지 알 수 없으므로, 첫 노드부터 링크를 하나씩 따라가야 한다. 읽기는 O(N)이다 — 배열에 비해 큰 단점이다.

검색은 배열과 동등하다. 둘 다 첫 셀부터 하나씩 보며 찾으므로 O(N)이다.

모션 · 연결 리스트에서 인덱스 2 읽기 단계 01 / 0
space 재생 · → 단계 · R 리셋

§ 3 · 거울상삽입과 삭제

여기서 연결 리스트가 빛난다. 배열은 시작에 삽입할 때 모든 데이터를 밀어야 해 O(N)이지만, 연결 리스트는 새 노드를 만들어 링크만 바꾸면 되니 시작 삽입이 O(1)이다.

흥미롭게도 배열과 연결 리스트는 정확히 거울상이다.

삽입·삭제 — 배열과 연결 리스트는 거울상
시나리오배열연결 리스트
시작에 삽입/삭제최악의 경우최고의 경우
중간에 삽입/삭제평균 경우평균 경우
끝에 삽입/삭제최고의 경우최악의 경우
모션 · 연결 리스트 중간에 노드 삽입 단계 01 / 0
space 재생 · → 단계 · R 리셋
배열 vs 연결 리스트 — 네 연산 종합
연산배열연결 리스트
읽기O(1)O(N)
검색O(N)O(N)
삽입O(N) · 끝은 O(1)O(N) · 시작은 O(1)
삭제O(N) · 끝은 O(1)O(N) · 시작은 O(1)

§ 4 · 다중 삭제연결 리스트의 활약

읽기는 배열이 압도적인데, 왜 연결 리스트를 쓸까? 연결 리스트가 빛나는 경우 — 리스트를 한 번 훑으며 많은 요소를 삭제할 때다.

유효하지 않은 이메일 주소를 걸러내는 프로그램을 보자. 1,000개 목록에 100개가 무효라고 하자.

  • 배열 — 삭제할 때마다 데이터를 왼쪽으로 미는 O(N). 읽기 1,000단계 + 삭제 100 × N ≈ 100,000단계.
  • 연결 리스트 — 삭제는 링크만 바꾸면 되는 O(1). 읽기 1,000단계 + 삭제 100단계 = 1,100단계.

§ 5 · 양방향 링크이중 연결 리스트

이중 연결 리스트(doubly linked list)는 각 노드가 두 개의 링크를 가진다 — 다음 노드와 이전 노드. 또 첫 번째와 마지막 노드를 모두 추적한다.

덕분에 양쪽 끝에 각각 O(1)로 접근할 수 있다. 끝에 O(1)로 삽입하고, 앞에서 O(1)로 삭제할 수 있다 — 이는 큐의 완벽한 기반이 된다.

이중 연결 리스트로 만든 큐 — Ruby
class Queue
  def initialize
    @queue = DoublyLinkedList.new
  end

  def enque(value)
    @queue.insert_at_end(value)        # O(1)
  end

  def deque
    removed_node = @queue.remove_from_front   # O(1)
    return removed_node.data
  end
end
모션 · 이중 연결 리스트 끝에 노드 삽입 단계 01 / 0
space 재생 · → 단계 · R 리셋

§ 6 · 정리이 장이 남긴 것

연결 리스트는 특정 상황에서 성능을 향상시킨다. 핵심은 선택지를 갖는 것, 그리고 올바른 시점에 올바른 선택을 하는 것이다. 다음 장에서는 더 복잡한 노드 기반 자료구조 — 이진 트리를 만난다.