2023-12-06 20:54阅读: 4评论: 0推荐: 0

第14章. 堆

一、堆的引入

现在我们想专门设计一种数据结构,用来存放整数,要求提供3个接口:

  • 添加元素
  • 获取最大值(或最小值)
  • 删除最大值(或最小值)

有一种最优的数据结构就是
时间复杂度:获取最大值的:O(1)、删除最大值O(log n)、添加元素O(log n)

二、堆的相关概念

堆(Heap是一种树状的数据结构),我们只学习二叉堆(也叫完全二叉堆),堆实际就是在完全二叉树的基础上进行了一些调整堆结构就是用数组实现的完全二叉树结构

堆的一个重要性质:任意节点的值总是>=(或<=)子节点的值:

  • 如果任意节点的值总是大于等于子节点的值,称为最大堆、大根堆、大顶堆。
  • 如果任意节点的值总是小于等于子节点的值,称为最小堆、小根堆、小顶堆。
  1. 堆必须是完全二叉树
  2. 每一棵树的根节点必须小于/大于左右孩子结点
  3. 在堆中并不意味着,上一层节点的值一定大于下一层节点的值
  4. 最大堆的最大值肯定在根节点处,最小堆的最小值也是在根节点处
  5. 堆中的元素必须具备可比较性

三、二叉堆

  • 二叉堆的逻辑结构就是一棵完全二叉树,所以也叫完全二叉堆
  • 鉴于完全二叉树的一些特性,二叉堆的底层(物理结构)一般用数组实现即可。
  • 索引 i 的规律(n是节点数量):
    • 如果 i = 0,它是根节点
    • 如果 i > 0,它的父节点的索引为 floor((i - 1) / 2)
    • 如果 2 × i + 1 <= n - 1,它的左子节点的索引为 2 × i + 1
    • 如果 2 × i + 1 > n - 1,它无左子节点
    • 如果 2 × i + 2 <= n - 1,它的右子节点的索引为 2 × i + 2
    • 如果 2 × i + 2 > n - 1,它无右子节点

因为二叉堆是用数组存储的,索引是0 ~ n-1,所以当2 × i + 1 <= n - 1时,它的左子节点存在,索引位2 × i + 1。

四、大根堆——插入操作

流程

  • 循环执行以下操作(图中的80简称为node节点):
    • 如果node节点 > 父节点:node节点与父节点交换位置
    • 如果node节点 <= 父节点,或者node节点没有父节点(已到达根节点):退出循环

这个过程叫做上溢(上滤),时间复杂度:O(logn)

private void heapInsert(int[] arr, int index) {
	// 当前元素arr[index],当前元素的父结点arr[(index - 1) / 2]
	// 退出会有两种情况:arr[index]不必arr[父]大了、index来到了树的根节点位置
	while (arr[index] > arr[(index - 1) / 2]) {
		swap(arr, index, (index - 1) / 2);
		index = (index - 1) / 2;
	}
}

交换位置的优化。

private void siftUp(int index) {
	E element = elements[index];
	while (index > 0) {
		int parentIndex = (index - 1) >> 1;
		E parent = elements[parentIndex];
		if (compare(parent, element) >= 0) break;
		elements[index] = parent;
		index = parentIndex;
 	}
	elements[index] = element;
}

五、大根堆——删除操作

流程:

  1. 用最后一个节点覆盖根节点
  2. 删除最后一个节点(也就是删除堆顶元素)
  3. 循环执行以下操作(图中的43简称为node节点)
    1. 如果node节点 < 子节点:与最大的子节点交换位置
    2. 如果node节点 >= 子节点,或者node没有子节点:退出循环

这个过程叫做下滤(Sift Down),时间复杂度:O(logn)
同样的,交换过程也可以像删除过程一样进行优化。

// 从index位置,往下看,不断地下沉
// 停:我的孩子都不再比我大;已经没有孩子了
private void shifDown(int[] arr, int index. int heapSize) {
	int left = index * 2 + 1;
	while (left < heapSize) {
		// 左右两个孩子中,谁大,谁把自己的下标给largest
		// 右孩子大--> 1)有右孩子 2)右孩子的值比左孩子大
		// 否则都是左孩子大
		int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
		// 最大值与index(父节点)进行比较
		largest = arr[largest] > arr[index] ? largest : index;
		if (largest == index) break;
		swap(arr, largest, index);
		index = largest;
		left = index * 2 + 1;
	}
} 

六、最大堆——批量建堆(heapify)

  • 批量建堆,有2种做法:
    • 自上而下的上滤
    • 自下而上的下滤(效率比较高)

自上而下的下滤

// 根节点不用上滤,所以索引从1开始即可
for (int i = 1; i < size; i ++) {
	siftUp(i);
}

自下而上的下滤

// 从最后一个非叶子节点开始
for (int i = (size >> 1) - 1; i >= 0; i --) {
	siftDown(i);
}

效率对比


七、优先队列(Priority Queue)

  • 普通的队列是FIFO原则,也就是先进先出
  • 优先级队列是按照优先级高低进行出队,比如将优先级最高的元素作为队头优先出队
  • 根据优先队列的特点,很容易想到:可以直接利用二叉堆作为优先队列的底层实现
  • 可以通过Comparator 或 Comparable去自定义优先级高低

八、LeetCode相关题目

  • LeetCode23. 合并K个升序链表
  • LeetCode215. 数组中的第K个最大元素
  • LeetCode703. 数据流中的第K大元素

本文作者:Ac_c0mpany丶

本文链接:https://www.cnblogs.com/keyongkang/p/17880521.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Ac_c0mpany丶  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 You Are My Sunshine REOL
You Are My Sunshine - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Traditional

You are my sunshine

My only sunshine.

You make me happy

When skies are gray.

You'll never know, dear,

How much I love you.

Please don't take my sunshine away

The other night, dear,

When I lay sleeping

I dreamed I held you in my arms.

When I awoke, dear,

I was mistaken

So I hung my head and cried.

You are my sunshine,

My only sunshine.

You make me happy

When skies are gray.

You'll never know, dear,

How much I love you.

Please don't take my sunshine away.

You are my sunshine,

My only sunshine

You make me happy

When skies are gray.

You'll never know, dear

How much I love you

Please don't take my sunshine away

Please don't take my sunshine away.

Please don't take my sunshine away.