堆--go语言自己实现以及go源码解析

堆通常是一种完全二叉树(二叉堆),分为大顶堆和小顶堆。大顶堆的性质是,堆中某个结点的值总是不大于父结点的值(表现形式就是树越往上越大,大在顶上,所以是大顶堆)。小顶堆相反。

  • 完全二叉树的性质是:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。完全二叉树可以用数组来表示,树的根节点是数组下标为0处,父节点的左子节点下标在2父结点下标+1处,右节点下标在2父结点下标+2处。
  • 满二叉树的性质是:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。

实现堆需要实现以下几种功能(由于go语言默认的堆为小顶堆,所以这里也已小顶堆为例进行分析)

  1. 给定一棵完全二叉树,将其初始化为堆。
  2. 从堆中弹出最小值
  3. 向堆中插入新元素

初始化堆的步骤,先有一个长度为n的数组。

  1. 首先,找到堆中的最后一个父节点。堆的最后一个父节点即为最后一个节点的父节点。最后一个节点的下标为n-1,其父节点的位置为int((n-1-1)/2)=int(n/2-1)。
  2. 然后进行父节点的下沉操作,即判断父节点是否比子节点小,如果是则不操作,否则,父节点与子节点中位置最小的节点进行位置交换。交换位置后,继续与交换后位置的子节点进行比较。整个过程节点会不断向下移动,所以是下沉的操作。
  3. 最后对最后一个父节点之前的所有节点都进行这样的操作,即对所有的父节点都进行这样的操作,最后得到堆。
func Init(a []int) {
    
    
	for i := len(a)/2 - 1; i > 0; i-- {
    
    
		down(a, i, len(a))
	}
}
func down(a []int, i0 int, n int) bool {
    
    
	i := i0
	for {
    
    
		j0 := i*2 + 1//左子节点
		if j0 >= n {
    
    
			break
		}
		j1 := j0 + 1//右子节点
		if j1 < n && a[j1] < a[j0] {
    
    
			j0 = j1
		}
		if a[j0] > a[i] {
    
    
			break
		}
		swap(a, j0, i)
		i = j0
	}
	return i > i0
}

从堆中弹出最小值的步骤:

  1. 首先找到最小值的下标,小顶堆中的最小值即堆顶,下标为0
  2. 将其与堆中最后一个元素交换
  3. 对交换后的堆顶元素进行下沉操作(堆的第二层以后已经符合堆的要求,无需额外操作)
  4. 堆中删除最后一个元素(即原先的堆顶元素),并返回其值
func pop(a *[]int) int {
    
    
	n := len(*a) - 1
	res := (*a)[0]
	swap(*a, 0, n)
	down(*a, 0, n)
	*a = (*a)[:n]
	return res
	
	//等价于 return remove(a, 0)
}

//可以删除堆中指定索引位置的元素
func remove(a *[]int, i int) int {
    
    
	n := len(*a) - 1
	res := (*a)[i]
	if i != n {
    
    
		swap(*a, i, n)
		if !down(*a, i, n) {
    
    
			up(*a, i)
		}
	}
	*a = (*a)[:n]
	return res
}

func up(a []int, j int) {
    
    
	for {
    
    
		i := (j - 1) / 2 //parent
		if i == j || a[i] < a[j] {
    
    
			break
		}
		swap(a, i, j)
		j = i
	}
}

func down(a []int, i0 int, n int) bool {
    
    
	i := i0
	for {
    
    
		j0 := i*2 + 1
		if j0 >= n {
    
    
			break
		}
		j1 := j0 + 1
		if j1 < n && a[j1] < a[j0] {
    
    
			j0 = j1
		}
		if a[j0] > a[i] {
    
    
			break
		}
		swap(a, j0, i)
		i = j0
	}
	return i > i0
}

向堆中插入新元素的方法:

  1. 将新元素追加到堆的最末尾处
  2. 对该元素进行上浮操作。上浮操作指的是将该元素与父节点比较,该节点是否比父节点小,如果不是,则不进行操作,如果是,则将其与父节点交换位置,交换位置后,继续与交换位置后的父节点进行比较。
func push(a *[]int, num int) {
    
    
	*a = append(*a, num)
	up(*a, len(*a)-1)
}
func up(a []int, j int) {
    
    
	for {
    
    
		i := (j - 1) / 2 //parent
		if i == j || a[i] < a[j] {
    
    
			break
		}
		swap(a, i, j)
		j = i
	}
}

到此处,堆的基本方法已经完成了。代码中间还有一个移除堆指定下标位置处的值的方法。方法为,将该元素与堆中的最后一个元素进行交换,交换后对该元素进行一次下沉操作,如果下沉操作后元素没有位置变换,则对其进行上浮操作。最后弹出堆中最后一个元素(即原先指定下标位置的元素)

