二叉堆是一组能够用堆有序的完全二叉树排序的元素,一般用数组来存储。

大顶堆, 每个结点的值都大于或等于其左右孩子结点的值,其顶部为最大值。

小顶堆,每个结点的值都小于或等于其左右孩子结点的值,其顶部为最小值。

二叉堆

性质

  • 根节点在数组中的位置是 1
    • 左边子节点 2i
    • 右子节点 2i+1
    • 父节点 i / 2
    • 最后一个非叶子节点为 len / 2
  • 根节点在数组中的位置是 0
  • 左子节点 2i + 1
  • 右边子节点 2i+ 2
  • 父节点的下标是 (i − 1) / 2
  • 最后一个非叶子节点为 len / 2 - 1

图片来自知乎

实现

构造二叉堆

  • 找到最后一个非叶子节点 ( len / 2 或者 len / 2 - 1)
  • 从最后一个非叶子节点下标索引开始递减,逐个下沉

插入节点

  • 在数组的最末尾插入新节点
  • 将最后一个节点上浮,时间复杂度为O(log n)
    • 比较当前节点与父节点
    • 不满足 堆性质* *则交换

删除根节点

删除根节点用于堆排序

对于最大堆,删除根节点就是删除最大值;

对于最小堆,是删除最小值。

  • 交换根节点和最后一个节点
  • 将此时的根节点下沉,时间复杂度为O(log n)
    • 比较当前节点与子节点(左,右)
    • 不满足 堆性质 则交换
  • 删除最后一个节点

代码

https://github.com/Allenxuxu/dsa/blob/master/heap/heap.go

堆排序

堆排序是借助“堆”这种数据结构进行排序的排序算法。

实现

  • 将原数组构造成堆
  • 将堆顶元素和数组最后一个元素交换,然后执行下沉操作修复堆(此时修复的堆长度-1,最后一个元素用来存放有序数据)
  • 重复上述步骤,直至堆为空

代码

https://github.com/Allenxuxu/dsa/blob/master/sort/heapsort.go

type Interface interface {
   // Len is the number of elements in the collection.
   Len() int
   // Less reports whether the element with
   // index i should sort before the element with index j.
   Less(i, j int) bool
   // Swap swaps the elements with indexes i and j.
   Swap(i, j int)
}



func down(data Interface, root, n int) {
   for {
      child := 2*root + 1 // left child
      if child >= n {
         break
      }
      if child+1 < n && data.Less(child, child+1) {
         // right = child+1
         child++
      }
      if data.Less(child, root) {
         return
      }
      data.Swap(root, child)
      root = child
   }
}


func HeapSort(data Interface) {
   n := data.Len()
   // Build heap with greatest element at top.
   for i := n/2 - 1; i >= 0; i-- {
      down(data, i, n)
   }

   // Pop elements, largest first, into end of data.
   for i := n - 1; i >= 0; i-- {
      data.Swap(0, i)
      down(data, 0, i)
 	}
}

应用

堆排序是唯一能够同时最优化的利用空间和时间的方法 – 在最坏的情况下也能保证使用 2NlogN 次比较和恒定额外空间。

但是,现代系统中许多应用很少使用它,因为它无法利用缓存 – 数组元素很少和相邻的元素进行比较。因此缓存命中次数远低于在相邻元素进行比较的算法,如快速排序,归并排序,甚至是希尔排序。