堆排序简单梳理

背景

最近在解决算法问题时用到了优先队列,然后发现对其底层实现的一些细节好像没那么熟悉了,于是乎梳理一下。

算法思维

理解堆排序的核心是堆的定义是什么, 通常堆分为大顶堆,小顶堆。这里我以大顶堆为例聊一下堆排序。
大顶堆需要满足一下性质:

  1. 是一棵完全二叉树; 这个性质使得我们可以用数组来对树结构进行描述,可以通常数组下标之间的关系表达树节点的父子关系。
  2. 对于这棵树的任意子树,父节点的值都大于子节点的值;这是一种递归的定义,从这条性质我们可以得出堆是一棵满足特定条件的完全二叉树

接下来,对于堆对外提供的所有操作,我们只需要保证不破坏堆的这两条性质就好。

举例来说:无序数组{3,1,4,6,8,9,1,3,2}
其对应的树如下:
在这里插入图片描述
所谓树化操作就是将原树改造为符合如下性质的树(大顶堆)。 改造过程比较简单,可以递归的取做这个事情,

  • 左右子树分别改造为堆
  • 自顶向下调整树节点,使得整棵树符合堆的特性
    这里需要注意的是,左右子树为堆之后不能仅仅简单的只交换父子节点使得整棵树为大顶堆,因为交换的过程可能会破坏已有的堆。

插入/删除:
在这里插入图片描述
插入/删除操作都有可能破坏以上两条性质,插入时需要调整叶子节点到根节点这条路径,以保证满足堆的性质。这里的删除只能删除根节点,且算法比较巧妙,交换根节点和最后一个节点,新的根节点可能会破坏堆的性质,此时需要再次自顶向下调整。

代码实现

package algo

import (
	"errors"
	"fmt"
)

/**
 * 实现简单的堆排序,这里实现简单的大顶堆
 * 用数组下标之间的关系描述父子节点关系
 */
type Heap struct {
	Arr []int //用数组描述堆
}

/**
 * 建立堆
 */
func (h *Heap) BuildHeap(arr []int) {
	h.Arr = arr
	h.recurBuildHeap(0)
}

func (h *Heap) recurBuildHeap(root int) {
	if root >= len(h.Arr) {
		return
	}
	//叶子节点则直接返回
	if 2*root+1 >= len(h.Arr) {
		return
	}
	//左右树化
	h.recurBuildHeap(2*root + 1)
	h.recurBuildHeap(2*root + 2)
	h.Heapify(root)
	return
}

/**
 * 将以root为根节点的子树堆化
 * 这里的前提是root的左右子树已经满足最大堆的性质了
 */
func (h *Heap) Heapify(root int) {
	if root < 0 || root >= len(h.Arr) {
		return
	}
	maxIndex := root
	leftChild, rightChild := 2*root+1, 2*root+2
	if leftChild < len(h.Arr) && h.Arr[leftChild] > h.Arr[maxIndex] {
		maxIndex = leftChild
	}
	if rightChild < len(h.Arr) && h.Arr[rightChild] > h.Arr[maxIndex] {
		maxIndex = rightChild
	}
	h.Arr[maxIndex], h.Arr[root] = h.Arr[root], h.Arr[maxIndex]
	if maxIndex != root {
		h.Heapify(maxIndex)
	}
}

/**
 * 新插入一个节点之后,新的节点为叶子节点,可能会破坏该叶子节点所属最小子树的堆特性
 * 因此自底向上堆排序
 */
func (h *Heap) Insert(val int) {
	h.Arr = append(h.Arr, val)
	cur := len(h.Arr) - 1
	for cur > 0 { //cur==0时为根节点 没有父节点
		parent := (cur - 1) / 2
		if h.Arr[parent] < h.Arr[cur] {
			h.Arr[parent], h.Arr[cur] = h.Arr[cur], h.Arr[parent]
			cur = parent
		} else {
			break
		}
	}
	return
}

/**
 * 取堆最值
 */
func (h *Heap) Pop() (val int, err error) {
	if len(h.Arr) == 0 {
		err = errors.New("empty heap")
		return
	}
	val = h.Arr[0]
	h.Arr[0], h.Arr[len(h.Arr)-1] = h.Arr[len(h.Arr)-1], h.Arr[0]
	h.Arr = h.Arr[:len(h.Arr)-1]
	h.Heapify(0)
	return
}

func Demo() {
	arr := []int{3, 1, 5, 1, 2, 3, 6, 8, 132, 45, 1, 2}
	h := &Heap{}
	h.BuildHeap(arr)
	for i := 0; i < len(arr); i++ {
		val, _ := h.Pop()
		fmt.Printf("%d ", val)
	}
	fmt.Println()
	//
	h.BuildHeap(arr)
	h.Insert(12333)
	for i := 0; i < len(arr); i++ {
		val, _ := h.Pop()
		fmt.Printf("%d ", val)
	}
	fmt.Println()
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值