堆
1. 堆逻辑上是一棵完全二叉树
2. 堆物理上是保存在数组中
3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
4. 满足任意结点的值都小于其子树中结点的值,叫做小堆,或者小根堆,或者最小堆
5. 堆的基本作用是,快速找集合中的最值
存储方式
二叉树在代码中的表示方式:
孩子兄弟表示法(N叉树)
左右孩子表示法(二叉树)
使用数组表示二叉树:
使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中,一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费,这种方式的主要用法就是堆的表示。
下标关系
在数组中存储的下标关系:
A 下标:0 左子树下标:1 右子树下标:2
B 下标:1 左子树下标:3 右子树下标:4
C 下标:2 左子树下标:5 右子树下标:6
D 下标:3 左子树下标:7 右子树下标:8
.。。。
已知双亲(parent)的下标:
左孩子(left)下标 = 2 * parent + 1
右孩子(right)下标 = 2 * parent + 2
已知孩子(不区分左右)(child)下标:
双亲(parent)下标 = (child – 1) / 2
操作-向下调整(大堆)
给定一个数组,当前这个数组不满足堆的特性,经过操作使其变成一个堆。
**前提:**要求当前这个树中,除了根节点之外,其他节点都符合堆的要求
除了根节点,其它节点已经符合大堆结构,从根结点出发进行向下调整。
1、从根节点出发,先找到左右子树根节点中较大值
2、把找到的较大的值和原根节点进行交换
3、如果没有完成调整,从此时的当前节点出发,重复1、2步骤
public static void shiftDown(int[] array, int size,int index){
int parent = index;
//根据父节点下标,先找到左子树下标
int child = 2*parent+1;
while (child < size){
//再去找下右子树对应的节点
if(child + 1 < size && array[child + 1] > array[child]){//child合法并且如果右子树大于根
child = child + 1;
}
//child经过上面的条件,指向左右子树中较大的一个
//拿刚刚child位置的元素和parent位置进行对比,看是否符合大堆要求
//如果不符合大堆要求(child位置的元素比parent位置的元素大)交换child和parent位置的元素
if(array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else{
//当前child和parent的关系符合大堆要求,调整完毕
break;
}
//下次循环之前,需要先更新parent和child
parent = child;
child = 2*parent + 1;
}
}
操作-向上调整(大堆)
和向下调整基本类似
private void shiftUp(int[]array,int size,int index){
int child = index;
int parent = (child - 1)/2;
while (child > 0){
if(array[parent] < array[child]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else {
break;
}
child = parent;
parent = (child -1)/2;
}
建堆(大堆)
基于向下调整的方式建堆,必须从后往前遍历
基于向上调整的方式建堆,必须从前往后遍历
从后往前遍历,从最后一个节点的父节点出发(最后一个非叶子节点)
public static void creatHeap(int[] array,int size) {
//从后向前遍历,从最后一个非叶子节点出发,依次进行向下调整
//size-1最后一个节点,再-1/2是找到最后一个节点的父节点
for(int i = (size - 1 -1)/2; i > 0 ;i--){
shiftDown(array,size,i);
}
}
优先队列(大堆)
每次出队列的元素都是优先级最高的元素
优先级队列的实现方式有很多,但最常见的是使用堆来构建。
入队列的时候,就把元素插入到数组末尾,然后向上调整。
进行出队列的时候,就把堆顶的元素删除,并且进行向下调整
时间复杂度
入队列 =》O(logN)
出队列 =》O(logN)
取队首元素 O(1)
建堆=》O(N)
入队列:
1. 首先按尾插方式放入数组
2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤
4. 直到根结点
public void offer(int x){
array[size] = x;
size++;
shiftUp(array,size,size-1);
}
private void shiftUp(int[]array,int size,int index){
int child = index;
int parent = (child - 1)/2;
while (child > 0){
if(array[parent] < array[child]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else {
break;
}
child = parent;
parent = (child -1)/2;
}
}
出队列
为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆
1、取最后一个元素复制到根节点
2、尾删最后一个
3、进行向下调整
public Integer poll(){
if(size <= 0){
return null;
}
int ret = array[0];
array[0] = array[size-1];
size--;
shiftDown(array,size,0);
return ret;
}
private void shiftDown(int[] array,int size,int index){
int parent = index;
int child = 2 * parent + 1;
while (child < size){
if(child + 1 < size && array[child+1] > array[child]){
child = child+1;
}
if(array[parent] < array[child]){
int tmp = array[child];
array[parent] = array[child];
array[child] = tmp;
}else {
break;
}
parent = child;
child = 2 * parent + 1;
}
}
取队首元素
返回堆顶元素即可
public Integer peek(){
if(size == 0){
return null;
}
return array[0];
}
判空
public boolean isEmpty(){
return size == 0;
}
总代码:
public class MyPriority {
private int[] array = new int[100];
private int size = 0;//[0,size)表示有效元素区间
public void offer(int x){
//1、先把x放到数组元素的末尾
array[size] = x;
size++;
//2、把x进行向上调整
//第一个参数表示数组,第二个为有效元素的个数,第三个参数表示从哪个位置进行向上调整
shifUp(array,size,size-1);
}
private void shifUp(int[] array, int size, int index) {
int child = index;
//根据父节点下标,先找到左子树下标
int parent = (child-1)/2;
//如果child=0,说明child已经是根节点,根节点无父节点
while (child > 0){
//比较当前child和parent之间的大小关系看看是否符合大堆
//再去找下右子树对应的节点
if(array[parent] < array[child]){//child合法并且如果右子树大于根
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
} else{
//当前child和parent的关系符合大堆要求,调整完毕
break;
}
//下次循环之前,需要先更新parent和child
child = parent;
parent = (child-1) / 2 ;
}
}
public Integer poll(){
if(size <= 0){
return null;
}
int ret = array[0];
//如何删除队首元素(根节点)
//1、把最后一个元素的值填入到0号元素
array[0] = array[size-1];
//2、删除最后一个元素
size--;
//3、从0下标开始向下调整
shifDown(array,size,0);
return ret;
}
private void shifDown(int[] array,int size,int index){
int parent = index;
//根据父节点下标,先找到左子树下标
int child = 2 * parent + 1;
while (child < size){
//再去找下右子树对应的节点
if(child + 1 < size && array[child + 1] > array[child]){//child合法并且如果右子树大于根
child = child + 1;
}
//child经过上面的条件,指向左右子树中较大的一个
//拿刚刚child位置的元素和parent位置进行对比,看是否符合大堆要求
//如果不符合大堆要求(child位置的元素比parent位置的元素大)交换child和parent位置的元素
if(array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else{
//当前child和parent的关系符合大堆要求,调整完毕
break;
}
//下次循环之前,需要先更新parent和child
parent = child;
child = 2 * parent + 1;
}
}
public Integer peek(){
if(size == 0){
return null;
}
return array[0];
}
public boolean isEmpty(){
return size == 0;
}
public static void main(String[] args) {
MyPriority myPriorityQueue = new MyPriority();
myPriorityQueue.offer(5);
myPriorityQueue.offer(6);
myPriorityQueue.offer(9);
myPriorityQueue.offer(1);
myPriorityQueue.offer(3);
myPriorityQueue.offer(0);
myPriorityQueue.offer(7);
while (!myPriorityQueue.isEmpty()){
Integer cur = myPriorityQueue.poll();
System.out.print(cur+" ");
}
}
}
结果:
9 7 6 5 3 1 0
优先队列(小堆)
只要把上述的大堆代码稍作修改即可
topK问题
从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。
就是在一个数组中把较大的前K个数输出
把N个元素建个堆,循环poll k次
如果把N个元素建堆,然后出队列N次就得到一个有序序列
Java库中的优先队列
标准库中的优先队列默认的是小堆,定义了“值越小,优先级越高”
import java.util.PriorityQueue;
public class Test {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.offer(9);
queue.offer(5);
queue.offer(2);
queue.offer(0);
queue.offer(3);
queue.offer(4);
queue.offer(7);
while (!queue.isEmpty()){
Integer cur = queue.poll();
System.out.print(cur+" ");
}
}
}
可以通过手动定义一个比较器对象改变建堆的方式,借助比较器对象。
匿名内部类:
java8中lambda表达式
import java.util.Comparator;
import java.util.PriorityQueue;
public class Test {
static class MyComp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
//如果认为01优先级比o2高,先出o1 compare返回<0的整数
//如果认为02优先级比o1高,先出o2 compare返回>0的整数
//如果一样,返回0
return o2 - o1;
}
}
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>(new MyComp());
queue.offer(9);
queue.offer(5);
queue.offer(2);
queue.offer(0);
queue.offer(3);
queue.offer(4);
queue.offer(7);
while (!queue.isEmpty()){
Integer cur = queue.poll();
System.out.print(cur+" ");
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/152996.html