1:堆
堆:堆是一棵
完全二叉树
大根堆:完全二叉树,根节点比左右孩子都大
小根堆:完全二叉树,根节点比左右孩子都小
下图是一个小根堆:层序的规则采用顺序的方式来高效存储,使用数组来存储层序遍历的结果
- 已知孩子节点下标i,父亲节点下标为 ( i − 1 ) / 2 (i-1)/2 (i−1)/2
- 已知父亲节点下标i,
- 左孩子: 2 ∗ i + 1 2*i+1 2∗i+1
- 右孩子: 2 ∗ i + 2 2*i+2 2∗i+2
2:大根堆/小根堆
如何将一个完全二叉树调整为大根堆/小根堆?
//堆的成员变量,一个数组,一个记录节点数量的int型变量
public int elem[];
public int usedSize;
//构造方法初始化数组为长度为10的数组
public MyHeap() {
this.elem = new int[10];
}
大根堆创建: 时间复杂 O ( N ) O(N) O(N)
public void createBigHeap(int [] array){
//将传入的数组的值依次放到堆数组中
for(int i=0;i<array.length;i++){
elem[i]=array[i];
this.usedSize++;
}
//可以直到最后一个节点下标,即可知道最后一个有孩子的父节点的下标
//如果一颗完全二叉树要成为大根堆,那么它的每一棵子树都必须是大根堆
for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
//每次调用向下调整,parent变化,而usedSize不变
shiftDown(parent, usedSize);
}
}
/**
*调整为大根堆
* @param parent 每次调整的根节点的下标
* @param len 调整的边界,如果超过则表示已经调整完毕
*/
public void shiftDown(int parent,int len){
int child=(parent*2+1);//左孩子下标
//判断左孩子下标是否超过边界
while (child<len){
//如果左孩子下标+1还是再边界内则代表它还有右孩子
//如果右孩子的值大,则child变为右孩子下标
if(child+1<len && elem[child]<elem[child+1]){
child++;
}
//如果孩子节点的值大于父亲节点则交换位置
if(elem[child]>elem[parent]){
int tmp=elem[parent];
elem[parent]=elem[child];
elem[child]=tmp;
//父亲节点向下移动
parent=child;
child=parent*2+1;
}else {
break;
}
}
}
小根堆创建: 基本上和大根堆相似,时间复杂度 O ( N ) O(N) O(N)
public void createSmallHeap(int [] array){
for(int i=0;i<array.length;i++){
elem[i]=array[i];
this.usedSize++;
}
for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
shiftDownMine(parent, usedSize);
}
}
/**
* @param parent 每次调整的根节点的下标
* @param len 调整的边界,如果超过则表示已经调整完毕
*/
public void shiftDownMine(int parent,int len){
int child=(parent*2+1);//左孩子下标
while (child<len){
//如果右孩子存在,且右孩子比左孩子小,则child为右孩子下标
if(child+1<len && elem[child]>elem[child+1]){
child++;
}
//如果孩子节点小于根节点则交换
if(elem[child]<elem[parent]){
int tmp=elem[parent];
elem[parent]=elem[child];
elem[child]=tmp;
parent=child;
child=parent*2+1;
}else {
break;
}
}
}
3:插入/删除元素
大(小)根堆插入元素:
向大根堆中插入元素,先要判断是否要对数组进行扩容,再将插入的元素放入最后一个位置,进行向上调整
/**
* 插入元素到堆中
* @param val
*/
public void push(int val){
//二倍扩容
if(isFull()){
this.elem= Arrays.copyOf(elem, 2*elem.length);
}
elem[usedSize]=val;
usedSize++;
//孩子下标最大值
shiftUp(usedSize-1);
}
//向上调整
public void shiftUp(int child){
//找到最后一个元素的父节点下标
int parent=(child-1)/2;
//当父节点下标在边界内
while (parent>=0){
//如果孩子节点的值大于父亲节点则交换
//交换后的父亲节点作为孩子节点继续向上调整
if(elem[child]>elem[parent]){
int tmp=elem[parent];
elem[parent]=elem[child];
elem[child]=tmp;
child=parent;
parent=(child-1)/2;
}else {
break;
}
}
}
大(小)根堆删除元素:
从大根堆中删除元素,一定删除的是堆顶元素,删除优先级最高的元素
将堆顶元素和最末尾元素互换位置并删除最后一个元素,将堆顶元素进行向下调整
/**
* 删除堆顶元素
*/
public void pop(){
if(isEmpty()){
throw new RuntimeException("堆为空");
}
this.elem[0]=this.elem[usedSize-1];
usedSize--;
shiftDown(0, usedSize);
}
public void shiftDown(int parent,int len){
int child=(parent*2+1);//左孩子
while (child<len){
if(child+1<len && elem[child]<elem[child+1]){
child++;
}
if(elem[child]>elem[parent]){
int tmp=elem[parent];
elem[parent]=elem[child];
elem[child]=tmp;
parent=child;
child=parent*2+1;
}else {
break;
}
}
}
4:堆排序
堆排序:
升序
(从小到大):创建大根堆
降序
(从大到小):创建小根堆
以大根堆为例,排序时,将最大元素也就是堆顶元素和最后一个元素交换位置,然后忽略最后一个元素也就是最大的元素,进行向下调整,以此类推,直到剩余堆顶一个元素即可
//堆排序,从小到大,升序,需要一个大根堆
public void heapSort(){
int end=usedSize-1;
int tmp=0;
while (end>0){
tmp=elem[0];
elem[0]=elem[end];
elem[end]=tmp;
shiftDown(0, end);
end=end-1;
}
}
top-k问题: 时间复杂度: N ∗ l o g 2 ( K ) N*log2(K) N∗log2(K)
找最大
的前k个,则建立为k的小根堆
找最小
的前k个,则建立为k的大根堆
5:Java的优先级队列
Java中的堆(优先级队列)
public class PriorityQueue<E> extends AbstractQueue<E> implements java.io.Serializable
PriorityQueue 底层为小根堆,且插入的数据类型必须可以进行大小比较,不然第二次插入时会报错。
PriorityQueue 的初始化大小为11
//构造方法 public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } //常量为11 private static final int DEFAULT_INITIAL_CAPACITY = 11;
扩容机制:
小于64则
2倍扩容+2
,大于64返回1.5倍扩容
如果扩容后超过MAX_ARRAY_SIZE则按照MAX_ARRAY_SIZE扩容
private void grow(int minCapacity) { int oldCapacity = queue.length; // Double size if small; else grow by 50% int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // overflow-conscious code if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); }
Java的优先级队列底层为最小堆,如何更改最小堆为最大堆?
PriorityQueue:底层的两个方法是针对插入元素进行比较的,两种比较方式分别是
Comparable
和Comparator
//针对重写Comparable的比较规则 private void siftUpComparable(int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (key.compareTo((E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = key; } //针对重写Comparator的比较规则 private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }
Java中comparable和comparator接口详解
comparator:
小根堆实例
public static void main1(String[] args) { int[] array=new int[]{1,2,3,4,5,6}; PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { //o1-o2也是默认的小根堆 return o1-o2; } }); //找出最大的3个数 建立小根堆 放入数组前3个数字 for (int i = 0; i < 3; i++) { priorityQueue.offer(array[i]); } //从3下标开始遍历整个数组 for (int i = 3; i < array.length; i++) { //如果当前数字大于堆顶元素则将堆顶元素删除,并放入该元素 if(array[i]>priorityQueue.peek()){ priorityQueue.poll(); priorityQueue.offer(array[i]); } } System.out.println(priorityQueue);//456 }
大根堆实例
public static void main(String[] args) { int[] array=new int[]{1,2,3,4,5,6}; PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { //o2-o1为大根堆 return o2-o1; } }); //找出最小的3个数 建立大根堆 放入数组前3个数字 for (int i = 0; i < 3; i++) { priorityQueue.offer(array[i]); } //从3下标开始遍历整个数组 for (int i = 3; i < array.length; i++) { //如果当前数字小于堆顶元素则将堆顶元素删除,并放入该元素 if(array[i]<priorityQueue.peek()){ priorityQueue.poll(); priorityQueue.offer(array[i]); } } System.out.println(priorityQueue);//312 }
comparable:
小根堆实例
class Data implements Comparable<Object>{ int age; public Data(int age) { this.age = age; } @Override public int compareTo(Object o) { if(o instanceof Data){ //this-O为小根堆也是默认的 return this.age-((Data) o).age; } return 0; } } public static void main(String[] args) { Data data1=new Data(1); Data data2=new Data(2); Data data3=new Data(3); Data data4=new Data(4); List<Data> list=new ArrayList<>(); list.add(data1); list.add(data2); list.add(data3); list.add(data4); PriorityQueue<Data> priorityQueue=new PriorityQueue<>(); priorityQueue.offer(data1); priorityQueue.offer(data2); //找出age最大的两个Data对象 for(int i=2;i<list.size();i++){ if(list.get(i).age>priorityQueue.peek().age){ priorityQueue.poll(); priorityQueue.offer(list.get(i)); } } while (!priorityQueue.isEmpty()){ System.out.print(priorityQueue.poll().age+" ");//3 4 } }
大根堆实例
class Data implements Comparable<Object>{ int age; public Data(int age) { this.age = age; } @Override public int compareTo(Object o) { if(o instanceof Data){ //O-this为大根堆 return ((Data) o).age-this.age; } return 0; } } public static void main(String[] args) { Data data1=new Data(1); Data data2=new Data(2); Data data3=new Data(3); Data data4=new Data(4); List<Data> list=new ArrayList<>(); list.add(data1); list.add(data2); list.add(data3); list.add(data4); PriorityQueue<Data> priorityQueue=new PriorityQueue<>(); priorityQueue.offer(data1); priorityQueue.offer(data2); //找出age最小的两个Data对象 for(int i=2;i<list.size();i++){ if(list.get(i).age<priorityQueue.peek().age){ priorityQueue.poll(); priorityQueue.offer(list.get(i)); } } while (!priorityQueue.isEmpty()){ System.out.print(priorityQueue.poll().age+" ");//2 1 } }
总结:
小根堆 | 大根堆 | |
---|---|---|
Comparator | O1-O2 | O2-O1 |
Comparable | this-O | O-this |