algo — C++23 日本語リファレンス GitHub

未検証 · 最終更新 2026-07-24 · GitHub

二分ヒープ(decrease-key 対応)

lib/heap/binary_heap.hpp

概要

順序基準の Key と付随データ Value を保持し、Comp で Key を比較する。 Comp = std::greater<> で Key 最小をルートにする最小ヒープになる (dary_heap / fibonacci_heap と同じ規約)。 連続領域(std::vector)に要素を並べる flat 実装で、new/delete を 一切行わない。push は安定なハンドルを返し、pop/push/update を 挟んでもそのハンドルで同じ要素を指し続けられる(要素はノード ID で 管理し、ヒープ配列の再配置から切り離す)。

インクルード

#include "heap/binary_heap.hpp"

クラス

名前 説明
binary_heap 二分ヒープ(decrease-key 対応)
実装
#pragma once
#include <functional>
#include <utility>
#include <vector>

/// @brief 二分ヒープ(decrease-key 対応)
/// @details 順序基準の `Key` と付随データ `Value` を保持し、`Comp` で `Key` を比較する。
///          `Comp = std::greater<>` で `Key` 最小をルートにする最小ヒープになる
///          (`dary_heap` / `fibonacci_heap` と同じ規約)。
///          連続領域(`std::vector`)に要素を並べる flat 実装で、`new`/`delete` を
///          一切行わない。`push` は安定なハンドルを返し、`pop`/`push`/`update` を
///          挟んでもそのハンドルで同じ要素を指し続けられる(要素はノード ID で
///          管理し、ヒープ配列の再配置から切り離す)。
/// @tparam Key 順序基準。`Comp` で比較される側。
/// @tparam Value 付随データ。順序には関与しない。
/// @tparam Comp `Key` の比較子。`comp(a, b) == true` のとき a が b より下位
///              (ルートから遠い側)になる。
template <class Key, class Value, class Comp = std::less<>>
struct binary_heap {
  private:
    static constexpr int nil = -1;

    struct _node {
        Key key;
        Value value;
    };

  public:
    /// @brief `push` が返す安定ハンドル。`update` に渡してその要素を decrease-key する。
    /// @details 既定構築(`handle{}`)は無効値で、`bool` 文脈で `false`。
    ///          要素を `pop` してもハンドルは無効化しない(呼び出し側で寿命を管理する)。
    /// @complexity 構築・比較は $O(1)$
    struct handle {
        /// @brief 無効なハンドルを作る
        /// @complexity $O(1)$
        constexpr handle() : idx(nil) {}
        /// @brief 有効なハンドルなら true を返す
        /// @complexity $O(1)$
        constexpr explicit operator bool() const { return idx >= 0; }
        /// @brief 2 つのハンドルが同じ要素を指すか比較する
        /// @complexity $O(1)$
        constexpr bool operator==(const handle &) const = default;

      private:
        friend struct binary_heap;
        constexpr explicit handle(int i) : idx(i) {}
        int idx;
    };

    /// @brief 空のヒープを構築する
    /// @complexity $O(1)$
    binary_heap() : nodes(), heap(), pos(), comp() {}

    /// @brief 空か返す
    /// @complexity $O(1)$
    constexpr bool empty() const { return heap.empty(); }

    /// @brief 要素数を返す
    /// @complexity $O(1)$
    constexpr int size() const { return (int)heap.size(); }

    /// @brief 最上位のkeyとvalueを返す
    /// @complexity $O(1)$
    // 返値は {順序基準 key, 付随データ value} の順。
    std::pair<Key, Value> top() const {
        const _node &n = nodes[heap[0]];
        return {n.key, n.value};
    }

    /// @brief 要素を追加して安定ハンドルを返す
    /// @complexity $O(\log n)$
    handle push(Key key, Value value) {
        int id = (int)nodes.size();
        nodes.push_back({std::move(key), std::move(value)});
        pos.push_back((int)heap.size());
        heap.push_back(id);
        sift_up((int)heap.size() - 1);
        return handle(id);
    }
    /// @brief 要素を追加して安定ハンドルを返す
    /// @complexity $O(\log n)$
    handle emplace(Key key, Value value) { return push(std::move(key), std::move(value)); }

