Golangで標準ライブラリを利用した最小・最大ヒープの実装

Golangで標準ライブラリを利用した最小・最大ヒープの実装

2025年5月29日

image

コーディングテストや、特定の要件に合ったアルゴリズムを実装する際に、整列された形のデータが必要になります。

例えば、ダイジェストラアルゴリズムを実装する際には、現在のノードからアクセス可能なノードの中から最も距離の短いノードを選択する必要があります。この時、優先度キューを使用することで、毎回整列するよりもはるかに効率的に実装できます。

この時、短いノードを保持するためのデータ構造として最小ヒープ(Minimum Heap)を利用すれば、抽象データ型である優先度キューを簡単に実装できます。

Goではcontainer/heapパッケージを利用して最小ヒープを実装できます。

containerパッケージはGo言語の標準ライブラリで、Circular listを実装するためのcontainer/ring、リストを実装するためのcontainer/list、ヒープを実装するためのcontainer/heapなどがあります。

Go言語でのヒープ実装

Go言語でヒープを実装するには、container/heapパッケージを使用する必要があります。このパッケージは最小ヒープと最大ヒープの両方をサポートしており、ヒープを実装するためにはいくつかのインターフェースを実装する必要があります。

インターフェースの実装

ヒープを実装するには、heap.Interfaceインターフェースを実装しなければなりません。このインターフェースには、以下のメソッドが含まれています。

package heap

// 便利上すべて展開して書いていますが、実際のインターフェースはsort.Interfaceを埋め込んでいます。
type Interface interface {
    Len() int           // ヒープの長さを返す
    Less(i, j int) bool // i番目の要素がj番目の要素より小さいとtrueを返す
    Swap(i, j int)      // i番目の要素とj番目の要素を交換
    Push(x any)         // ヒープに新しい要素を追加
    Pop() any           // ヒープから最小の要素を削除して返す
}

他の言語、例えばPythonでは単にheapqを使えば良いですが、Goでは実装から始める必要があるため、面倒な面があります。

では、実際に実装してみましょう。

筆者は整列されていることがわかりやすいようにint型のスライスを用いて実装してみます。

package main

import (
	"container/heap"
	"fmt"
)

// IntHeap - int型のスライスをヒープとして実装した構造体。
type IntHeap []int

// Len - ヒープの長さを返します。
func (h *IntHeap) Len() int {
	return len(*h)
}

// Less - i番目の要素がj番目の要素より小さい場合はtrueを返します。
// NOTE: ソートの基準を変更したい場合、こちらのメソッドを使用できます。
// 特定の構造体ヒープであれば、構造体の特定のフィールド:ex) `(*h)[i].Field < (*h)[j].Field`のように記述します。
func (h *IntHeap) Less(i, j int) bool {
	return (*h)[i] < (*h)[j]
}

// Swap - i番目の要素とj番目の要素を交換します。実際のヒープではUpheapとDownheapを実装するために使用されます。
func (h *IntHeap) Swap(i, j int) {
	(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}

// Push - ヒープに新しい要素を追加します。
func (h *IntHeap) Push(x any) {
	*h = append(*h, x.(int))
}

// Pop - ヒープから最小の要素を削除して返します。
func (h *IntHeap) Pop() any {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}

func main() {
	// IntHeapを初期化します。
	h := &IntHeap{2, 1, 5, 3, 4}
	heap.Init(h) // ヒープを初期化します。

	// ヒープに新しい要素を追加します。
	head.Push(h, 0)

	for h.Len() > 0 {
		fmt.Println(heap.Pop(h)) // ヒープから最小の要素を削除して出力します。
	}
}

上記のコードを実行すると、以下のような結果が出力されます。見ての通り、整列がうまくいって出力されていることがわかります。

0
1
2
3
4
5

参考