满足这两点,它就是一个堆:

  • 堆是一个完全二叉树;
  • 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。

在这里插入图片描述从图中我们可以看到,数组中下标为 i 的节点的左子节点,就是下标为 i∗2 的节点,右子节点就是下标为 i∗2+1 的节点,父节点就是下标为 2i​ 的节点。

  1. 往堆中插入一个元素

往堆中插入一个元素后,我们需要继续满足堆的两个特性。就需要进行调整,让其重新满足堆的特性,这个过程我们起了一个名字,就叫作堆化(heapify)。

堆化实际上有两种,从下往上和从上往下。这里我先讲从下往上的堆化方法。堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。

从下往上堆化
在这里插入图片描述

  1. 删除堆顶元素

删除堆顶元素之后,把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。

从上往下堆化
在这里插入图片描述堆化的时间复杂度跟树的高度成正比,也就是 O(logn)。

我们可以把堆排序的过程大致分解成两个大的步骤,建堆和排序。

  1. 建堆

第一种是在堆中插入一个元素的思路。尽管数组中包含 n 个数据,但是我们可以假设,起初堆中只包含一个数据,就是下标为 1 的数据。然后,我们调用前面讲的插入操作,将下标从 2 到 n 的数据依次插入到堆中。这样我们就将包含 n 个数据的数组,组织成了堆。

第二种实现思路是从后往前处理数组,并且每个数据都是从上往下堆化。因为叶子节点往下堆化只能自己跟自己比较,所以我们直接从第一个非叶子节点开始,依次堆化就行了。
在这里插入图片描述在这里插入图片描述建堆过程的时间复杂度是 O(n)。

  1. 排序

建堆结束之后,把堆顶跟最后一个元素交换,然后再通过堆化的方法,将剩下的 n−1 个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是 n−1 的位置,一直重复这个过程,直到最后堆中只剩下标为 1 的一个元素,排序工作就完成了。
在这里插入图片描述堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是 O(n),排序过程的时间复杂度是 O(nlogn),所以,堆排序整体的时间复杂度是 O(nlogn)。

堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。

堆排序的应用:

  • 优先级队列
  1. 合并有序小文件
  2. 高性能定时器
  • 利用堆求 Top K
  • 利用堆求中位数

heap.go

package pqueue

func adjustHeap(src []Node, start, end int) {
	if start >= end {
		return
	}

	// 只需要保证优先级最高的节点在 src[1] 的位置即可
	for i := end / 2; i >= start; i-- {
		high := i
		if src[high].priority < src[2*i].priority {
			high = 2 * i
		}
		if 2*i+1 <= end && src[high].priority < src[2*i+1].priority {
			high = 2*i + 1
		}
		if high == i {
			continue
		}
		src[high], src[i] = src[i], src[high]
	}
}

priority_queue.go

package pqueue

// Node 队列节点
type Node struct {
	value    int
	priority int
}

// PQueue priority queue
type PQueue struct {
	heap []Node

	capacity int
	used     int
}

// NewPriorityQueue new
func NewPriorityQueue(capacity int) PQueue {
	return PQueue{
		heap:     make([]Node, capacity+1, capacity+1),
		capacity: capacity,
		used:     0,
	}
}

// Push 入队
func (q *PQueue) Push(node Node) {

	if q.used > q.capacity {
		// 队列已满
		return
	}
	q.used++
	q.heap[q.used] = node
	// 堆化可以放在 Pop 中
	// adjustHeap(q.heap, 1, q.used)
}

// Pop 出队列
func (q *PQueue) Pop() Node {
	if q.used == 0 {
		return Node{-1, -1}
	}
	// 先堆化, 再取堆顶元素
	adjustHeap(q.heap, 1, q.used)
	node := q.heap[1]

	q.heap[1] = q.heap[q.used]
	q.used--

	return node
}

// Top 获取队列顶部元素
func (q *PQueue) Top() Node {
	if q.used == 0 {
		return Node{-1, -1}
	}

	adjustHeap(q.heap, 1, q.used)
	return q.heap[1]
}

以上内容摘自《数据结构与算法之美》课程,来学习更多精彩内容吧。

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值