    /// @brief 最上位の要素を削除する
    /// @complexity $O(\log n)$
    void pop() {
        int last = (int)heap.size() - 1;
        swap_heap(0, last);
        heap.pop_back();
        if (!heap.empty()) sift_down(0);
    }

    /// @brief ハンドルの順序基準 `key` を更新する(ルート側へ近づく更新のみ反映)。
    /// @details `comp(現在の key, key) == true`(= 現在値が下位)のときだけ上書きし
    ///          sift-up する。最小ヒープ(`greater<>`)では「より小さい key」への
    ///          更新、つまり decrease-key に対応する。逆向きの更新は無視する。
    /// @complexity $O(\log n)$
    void update(handle h, Key key) {
        _node &n = nodes[h.idx];
        if (!comp(n.key, key)) return;
        n.key = std::move(key);
        sift_up(pos[h.idx]);
    }

  private:
    // ヒープ配列の i, j を入れ替え、スロット → 位置の対応も同期する。
    void swap_heap(int i, int j) {
        std::swap(heap[i], heap[j]);
        pos[heap[i]] = i;
        pos[heap[j]] = j;
    }

    // i の要素を親より下位な間だけ上へ運ぶ。
    void sift_up(int i) {
        while (i > 0) {
            int parent = (i - 1) >> 1;
            if (!comp(nodes[heap[parent]].key, nodes[heap[i]].key)) break;
            swap_heap(i, parent);
            i = parent;
        }
    }

    // i の要素を子のうち最上位なものより下位な間だけ下へ運ぶ。
    void sift_down(int i) {
        int n = (int)heap.size();
        while (true) {
            int left = 2 * i + 1;
            if (left >= n) break;
            int best = left;
            int right = left + 1;
            if (right < n && comp(nodes[heap[best]].key, nodes[heap[right]].key)) best = right;
            if (!comp(nodes[heap[i]].key, nodes[heap[best]].key)) break;
            swap_heap(i, best);
            i = best;
        }
    }

    std::vector<_node> nodes;  // ノード ID → 要素(ID は安定、解放しない)
    std::vector<int> heap;     // ヒープ位置 → ノード ID
    std::vector<int> pos;      // ノード ID → ヒープ位置
    Comp comp;
};

bundled

#line 2 "lib/heap/binary_heap.hpp"
#include <functional>
#include <utility>
#include <vector>

/// @brief 二分ヒープ(decrease-key 対応)
/// @details 順序基準の `Key` と付随データ `Value` を保持し、`Comp` で `Key` を比較する。
///          `Comp = std::greater<>` で `Key` 最小をルートにする最小ヒープになる
///          (`dary_heap` / `fibonacci_heap` と同じ規約)。
///          連続領域(`std::vector`)に要素を並べる flat 実装で、`new`/`delete` を
///          一切行わない。`push` は安定なハンドルを返し、`pop`/`push`/`update` を
///          挟んでもそのハンドルで同じ要素を指し続けられる(要素はノード ID で
///          管理し、ヒープ配列の再配置から切り離す)。
/// @tparam Key 順序基準。`Comp` で比較される側。
/// @tparam Value 付随データ。順序には関与しない。
/// @tparam Comp `Key` の比較子。`comp(a, b) == true` のとき a が b より下位
///              (ルートから遠い側)になる。
template <class Key, class Value, class Comp = std::less<>>
struct binary_heap {
  private:
    static constexpr int nil = -1;

    struct _node {
        Key key;
        Value value;
    };

