Compare commits
2 Commits
8ed29753d5
...
65a9ba8fdb
| Author | SHA1 | Date | |
|---|---|---|---|
| 65a9ba8fdb | |||
| ee7490988b |
@@ -12,7 +12,7 @@
|
||||
|
||||
- [x] Binary Search Tree
|
||||
- [x] AVL Tree
|
||||
- [ ] Heap (min/max)
|
||||
- [x] Heap (min/max)
|
||||
- [ ] Trie
|
||||
|
||||
## Hash Based — key/value
|
||||
@@ -42,7 +42,7 @@ go doc -all ./algo | bat -l go
|
||||
```
|
||||
|
||||
```bash
|
||||
go doc -all ./trees/ | bat -l go && go doc -all ./trees/avl | bat -l go
|
||||
for p in ./trees/ ./trees/avl ./trees/heap; do go doc -all "$p"; done | bat -l go
|
||||
```
|
||||
|
||||
# Tests
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"datastructures/trees/heap"
|
||||
)
|
||||
|
||||
// isMinHeap reports whether arr satisfies the min-heap property: every parent
|
||||
// is <= its children.
|
||||
func isMinHeap(arr []int) bool {
|
||||
for i := range arr {
|
||||
left := 2*i + 1
|
||||
right := 2*i + 2
|
||||
if left < len(arr) && arr[left] < arr[i] {
|
||||
return false
|
||||
}
|
||||
if right < len(arr) && arr[right] < arr[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestPopMinEmpty(t *testing.T) {
|
||||
h := heap.NewMinHeap[int]()
|
||||
if v, ok := h.PopMin(); ok {
|
||||
t.Fatalf("PopMin on empty heap returned (%d, true), want (0, false)", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertMaintainsHeapProperty(t *testing.T) {
|
||||
h := heap.NewMinHeap[int]()
|
||||
for _, v := range []int{5, 3, 8, 1, 9, 2, 7, 0, 4, 6} {
|
||||
h.Insert(v)
|
||||
if !isMinHeap(h.Array) {
|
||||
t.Fatalf("heap property violated after inserting %d: %v", v, h.Array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertThenPopIsSorted(t *testing.T) {
|
||||
in := []int{5, 3, 8, 1, 9, 2, 7, 0, 4, 6}
|
||||
h := heap.NewMinHeap[int]()
|
||||
for _, v := range in {
|
||||
h.Insert(v)
|
||||
}
|
||||
|
||||
got := []int{}
|
||||
for {
|
||||
v, ok := h.PopMin()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if !isMinHeap(h.Array) {
|
||||
t.Fatalf("heap property violated after popping %d: %v", v, h.Array)
|
||||
}
|
||||
got = append(got, v)
|
||||
}
|
||||
|
||||
want := slices.Clone(in)
|
||||
slices.Sort(want)
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("popped order = %v, want ascending %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeekRootIsMinimum(t *testing.T) {
|
||||
h := heap.NewMinHeap[int]()
|
||||
for _, v := range []int{42, 17, 99, 3, 58} {
|
||||
h.Insert(v)
|
||||
if h.Array[0] != slices.Min(h.Array) {
|
||||
t.Fatalf("root = %d, want min %d (heap: %v)", h.Array[0], slices.Min(h.Array), h.Array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicates(t *testing.T) {
|
||||
in := []int{4, 4, 1, 1, 4, 1, 2, 2}
|
||||
h := heap.NewMinHeap[int]()
|
||||
for _, v := range in {
|
||||
h.Insert(v)
|
||||
}
|
||||
got := []int{}
|
||||
for {
|
||||
v, ok := h.PopMin()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
got = append(got, v)
|
||||
}
|
||||
want := slices.Clone(in)
|
||||
slices.Sort(want)
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("popped order = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeapify(t *testing.T) {
|
||||
in := []int{9, 4, 7, 1, 0, 3, 8, 2, 6, 5}
|
||||
h := heap.NewMinHeap[int]()
|
||||
if err := h.Heapify(in); err != nil {
|
||||
t.Fatalf("Heapify returned unexpected error: %v", err)
|
||||
}
|
||||
if !isMinHeap(h.Array) {
|
||||
t.Fatalf("Heapify did not produce a valid heap: %v", h.Array)
|
||||
}
|
||||
|
||||
// Heapify must clone: mutating the source afterwards must not affect the heap.
|
||||
in[0] = -100
|
||||
if slices.Contains(h.Array, -100) {
|
||||
t.Fatal("Heapify did not clone its input; heap was mutated by source change")
|
||||
}
|
||||
|
||||
got := []int{}
|
||||
for {
|
||||
v, ok := h.PopMin()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
got = append(got, v)
|
||||
}
|
||||
want := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("popped order = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeapifyOnNonEmptyErrors(t *testing.T) {
|
||||
h := heap.NewMinHeap[int]()
|
||||
h.Insert(1)
|
||||
if err := h.Heapify([]int{2, 3}); err == nil {
|
||||
t.Fatal("Heapify on non-empty heap returned nil error, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzAgainstSort(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for trial := 0; trial < 200; trial++ {
|
||||
n := rng.Intn(50)
|
||||
in := make([]int, n)
|
||||
for i := range in {
|
||||
in[i] = rng.Intn(100)
|
||||
}
|
||||
|
||||
h := heap.NewMinHeap[int]()
|
||||
for _, v := range in {
|
||||
h.Insert(v)
|
||||
}
|
||||
|
||||
got := []int{}
|
||||
for {
|
||||
v, ok := h.PopMin()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
got = append(got, v)
|
||||
}
|
||||
|
||||
want := slices.Clone(in)
|
||||
slices.Sort(want)
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("trial %d: got %v, want %v (input %v)", trial, got, want, in)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package heap
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// node is stored at index i:
|
||||
// left child is at index 2*i + 1.
|
||||
// right child is at index 2*i + 2.
|
||||
// parent index [(i-1)/2].
|
||||
|
||||
// MinHeap :: binary heap implementation, Insertion - O(log n).
|
||||
// Array[] is managed internally , treat it as read only
|
||||
type MinHeap[T cmp.Ordered] struct {
|
||||
Array []T
|
||||
}
|
||||
|
||||
// NewMinHeap() :: Creates a new min heap tree.
|
||||
func NewMinHeap[T cmp.Ordered]() *MinHeap[T] {
|
||||
return &MinHeap[T]{}
|
||||
}
|
||||
|
||||
// Insert() -> adds new value to heap.
|
||||
// O(logn)
|
||||
func (h *MinHeap[T]) Insert(val T) {
|
||||
h.Array = append(h.Array, val)
|
||||
|
||||
h.siftUp(len(h.Array) - 1)
|
||||
}
|
||||
|
||||
// siftUp() :: moves the smaller value node up, used when inserting.
|
||||
// O(logn)
|
||||
func (h *MinHeap[T]) siftUp(i int) {
|
||||
for i > 0 {
|
||||
parentIndex := (i - 1) / 2
|
||||
|
||||
if h.Array[i] < h.Array[parentIndex] {
|
||||
// swap places
|
||||
h.Array[i], h.Array[parentIndex] = h.Array[parentIndex], h.Array[i]
|
||||
}
|
||||
|
||||
i = parentIndex
|
||||
}
|
||||
}
|
||||
|
||||
// siftDown() :: receives an index to move its value down the tree.
|
||||
// O(logn)
|
||||
func (h *MinHeap[T]) siftDown(i int) {
|
||||
n := len(h.Array)
|
||||
|
||||
for {
|
||||
|
||||
smallest := i
|
||||
|
||||
left := 2*i + 1
|
||||
right := 2*i + 2
|
||||
|
||||
if left < n && h.Array[left] < h.Array[smallest] {
|
||||
smallest = left
|
||||
}
|
||||
if right < n && h.Array[right] < h.Array[smallest] {
|
||||
smallest = right
|
||||
}
|
||||
|
||||
if smallest == i {
|
||||
break
|
||||
}
|
||||
|
||||
// sink down
|
||||
h.Array[i], h.Array[smallest] = h.Array[smallest], h.Array[i]
|
||||
i = smallest
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// PopMin() -> returns the root
|
||||
func (h *MinHeap[T]) PopMin() (T, bool) {
|
||||
var zero T
|
||||
if len(h.Array) <= 0 {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
last := len(h.Array) - 1
|
||||
root := h.Array[0]
|
||||
|
||||
// move last value to the root
|
||||
h.Array[0] = h.Array[last]
|
||||
|
||||
// GC cleanup
|
||||
h.Array[last] = zero
|
||||
|
||||
h.Array = h.Array[:last]
|
||||
|
||||
h.siftDown(0)
|
||||
|
||||
return root, true
|
||||
}
|
||||
|
||||
// Heapify() -> makes arbitrary slice a heap from scratch . returns error if
|
||||
// the heap is not empty
|
||||
func (h *MinHeap[T]) Heapify(array []T) error {
|
||||
if len(h.Array) != 0 {
|
||||
return errors.New("Can only init Heapify on empty heap")
|
||||
}
|
||||
|
||||
h.Array = slices.Clone(array)
|
||||
|
||||
for i := len(h.Array)/2 - 1; i >= 0; i-- {
|
||||
h.siftDown(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user