数据结构 ----- 堆

堆数据结构详解

堆的概念

堆就是利用数组作为底层实现的完全二叉树,堆根据跟节点的性质可以分为两种

  • 大根堆:根节点为整个二叉树中值最大的节点,且需要满足任何一子树的跟节点值都大于自身的孩子节点的值
  • 小根堆:与大根堆定义类似,根节点为二叉树中值最小的节点,且需要满足任何一株子树的跟节点值都小于孩子的节点的值

需要注意的是,堆只有根节点可以认定为是整个树中的最大值或者最小值,不能根据其左右子树来判定节点值的大小。

堆底层数组与节点的映射

堆是用数组来实现的,可以根据层序遍历的方式将一个数组还原成为一棵完全二叉树。
可以使用数组下标直接找到该节点的叶子节点的位置,数组中下标为0的元素即为树的根节点

  • 元素下标为 i
  • 左节点:2*i + 1
  • 右节点:2*i + 2
    需要注意的是,在查询左右节点时,需要判定计算出的位置是否超出数组的范围。

初始化堆

给定一个初始的数组,将该数组初始化为堆数组的流程如下:(以小根堆为例)

  • 采用的方式为遍历所有的非叶子节点,将该节点值与其左右孩子节点做比较,根据比较的值进行下一步操作,根据完全二叉树的概念,第一非叶子节点的下标为:n/2 - 1
  • 根节点与两个孩子节点中的最小值比较,若根节点的大于该值,则交换根节点与最小值对应的孩子节点,并对以交换后的孩子节点对应的子树执行比较操作。若未发生交换,则继续下一步比较操作。
  • 当所有的比较操作完成之后,该小根堆即构建完成。
  • Init方法中调用的down方法,在后面的获取最小值方法中,也会使用到。
    初始化堆的时间复杂度是O(n)
func Init(arr []int) {
	n := len(arr)
	for i := n/2 - 1; i >= 0; i-- {
		down(arr, i, n)
	}
}
// 将根节点上的元素放置到子树中正确的位置
func down(arr []int, i, len int) {
	for {
		temp := 2*i + 1 // 与根节点比较值的孩子节点下标
		if temp >= len || temp < 0 {
			break
		}
		// 当存在右子节点且右子节点的值小于左子节点时,根节点与右子节点比较
		if a := temp + 1; a < len && arr[a] < arr[temp] {
			temp = a
		}
		// 若根节点的值小于叶子节点的值,则该子树构建完成
		if arr[i] < arr[temp] {
			break
		}
		swap(arr, i, temp)
		i = temp
	}
}

// 交换数组中两个元素
func swap(arr []int, i, j int) {
	temp := arr[i]
	arr[i] = arr[j]
	arr[j] = temp
}

新增数据

向堆中新增一个数据时,需要调整堆,使其维持最小堆的形态。
新增数据的时间复杂度为 O(log(n)) , n为底层数组的长度

  • 将新元素添加到底层数组的末尾
  • 将末尾元素与其父节点的值进行比较,若新元素值大于父节点的值,则堆形态未被破坏,不需要调整。
  • 将末尾元素与其父节点的值进行比较,如果新元素的值小于父节点的值,则交换节点的位置,然后继续比较新元素当前位置与其新父节点的值,以此循环。
func Push(arr []int, k int) []int {
	arr = append(arr, k)
	len := len(arr)
	// 调整新元素在堆中的位置
	up(arr, len-1)
	return arr
}

func up(arr []int, i int) {
	for {
		// j为i位置对应的父节点
		j := (i - 1) / 2 
		if j == i || arr[i] >= arr[j] {
			break
		}
		swap(arr, i, j)
		i = j
	}
}

删除元素最小值

删除最小元素即为删除堆中的根节点,然后重新构建堆。为了维持堆的形态,通常的做法是先交换第一个元素和最后一个元素的位置,然后删除最后一个元素,再重新构建堆

  • 交换数组的第一个元素与最后一个元素
  • 删除数组中的最后一个元素
  • 对数组中的第一个元素执行down操作,使其放置到适合的位置

时间复杂度为 O(log(n))

func Pop(arr []int) (int, []int) {
	swap(arr, 0, len(arr)-1)
	val := arr[len(arr)-1]
	arr = arr[:len(arr)-1]
	down(arr, 0, len(arr))
	return val, arr
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值