当前位置:   article > 正文

#### golang中【堆】的使用及底层 ####

#### golang中【堆】的使用及底层 ####

 声明,本文部分内容摘自:

 Go: 深入理解堆实现及应用-腾讯云开发者社区-腾讯云

数组实现堆 | WXue

堆(Heap)是实现优先队列的数据结构,Go提供了接口和方法来操作堆。

应用

  1. package main
  2. import (
  3. "container/heap"
  4. "sort"
  5. )
  6. /*
  7. 题目:
  8. 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。
  9. 你只可以看到在滑动窗口内的 k 个数字,滑动窗口每次只向右移动一位,返回滑动窗口中的最大值。
  10. 示例:
  11. 输入:
  12. nums = [1,3,-1,-3,5,3,6,7], k = 3
  13. 输出:
  14. [3,3,5,5,6,7]
  15. 解释:
  16. 滑动窗口的位置 最大值
  17. ---------------------------------
  18. [1 3 -1] -3 5 3 6 7 3
  19. 1 [3 -1 -3] 5 3 6 7 3
  20. 1 3 [-1 -3 5] 3 6 7 5
  21. 1 3 -1 [-3 5 3] 6 7 5
  22. 1 3 -1 -3 [5 3 6] 7 6
  23. 1 3 -1 -3 5 [3 6 7] 7
  24. 题解:
  25. 大根堆可以帮助我们实时维护一系列元素中的最大值。
  26. 初始时,我们将数组 nums 的前 k 个元素放入优先队列中。
  27. 每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。
  28. 然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums 中的位置出现在滑动窗口左边界的左侧。
  29. 因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。
  30. 我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。
  31. 为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素 num 在数组中的下标为 index。
  32. */
  33. var a []int
  34. // heap 实现了标准库的heap.Interface接口
  35. type hp struct {
  36. sort.IntSlice // type IntSlice []int
  37. }
  38. func (h hp) Less(i, j int) bool {
  39. return a[h.IntSlice[i]] > a[h.IntSlice[j]]
  40. }
  41. func (h *hp) Push(v interface{}) {
  42. h.IntSlice = append(h.IntSlice, v.(int))
  43. }
  44. func (h *hp) Pop() interface{} {
  45. a := h.IntSlice
  46. v := a[len(a)-1]
  47. h.IntSlice = a[:len(a)-1]
  48. return v
  49. }
  50. func maxSlidingWindow(nums []int, k int) (ans []int) {
  51. ans = make([]int, 1, len(nums)-k+1)
  52. a = nums
  53. // 初始化堆(优先队列)
  54. queue := &hp{make([]int, k)} // 优先队列
  55. for i := 0; i < k; i++ {
  56. queue.IntSlice[i] = i // 注意堆里存的是数组下标而非数组值,对应Less函数里的比较时需要a[h.IntSlice[i]]来比较值
  57. }
  58. heap.Init(queue) // 初始化+向下调整
  59. // 赋值ans[0],因为不需要判断IntSlice[0]的元素是不是在边界外的左侧
  60. ans[0] = nums[queue.IntSlice[0]] // IntSlice[0] 下标为0=数组IntSlice的头部=堆顶元素
  61. // 窗口滑动
  62. for i := k; i < len(nums); i++ {
  63. heap.Push(queue, i) // 入堆+向上调整
  64. for queue.IntSlice[0] <= i-k { // 判断IntSlice[0]的元素是不是在边界外的左侧
  65. heap.Pop(queue) // 出堆+向下调整
  66. }
  67. ans = append(ans, nums[queue.IntSlice[0]]) // IntSlice[0] 下标为0=数组头部=堆顶元素
  68. }
  69. return ans
  70. }
  71. func main() {
  72. res := maxSlidingWindow([]int{1, 3, -1, -3, 5, 3, 6, 7}, 3)
  73. println(res)
  74. }

底层

包:container/heap

接口:heap.Interface

源码:

  1. type Interface interface {
  2. sort.Interface
  3. Push(x interface{}) // 添加元素
  4. Pop() interface{} // 弹出元素
  5. }

其中,注意,实现heap.Interface接口需要嵌入sort.Interface,后者包含Len()、Less(i, j int) bool和Swap(i, j int)方法,用于确定元素间的排序。

全部源码:

  1. type Interface interface {
  2. sort.Interface
  3. Push(x any) // add x as element Len()
  4. Pop() any // remove and return element Len() - 1.
  5. }
  6. func Init(h Interface) {
  7. // heapify
  8. n := h.Len()
  9. for i := n/2 - 1; i >= 0; i-- {
  10. down(h, i, n)
  11. }
  12. }
  13. // Push pushes the element x onto the heap.
  14. // The complexity is O(log n) where n = h.Len().
  15. func Push(h Interface, x any) {
  16. h.Push(x)
  17. up(h, h.Len()-1)
  18. }
  19. func Pop(h Interface) any {
  20. n := h.Len() - 1
  21. h.Swap(0, n)
  22. down(h, 0, n)
  23. return h.Pop()
  24. }
  25. func Remove(h Interface, i int) any {
  26. n := h.Len() - 1
  27. if n != i {
  28. h.Swap(i, n)
  29. if !down(h, i, n) {
  30. up(h, i)
  31. }
  32. }
  33. return h.Pop()
  34. }
  35. func Fix(h Interface, i int) {
  36. if !down(h, i, h.Len()) {
  37. up(h, i)
  38. }
  39. }
  40. func up(h Interface, j int) {
  41. for {
  42. i := (j - 1) / 2 // parent
  43. if i == j || !h.Less(j, i) {
  44. break
  45. }
  46. h.Swap(i, j)
  47. j = i
  48. }
  49. }
  50. func down(h Interface, i0, n int) bool {
  51. i := i0
  52. for {
  53. j1 := 2*i + 1
  54. if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
  55. break
  56. }
  57. j := j1 // left child
  58. if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
  59. j = j2 // = 2*i + 2 // right child
  60. }
  61. if !h.Less(j, i) {
  62. break
  63. }
  64. h.Swap(i, j)
  65. i = j
  66. }
  67. return i > i0
  68. }

其中:

    ① 初始化(Init): 对一个未排序的切片构建堆。这是通过down方法实现的,down方法确保元素下沉到正确的位置,维持堆的性质。

    ② 添加元素(Push): 元素被添加到切片的末尾,然后通过up方法上浮到正确的位置。

注意:标准库中的push函数中,第一行调用的【h.Push(x)】是上层业务代码中自行实现的heap.Interface的堆实例的push方法。

func Push(h Interface, x any) {
    h.Push(x)
    up(h, h.Len()-1)
}

    ③ 删除元素(Pop): 堆顶元素(切片的第一个元素)被移动到切片末尾并返回,然后新的堆顶元素通过down方法恢复堆的性质。

    ④ 删除任意元素(Remove): 类似Pop,但可以移除指定位置的元素。此操作需要综合up和down方法来调整堆。

    ⑤ 修改元素并调整堆(Fix): 如果堆中某个元素被外部修改了(比如优先级改变),Fix方法会根据这个修改后的新值重新调整堆。

堆是一颗完全二叉树,可由数组表示

完全二叉树,逐层而下,从左到右,结点的位置完全由其序号觉得,因此可以用数组来实现。

计算各结点下标的公式,其中 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/790189
推荐阅读
相关标签