카테고리 없음

12월 자료구조 - 세그먼트 트리(Segment Tree)

Halfand 2024. 12. 4. 12:23

 

 

이번에는 세그먼트 트리(Segment Tree) 알고리즘을 다뤄보겠습니다. 세그먼트 트리는 주로 구간 쿼리 문제에서 사용됩니다. 특정 구간의 합, 최소값, 최대값 등을 빠르게 구하는 데 매우 유용한 자료구조입니다. 구간 쿼리 문제에서 일반적인 배열로는 O(n) 시간이 걸리지만, 세그먼트 트리는 O(log n) 시간에 구할 수 있습니다.

 

 

 

1. 알고리즘 주제

세그먼트 트리 (Segment Tree)

 

2. 알고리즘의 정의

세그먼트 트리는 이진 트리의 형태를 가진 자료구조로, 배열의 각 구간에 대한 정보를 저장하는 데 사용됩니다. 이 자료구조는 주로 배열의 구간 합 또는 구간 최소값/최대값을 빠르게 구하고, 구간에 대한 업데이트를 효율적으로 처리할 수 있습니다.

세그먼트 트리는 다음과 같은 연산을 빠르게 처리할 수 있습니다:

  • 구간 합/최소값/최대값 쿼리: 배열의 특정 구간에 대한 정보를 빠르게 구합니다.
  • 구간 업데이트: 배열의 특정 값을 빠르게 업데이트하고, 그에 따른 변경 사항을 트리에 반영합니다.

세그먼트 트리의 시간 복잡도는 O(log n)입니다. 이를 통해 효율적인 구간 쿼리와 업데이트를 할 수 있습니다.

 

3. 알고리즘을 구현한 파이썬 코드 전문

다음은 구간 합을 구하는 세그먼트 트리의 구현 예시입니다. 여기서 구간 합을 구하는 방식으로 세그먼트 트리를 구현합니다.

class SegmentTree:
    def __init__(self, arr):
        self.n = len(arr)
        self.tree = [0] * (2 * self.n)  # 세그먼트 트리 배열
        self.build(arr)

    def build(self, arr):
        # 리프 노드 초기화
        for i in range(self.n):
            self.tree[self.n + i] = arr[i]
        
        # 내부 노드 채우기
        for i in range(self.n - 1, 0, -1):
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]

    def update(self, index, value):
        # 세그먼트 트리에서 해당 인덱스 값을 업데이트
        index += self.n  # 리프 노드로 이동
        self.tree[index] = value
        
        # 트리의 값을 업데이트 (상향식)
        while index > 1:
            index //= 2
            self.tree[index] = self.tree[2 * index] + self.tree[2 * index + 1]

    def query(self, left, right):
        # 구간 합을 구하는 쿼리
        left += self.n  # 리프 노드로 이동
        right += self.n  # 리프 노드로 이동
        result = 0
        
        while left <= right:
            if left % 2 == 1:  # 왼쪽 끝이 리프 노드일 경우
                result += self.tree[left]
                left += 1
            if right % 2 == 0:  # 오른쪽 끝이 리프 노드일 경우
                result += self.tree[right]
                right -= 1
            left //= 2
            right //= 2

        return result

# 예시 사용
arr = [1, 3, 5, 7, 9, 11]
seg_tree = SegmentTree(arr)

print("Initial sum of range [1, 3]:", seg_tree.query(1, 3))  # 3 + 5 + 7 = 15
seg_tree.update(2, 6)  # arr[2] = 6
print("Updated sum of range [1, 3]:", seg_tree.query(1, 3))  # 3 + 6 + 7 = 16

 

4. 알고리즘의 원리를 어떻게 파이썬으로 구현했는지

위 코드에서는 세그먼트 트리(Segment Tree) 자료구조를 사용하여 구간 합을 처리하는 방법을 설명하고 있습니다. 코드 해설을 해보겠습니다.

  • SegmentTree 클래스: 이 클래스는 세그먼트 트리의 구조를 정의합니다. 트리는 self.tree 리스트로 표현되며, 2 * n 크기로 선언합니다. 세그먼트 트리의 크기는 배열의 두 배 크기여야 하므로, 트리 배열의 크기를 2 * n으로 설정합니다.
  • build 메서드: 주어진 배열을 바탕으로 세그먼트 트리를 구성하는 함수입니다. 먼저 배열의 값을 트리의 리프 노드에 저장하고, 그 이후 부모 노드들을 계산하여 채웁니다. 세그먼트 트리의 부모 노드는 자식 노드들의 합으로 계산됩니다.
  • update 메서드: 주어진 인덱스의 값을 세그먼트 트리에서 업데이트하는 함수입니다. 먼저 리프 노드를 업데이트한 후, 상향식으로 부모 노드들도 갱신합니다. 이때 트리의 깊이는 O(log n)이므로, 업데이트 연산의 시간 복잡도는 O(log n)입니다.
  • query 메서드: 주어진 구간의 합을 구하는 함수입니다. 구간 쿼리는 트리의 리프 노드에서 시작하여 상위 노드로 올라가며 구간 합을 구합니다. 구간의 양 끝이 리프 노드일 경우, 그 값은 바로 더해지고, 구간이 트리 내의 노드를 포함할 때는 그 노드의 값을 합산합니다. 이 과정은 O(log n) 시간에 끝납니다.

 

5. 느낀점

세그먼트 트리는 구간 쿼리를 매우 효율적으로 처리할 수 있는 자료구조입니다. 특히 O(log n) 시간에 구간 합을 구할 수 있다는 점에서 매우 강력합니다. 이를 통해, 대규모 배열에서 수많은 구간 쿼리와 업데이트를 처리할 때 매우 유용한 자료구조임을 실감할 수 있었습니다.

세그먼트 트리는 일반적인 배열로는 해결하기 어려운 문제를 효율적으로 풀 수 있게 해주며, 다른 자료구조와의 차별성이 뚜렷합니다. 예를 들어, 배열을 사용하면 구간 합을 구하는 데 O(n) 시간이 걸리지만, 세그먼트 트리를 사용하면 O(log n) 시간으로 처리할 수 있습니다.

하지만 세그먼트 트리의 단점은 공간 복잡도입니다. 트리 구조를 구현해야 하므로, 메모리 사용량이 증가할 수 있습니다. 또한 구현이 다소 복잡하고, 잘못된 범위 업데이트나 쿼리 처리가 문제를 일으킬 수 있으므로 정확하게 구현하는 것이 중요합니다.

이 알고리즘을 활용하면, 예를 들어 게임에서 점수 구간을 빠르게 처리하거나, 실시간 데이터에서 특정 범위의 합을 계산하는 등의 다양한 문제를 해결할 수 있습니다. 세그먼트 트리의 구현을 통해 효율적인 알고리즘의 중요성과 응용 가능성에 대해 더 깊이 이해할 수 있었습니다.