Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verify method to Heap interfaces #234

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions heap/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,45 @@ func NewBinary[K, V any](size int, cmpKey CompareFunc[K], eqVal EqualFunc[V]) He
}
}

// nolint: unused
// This method verifies the integrity of a binary heap.
func (h *binary[K, V]) verify() bool {
if h.heap[0] != nil {
return false
}

// Verify the heap is a complete tree.
for i := 1; i <= h.n; i++ {
if h.heap[i] == nil {
return false
}
}

// Verify the deleted items are dereferenced.
for i := h.n + 1; i < len(h.heap); i++ {
if h.heap[i] != nil {
return false
}
}

// Verify the heap property (heap order).
for k := 1; k <= h.n; k++ {
if l := 2 * k; l <= h.n {
if h.cmpKey(h.heap[k].Key, h.heap[l].Key) > 0 {
return false
}
}

if r := 2*k + 1; r <= h.n {
if h.cmpKey(h.heap[k].Key, h.heap[r].Key) > 0 {
return false
}
}
}

return true
}

func (h *binary[K, V]) resize(size int) {
newH := make([]*KeyValue[K, V], size)
copy(newH, h.heap)
Expand Down
51 changes: 50 additions & 1 deletion heap/binomial.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,55 @@ func NewBinomial[K, V any](cmpKey CompareFunc[K], eqVal EqualFunc[V]) MergeableH
}
}

// nolint: unused
// This method verifies the integrity of a binomial heap.
func (h *binomial[K, V]) verify() bool {
if h.head == nil {
return true
}

// Verify the structural property:
// Ensure the root list is sorted in monotonically increasing order of binomial tree orders.
for curr, next := h.head, h.head.sibling; next != nil; curr, next = next, next.sibling {
if curr.order >= next.order {
return false
}
}

// Verify the properties of each binomial tree in the root list.
for curr := h.head; curr != nil; curr = curr.sibling {
if !h.verifyBinomialTree(curr) {
return false
}
}

return true
}

// nolint: unused
// verifyBinomialTree verifies the properties of a binomial tree rooted at the given node.
func (h *binomial[K, V]) verifyBinomialTree(n *binomialNode[K, V]) bool {
for i, curr := 1, n.child; curr != nil; i, curr = i+1, curr.sibling {
// In a min-heap, each node's key must be smaller than or equal to its children's keys.
// In a max-heap, each node's key must be greater than or equal to its children's keys.
if h.cmpKey(n.key, curr.key) > 0 {
return false
}

// A binomial node of order k has children with orders k-1, k-2, ..., 0 from left to right.
if curr.order != n.order-i {
return false
}

// Recursively, verify each binomial tree root at the current child.
if !h.verifyBinomialTree(curr) {
return false
}
}

return true
}

