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

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

基数ヒープ(単調ヒープ)

lib/heap/radix_heap.hpp

概要

順序基準 Key(符号なし整数)で最小値を取り出す。取り出した値より小さい Key は以後 push できない(単調性)。Dijkstra など距離が単調増加する用途向け。 連続領域(std::vector)に値を並べる flat 実装で、new/delete を行わない。

インクルード

#include "heap/radix_heap.hpp"

クラス

名前 説明
radix_heap 基数ヒープ(単調ヒープ)
実装
#pragma once
#include <array>
#include <bit>
#include <cassert>
#include <concepts>
#include <limits>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

/// @brief 基数ヒープ(単調ヒープ)
/// @details 順序基準 `Key`(符号なし整数)で最小値を取り出す。取り出した値より小さい
///          `Key` は以後 `push` できない(単調性)。Dijkstra など距離が単調増加する用途向け。
///          連続領域(`std::vector`)に値を並べる flat 実装で、`new`/`delete` を行わない。
/// @tparam Key 順序基準。符号なし整数で、最上位ビット位置によりバケットへ振り分ける。
/// @tparam Value 付随データ。`void` なら順序基準のみを格納し、`push(key)` /
///               `top() -> Key` の 1 引数 API になる。
/// @note `void` は内部で `std::monostate` に正規化し、本体を 1 つに保つ。
template <std::unsigned_integral Key = unsigned, class Value = void>
struct radix_heap {
  private:
    static constexpr bool has_value = !std::is_void_v<Value>;
    // void を保持できる型へ正規化する(メンバに持てるようにする)。
    using stored_value = std::conditional_t<has_value, Value, std::monostate>;

    // バケット数。最上位ビット位置(0..digits)に対応。
    static constexpr int bucket_count = std::numeric_limits<Key>::digits + 1;

    struct _node {
        Key key;
        [[no_unique_address]] stored_value value;
    };

  public:
    /// @brief キーの型
    /// @complexity 型別名であり実行時計算量はない
    using key_type = Key;

    /// @brief 付随値の型
    /// @complexity 型別名であり実行時計算量はない
    using value_type = Value;

    /// @brief 空の基数ヒープを構築する
    /// @complexity $O(1)$
    radix_heap() : buckets(), _last(), _size() {}

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

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

    /// @brief キーと付随値を追加する
    /// @complexity 償却 $O(\log C)$
    void push(Key key, stored_value value)
    requires has_value
    {
        push_node({key, std::move(value)});
    }
    /// @brief キーを追加する
    /// @complexity 償却 $O(\log C)$
    void push(Key key)
    requires(!has_value)
    {
        push_node({key, {}});
    }
    /// @brief キーと付随値を直接構築して追加する
    /// @complexity 償却 $O(\log C)$
    template <class... Args>
    void emplace(Key key, Args &&...args) {
        push_node({key, stored_value(std::forward<Args>(args)...)});
    }

    /// @brief 最小のキーと必要なら付随値を返す
    /// @complexity 償却 $O(\log C)$
    auto top() {
        if (buckets[0].empty()) relocate();
        const _node &n = buckets[0].back();
        if constexpr (has_value) return std::pair<Key, Value>{n.key, n.value};
        else return n.key;
    }

    /// @brief 最小のキーを持つ要素を削除する
    /// @complexity 償却 $O(\log C)$
    void pop() {
        assert(_size);
        if (buckets[0].empty()) relocate();
        buckets[0].pop_back();
        --_size;
    }

  private:
    std::array<std::vector<_node>, bucket_count> buckets;
    Key _last;
    int _size;

    void push_node(_node n) {
        assert(n.key >= _last);
        ++_size;
        buckets[find_bucket(n.key ^ _last)].push_back(std::move(n));
    }

    // key(= 実値 XOR _last)が属するバケット番号。0 は _last 自身。
    static int find_bucket(Key key) { return key == 0 ? 0 : std::numeric_limits<Key>::digits - std::countl_zero(key); }

    // 最小の非空バケットを走査して新しい _last を決め、要素を撒き直す。
    void relocate() {
        int index = 1;
        while (buckets[index].empty()) ++index;
        Key new_last = buckets[index][0].key;
        for (const _node &n : buckets[index]) new_last = std::min(new_last, n.key);
        for (_node &n : buckets[index]) buckets[find_bucket(n.key ^ new_last)].push_back(std::move(n));
        _last = new_last;
        buckets[index].clear();
    }
};

bundled

#line 2 "lib/heap/radix_heap.hpp"
#include <array>
#include <bit>
#include <cassert>
#include <concepts>
#include <limits>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

/// @brief 基数ヒープ(単調ヒープ)
/// @details 順序基準 `Key`(符号なし整数)で最小値を取り出す。取り出した値より小さい
///          `Key` は以後 `push` できない(単調性)。Dijkstra など距離が単調増加する用途向け。
///          連続領域(`std::vector`)に値を並べる flat 実装で、`new`/`delete` を行わない。
/// @tparam Key 順序基準。符号なし整数で、最上位ビット位置によりバケットへ振り分ける。
/// @tparam Value 付随データ。`void` なら順序基準のみを格納し、`push(key)` /
///               `top() -> Key` の 1 引数 API になる。
/// @note `void` は内部で `std::monostate` に正規化し、本体を 1 つに保つ。
template <std::unsigned_integral Key = unsigned, class Value = void>
struct radix_heap {
  private:
    static constexpr bool has_value = !std::is_void_v<Value>;
    // void を保持できる型へ正規化する(メンバに持てるようにする)。
    using stored_value = std::conditional_t<has_value, Value, std::monostate>;

    // バケット数。最上位ビット位置(0..digits)に対応。
    static constexpr int bucket_count = std::numeric_limits<Key>::digits + 1;

    struct _node {
        Key key;
        [[no_unique_address]] stored_value value;
    };

  public:
    /// @brief キーの型
    /// @complexity 型別名であり実行時計算量はない
    using key_type = Key;

    /// @brief 付随値の型
    /// @complexity 型別名であり実行時計算量はない
    using value_type = Value;

    /// @brief 空の基数ヒープを構築する
    /// @complexity $O(1)$
    radix_heap() : buckets(), _last(), _size() {}

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

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

    /// @brief キーと付随値を追加する
    /// @complexity 償却 $O(\log C)$
    void push(Key key, stored_value value)
    requires has_value
    {
        push_node({key, std::move(value)});
    }
    /// @brief キーを追加する
    /// @complexity 償却 $O(\log C)$
    void push(Key key)
    requires(!has_value)
    {
        push_node({key, {}});
    }
    /// @brief キーと付随値を直接構築して追加する
    /// @complexity 償却 $O(\log C)$
    template <class... Args>
    void emplace(Key key, Args &&...args) {
        push_node({key, stored_value(std::forward<Args>(args)...)});
    }

    /// @brief 最小のキーと必要なら付随値を返す
    /// @complexity 償却 $O(\log C)$
    auto top() {
        if (buckets[0].empty()) relocate();
        const _node &n = buckets[0].back();
        if constexpr (has_value) return std::pair<Key, Value>{n.key, n.value};
        else return n.key;
    }

    /// @brief 最小のキーを持つ要素を削除する
    /// @complexity 償却 $O(\log C)$
    void pop() {
        assert(_size);
        if (buckets[0].empty()) relocate();
        buckets[0].pop_back();
        --_size;
    }

  private:
    std::array<std::vector<_node>, bucket_count> buckets;
    Key _last;
    int _size;

    void push_node(_node n) {
        assert(n.key >= _last);
        ++_size;
        buckets[find_bucket(n.key ^ _last)].push_back(std::move(n));
    }

    // key(= 実値 XOR _last)が属するバケット番号。0 は _last 自身。
    static int find_bucket(Key key) { return key == 0 ? 0 : std::numeric_limits<Key>::digits - std::countl_zero(key); }

    // 最小の非空バケットを走査して新しい _last を決め、要素を撒き直す。
    void relocate() {
        int index = 1;
        while (buckets[index].empty()) ++index;
        Key new_last = buckets[index][0].key;
        for (const _node &n : buckets[index]) new_last = std::min(new_last, n.key);
        for (_node &n : buckets[index]) buckets[find_bucket(n.key ^ new_last)].push_back(std::move(n));
        _last = new_last;
        buckets[index].clear();
    }
};