go 建堆过程

参考解析

题目来源: 字节跳动

答案:

堆的概念

  1. 堆是一个完全二叉树 (除了最后一层,其他都是满节点,最后一层先排左节点)
  2. 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。
  3. 完全二叉树适合用数组存储,因为下标为 i 的元素,它的左子树下标为 2i, 右子树下标为 2i+1。父节点就是 i/2 的 overflow。

大顶堆: 堆中每一个节点的值都必须大于等于其子树中每个节点的值。
小顶堆:堆中每一个节点的值都必须小于等于其子树中每个节点的值。

建堆

堆是用数组存储的,而且 0 下标不存,从 1 开始存储,建堆就是在原地通过交换位置,达到建堆的目的。完全二叉树我们知道,如果最后一个元素的下标为 n, 则 1 到 n/2 是非叶子节点,需要自上而下的堆化(和子节点比较),n/2 +1 到 n 是叶子节点,不需要堆化。

  1. type heap struct{
  2. m []int
  3. len int //堆中有多少元素
  4. }
  5. func main() {
  6. m := []int{0,9,3,6,2,1,7} //第0个下标不放目标元素
  7. h := buildHeap(m) //建堆,返回一个heap结构
  8. h.Push(50)
  9. h.Pop()
  10. fmt.Println(h.m)
  11. }
  12. /**
  13. 建堆,就是在原切片上操作,形成堆结构
  14. 只要按照顺序,把切片下标为n/2到1的节点依次堆化,最后就会把整个切片堆化
  15. */
  16. func buildHeap(m []int) *heap{
  17. n := len(m)-1
  18. for i:=n/2; i>0; i-- {
  19. heapf(m, n, i)
  20. }
  21. return &heap{m,n}
  22. }
  23. func (h *heap)Push(data int) {
  24. h.len++
  25. h.m = append(h.m, data)//向切片尾部插入数据(推断出父节点下标为i/2)
  26. i := h.len
  27. for i/2 >0 && h.m[i/2]<h.m[i] { //自下而上的堆化
  28. h.m[i/2], h.m[i] = h.m[i], h.m[i/2]
  29. i = i/2
  30. }
  31. }
  32. /**
  33. 弹出堆顶元素,为防止出现数组空洞,需要把最后一个元素放入堆顶,然后从上到下堆化
  34. */
  35. func (h *heap)Pop() int{
  36. if h.len < 1 {
  37. return -1
  38. }
  39. out := h.m[1]
  40. h.m[1] = h.m[h.len] //把最后一个元素给堆顶
  41. h.len--
  42. //对堆顶节点进行堆化即可
  43. heapf(h.m, h.len, 1)
  44. return out
  45. }
  46. //对下标为i的节点进行堆化, n表示堆的最后一个节点下标
  47. //2i,2i+1
  48. func heapf(m []int, n,i int) {
  49. for {
  50. maxPos := i
  51. if 2*i<= n && m[2*i] > m[i] {
  52. maxPos = 2*i
  53. }
  54. if 2*i+1 <=n && m[2*i+1] > m[maxPos] {
  55. maxPos = 2*i + 1
  56. }
  57. if maxPos == i { //如果i节点位置正确,则退出
  58. break
  59. }
  60. m[i],m[maxPos] = m[maxPos],m[i]
  61. i = maxPos
  62. }
  63. }

参考文章