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           // 힙에서 가장 작은 요소를 제거하고 반환
}

다른 언어, 예를 들어 파이썬에서는 단순히 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) // 힙을 초기화한다.

	// 힙에 새로운 요소를 추가한다.
	heap.Push(h, 0)

	for h.Len() > 0 {
		fmt.Println(heap.Pop(h)) // 힙에서 가장 작은 요소를 제거하고 출력한다.
	}
}

위 코드를 실행하면 다음과 같은 결과가 출력된다. 보다시피 정렬이 잘 되어 출력되는 것을 알 수 있다.

0
1
2
3
4
5

Reference