常见算法之堆

102 阅读4分钟

一、基础知识

堆是一种特殊的树形数据结构。根据根节点的值与子节点的值的大小关系,堆又分为最大堆和最小堆。在最大堆中,每个节点的值总是大于或等于其任意子节点的值,因此最大堆的根节点就是整个堆的最大值。在最小堆中,每个节点的值总是小于或等于其任意子节点的值,因此最小堆的根节点就是整个堆的最小值。

image.png

堆通常用完全二叉树实现。在完全二叉树中,除最低层之外,其他层都被节点填满,最低层尽可能从左到右插入节点。

完全二叉树又可以用数组实现,因此堆也可以用数组实现。如果从堆的根节点开始从上到下按层遍历,并且每层从左到右将每个节点按照0、1、2等的顺序编号,将编号为0的节点放入数组中下标为0的位置,编号为1的节点放入数组中下标为1的位置,以此类推就可以将堆的所有节点都添加到数组中。

image.png

如果用数组表示堆,那么数组中的每个元素对应堆的一个节点。如果数组中的一个元素的下标为i,那么它在堆中对应节点的父节点在数组中的下标为(i-1)/2,而它的左右子节点在数组中的下标分别为2i+1和2i+2。

为了在最大堆中添加新的节点,应该先从上到下、从左到右找出第1个空缺的位置,并将新节点添加到该空缺位置。如果新节点的值比它的父节点的值大,那么交换它和它的父节点。重复这个过程,直到新节点的值小于或等于它的父节点,或者它已经到达堆的顶部位置。在最小堆中添加新节点的过程与此类似,唯一的不同是要确保新节点的值要大于或等于它的父节点。

通常只删除位于堆顶部的元素。如果删除最大堆的顶部节点,则将堆最低层最右边的节点移到堆的顶部。如果此时它的左子节点或右子节点的值大于它,那么它和左右子节点中值较大的节点交换。如果交换之后节点的值仍然小于它的子节点的值,则再次交换,直到该节点的值大于或等于它的左右子节点的值,或者到达最低层为止。删除最小堆的顶部节点的过程与此类似,唯一的不同是要确保节点的值要小于它的左右子节点的值。

二、常用算法

通常用堆这种数据结构解决的问题有:

  1. TopK问题。如果求最大的前K个值,就用小顶堆;如果求最小的前K个值,就使用大顶堆。
  2. 中间值问题。求一个乱序数据的中间值。维护两个推,一个小顶堆,一个大顶堆。

一个堆结构的操作

type Heap struct {
   List []int
   Capacity int
   SortType int8 //1为大顶推,-1为小顶堆
}

func NewHeap(cap int, sort int8) *Heap {
   return &Heap{
      List: make([]int, 1), //为方便操作,第一个元素空出
      Capacity: cap+1,
      SortType: sort,
   }
}

/*
新建一个元素
1.如果容量没满,那就插入到尾部,向上交换
2.如果容量没满,那么就删除顶元素,替换掉顶元素,然后向下交换
 */

func (h *Heap) Insert(val int) {
   //容量没满
   if h.Capacity > len(h.List) {
      h.List = append(h.List, val)
      idx := len(h.List) - 1
      h.up(idx)
   } else if h.Capacity == len(h.List) {
      if h.SortType > 0 && val < h.List[1] {
         h.List[1] = val
         h.down(1)
      } else if h.SortType < 0 && val > h.List[1] {
         h.List[1] = val
         h.down(1)
      }
   }
}

/*
删除一个元素,去掉顶部元素,然后将最底部最后一个元素,放到顶部,然后从顶部进行置换
*/

func (h *Heap) Push() int {
   var res int
   if len(h.List) > 2 {
      res = h.List[1]
      h.List[1] = h.List[len(h.List)-1]
      h.List = h.List[:len(h.List)-1]
      fmt.Println(h.List)
      h.down(1)
   } else if len(h.List) == 2 {
      res = h.List[1]
      h.List = h.List[0:1]
   }
   return res
}

/*
从底部向上交换
 */
func (h *Heap) up(idx int) {
   var parent int
   parent = idx/2
   if parent == 0 {
      return
   }

   if h.SortType > 0 {
      if h.List[idx] > h.List[parent] {
         h.swap(&h.List[idx], &h.List[parent])
         h.up(parent)
      }
   } else {
      if h.List[idx] < h.List[parent] {
         h.swap(&h.List[idx], &h.List[parent])
         h.up(parent)
      }
   }
}

/*
从顶部向下交换
 */
func (h *Heap) down(parent int) {
   var (
      left = parent*2
      right = parent*2+1
   )
   ll := len(h.List)-1

   if left < ll {
      if h.SortType > 0 {
         if h.List[left] > h.List[right] {
            if h.List[parent] < h.List[left] {
               h.swap(&h.List[parent], &h.List[left])
               h.down(left)
            }
         } else {
            if h.List[parent] < h.List[right] {
               h.swap(&h.List[parent], &h.List[right])
               h.down(right)
            }
         }
      } else if h.SortType < 0 {
         if h.List[left] < h.List[right] {
            if h.List[parent] > h.List[left] {
               h.swap(&h.List[parent], &h.List[left])
               h.down(left)
            }
         } else {
            if h.List[parent] > h.List[right] {
               h.swap(&h.List[parent], &h.List[right])
               h.down(right)
            }
         }
      }
   } else if left == ll {
      if h.SortType > 0 && h.List[parent] < h.List[left] {
         h.swap(&h.List[parent], &h.List[left])
         h.down(left)
      } else if h.SortType < 0 && h.List[parent] > h.List[left] {
         h.swap(&h.List[parent], &h.List[left])
         h.down(left)
      }
   }

   return
}

/*
交换
 */
func (h *Heap) swap(a, b *int) {
   tmp := *a
   *a = *b
   *b = tmp
}