heap.odin 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. Copyright 2022 Dale Weiler <[email protected]>.
  3. Made available under Odin's BSD-3 license.
  4. List of contributors:
  5. Dale Weiler: Initial implementation
  6. */
  7. // Package implements a generic max heap in-place on a slice for any type.
  8. package heap
  9. /*
  10. Constructs a max heap in slice given by data with comparator. A max heap is
  11. a range of elements which has the following properties:
  12. 1. With N = len(data), for all 0 < i < N, data[(i - 1) / 2] does not compare
  13. less than data[i].
  14. 2. A new element can be added using push in O(log n) time.
  15. 3. The first element can be removed using pop in O(log n) time.
  16. The comparator compares elements of type T and can be used to construct a
  17. max heap (less than) or min heap (greater than) for T.
  18. */
  19. make :: proc(data: []$T, less: proc(a, b: T) -> bool) {
  20. // amoritize length lookup
  21. length := len(data)
  22. if length <= 1 do return
  23. // start from data parent, no need to consider children
  24. for start := (length - 2) / 2; start >= 0; start -= 1 {
  25. sift_down(data, less, start)
  26. }
  27. }
  28. /*
  29. Inserts the element at the position len(data)-1 into the max heap with
  30. comparator.
  31. At most log(N) comparisons where N = len(data) will be performed.
  32. */
  33. push :: proc(data: []$T, less: proc(a, b: T) -> bool) {
  34. sift_up(data, less)
  35. }
  36. /*
  37. Swaps the value in position data[0] and the value in data[len(data)-1] and
  38. makes subrange [0, len(data)-1) into a heap. This has the effect of removing
  39. the first element from the heap.
  40. At most 2 * log(N) comparisons where N = len(data) will be performed.
  41. */
  42. pop :: proc(data: []$T, less: proc(a, b: T) -> bool) {
  43. length := len(data)
  44. if length <= 1 do return
  45. last := length
  46. // create a hole at 0
  47. top := data[0]
  48. hole := floyd_sift_down(data, less)
  49. last -= 1
  50. if hole == last {
  51. data[hole] = top
  52. } else {
  53. data[hole] = data[last]
  54. hole += 1
  55. data[last] = top
  56. sift_up(data[:hole], less)
  57. }
  58. }
  59. /*
  60. Converts the max heap into a sorted range in ascending order. The resulting
  61. slice will no longer be a heap after this.
  62. At most 2 * N * log(N) comparisons where N = len(data) will be performed.
  63. */
  64. sort :: proc(data: []$T, less: proc(a, b: T) -> bool) {
  65. for n := len(data); n >= 1; n -= 1 {
  66. pop(data[:n], less)
  67. }
  68. }
  69. /*
  70. Examines the slice and finds the largest range which is a max-heap. Elements
  71. are compared with user-supplied comparison procedure.
  72. This returns the upper bound of the largest range in the slice which is a
  73. max heap. That is, the last index for which data is a max heap.
  74. At most O(n) comparisons where N = len(data) will be performed.
  75. */
  76. is_heap_until :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int {
  77. length := len(data)
  78. a := 0
  79. b := 1
  80. for b < length {
  81. if less(data[a], data[b]) {
  82. return b
  83. }
  84. b += 1
  85. if b == length || less(data[a], data[b]) {
  86. return b
  87. }
  88. a += 1
  89. b = 2 * a + 1
  90. }
  91. return length
  92. }
  93. /*
  94. Checks if a given slice is a max heap.
  95. At most O(n) comparisons where N = len(data) will be performed.
  96. */
  97. is_heap :: #force_inline proc(data: []$T, less: proc(a, b: T) -> bool) -> bool {
  98. return is_heap_until(data, less) == len(data)
  99. }
  100. @(private="file")
  101. floyd_sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool) -> int {
  102. length := len(data)
  103. assert(length >= 2)
  104. hole := 0
  105. child := 0
  106. index := 0
  107. for {
  108. index += child + 1
  109. child = 2 * child + 1
  110. if child + 1 < length && less(data[index], data[index + 1]) {
  111. child += 1
  112. index += 1
  113. }
  114. data[hole] = data[index]
  115. hole = index
  116. if child > (length - 2) / 2 {
  117. return hole
  118. }
  119. }
  120. unreachable()
  121. }
  122. @(private="file")
  123. sift_down :: proc(data: []$T, less: proc(a, b: T) -> bool, start: int) {
  124. start := start
  125. child := start
  126. // amoritize length lookup
  127. length := len(data)
  128. // left child of start is at 2 * start + 1
  129. // right child of start is at 2 * start + 2
  130. if length < 2 || (length - 2) / 2 < child {
  131. return
  132. }
  133. child = 2 * child + 1
  134. if child + 1 < length && less(data[child], data[child + 1]) {
  135. // right child exists and is greater than left child
  136. child += 1
  137. }
  138. // check if in heap order
  139. if less(data[child], data[start]) {
  140. // start is larger than its largest child
  141. return
  142. }
  143. top := data[start]
  144. for {
  145. // not in heap order, swap parent with its largest child
  146. data[start] = data[child]
  147. start = child
  148. if (length - 2) / 2 < child {
  149. break
  150. }
  151. // recompute child based off updated parent
  152. child = 2 * child + 1
  153. if child + 1 < length && less(data[child], data[child + 1]) {
  154. // right child exists and is greater than left child
  155. child += 1
  156. }
  157. // check if we are in heap order
  158. if less(data[child], top) {
  159. break
  160. }
  161. }
  162. data[start] = top
  163. }
  164. @(private="file")
  165. sift_up :: proc(data: []$T, less: proc(a, b: T) -> bool) {
  166. // amoritize length lookup
  167. length := len(data)
  168. if length <= 1 do return
  169. last := length
  170. length = (length - 2) / 2
  171. index := length
  172. last -= 1
  173. if less(data[index], data[last]) {
  174. top := data[last]
  175. for {
  176. data[last] = data[index]
  177. last = index
  178. if length == 0 {
  179. break
  180. }
  181. length = (length - 1) / 2
  182. index = length
  183. if !less(data[index], top) {
  184. break
  185. }
  186. }
  187. data[last] = top
  188. }
  189. }