스킵리스트

IT 위키

스킵리스트(skip list)는 정렬된 요소들의 리스트에서 효율적인 탐색, 삽입, 삭제를 가능하게 하기 위해 여러 개의 레벨을 도입한 확률 기반의 자료구조이다.

1 개요[편집 | 원본 편집]

스킵리스트는 1990년 William Pugh가 제안한 자료구조로, 균형 이진 탐색 트리와 유사한 성능을 가지면서도 구현이 간단한 것이 특징이다. 각 요소는 여러 레벨의 연결 리스트에 포함될 수 있으며, 레벨이 높을수록 더 많은 원소를 건너뛰며 빠르게 탐색할 수 있다. 이러한 구조를 통해 평균적으로 O(log n)의 시간 복잡도로 탐색, 삽입, 삭제가 가능하다.

2 구조[편집 | 원본 편집]

스킵리스트는 여러 층의 연결 리스트로 구성되며, 각 층은 하위 층을 참조할 수 있는 노드들로 이루어져 있다. 가장 아래층은 모든 원소를 포함한 기본 리스트이며, 위쪽 레벨로 올라갈수록 선택된 일부 노드들만 포함된다. 노드가 상위 레벨로 포함될 확률은 일반적으로 1/2로 설정된다.

3 연산[편집 | 원본 편집]

스킵리스트에서의 주요 연산은 다음과 같다.

  • 탐색: 가장 높은 레벨부터 시작하여 오른쪽으로 이동하며 탐색하고, 더 이상 진행할 수 없으면 한 단계 아래로 내려가며 반복한다.
  • 삽입: 삽입 위치를 찾은 후, 임의의 확률로 레벨을 결정하여 해당 위치에 노드를 삽입한다.
  • 삭제: 탐색을 통해 삭제할 노드를 찾고, 각 레벨에서 해당 노드를 제거한다.

4 시간 복잡도[편집 | 원본 편집]

스킵리스트의 연산 시간 복잡도는 다음과 같다.

  • 탐색: 평균 O(log n), 최악 O(n)
  • 삽입: 평균 O(log n)
  • 삭제: 평균 O(log n)

이러한 성능은 각 노드가 상위 레벨에 포함될 확률이 일정하다는 가정 하에 성립한다.

5 장점과 단점[편집 | 원본 편집]

  • 장점
    • 구현이 단순하며, 트리 기반 구조보다 직관적이다.
    • 동적 삽입과 삭제가 용이하다.
    • 병렬 처리 및 동시성 제어에 유리한 구조를 가질 수 있다.
  • 단점
    • 최악의 경우 성능이 저하될 수 있다.
    • 많은 포인터를 사용하므로 메모리 사용량이 높다.

6 활용[편집 | 원본 편집]

스킵리스트는 다음과 같은 분야에서 활용된다.

  • 메모리 내 데이터베이스 및 키-값 저장소 (예: Redis)
  • 트랜잭션 시스템에서의 인덱싱
  • 분산 시스템에서의 정렬된 데이터 저장

7 파이썬 구현[편집 | 원본 편집]

다음은 간단한 스킵리스트의 파이썬 구현 예시이다.

import random

class Node:
    def __init__(self, key, level):
        self.key = key
        self.forward = [None] * (level + 1)

class SkipList:
    MAX_LEVEL = 16
    P = 0.5

    def __init__(self):
        self.level = 0
        self.header = Node(None, self.MAX_LEVEL)

    def random_level(self):
        lvl = 0
        while random.random() < self.P and lvl < self.MAX_LEVEL:
            lvl += 1
        return lvl

    def insert(self, key):
        update = [None] * (self.MAX_LEVEL + 1)
        current = self.header

        for i in reversed(range(self.level + 1)):
            while current.forward[i] and current.forward[i].key < key:
                current = current.forward[i]
            update[i] = current

        current = current.forward[0]
        if current is None or current.key != key:
            lvl = self.random_level()
            if lvl > self.level:
                for i in range(self.level + 1, lvl + 1):
                    update[i] = self.header
                self.level = lvl
            new_node = Node(key, lvl)
            for i in range(lvl + 1):
                new_node.forward[i] = update[i].forward[i]
                update[i].forward[i] = new_node

    def search(self, key):
        current = self.header
        for i in reversed(range(self.level + 1)):
            while current.forward[i] and current.forward[i].key < key:
                current = current.forward[i]
        current = current.forward[0]
        if current and current.key == key:
            return True
        return False

8 같이 보기[편집 | 원본 편집]

9 참고 문헌[편집 | 원본 편집]

  • William Pugh, "Skip Lists: A Probabilistic Alternative to Balanced Trees", Communications of the ACM, 1990.
  • Thomas H. Cormen et al., "Introduction to Algorithms", MIT Press.

10 각주[편집 | 원본 편집]