// findExtremum traverses the sibling linked list starting from the given node n
// and finds the extremum (minimum in min heap or maximum in max heap) node.
//
Expand Down Expand Up @@ -172,7 +221,7 @@ func (_ *binomial[K, V]) link(child, parent *binomialNode[K, V]) {

// merge performs a merge sort on two root lists of binomial trees.
//
// The resulting root list is sorted in monotonically increasing order of binomial tree orders,
// The resulting root list is sorted in monotonically non-decreasing order of binomial tree orders,
// and it may contain up to two binomial trees of the same order.
// These trees are combined later during the consolidate operation, which follows this merge operation.
//
Expand Down
4 changes: 4 additions & 0 deletions heap/heap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package heap

// Heap represents a heap (priority queue) abstract data type.
type Heap[K, V any] interface {
verify() bool

Size() int
IsEmpty() bool
Insert(K, V)
Expand All @@ -18,6 +20,8 @@ type Heap[K, V any] interface {

// IndexedHeap represents an indexed heap (priority queue) abstract data type.
type IndexedHeap[K, V any] interface {
verify() bool

Size() int
IsEmpty() bool
Insert(int, K, V) bool
Expand Down
24 changes: 24 additions & 0 deletions heap/heap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,8 @@ func getMergeableHeapTests() []mergeableHeapTest[int, string] {
func runHeapTest(t *testing.T, heap Heap[int, string], test heapTest[int, string]) {
t.Run(test.name, func(t *testing.T) {
t.Run("Before", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsKey(0))
Expand All @@ -840,6 +842,7 @@ func runHeapTest(t *testing.T, heap Heap[int, string], test heapTest[int, string
t.Run("Insert", func(t *testing.T) {
for _, kv := range test.inserts {
heap.Insert(kv.Key, kv.Val)
assert.True(t, heap.verify())
}
})

Expand Down Expand Up @@ -881,6 +884,7 @@ func runHeapTest(t *testing.T, heap Heap[int, string], test heapTest[int, string
assert.Equal(t, kv.Key, deleteKey)
assert.Equal(t, kv.Val, deleteVal)
assert.True(t, deleteOK)
assert.True(t, heap.verify())
}
})

Expand All @@ -890,9 +894,12 @@ func runHeapTest(t *testing.T, heap Heap[int, string], test heapTest[int, string
}

heap.DeleteAll()
assert.True(t, heap.verify())
})

t.Run("After", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsKey(0))
Expand All @@ -914,6 +921,8 @@ func runHeapTest(t *testing.T, heap Heap[int, string], test heapTest[int, string
func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexedHeapTest[int, string]) {
t.Run(test.name, func(t *testing.T) {
t.Run("Before", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsIndex(0))
Expand Down Expand Up @@ -948,6 +957,7 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
for _, kv := range test.inserts {
ok := heap.Insert(kv.index, kv.key, kv.val)
assert.True(t, ok)
assert.True(t, heap.verify())
}

// Try inserting an index already on the heap.
Expand All @@ -962,6 +972,7 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
for _, kv := range test.changeKeys {
ok := heap.ChangeKey(kv.index, kv.key)
assert.True(t, ok)
assert.True(t, heap.verify())
}

// Try changing the key for an index not on the heap.
Expand Down Expand Up @@ -1020,6 +1031,7 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
assert.Equal(t, kv.key, deleteKey)
assert.Equal(t, kv.val, deleteVal)
assert.True(t, deleteOK)
assert.True(t, heap.verify())
}
})

Expand All @@ -1029,6 +1041,7 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
assert.Equal(t, kv.key, deleteKey)
assert.Equal(t, kv.val, deleteVal)
assert.True(t, deleteOK)
assert.True(t, heap.verify())
}
})

Expand All @@ -1038,9 +1051,12 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
}

heap.DeleteAll()
assert.True(t, heap.verify())
})

t.Run("After", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsKey(0))
Expand Down Expand Up @@ -1075,6 +1091,8 @@ func runIndexedHeapTest(t *testing.T, heap IndexedHeap[int, string], test indexe
func runMergeableHeapTest(t *testing.T, heap MergeableHeap[int, string], test mergeableHeapTest[int, string]) {
t.Run(test.name, func(t *testing.T) {
t.Run("Before", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsKey(0))
Expand All @@ -1094,11 +1112,13 @@ func runMergeableHeapTest(t *testing.T, heap MergeableHeap[int, string], test me
t.Run("Insert", func(t *testing.T) {
for _, kv := range test.inserts {
heap.Insert(kv.Key, kv.Val)
assert.True(t, heap.verify())
}
})

t.Run("Merge", func(t *testing.T) {
heap.Merge(test.merge)
assert.True(t, heap.verify())
})

t.Run("Size", func(t *testing.T) {
Expand Down Expand Up @@ -1139,6 +1159,7 @@ func runMergeableHeapTest(t *testing.T, heap MergeableHeap[int, string], test me
assert.Equal(t, kv.Key, deleteKey)
assert.Equal(t, kv.Val, deleteVal)
assert.True(t, deleteOK)
assert.True(t, heap.verify())
}
})

Expand All @@ -1148,9 +1169,12 @@ func runMergeableHeapTest(t *testing.T, heap MergeableHeap[int, string], test me
}

heap.DeleteAll()
assert.True(t, heap.verify())
})

t.Run("After", func(t *testing.T) {
assert.True(t, heap.verify())

assert.Zero(t, heap.Size())
assert.True(t, heap.IsEmpty())
assert.False(t, heap.ContainsKey(0))
Expand Down
28 changes: 28 additions & 0 deletions heap/indexed_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ func NewIndexedBinary[K, V any](cap int, cmpKey CompareFunc[K], eqVal EqualFunc[
}
}

// nolint: unused
// This method verifies the integrity of an indexed binary heap.
func (h *indexedBinary[K, V]) verify() bool {
// Verify the heap is a complete tree.
for i := 1; i <= h.n; i++ {
if j := h.heap[i]; h.pos[j] == -1 || h.kvs[j] == nil {
return false
}
}

// Verify the heap property (heap order).
for k := 1; k <= h.n; k++ {
if l := 2 * k; l <= h.n {
if h.compare(k, l) > 0 {
return false
}
}

if r := 2*k + 1; r <= h.n {
if h.compare(k, r) > 0 {
return false
}
}
}

return true
}

// compare compares two keys on the heap by their positions.
func (h *indexedBinary[K, V]) compare(a, b int) int {
i, j := h.heap[a], h.heap[b]
Expand Down
58 changes: 56 additions & 2 deletions heap/indexed_binomial.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,60 @@ func NewIndexedBinomial[K, V any](cap int, cmpKey CompareFunc[K], eqVal EqualFun
}
}

// nolint: unused
// This method verifies the integrity of an indexed binomial heap.
func (h *indexedBinomial[K, V]) verify() bool {
if h.head == nil {
return true
}

// Verify the structural property:
// Ensure the root list is sorted in monotonically increasing order of binomial tree orders.
for curr, next := h.head, h.head.sibling; next != nil; curr, next = next, next.sibling {
if curr.order >= next.order {
return false
}
}

// Verify the properties of each binomial tree in the root list.
for curr := h.head; curr != nil; curr = curr.sibling {
if !h.verifyBinomialTree(curr) {
return false
}
}

return true
}

// nolint: unused
// verifyBinomialTree verifies the properties of a binomial tree rooted at the given node.
func (h *indexedBinomial[K, V]) verifyBinomialTree(n *indexedBinomialNode[K, V]) bool {
// Verifry the index map for the current node.
if h.nodes[n.index] != n {
return false
}

for i, curr := 1, n.child; curr != nil; i, curr = i+1, curr.sibling {
// In a min-heap, each node's key must be smaller than or equal to its children's keys.
// In a max-heap, each node's key must be greater than or equal to its children's keys.
if h.cmpKey(n.key, curr.key) > 0 {
return false
}

// A binomial node of order k has children with orders k-1, k-2, ..., 0 from left to right.
if curr.order != n.order-i {
return false
}

// Recursively, verify each binomial tree root at the current child.
if !h.verifyBinomialTree(curr) {
return false
}
}

return true
}

// swap exchanges the indices, keys, and values between the given child and parent nodes.
// It also updates the index map to reflect the changes.
// This operation preserves the structural relationships within the heap.
Expand Down Expand Up @@ -133,7 +187,7 @@ func (_ *indexedBinomial[K, V]) link(child, parent *indexedBinomialNode[K, V]) {

// merge performs a merge sort on two root lists of binomial trees.
//
// The resulting root list is sorted in monotonically increasing order of binomial tree orders,
// The resulting root list is sorted in monotonically non-decreasing order of binomial tree orders,
// and it may contain up to two binomial trees of the same order.
// These trees are combined later during the consolidate operation, which follows this merge operation.
//
Expand Down Expand Up @@ -453,7 +507,7 @@ func (h *indexedBinomial[K, V]) DOT() string {

if n.parent != nil {
parent := fmt.Sprintf("%d", n.parent.index)
graph.AddEdge(dot.NewEdge(name, parent, dot.EdgeTypeDirected, "", "", "turquoise", dot.StyleDashed, "", ""))
graph.AddEdge(dot.NewEdge(name, parent, dot.EdgeTypeDirected, "", "", dot.ColorTurquoise, dot.StyleDashed, "", ""))
}

if n.child != nil {
Expand Down
Loading