总结堆知识点

优先级队列:队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列

优先级队列的模拟实现:堆,他就是在二叉树的基础上,堆数据的操作进行了一些调整(让执行的数据有了优先级)。

:底层是一个数组(物理结构),它是一个顺序存储的二叉树(逻辑结构),并且必须是一颗完全二叉树。(从上到下,从左到右,节点是依次排列的,不能有空着的节点。)

大根堆:每棵树的根节点最大,大于两个孩子节点。

小根堆:每棵树的根节点最小,小于两个孩子节点。

因为堆是一颗完全二叉树,所以用顺序存储更好一些,因为数组一是块连续的空间,完全二叉树是没有空节点的,所以如果是一颗非完全二叉树,这棵树的节点可能是空的,那么在存储的时候就要存储一个null,所以就浪费了空间。对于非完全二叉树来说,显然是用链式存储更好一些。

求parent(父亲节点)下标和child(孩子节点)下标:

虽然数据在数组中进行存储,我们还是可以根据二叉树的一些性质来还原这颗二叉树。

   如果下标i是0,就代表的是父亲节点,否则父亲节点就是;(i-1)/ 2

   如果已知父亲节点,求左右孩子节点就是  i*2+1

堆的创建:(创建大根堆和小根堆)

向下调整建堆算法:首先思路就是从当前二叉树的最后一颗子树开始比较,比较的是孩子节点和当前子树的父亲节点,根据大根堆和小根堆的性质来决定是否要交换父亲节点和孩子节点。

创建大根堆: 用向下调正算法(是对于父亲节点来说的,每一棵子树都要从父亲节点向下调正), 只讲一下代码的实现:  首先要有一个父亲节点和孩子节点的下标,有数组的当前长度,从最后一棵树的子树开始,先让左右孩子进行比较,如果左孩子大,就左孩子和父亲节点交换,右孩子大,就右孩子和父亲节点进行交换,但是首先一个前提是要有左孩子和右孩子,也就是算出的孩子节点下标后< 当前数组的长度,否则数组越界。

 然后判断左右孩子谁大,如果有孩子大,就让child下标走到右孩子,然后去判断child节点和parent节点谁大,如果>父亲节点,就交换位置,否则就呆在原来的位置就可以,不用交换,所以是break,然后在创建树的方法中调用向下调整算法就可以了。

如果是创建小根堆也是一样的,从最后一棵树的子树开始进行比较,先比较左右孩子的最小值,然后再去和父亲节点比较,根据小根堆的性质决定是否交换。

:先让child标记左孩子节点,因为当前的子树没有右孩子,所以在child++的时候,要保证它有右孩子节点,同样也是< 数组的长度。

(建立大根堆或者小根堆)时间复杂度:

向下调正的时间复杂度是O(n),

 建堆的时候要考虑调正节点的个数和调整的高度,最后一层是不需要向下调整的,从第一层开始,有一个节点,要向下调整h-1层,(最坏就是调整到最后一层),依次类推,时间复杂度是调整的节点个数 * 调整的高度。(因为每一层调整的高度是不一样的,每一层调整的节点个数也是不一样的)。

向上调整算法时间复杂度:O(n * logn)

一般向上调整算法用在堆的插入操作:先插入到最后一个节点,然后再向上调整,要保证调整完之后依然是大(小)根堆,这个时候只需要插入的元素和父亲节点去比较即可,因为此时父亲节点已经是最大的(/最小的)。

堆的删除:

首先要删除的一定是堆顶元素

1.让堆顶元素和最后一个节点进行交换

2.交换之后有效数据减少一个

 3.进行向下调整

TopK问题:前十名,500强......

要比较前k个数据,如果是前k个最大的数据,要建立小根堆,如果是前k个最小的数据,要建立大根堆,因为topk问题都是比较大的数据,在有很多的数据的时候要进行排序时间复杂度很低,所以以建堆的方式去比较,首先遍历数组中的前k个元素,拿这前k个数据建立堆,例如:比较的是前k个最大的数据,就建立小根堆,再去遍历剩下数组中的元素,如果谁比堆顶数据大,就让堆顶元素和数组中的那个元素交换位置,(因为此时的堆顶元素是最小的,如果数组中有比这个大的数据,那么堆顶元素一定不是前k个最大的),同样的,建立大根堆也是一样的。

Java提供的PriorityQueue(优先级队列)是一个小根堆,所以只能比较前k个最大的元素,要比较前k个最小的元素,就需要把小根堆变成大根堆。

对象的比较:

有三种方法: 1.equals方法,

 比如我们已经创建了一个学生类,这个类中并没有equals方法,但是它是继承于object类的,所以在比较两个对象的时候会去调用object的equals方法,但是这个方法也是用==去比较的,可以看源码;

 所以这个时候我们需要去重写equals方法,让这个比较是按照我们想要的方式去比较,

 就像上述代码,他是按照这个学生类中的age相等并且name相等去判定的。

2.comparable中的comparaeTo方法,实现comparable接口然后重写这个接口中的comparaeTo方法,只是这种比较的方法堆类的侵入性比较强,一旦重写了comparaeTo方法,以后就只能按照重写的方法中的方式去比较。

 可以看到上边,重写compareTo方法之后,调用这个方法的时候就只能是用age去比较,this代表当前对象的引用,o是传过来的参数,如果this > o,会返回一个正数,否则返回一个负数,相等就返回0。

3.比较器:写一个类,实现comparator接口,重写接口中的compare方法,这个比较方法也就是我们说的要把小根堆转化为大根堆,因为已经是一个大根堆了,所以我们可以传一个比较器,

 在compare方法中写我们想要的方式去比较,我们可以看一下源码,优先级队列中是支持传带一个参数的构造方法的,他会判断这个比较器空不空,如果传过去不为空,就回去调用我们自己写的比较器,这个时候如果把参数换位置,

 此时在进行比较的时候就 变成了大根堆,因为只有o1 > o2 的时候返回的是一个负数,此时就把优先级队列中的小根堆换成了大根堆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

良月初十♧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值