Documentation
¶
Overview ¶
Package deheap provides a doubly-ended heap (min-max heap).
A min-max heap gives O(log n) access to both the smallest AND largest element in a collection — something a standard binary heap cannot do.
How a min-max heap works ¶
Like a binary heap, a min-max heap is a complete binary tree stored in a flat slice. The difference is that tree levels alternate between "min levels" and "max levels":
Level 0 (min): 1 ← guaranteed minimum
/ \
Level 1 (max): 9 5 ← maximum is among these
/ | \ / \
Level 2 (min): 2 3 4 3 2 ← each ≤ all descendants
/|
Level 3 (max): 6 8 ← each ≥ all descendants
The invariant: every node on a min level is ≤ all of its descendants, and every node on a max level is ≥ all of its descendants.
This means:
- The root (index 0) is always the global minimum.
- The global maximum is one of the root's children (index 1 or 2).
- Both Peek and PeekMax are O(1).
The tree is stored as a flat slice in level-order, exactly like a standard binary heap:
Index: 0 1 2 3 4 5 6 7 8 9 Value: [ 1, 9, 5, 2, 3, 4, 3, 2, 6, 8 ] Level: 0 1 1 2 2 2 2 3 3 3 Kind: min max min max
Worked example: Push(0) into [1, 9, 5, 4, 6, 8, 7, 3, 2] ¶
We append 0 at index 9, then bubble up:
Step 0 — append to end:
1
/ \
9 5
/ | \ / \
4 6 8 7 3
/|
2 0 ← new element at index 9 (max level)
Step 1 — index 9 is on a max level, but 0 < parent 6 (index 4).
0 is smaller than its parent, so it belongs on a min level.
Swap with parent:
1
/ \
9 5
/ | \ / \
4 0 8 7 3
/|
2 6
Step 2 — now at index 4 (max level), but we switched to min-level
bubbling. Compare with grandparent (index 0): 0 < 1.
Swap with grandparent:
0 ← new minimum
/ \
9 5
/ | \ / \
4 1 8 7 3
/|
2 6
Done. The heap property is restored. The new minimum 0 is at the root.
Worked example: Pop() from [1, 9, 5, 4, 6, 3, 2] ¶
Step 0 — swap root (index 0) with last element (index 6), then
remove the last element (the old root, value 1):
Before: After swap & remove:
1 2
/ \ / \
9 5 9 5
/|\ / /|\
4 6 3 2 4 6 3
Step 1 — bubble down from index 0 (min level). Find the smallest
among children {9,5} and grandchildren {4,6,3}. Smallest
is 3 at index 5 (a grandchild).
Swap index 0 with index 5:
3
/ \
9 5
/|\
4 6 2 ← but wait, 2 < parent 5, so swap with parent
Step 2 — after grandchild swap, check if the moved element (2)
violates the max-level parent. 2 < 5, so swap 2 and 5:
3
/ \
9 2
/|\
4 6 5
Done. Returned 1 (the old minimum). New minimum is 3.
Example (IntHeap) ¶
This example inserts several ints into an IntHeap, checks the minimum, and removes them in order of priority.
package main
import (
"fmt"
"github.com/aalpar/deheap"
)
// An IntHeap is a min-heap of ints.
type IntDeheap []int
func (h IntDeheap) Len() int { return len(h) }
func (h IntDeheap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntDeheap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntDeheap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntDeheap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func main() {
h := &IntDeheap{2, 1, 5, 6}
deheap.Init(h)
deheap.Push(h, 3)
fmt.Printf("minimum: %d\n", (*h)[0])
for h.Len() > 3 {
fmt.Printf("%d ", deheap.PopMax(h))
}
for h.Len() > 1 {
fmt.Printf("%d ", deheap.Pop(h))
}
fmt.Printf("middle value: %d\n", (*h)[0])
}
Output: minimum: 1 6 5 1 2 middle value: 3
Example (Ordered) ¶
package main
import (
"fmt"
"github.com/aalpar/deheap"
)
func main() {
h := deheap.From(2, 1, 5, 6)
h.Push(3)
fmt.Printf("minimum: %d\n", h.Peek())
fmt.Printf("maximum: %d\n", h.PeekMax())
for h.Len() > 3 {
fmt.Printf("%d ", h.PopMax())
}
for h.Len() > 1 {
fmt.Printf("%d ", h.Pop())
}
fmt.Printf("middle value: %d\n", h.Peek())
}
Output: minimum: 1 maximum: 6 6 5 1 2 middle value: 3
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Fix ¶ added in v1.1.1
Fix re-establishes the heap ordering after the element at index i has changed its value. Equivalent to, but cheaper than, Remove(h, i) followed by Push of the new value.
The index i must be in the range [0, h.Len()). It panics if i is out of bounds.
The complexity is O(log n) where n = h.Len().
func Init ¶
Init establishes the min-max heap ordering on an arbitrary slice. Call this once on a non-empty slice before calling Pop, PopMax, or Push.
If the data already satisfies the heap property, Init returns after a linear scan with no modifications. Otherwise it uses Floyd's bottom-up heap construction, processing nodes from the last non-leaf down to the root. Most work happens near the leaves where subtrees are small and memory accesses are local.
Time complexity is O(n), where n = h.Len().
func Pop ¶
Pop removes and returns the smallest element from the heap. Returns nil if the heap is empty.
The root (index 0) is always the minimum. To remove it, swap it with the last element, pop the last element off the slice, and bubbledown from the root to restore the heap property.
Time complexity is O(log n), where n = h.Len().
func PopMax ¶
PopMax removes and returns the largest element from the heap. Returns nil if the heap is empty.
The maximum lives at index 1 or 2 (the root's children, both on max level 1). We find which child is larger, swap it with the last element, pop the last off the slice, and bubbledown from the vacated child position using max-level ordering.
min: 1
/ \
max: [9] 5 ← max is at index 1; swap it out
Time complexity is O(log n), where n = h.Len().
func Push ¶
Push adds element o to the heap, maintaining the min-max heap property.
The element is appended to the end of the slice (the next open slot in the complete binary tree), then bubbled up to its correct position.
Time complexity is O(log n), where n = h.Len().
func Remove ¶
Remove removes and returns the element at index i from the heap.
The element at i is swapped with the last element, the last element is popped off the slice, and then the replacement is sifted into place. Because the replacement can come from anywhere in the tree, both bubbledown AND bubbleup are needed to restore the invariant.
The complexity is O(log n) where n = h.Len().
func Verify ¶ added in v1.1.1
Verify reports whether h satisfies the min-max heap property.
Time complexity is O(n), where n = h.Len().
Example ¶
This example demonstrates using Verify to check heap validity.
package main
import (
"fmt"
"github.com/aalpar/deheap"
)
// An IntHeap is a min-heap of ints.
type IntDeheap []int
func (h IntDeheap) Len() int { return len(h) }
func (h IntDeheap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntDeheap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntDeheap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntDeheap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func main() {
h := &IntDeheap{2, 1, 5, 6}
deheap.Init(h)
fmt.Println(deheap.Verify(h))
// Corrupt the heap by placing a small value at a max-level position.
(*h)[1] = -1
fmt.Println(deheap.Verify(h))
}
Output: true false
Types ¶
type Deheap ¶
Deheap is a type-safe doubly-ended heap for cmp.Ordered types.
Unlike the v1 interface-based API, this implementation operates directly on the underlying []T slice with native < comparisons, avoiding interface dispatch, adapter allocation, and boxing overhead.
Usage:
h := deheap.From(5, 3, 8, 1, 9) h.Peek() // 1 — O(1) minimum h.PeekMax() // 9 — O(1) maximum h.Pop() // 1 — O(log n) remove minimum h.PopMax() // 9 — O(log n) remove maximum
func From ¶
From constructs a Deheap from the given elements and initializes the heap ordering using Floyd's bottom-up heap construction.
If the input already satisfies the heap property, From returns after a linear scan with no modifications.
func (*Deheap[T]) Fix ¶ added in v1.1.1
Fix re-establishes the heap ordering after the element at index i has changed its value. Equivalent to, but cheaper than, Remove(i) followed by Push of the new value.
The index i must be in the range [0, p.Len()). It panics if i is out of bounds.
The complexity is O(log n) where n = p.Len().
func (*Deheap[T]) Peek ¶
func (p *Deheap[T]) Peek() T
Peek returns the smallest element without removing it. It panics if the heap is empty.
func (*Deheap[T]) PeekMax ¶
func (p *Deheap[T]) PeekMax() T
PeekMax returns the largest element without removing it. It panics if the heap is empty.
In a min-max heap the maximum is always one of the root's children (index 1 or 2), since they sit on the first max level. With only one element the root is both min and max; with two elements the sole child at index 1 is the max.
1 ← min (root) / \ [9] 5 ← max is the larger child
func (*Deheap[T]) Pop ¶
func (p *Deheap[T]) Pop() T
Pop removes and returns the smallest element from the heap. Returns the zero value of T if the heap is empty. Time complexity is O(log n), where n = h.Len().
func (*Deheap[T]) PopMax ¶
func (p *Deheap[T]) PopMax() T
PopMax removes and returns the largest element from the heap. Returns the zero value of T if the heap is empty. Time complexity is O(log n), where n = h.Len().
func (*Deheap[T]) Push ¶
func (p *Deheap[T]) Push(o T)
Push adds an element to the heap. Time complexity is O(log n), where n = h.Len().
func (*Deheap[T]) Remove ¶
Remove removes and returns the element at index i. It panics if i is out of bounds. Time complexity is O(log n), where n = h.Len().
func (*Deheap[T]) Verify ¶ added in v1.1.1
Verify reports whether the heap satisfies the min-max heap property.
Time complexity is O(n), where n = p.Len().
Example ¶
package main
import (
"fmt"
"github.com/aalpar/deheap"
)
func main() {
h := deheap.From(2, 1, 5, 6)
fmt.Println(h.Verify())
h.Push(3)
fmt.Println(h.Verify())
}
Output: true true