// A generic in-place max heap on a slice for any type. package heap /* Copyright 2022 Dale Weiler . Made available under Odin's license. List of contributors: Dale Weiler: Initial implementation */ /* Constructs a max heap in slice given by data with comparator. A max heap is a range of elements which has the following properties: 1. With N = len(data), for all 0 < i < N, data[(i - 1) / 2] does not compare less than data[i]. 2. A new element can be added using push in O(log n) time. 3. The first element can be removed using pop in O(log n) time. The comparator compares elements of type T and can be used to construct a max heap (less than) or min heap (greater than) for T. */ make :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) if length <= 1 { return } // start from data parent, no need to consider children for start := (length - 2) / 2; start >= 0; start -= 1 { sift_down(data, less, start) } } /* Inserts the element at the position len(data)-1 into the max heap with comparator. At most log(N) comparisons where N = len(data) will be performed. */ push :: proc(data: []$T, less: proc(a, b: T) -> bool) { sift_up(data, less) } /* Swaps the value in position data[0] and the value in data[len(data)-1] and makes subrange [0, len(data)-1) into a heap. This has the effect of removing the first element from the heap. At most 2 * log(N) comparisons where N = len(data) will be performed. */ pop :: proc(data: []$T, less: proc(a, b: T) -> bool) { length := len(data) if length <= 1 { return } last := length // create a hole at 0 top := data[0] hole := floyd_sift_down(data, less) last -= 1 if hole == last { data[hole] = top } else { data[hole] = data[last] hole += 1 data[last] = top sift_up(data[:hole], less) } } /* Converts the max heap into a sorted range in ascending order. The resulting slice will no longer be a heap after this. At most 2 * N * log(N) comparisons where N = len(data) will be performed. */ sort :: proc(data: []$T, less: proc(a, b: T) -> bool) { for n := len(data); n >= 1; n -= 1 { pop(data[:n], less) } } /* Examines the slice and finds the largest range which is a max-heap. Elements are compared with user-supplied comparison procedure. This returns the upper bound of the largest range in the slice which is a max heap. That is, the last index for which data is a max heap. At most O(n) comparisons where N = len(data) will be performed. */ is_heap_until :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int { length := len(data) a := 0 b := 1 for b < length { if less(data[a], data[b]) { return b } b += 1 if b == length || less(data[a], data[b]) { return b } a += 1 b = 2 * a + 1 } return length } /* Checks if a given slice is a max heap. At most O(n) comparisons where N = len(data) will be performed. */ is_heap :: #force_inline proc(data: []$T, less: proc(a, b: T) -> bool) -> bool { return is_heap_until(data, less) == len(data) } @(private="file") floyd_sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int { length := len(data) assert(length >= 2) hole := 0 child := 0 index := 0 for { index += child + 1 child = 2 * child + 1 if child + 1 < length && less(data[index], data[index + 1]) { child += 1 index += 1 } data[hole] = data[index] hole = index if child > (length - 2) / 2 { return hole } } unreachable() } @(private="file") sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool, start: int) { start := start child := start // amoritize length lookup length := len(data) // left child of start is at 2 * start + 1 // right child of start is at 2 * start + 2 if length < 2 || (length - 2) / 2 < child { return } child = 2 * child + 1 if child + 1 < length && less(data[child], data[child + 1]) { // right child exists and is greater than left child child += 1 } // check if in heap order if less(data[child], data[start]) { // start is larger than its largest child return } top := data[start] for { // not in heap order, swap parent with its largest child data[start] = data[child] start = child if (length - 2) / 2 < child { break } // recompute child based off updated parent child = 2 * child + 1 if child + 1 < length && less(data[child], data[child + 1]) { // right child exists and is greater than left child child += 1 } // check if we are in heap order if less(data[child], top) { break } } data[start] = top } @(private="file") sift_up :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) if length <= 1 { return } last := length length = (length - 2) / 2 index := length last -= 1 if less(data[index], data[last]) { top := data[last] for { data[last] = data[index] last = index if length == 0 { break } length = (length - 1) / 2 index = length if !less(data[index], top) { break } } data[last] = top } }