这样一个堆可以用于实现优先队列。

接下来对go中的container/heap进行分析,源码的注释写的也很好,我的分析做一点点补充。

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package heap provides heap operations for any type that implements
// heap.Interface. A heap is a tree with the property that each node is the
// minimum-valued node in its subtree.
//
// The minimum element in the tree is the root, at index 0.
//
// A heap is a common way to implement a priority queue. To build a priority
// queue, implement the Heap interface with the (negative) priority as the
// ordering for the Less method, so Push adds items while Pop removes the
// highest-priority item from the queue. The Examples include such an
// implementation; the file example_pq_test.go has the complete source.
//
package heap

import "sort"

// The Interface type describes the requirements
// for a type using the routines in this package.
// Any type that implements it may be used as a
// min-heap with the following invariants (established after
// Init has been called or if the data is empty or sorted):
//
//	!h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
//
// Note that Push and Pop in this interface are for package heap's
// implementation to call. To add and remove things from the heap,
// use heap.Push and heap.Pop.

//源码的实现更加通用,在我的实现里,固定为整型切片而源码里为实现了
//push、pop、len、less、swap方法的接口
//下面这个interface是我从sort中复制过来的, 
		type Interface interface {
    
    
			// Len is the number of elements in the collection.
			Len() int
		
			// Less reports whether the element with index i
			// must sort before the element with index j.
			//
			// If both Less(i, j) and Less(j, i) are false,
			// then the elements at index i and j are considered equal.
			// Sort may place equal elements in any order in the final result,
			// while Stable preserves the original input order of equal elements.
			//
			// Less must describe a transitive ordering:
			//  - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
			//  - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
			//
			// Note that floating-point comparison (the < operator on float32 or float64 values)
			// is not a transitive ordering when not-a-number (NaN) values are involved.
			// See Float64Slice.Less for a correct implementation for floating-point values.
			Less(i, j int) bool
		
			// Swap swaps the elements with indexes i and j.
			Swap(i, j int)
		}

type Interface interface {
    
    
	sort.Interface
	Push(x interface{
    
    }) // add x as element Len()
	Pop() interface{
    
    }   // remove and return element Len() - 1.
}

// Init establishes the heap invariants required by the other routines in this package.
// Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// The complexity is O(n) where n = h.Len().
func Init(h Interface) {
    
    
	// heapify
	n := h.Len()
	for i := n/2 - 1; i >= 0; i-- {
    
    
		down(h, i, n)
	}
}

// Push pushes the element x onto the heap.
// The complexity is O(log n) where n = h.Len().
func Push(h Interface, x interface{
    
    }) {
    
    
	h.Push(x)
	up(h, h.Len()-1)
}

// Pop removes and returns the minimum element (according to Less) from the heap.
// The complexity is O(log n) where n = h.Len().
// Pop is equivalent to Remove(h, 0).
func Pop(h Interface) interface{
    
    } {
    
    
	n := h.Len() - 1
	h.Swap(0, n)
	down(h, 0, n)
	return h.Pop()
}

// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func Remove(h Interface, i int) interface{
    
    } {
    
    
	n := h.Len() - 1
	if n != i {
    
    
		h.Swap(i, n)
		if !down(h, i, n) {
    
    
			up(h, i)
		}
	}
	return h.Pop()
}

// Fix re-establishes the heap ordering after the element at index i has changed its value.
// Changing the value of the element at index i and then calling Fix is equivalent to,
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
// The complexity is O(log n) where n = h.Len().
//这个方法与是用于修正一个元素,使用的场景比如说,修改了堆中某个元素的值
//然后对这个元素使用这个函数,可以保证还是一个堆
//因为修改一个元素可以视作,删除该元素,插入新的元素
//注释想说的是fix比上面这种做法锁消耗的代价更小less expensive
func Fix(h Interface, i int) {
    
    
	if !down(h, i, h.Len()) {
    
    
		up(h, i)
	}
}

func up(h Interface, j int) {
    
    
	for {
    
    
		i := (j - 1) / 2 // parent
		if i == j || !h.Less(j, i) {
    
    
			break
		}
		h.Swap(i, j)
		j = i
	}
}

func down(h Interface, i0, n int) bool {
    
    
	i := i0
	for {
    
    
		j1 := 2*i + 1
		if j1 >= n || j1 < 0 {
    
     // j1 < 0 after int overflow
			break
		}
		j := j1 // left child
		if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
    
    
			j = j2 // = 2*i + 2  // right child
		}
		if !h.Less(j, i) {
    
    
			break
		}
		h.Swap(i, j)
		i = j
	}
	return i > i0
}

猜你喜欢

转载自blog.csdn.net/rglkt/article/details/118926810