堆的实现

718 阅读3分钟

前言

堆又称为优先队列,支持插入和删除堆顶操作;
分为最小堆、最大堆、还有一种扩展实现对顶堆,常用语实时获取中位数;
在贪婪算法的实现即是基于堆,该算法通过反复最小元来进行操作;

下虑插入

下虑插入主要用在delete操作,此时顶刚好空缺并且堆中少了一个元素,因此现在堆中最后一个元素(暂且命名为X)必须移动到该堆的某个位置。

如果X可以放入堆顶,那么delete操作完成。

但是这一般不太可能,因此我们将空缺位置的两个儿子中优先级(根据最大堆或者最小堆优先级可能不同)更大与空缺位置调换;此时空缺位置被推向想一层。重复改步鄹直到X可以放入空缺位置。

这种一般的策略叫做下虑;

下虑插入示意图 下虑插入示意图

上虑插入

上虑插入主要用于insert操作,此时堆中处于平衡状态,因此现在在堆尾建立一个一个空缺位置;

在空缺位置与其父节点进行对比优先级,若插入元素优先级更高,则将空缺位置与父节点调换位置;

此时空缺位置已被推上一层,重复吃步鄹直到找到合适的空缺位置。

这种一般的策略叫做上虑;

上虑插入示意图1 上虑插入示意图1
上虑插入示意图2 上虑插入示意图2

代码

go

package adt
import "fmt"
// Heap 堆结构
// 数组形式为index为0的元素默认为空,总index为1元素开始
// 方便定位元素
// 即如往堆里插入1,2,3,4,5
// 数组元素为[0,1,2,3,4,5]
type Heap struct {
	array []int
	isMax bool // 表示最大堆还是最小堆
}
// GetHeap 获取一个Heap实列
func GetHeap(isMax bool) *Heap {
	return &Heap{array: make([]int, 1, 8), isMax: isMax}
}
// Insert 插入一个元素
func (h *Heap) Insert(value int) {
	if h.array == nil {
		h.array = make([]int, 1, 8)
	}
	h.array = insert(h.array, value, h.isMax)
}
// Delete 删除并获取堆顶
func (h *Heap) Delete() (top int, err error) {
	h.array, top, err = reset(h.array, h.isMax)
	return top, err
}
// getCompareResult 比较
func getCompareResult(o1, o2 int, isMax bool) int {
	if o1 == o2 {
		return 0
	}
	if isMax {
		if o1 > o2 {
			return 1
		}
		return -1
	}
	if o1 < o2 {
		return 1
	}
	return -1
}
//
//
// Insert 相关函数
//
//
// insert 根据堆性质想数组中插入一个元素
func insert(array []int, value int, isMax bool) (nowArray []int) {
	nowArray = append(array, 0)
	insertIndex := getInsertIndex(nowArray, len(array), value, isMax)
	nowArray[insertIndex] = value
	return nowArray
}
// getInsertIndex 递归获取插入元素的数组下标
// 使用上虑策略
func getInsertIndex(array []int, nowIndex int, value int, isMax bool) int {
	if nowIndex < 2 {
		return 1
	}
	faIndex := getFaIndex(nowIndex)
	if getCompareResult(array[faIndex], value, isMax) >= 0 {
		return nowIndex
	}
	array[faIndex], array[nowIndex] = array[nowIndex], array[faIndex]
	return getInsertIndex(array, faIndex, value, isMax)
}
// getFaIndex 获取一个元素的父级元素数组下标
func getFaIndex(index int) int {
	return index / 2
}
//
//
// Delete 相关函数
//
//
const (
	noGo = iota
	goLeft
	goRight
)
// reset 重设置一个数组
func reset(array []int, isMax bool) (nowArray []int, topValue int, err error) {
	lastIndex := len(array) - 1
	if lastIndex == 1 {
		topValue = array[1]
		nowArray = array[:lastIndex]
		return
	} else if lastIndex < 1 {
		return array, 0, fmt.Errorf("堆中已无元素")
	}
	topValue, array[1] = array[1], 0
	value := array[len(array)-1]
	nowArray = array[:len(array)-1]
	lastInsertIndex := getLastInsertIndex(nowArray, 1, value, isMax)
	nowArray[lastInsertIndex] = value
	return
}
// getLastInsertIndex 下虑
func getLastInsertIndex(array []int, nowIndex int, value int, isMax bool) int {
	status := noGo
	lastIndex := len(array) - 1
	leftIndex, rightIndex := getSonIndex(nowIndex)
	if lastIndex >= rightIndex {
		left, right := array[leftIndex], array[rightIndex]
		status = getStatus(left, right, value, isMax)
	} else if lastIndex == leftIndex && getCompareResult(array[leftIndex], value, isMax) >= 0 {
		status = goLeft
	}
	switch status {
	case noGo:
		return nowIndex
	case goLeft:
		array[nowIndex], array[leftIndex] = array[leftIndex], value
		return getLastInsertIndex(array, leftIndex, value, isMax)
	case goRight:
		array[nowIndex], array[rightIndex] = array[rightIndex], value
		return getLastInsertIndex(array, rightIndex, value, isMax)
	}
	panic("--")
}
// getSonIndex 获取一个元素的左右子元素数组下标
func getSonIndex(index int) (left, right int) {
	return 2 * index, 2*index + 1
}
// getStatus 根据三个值获取走那边获取步鄹
func getStatus(left, right, value int, isMax bool) (res int) {
	res = 0
	if getCompareResult(left, value, isMax) > 0 || getCompareResult(right, value, isMax) > 0 {
		res++
	} else {
		return
	}
	if getCompareResult(right, left, isMax) > 0 {
		res++
	}
	return
}

golang code on github

总结

插入时间复杂度:O(logN)

堆的实现确实惊为天人,如何的有魅力。
特别是上虑与下虑策略的思想,真是让人大开眼界。

最后更新时间: