Java数据结构中堆的向下和向上调整解析
作者:dhdhdhdhg 发布时间:2022-01-02 07:34:58
一、关于堆
JDK1.8中的PriortyQueue(优先级队列)底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。
1.堆的概念
堆有最大堆和最小堆之分。
最大(最小)堆是一棵每一个节点的元素都不小于(大于)其孩子(如果存在)的元素的树。大堆是一棵完全二叉树,同时也是一棵最大树。小堆是一棵完全二叉树,同时也是一棵最小树。
注意: 堆中的任一子树也是堆,即大堆的子树也都是大堆,小堆亦是。
2.堆的性质
堆中某个结点的值总是不大于或不小于其父结点的值
堆总是一颗完全二叉树
3.堆的存储方式
由堆的概念可知,堆是一颗完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。
注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要能够存储空结点,就会导致空间利用率比较低
二、堆的创建
1.堆向下调整
对于给出的一个数据,如何将其创建为堆呢?例如下图:
仔细观察上图后发现:根结点的左右子树已经完全满足堆的性质,因此只需将根结点向下调整好即可。
以小堆为例:
1.让parent标记需要调整的结点,child标记parent的左孩子(注意:parent如果有孩子一定是先有左孩子)
2.如果parent的左孩子存在,即child<size,进行如下操作,直到parent的左孩子不存在
parent右孩子是否存在,如果存在则找出左右孩子中较小的孩子,使用child进行标记
将parent与较小的孩子(也就是此时的child)比较,如果:
parent小于较小的孩子child,这个结点已经调整
否则:将parent与child进行交换,交换成功后,这时parent中大的元素已经向下移动,可能会导致子树不满足堆的特性,就需要继续向下调整,即parent=child,child=parent*2+1,然后循环起来
图解如下:
代码实现:
private void shiftDown(int parent){
//默认让child先标记左孩子---因为:parent可能有左没有右
int child=parent*2+1;
//while循环条件可以保证:parent的左孩子一定存在
// 但是不能保证parent的右孩子是否存在
while(child<size){
//1.找到左右孩子中较小的孩子
if(child+1<size&&array[child+1]<array[child]){
child+=1;
}
//2.较小的孩子已经找到了
//检测双亲和孩子之间是否满足堆的特性
if(array[parent]>array[child]){
swap(parent,child);
//大的双亲往下走,可能会导致子树又不满足堆的特性
//因此需要继续往下调整
parent=child;
child=parent*2+1;
}else{
//以parent为根的二叉树已经是堆了
return;
}
}
}
注意: 在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。
时间复杂度(看最坏的情况): 从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(logn)。
2.堆的创建
向下调整的情况只能针对左右子树已经是堆了才可以调整,那假如根结点的左右子树不满足堆的特性,又该如何调整呢?例如下图:
我们要从3这里的位置开始向下调整,然后逐渐向前依次向上调整
3这个位置很特殊,他是二叉树倒数第一个非叶子结点
步骤:
1.找到倒数第一个非叶子结点
2.从该结点位置开始往前一直到根结点,每遇到一个结点就使用向下调整
代码实现:
public static void createHeap(int[] array){
//注意:倒数第一个非叶子节点刚好是最后一个节点的双亲
//最后一个结点的编号是size-1,倒数第一个非叶子节点的下标为(size-1-1)/2
int lastLeafParent=(size-2)/2;
//从倒数第一个非叶子节点位置开始,一直到根节点的位置,使用向下调整
for(int root=lastLeafParent;root>=0;root--){
shiftDown(root);
}
}
建堆的时间复杂度:
因为堆是完全二叉树,满二叉树也是完全二叉树,为了简化计算,此处使用满二叉树来证明:
假设满二叉树高度h
第一层:20个结点,需要向下移动h-1层
第二层:21个结点,需要向下移动h-2层
第二层:22个结点,需要向下移动h-3层
…以此类推就可以求出所有的移动步数:每一层结点数与对应移动层数相乘再整体相加
然后再利用一定的数学巧妙运算(此处省略那些繁琐的数学公式,属实是头大)就得出T(n)=n=log(n+1)≈n
因此:建堆的时间复杂度为O(N)。
三、向上调整
向上调整主要的应用场景就是在堆的插入
堆的插入总共需要两个步骤:
1.先将元素插入到堆的末尾,即最后一个孩子之后
2.插入后如果堆的性质遭到破坏,将最后新插入的节点向上调整,直到满足堆的性质
代码实现:
private void shiftUp(int child){
int parent=(child-1)/2;
while(child!=0){
if(array[child]<array[parent]){
swap(child,parent);
child=parent;
parent=(child-1)/2;
}else{
return;
}
}
}
来源:https://blog.csdn.net/m0_51405559/article/details/121288336


猜你喜欢
- 本文实例讲述了C#计算字符串相似性的方法。分享给大家供大家参考。具体如下:计算字符串相似性的办法很多,甚至最笨的办法可以挨个匹配,这里要讲的
- 最新需要在项目启动后立即执行某个方法,然后特此记录下找到的四种方式注解@PostConstruct使用注解@PostConstruct是最常
- 一般情况下,在Word中添加文字水印仅支持添加一个文本字样的水印,但在复杂的办公环境中,由于对不同文档的设计要求,需要在Word文档中添加平
- Springboot内部提供的事务管理器是根据autoconfigure来进行决定的。比如当使用jpa的时候,也就是pom中加入了sprin
- 一、点睛邻接矩阵通常采用一个一维数组存储图中节点的信息,采用一个二维数组存储图中节点之间的邻接关系。邻接矩阵可以用来表示无向图、有向图和网。
- 一、# List泛型集合集合是OOP中的一个重要概念,C#中对集合的全面支持更是该语言的精华之一。为什么要用泛型集合?在C# 2.0之前,主
- Excelapache 为 java开发者们提供了一套excel表格读写的工具:POI ,对于一个小白来说每次读写使用POI需要写一套复杂的
- 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。常见的对称加密方法DES :
- 看到篇帖子, 国外一个技术面试官在面试senior java developer的时候, 问到一个weak reference相关的问题.
- /// <summary>/// 人民币大写/// </summary>/// <param name=&qu
- 前言平时开发经常会用到List等集合操作,在这里做一个小结java集合Collectionjava里面集合分为两大类:List和Set,下面
- 1 编程语言简介编程语言(programming language)可以简单的理解为一种计算机和人都能识别的语言。一种计算机语言让程序员能够
- 还记得我们之前说的ListView吗,(这个难用的控件-。+)我们在用他的同时也用到了一个叫做适配器Adapter的东西。一般我们用一个类继
- 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件
- Warning:这是《Java 程序员进阶之路》专栏的第 55 篇。回来后小二找到了我,于是我就写下了这篇文章丢给他,并严厉地告诉他:再搞不
- 描述输入一行字符串,分别统计出其中英文字母、空格、数字和其它字符的个数输入描述:控制台随机输入一串字符串输出描述:输出字符串中包含的英文字母
- 如今APP越来越多,我们每天所使用的的软件也越来越多,可是在我们不付费的情况下,App制造商如何实现,实现收入甚至是盈利呢?答案就是在我们打
- Collection 接口 :Collection是最基本的集合接口,声明了适用于JAVA集合(只包括Set和List)的通用方法。Set和
- 如何实现 WPF 代码查看器控件框架使用.NET40;Visual Studio 2019;代码展示需要使用到AvalonEdit是基于WP
- 在做多语言版本的时候,日期时间的格式话是一个很头疼的事情,幸好Android提供了DateFormate,可以根据指定的语言区域的默认格式来