数据结构(8)-二叉堆

二叉堆(binary heap)是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。
当父节点的键值总是大于或等于任何一个子节点的键值时该二叉堆被称为为“大顶堆”。当父节点的键值总是小于或等于任何一个子节点的键值时被称为为“小顶堆”。

一、基本概念

  • 堆有序:
    当一颗二叉树的每个节点都大于等于它的两个子节点或者每个节点都小于等于它的两个子节点时,它被称为堆有序。

  • 大顶堆:
    当一颗二叉树的每个节点都大于等于它的两个子节点时,这个堆被称为”大顶堆“,由大顶堆的性质可知,根节点就是这个堆中的最大值节点。

  • 小顶堆:
    当一颗二叉树的每个节点都小于等于它的两个子节点时,这个堆被称为”小顶堆“,由小顶堆的性质可知,根节点就是这个堆中的最小值节点。
    在这里插入图片描述

二、二叉堆表示法

如果我们使用指针来表示堆有序的二叉树,那么每个元素都需要三个指针来找到它的上下节点。但如果我们使用完全二叉树来表达二叉堆就会变得很方便:完全二叉树只用数组而不需要指针就可以表示出每个节点的位置。具体方式就是将二叉树的节点按照层级顺序放入数组中,根节点在位置1,它的子节点在位置2和3,而子节点的子节点则分别在位置4、 5、 6和7,以此类推。

所以

二叉堆是一组能够用堆有序的完全二叉树排序的元素,并且在数组中按照层级存储,而不使用数组的第一个位置。

2.1、确定节点位置

当我们使用数组来表示二叉堆,且不使用数组的0号索引时,我们可以很容易地找到第k个节点的父节点为[k/2],其左子节点和右子节点分别为[2*k][2*k+1],如下图
在这里插入图片描述

2.2、计算堆高度

一颗大小为N的完全二叉树的高度为[lgN],当N达到2的幂时树的高度会加1。

三、大顶堆的算法

我们使用长度为N+1的私有数组a[]来表示一个大小为N的堆,因为我们不会使用a[0],堆元素放置在a[1…N]中。堆的操作会首先进行一些简单的改动,打破堆的状态,然后通过遍历堆并且按照要求将堆的状态恢复,这被成为堆的有序化

3.1、由下至上的堆有序化

如果堆的有序状态因为某个节点变得比它的父节点更大而被打破,那么这时应该对这个节点采用交换操作,来交换它和它的父节点的位置来恢复堆的有序。如果交换后,该节点仍比它的父节点大,那么此时应该继续重复交换操作,这被称为上浮(swim)
在这里插入图片描述

3.2、由上至下的堆有序化

如果堆的有序状态因为某个节点变得比它的子节点更小而被打破,那么这时应该对这个节点采用交换操作,来交换它和它的子节点的位置来恢复堆的有序。如果交换后,该节点仍比它的子节点小,那么此时应该继续重复交换操作,这被称为下沉(sink)

3.3、如何理解大顶堆的上浮和下沉

算法第四版这本书中对大顶堆做了一个很有意思的比喻:

如果我们把大顶堆想象成一个严密的黑社会组织,每个子节点都对应着一个下属,父节点则表示他的直接上级,那么这些操作就可以得到很有意思的解释。swim()表示一个很有能力的新人加入组织并被逐级提升(将能力不足的上级踩在脚下),知道他遇到一个更强的领导。sink()则类似于整个社团的领导退休并被外来者取代后,如果他的下属比他更厉害,他们的角色就会被互换,直到找到比他能力弱的下属为止。

四、算法具体实现

  • swim()
 public void swim(int k) {
        while (k > 1 && a[k] > a[k/2]) {
            //当k节点不为根节点且大于其父节点时上浮到父节点位置
            int temp = a[k];
            a[k] = a[k / 2];
            a[k / 2] = temp;
            //重新定位节点位置
            k = k / 2;
        }
    }
  • sink()
public void sink(int k) {
        while (2 * k <= size) {
            //目前j为左子节点
            int j = 2 * k;
            //比较左子节点和右子节点哪个大,将节点位置调整为大的子节点
            if (j < size && a[j] < a[j + 1]) {
                j++;
            }
            //j代表的子节点和父节点k比较,调整位置
            if (a[k] >= a[j]) {
                break;
            }
            //k < j
            int temp = a[k];
            a[j] = a[k];
            a[k] = temp;
            //重新定位k的位置
            k = j;
        }
    }

五、使用PriorityQueue实现小顶堆和大顶堆

//小顶堆
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
minHeap.offer(xxx);
minHeap.peek();
minHeap.poll();

//大顶堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
	@Override
	public int compare(Integer i1, Integer i2) {
		return i2 - i1;
	}
});
maxHeap.offer(xxx);
maxHeap.peek();
maxHeap.poll();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringError

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值