セグメントツリー

はじめに

セグメントツリーは、動的な範囲クエリを効率的に処理するためのツリーデータ構造です。 このデータ構造が「セグメントツリー」と呼ばれるのは、配列を再帰的に分割し、最小単位が 1 になるまで区間を細かく分割する仕組みに由来します。 この分割の考え方は二分探索法にも似ています。

トレードオフ: メモリ消費量が多い。

実装方法

例: 範囲内の最大値を求める

class SegmentTree:
    def __init__(self, arr):
        self.n = len(arr)
        self.tree_size = 1
        self.init_value = float("-inf")

        while self.tree_size < self.n:
            self.tree_size *= 2

        # データセットの作成
        # 空間計算量: O(2n)
        self.data = [self.init_value] * (2 * self.tree_size)

        # セグメントツリーの構築
        # 時間計算量: O(n)
        for i in range(self.n):
            self.data[self.tree_size + i] = arr[i]
        
        # 時間計算量: O(n)
        for i in range(self.tree_size - 1, 0, -1):
            self.data[i] = max(self.data[i * 2], self.data[i * 2 + 1])

    def update_node(self, index, value):
        node = index - 1 + self.tree_size
        self.data[node] = value

        # 時間計算量: O(log n)
        while node > 1:
            parent = node // 2
            self.data[parent] = max(self.data[parent * 2], self.data[parent * 2 + 1])
            node = parent

    def range_max(self, left, right):
        # 基本ケース: left は right より小さくなければならない
        if left > right:
            return float("-inf")
        
        left += self.tree_size - 1
        right += self.tree_size - 1
        res = float("-inf")
        
        # 時間計算量: O(log n)
        while left <= right:
            if left % 2 == 1:
                res = max(res, self.data[left])
                left += 1
            if right % 2 == 0:
                res = max(res, self.data[right])
                right -= 1
            left //= 2
            right //= 2
        
        return res


# 使用例:
arr = [1, 2, 3, 4, 5, 6, 7]
seg_tree = SegmentTree(arr)

# 値の更新
seg_tree.update_node(1, 2)
seg_tree.update_node(2, 4)
seg_tree.update_node(3, 1)
seg_tree.update_node(4, 5)
seg_tree.update_node(5, 4)
seg_tree.update_node(6, 3)
seg_tree.update_node(7, 2)

# セグメントツリーのクエリ実行
print(seg_tree.range_max(6, 2))  # 無効な範囲 -> -inf を返す
print(seg_tree.range_max(2, 2))  # 4 を返すはず
print(seg_tree.range_max(3, 7))  # 5 を返すはず

いつ使用すべきか?

  • 範囲クエリに対して効率的な応答が必要なとき。
  • 動的に要素を更新する必要があるとき。

実世界での応用例

  • 株式市場分析: 特定の時間範囲内での最大/最小価格を求める。
  • ビッグデータクエリ: 範囲に基づいた効率的な計算。

他のアプローチとの比較

セグメントツリー vs. フェニックツリー(BIT)

  • フェニックツリー(BIT)はシンプルだが、範囲更新が効率的にできない。
  • セグメントツリーはより複雑なクエリ(max/min/sum/gcd など)をサポートできる。

セグメントツリー vs. スパーステーブル

  • スパーステーブルはクエリが高速(O(1) vs. O(log n))。
  • ただし、スパーステーブルは静的データにしか対応できず、更新ができない。

使用を避けるべきケース

  • メモリ消費が大きい (O(2n))ため、メモリが限られている場合は避ける。
  • 更新がほとんど発生しない場合: Python の max() を使うほうが簡単(O(n))。