堆的概念
- 堆逻辑上是一棵完全二叉树;
- 堆物理上是保存在数组中;
- 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;
- 反之,则是小堆,或者小根堆,或者最小堆;
- 堆的作用是快速找集合中的最值。
下标关系
已知双亲 (parent) 的下标,则:左孩子 (left) 下标 = 2 * parent + 1;右孩子 (right) 下标 = 2 * parent + 2;已知孩子(不区分左右) (child) 下标,则:双亲(parent)下标 = (child - 1) / 2 ;
初始条件
public int[] elem;
public int usedSize;
public TestHeap(){
this.elem=new int[10];
}
向下调整函数shiftDown
以转换为大堆为例
/** *向下调整函数的实现 * parent:每个树的根节点 * len:每棵树的调整结束位置 10 * **/向下调整就是每次交换完之后会沿着当前这个路径向下检测
1.从最后一个子树出发
2.每颗子树都是向下调整的
如何调整为大根堆
问题:
1.如何找到最后一颗子树?
父亲节点:(child-1)/2;
此时节点:((len-1)-1)/2
2.parent--就可以遍历完每一棵子树了
3.最主要的问题就是写一个函数进行向下调整
4.每棵树的调整结束位置,如何判定?
每棵树调整的结束位置实际都是一样的len。
//此时的时间复杂度:每层的节点个数*每层向下检测的层数O(n) public void shiftDown(int parent,int len){ int child=2*parent+1; //最起码是有左孩子的,至少有一个孩子 while(child<len){ if(child<len&&elem[child]<elem[child+1]){//如果只有左孩子,那左孩子就是较大值,不用再++,否则空指针异常 //child是用来保证较大孩子的下标 child++; } if(elem[child]>elem[parent]){ int temp=elem[child]; elem[child]=elem[parent]; elem[parent]=temp; //交换完之后需要继续向下检测 parent=child; child=2*parent+1; }else if(elem[child]<elem[parent]){ break; } } }
createHeap函数
public void createHeap(int [] array){
for (int i = 0; i < array.length; i++) {
elem[i]=array[i];
usedSize++;
}
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
shiftDown(parent,usedSize);
}
}
PriorityQueue的中常用方法模拟实现
offer();
isFull()
public boolean isFull(){ return usedSize==elem.length; }
shifUp()向上调整
public void shifUp(int child){ int parent=(child-1)/2; while(child>0){ if(elem[child]>elem[parent]){ int temp=elem[child]; elem[child]=elem[parent]; elem[parent]=temp; child=parent; parent=(child-1)/2; }else { break; } parent=(child-1)/2; } }
offer()
//向上调整 public void offer(int val){ if(isFull()){ //扩容 elem= Arrays.copyOf(elem,2*elem.length); } elem[usedSize++]=val; //注意: shifUp(usedSize-1); }
poll();
/**** * 关于出队列:每次保证出的是最大或者最小的元素 * 交换0下标和最后一个下标的元素 * 调整0下标这棵树 * **/isEmpty()
public boolean isEmpty(){ return usedSize==0; }
poll()
public int poll(){ if(isEmpty()){ throw new RuntimeException("优先级队列为空"); } int tmp=elem[0]; elem[0]=elem[usedSize-1]; elem[usedSize-1]=tmp; usedSize--; shiftDown(0,usedSize); return tmp; }
peek();
/** * 返回队首元素 * **/ public int peek(){ if(isEmpty()){ throw new RuntimeException("优先队列为空"); } return elem[0]; }
堆排序
/**
* 堆排序
* 从小到大排序
* **/
/***
* 从小到大排序:
* 1.调整为大根堆
* 2.0下标与最后一个未排序的元素进行交换
* 3.end--
* */
public void heapSort(){
int end=usedSize-1;
while(end>0){
int temp=elem[0];
elem[0]=elem[end];
elem[end]=temp;
shiftDown(0,end);
end--;
}
}
topk问题
/** * 求数组中前k个最大元素 间一个小堆,大小是k,堆顶元素就是第k大的元素; * 求数组中前k个最小的元素 建立一个大堆,大小是k,堆顶元素就是第k小的; * *///这里求前k个最小的元素: public static int[] topK(int[] array,int k){ //创建一个大小为K的大根堆 //java中对象的比较 PriorityQueue<Integer> maxHeap=new PriorityQueue<>(k, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2-o1; } }); //2.遍历数组当中的元素,前k个元素放在队列当中, for (int i = 0; i < array.length; i++) { if(maxHeap.size()<k){ maxHeap.offer(array[i]); }else{ //3.从k+1个元素开始每个元素需要和堆顶元素进行比较 int top=maxHeap.peek(); if(top>array[i]){ //先弹出 maxHeap.poll(); //后存入 maxHeap.offer(array[i]); } } } int[] tmp=new int[k]; for (int i = 0; i < k; i++) { tmp[i]=maxHeap.poll(); } return tmp; }