  public:
    /// @brief `push` が返す安定ハンドル。`update` に渡してその要素を decrease-key する。
    /// @details 既定構築(`handle{}`)は無効値で、`bool` 文脈で `false`。
    ///          要素を `pop` してもハンドルは無効化しない(呼び出し側で寿命を管理する)。
    /// @complexity 構築・比較は $O(1)$
    struct handle {
        /// @brief 無効なハンドルを作る
        /// @complexity $O(1)$
        constexpr handle() : idx(nil) {}
        /// @brief 有効なハンドルなら true を返す
        /// @complexity $O(1)$
        constexpr explicit operator bool() const { return idx >= 0; }
        /// @brief 2 つのハンドルが同じ要素を指すか比較する
        /// @complexity $O(1)$
        constexpr bool operator==(const handle &) const = default;

      private:
        friend struct binary_heap;
        constexpr explicit handle(int i) : idx(i) {}
        int idx;
    };

    /// @brief 空のヒープを構築する
    /// @complexity $O(1)$
    binary_heap() : nodes(), heap(), pos(), comp() {}

    /// @brief 空か返す
    /// @complexity $O(1)$
    constexpr bool empty() const { return heap.empty(); }

    /// @brief 要素数を返す
    /// @complexity $O(1)$
    constexpr int size() const { return (int)heap.size(); }

    /// @brief 最上位のkeyとvalueを返す
    /// @complexity $O(1)$
    // 返値は {順序基準 key, 付随データ value} の順。
    std::pair<Key, Value> top() const {
        const _node &n = nodes[heap[0]];
        return {n.key, n.value};
    }

    /// @brief 要素を追加して安定ハンドルを返す
    /// @complexity $O(\log n)$
    handle push(Key key, Value value) {
        int id = (int)nodes.size();
        nodes.push_back({std::move(key), std::move(value)});
        pos.push_back((int)heap.size());
        heap.push_back(id);
        sift_up((int)heap.size() - 1);
        return handle(id);
    }
    /// @brief 要素を追加して安定ハンドルを返す
    /// @complexity $O(\log n)$
    handle emplace(Key key, Value value) { return push(std::move(key), std::move(value)); }

    /// @brief 最上位の要素を削除する
    /// @complexity $O(\log n)$
    void pop() {
        int last = (int)heap.size() - 1;
        swap_heap(0, last);
        heap.pop_back();
        if (!heap.empty()) sift_down(0);
    }

    /// @brief ハンドルの順序基準 `key` を更新する(ルート側へ近づく更新のみ反映)。
    /// @details `comp(現在の key, key) == true`(= 現在値が下位)のときだけ上書きし
    ///          sift-up する。最小ヒープ(`greater<>`)では「より小さい key」への
    ///          更新、つまり decrease-key に対応する。逆向きの更新は無視する。
    /// @complexity $O(\log n)$
    void update(handle h, Key key) {
        _node &n = nodes[h.idx];
        if (!comp(n.key, key)) return;
        n.key = std::move(key);
        sift_up(pos[h.idx]);
    }

  private:
    // ヒープ配列の i, j を入れ替え、スロット → 位置の対応も同期する。
    void swap_heap(int i, int j) {
        std::swap(heap[i], heap[j]);
        pos[heap[i]] = i;
        pos[heap[j]] = j;
    }

    // i の要素を親より下位な間だけ上へ運ぶ。
    void sift_up(int i) {
        while (i > 0) {
            int parent = (i - 1) >> 1;
            if (!comp(nodes[heap[parent]].key, nodes[heap[i]].key)) break;
            swap_heap(i, parent);
            i = parent;
        }
    }

    // i の要素を子のうち最上位なものより下位な間だけ下へ運ぶ。
    void sift_down(int i) {
        int n = (int)heap.size();
        while (true) {
            int left = 2 * i + 1;
            if (left >= n) break;
            int best = left;
            int right = left + 1;
            if (right < n && comp(nodes[heap[best]].key, nodes[heap[right]].key)) best = right;
            if (!comp(nodes[heap[i]].key, nodes[heap[best]].key)) break;
            swap_heap(i, best);
            i = best;
        }
    }

    std::vector<_node> nodes;  // ノード ID → 要素(ID は安定、解放しない)
    std::vector<int> heap;     // ヒープ位置 → ノード ID
    std::vector<int> pos;      // ノード ID → ヒープ位置
    Comp comp;
};