Compare commits

...

2 Commits

Author SHA1 Message Date
Acid 65a9ba8fdb minHeap added
Go / build (push) Successful in 29s
2026-07-13 22:40:32 -04:00
Acid ee7490988b new file: trees/heap/minHeap.go 2026-07-13 22:28:43 -04:00
3 changed files with 284 additions and 2 deletions
+2 -2
View File
@@ -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
+168
View File
@@ -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)
}
}
}
+114
View File
@@ -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
}