python selenium使用xpath定位(python和java哪个更值得学)
275
2022-08-04
Java 详细讲解用堆解决Top
目录1、什么是堆?堆结构大根堆 VS 小根堆大根堆(最大堆)小根堆(最小堆)优先级队列(PriorityQueue)2、top-k问题解决思路总结:
要解决 top-k 问题,我们应该先熟悉一种数据结构 - 堆(优先级队列),已经了解的朋友可以跳过哦。
1、什么是堆?
堆结构
堆其实就是一种二叉树,但是普通的二叉树是以链式结构进行储存数据的,而堆是以数组进行顺序存储数据的。那么什么样的二叉树才适合用顺序存储的方式呢?
我们假设一颗普通的二叉树可以用数组存储,那么就可以得到如下结构:
我们可以看到,当二叉树中间有空值时,数组的存储空间会被浪费,那么什么情况下空间才不会被浪费呢? 那就是完全二叉树。
从以上结构中,我们不能用链式结构的指针来访问孩子节点或者父亲节点,只能通过对应下标来访问,其实也比较简单。
例如下图:
已知 2 节点的下标是1,那么
他的左孩子下标是:2 * 2 + 1 = 3
他的右孩子下标是:2 * 2 + 2 = 4
相反,已知 1 节点的下标是3,3 节点的下标是4,那么
1 节点的父亲节点下标是:(3 - 1) / 2 = 1
3 节点的父亲节点下标是:(4 - 1) / 2 = 1
大根堆 VS 小根堆
大根堆(最大堆)
大根堆保证,每颗二叉树的根节点都 大于 左右孩子节点
从最后一棵子树的根节点开始调整,来到每颗子树的根节点,使得每棵子树都向下调整为大根堆,最后再向下做最后调整,保证二叉树整体是大根堆(这个调整主要是为了后面的堆排序)。
具体调整过程如下:
怎么用代码实现呢?
我们首先从最后一棵子树调整,那么就要拿到最后一颗子树的根节点 parent ,我们知道数组最后一个节点下标是 len - 1,而这个节点是最后一棵子树的左孩子或者右孩子,根据孩子下标就可以拿到根节点下标( parent ) ,parent-- 就可以让每颗子树都进行调整,直到来到根节点,再向下调整最后一次,便可以得到大根堆。
// 将数组变成大根堆结构
public void createHeap(int[] arr){
for (int i = 0; i < arr.length; i++) {
elem[i] = arr[i];// 放入elem[],假设不需要扩容
usedSize++;
}
// 得到根节点parent, parent--依次来到每颗子树的根节点,
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
// 依次向下搜索,使得每颗子树都变成大根堆
shiftDown(parent,usedSize);
}
}
// 向下搜索变成大根堆
public void shiftDown(int parent,int len){
eGroZint child = parent*2+1;// 拿到左孩子
while (child < len){
// 如果有右孩子,比较左右孩子大小,得到较大的值和父节点比较
if (child+1 < len && (elem[child] < elem[child+1])){
child++;
}
// 比较较大的孩子和父节点,看是否要交换
int max = elem[parent] >= elem[child] ? parent : child;
if (max == parent) break;// 如果不需要调整了,说明当前子树已经是大根堆了,直接 break
swap(elem,parent,child);
parent = child;// 继续向下检测,看是否要调整
child = parent*2+1;
}
}
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
小根堆(最小堆)
小根堆保证,每颗二叉树的根节点都 小于 左右孩子节点
调整过程同上。
优先级队列(PriorityQueue)
在java中,提供了堆这种数据结构(PriorityQueue),也叫优先级队列,当我们创建一个这样的对象时,就得到了一个没有添加数据的 小根堆 ,我们可以向里面添加或者删除元素,每向里面删除或者添加一个元素,系统会整体进行一次调整,重新又调整为小根堆。
// 默认得到一个小根堆
PriorityQueue
smallHeap.offer(23);
smallHeap.offer(2);
smallHeap.offer(11);
System.out.println(smallHeap.poll());// 弹出2,剩余最小的元素就是11,会被调整到堆顶,下一次弹出
System.out.println(smallHeap.poll());// 弹出11
// 如果需要得到大根堆,在里面传一个比较器
PriorityQueue
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
2、top-k问题解决思路
例:有一堆元素,让你找出前三个最小的元素。
思路一: 将数组从小到大排序,拿到数组前3个元素。但是可以发现这样时间复杂度太高,不可取。
思路二: 将元素全部放入一个堆结构中,然后弹出三个元素,每次弹出的元素都是当前堆最小的,那么弹出的三个元素就是前最小的三个元素。
这种思路可以做,但是假设我有1000000个元素,只弹出前三个最小的元素,那么就要用到大小为1000000的堆。这么做空间复杂度太高,不建议用这种方法。
思路三:
我们需要得到三个最小的元素,那么就建一个大小为3的堆,假设目前的堆结构中刚好放满了3个元素,那么这三个元素就是当前最小的三个元素。假设第四个元素是我们想要的元素之一,那么前三个至少有一个元素不是我们想要的,就需要弹出,那么弹出谁呢?
我们要得到的是前三个最小的元素,所以当前堆结构中最大的元素一定不是我们想要的,所以这里我们建一个大根堆。弹出该元素,然后放入第四个元素,直到遍历完整个数组。
这样我们就得到了只含有前三个最小元素的堆,并且可以看到堆的大小一直都是3,而不是有多少数据就建多大的堆,然后再依次弹出元素就行了。
// 找前 k个最小的元素
public static int[] topK(int[] arr,int k){
// 创建一个大小为 k的大根堆
PriorityQueue
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int i = 0; i < arr.length; i++) {
if (i < k){
// 放入前 k 个元素
maxHeap.offer(arr[i]);
}else{
// 从第 k+1个元素开始进行判断是否要入堆
if (maxHeap.peek() > arr[i]){
maxHeap.poll();
maxHeap.offer(arr[i]);
}
}
}
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
ret[i] = maxHeap.poll();
}
return ret;
}
以上就是top-k问题的基本思路,其他的类似问题也是这样解。
总结:
1、如果求前K个最大的元素,要建一个小根堆。
2、如果求前K个最小的元素,要建一个大根堆。
3、如果求第K大的元素,要建一个小根堆 ( 堆顶元素就是 )。
4、如果求第K小的元素,要建一个大根堆 ( 堆顶元素就是 )。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~