1.概念
1.1优先级队列:
能够提供两个基本操作1️⃣返回最高优先级对象2️⃣添加新的对象
底层是顺序存储的二叉树(完全二叉树--调整-》堆)
1.2堆:
- 小根堆:根节点小于左右孩子的完全二叉树
- 大根堆: 大于
其存储结构存储的堆层序遍历的值,并且下标的规律:
- 已知孩子节点下标为i,那么父亲节点下标为(i - 1)/2;
- 已知父亲节点下标为i 那么左:2*i+1 右:2*i+2
2.堆的创建(模拟实现)
2.1堆向下调整
思路:
1.找到最后一棵子树的根节点p = (len-1 -1)/2,p--就能把每颗子树都调整结束
2.如何向下调整呢?
1️⃣引入c左孩子下标,比较左右孩子,c一定是最大值下标
2️⃣比较c与p,若c>p 交换 ;若c<p 打断
3️⃣最重要的一步:让p = c,c=2p+1,向下调整
public class TestHeap{
private int[] elem;
private int usedSize;
public TestHeap(){//构造方法
this.elem = new int[10];
}
public void initHeap(int[] array){
for(int i = 0;i< array.length;i++){
elem[i] = array[i];
usedSize++;
}
}
public void createHeap(){
for(int parent = (usedSize -1-1)/2;parent >= 0;parent--){//找到根节点
shiftDown(parent,usedSize);
}
}
//每一颗子树都向下调整
private void shiftDown(int parent, int usedSize) {
int child = (2*parent)+1;//左孩子下标
while(child < usedSize){
if(child+1 < usedSize && elem[child] <elem[child+1]){
child++;
}
//child一定是左右孩子最大值的下标
if(elem[child] > elem[parent]){
swap(child,parent);
parent = child;
child = 2*parent+1
}else{
//已经是大根堆了
break;
}
}
}
private void swap(int i,int j){
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
}
2.2.向下建堆的时间复杂度
当我们采用向下调整去建堆的时候,时间复杂度为O(n)
公式须知
1.等比数列求和公式 a1(1-q^n)/(1-q)
2.n个节点= 2^h-1 (h是树的高度)
3.h = log2(n+1)
3.堆的插入与删除
3.1堆的插入
问题:
1.80往哪里放?
2.放进去怎么放到合适的位置?向上调整
思路:
1.插入前先判断是否需要扩容
2.把要插入的元素放在最后一个位置
3.向上调整
1️⃣确定最后一个根节点p
2️⃣比较c和p,若c>p 交换 否则 打断
3️⃣让c=p,p=(c-1)2 向上调整,如此循环
/*插入元素 向上调整*/
public void offer(int val){
if(isFull()){
this.elem = Arrays.copyOf(elem,2*elem.length);
}
this.elem[usedSize] = val;//把要插入的元素放在最后一个位置
//向上调整
shiftUp(usedSize);
usedSize++;
}
private void shiftUp(int child){
int p = (child-1)/2;
while(child > 0){
if(elem[child] > elem[p]){
swap(child,p);
child = p;
p = (child-1)/2;
}else{
break;
}
}
}
private boolean isFull(){
return usedSize == elem.length;
}
}
向上调整的复杂度比向下调整高,向上调整建堆的时间复杂度为O(N*logN)
向下排序 复杂度是O(n)
向上排序 复杂度O(nlogn)
3.2堆的删除
堆的删除一定删除的是堆顶元素
思路:
1. 先保存堆顶元素的数值,将堆顶元素和堆中最后一个元素交换
2. 将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整
/*删除元素*/
public int poll(){
int tmp = elem[0];
swap(0,usedSize-1);
usedSize--;
shiftDown(0,usedSize);
return tmp;
}
测试代码:
public class Test {
public static void main(String[] args) {
TestHeap testHeap = new TestHeap();
int[] array = {27,15,19,18,28,34,65,49,25,37};
testHeap.initHeap(array);
testHeap.createHeap();
testHeap.offer(80);
int val = testHeap.poll();
System.out.println(val);
}
}
3.3习题
已知小根堆为8,15,10,21,34,16,12,删除关键字8之后需重建堆,在此过程中,关键字之间的比较次数是()
A: 1 B: 2 C: 3 D: 4
答案:C
1.15 和10比较一次
2.10和12比较一次
3.12和16比较一次
4.常用接口介绍
4.1PriorityQueue的特性
1. 使用时必须导入PriorityQueue所在的包,即:
import java.util.PriorityQueue;
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常(类型转换异常,不能够转换成Comparable类型)
3. 不能插入null对象,否则会抛出NullPointerException(空指针异常)
4. 插入和删除元素的时间复杂度为O(log2N) 【树的高度】
5.PriorityQueue底层使用了堆数据结构
6. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
4.2PriorityQueue常用接口介绍(重在理解)
4.2.1优先级队列的构造
- 创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
- 创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueueInteger> q2 = new PriorityQueue<>(100);
- 用一个集合来创建优先级队列(PriorityQueue(Collection<?extends E>c) 只要实现Collection接口的所有对象都能传)
ArrayListInteger> list = new ArrayList<>();
list.add(4);
list.add(3);
list.add(2);
list.add(1);
// 用ArrayList对象来构造一个优先级队列的对象
// q3中已经包含了三个元素
PriorityQueueInteger> q3 = new PriorityQueue<>(list);
System.out.println(q3.size());
System.out.println(q3.peek());
默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
4.2.2插入/删除/获取优先级最高的元素
4.2.3优先级队列的扩容方式:
- 如果容量小于64时,是按照oldCapacity的2倍方 式 扩 容 的
- 如果容量大于等于64 ,是按照oldCapacity的1.5倍方 式 扩 容 的
- 如果容量超过MAX_ARRAY_SIZE(整数的最大值-8), 按 照 MAX _ARRAY_ SIZE 来 进 行 扩 容(按照整数的最大值来算,也就是多加8个)
5.堆的应用✨
5.1.oj例题
import java.util.PriorityQueue;
class Solution{
public int[] smallestK(int[] array,int k){
int[] ret = new int[k];
if(array == null||k<=0){
return ret;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(array.length);
for(int i = 0;i< array.length;i++){
priorityQueue.offer(array[i]);
}
for(int i= 0;i<k;i++){
ret[i] =priorityQueue.poll();
}
return ret;
}
}
5.2Top-K问题
求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大
做法1:把数组排序 排序之后 取出前10个最大的 数据量非常大的时候,你无法在内存当中排序
做法2:把所有数据 放到优先级队列 出队k次不就好了?数据量非常大的时候,你无法把所有数据放到优先级队列!
求前k个最大的数据建议的做法:
1.前k个数据,小根堆k,堆顶是这k个元素里面的最小值
2.把剩下的元素 每次和堆顶元素比较,如果比堆顶大 那么出队 然后把当前数组元素存放到大小为k的堆当中(i下标的元素如果大于堆顶元素 那么说明堆顶元素一定不是前k个最大的元素之一)
时间复杂度:O(K*logK)+O((N-K)*logK)=O(N*logK)
import java.util.PriorityQueue;
class Solution{
public int[] naxLestK(int[] array,int k){
int[] ret = new int[k];
if(array == null||k<=0){
return ret;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(array.length);
for(int i = 0;i<k;i++){
priorityQueue.offer(array[i]);
}
for(int i = k;i<array.length;i++){
int top = priorityQueue.peek();
if(array[i]>top){
priorityQueue.poll();
priorityQueue.offer(array[i]);
}
}
for(int i = 0;i<k;i++){
ret[i] = priorityQueue.poll();
}
return ret;
}
}
求前K个最小的数据 要建大根堆!
o1-o2小根堆
o2-o1大根堆
import java.util.Comparator;
import java.util.PriorityQueue;
class IntCmp implements Comparator<Integer>{
public int compare(Integer o1,Integer o2){
return o2.compareTo(o1);//重写compare方法
}
}
class Solution{
public int[] smallestK(int[] array,int k){
int[] ret = new int[k];
if(array == null||k<=0){
return ret;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());
for(int i = 0;i<k;i++){
priorityQueue.offer(array[i]);
}
for(int i = k;i<array.length;i++){
int top = priorityQueue.peek();
if(array[i]<top){
priorityQueue.poll();
priorityQueue.offer(array[i]);
}
}
for(int i = 0;i<k;i++){
ret[i] = priorityQueue.poll();
}
return ret;
}
}
找第K大的元素 建的是小根堆,就是堆顶元素
找第K小的元素 建的是大根堆,就是堆顶元素
5.3.堆排序
应用背景:
一组数据:[27,15,19,18,28,34,65,49,25,37] 将其从小到大进行排序(对原来的数据进行排序!!!不可以让其变成小根堆,然后一个一个取数据)
思路:
1.调整为大根堆
2.让第一个元素和最后一个未排序的元素进行交换
public void heapSort(){
int end = usedSize-1;
while(end>0){
swap(0,end);
shiftDown(0,end);
end--;
}
}
5.4习题
一组记录排序码为 (5 11 7 2 3 17), 则利用堆排序方法建立的初始堆为 ()A: (11 5 7 2 3 17) B: (11 5 7 2 17 3) C: (17 11 7 2 3 5)D: (17 11 7 5 3 2) E: (17 7 11 3 5 2) F: (17 7 11 3 2 5)答案:C解析:没说升序还是降序,根据选项,5不在前,说明是大根堆,所以本题考察的是如何建大根堆
6.java对象的比较
- Comparable(对类的侵入性比较强,写好了就不能改了)不如Comparator(可以根据自己的逻辑实时调整思路)灵活,共同点是两者返回的都是整数,可以比较大小
- equals 返回值是boolean 比较这两个对象是否相同(必须要重写equals方法,否则还是和==一样,比较的是地址)
import java.util.Objects;
class Student{
public String name;
//右键generate equals and hashCode可自动生成
public boolean equals(Object obj) {
Student o = (Student)obj;
return o.name.equals(this.name);
}
}
public class Test{
public static void main(String[] args) {
Student student1 = new Student();
student1.name = "zhangsan";
Student student2 = new Student();
student2.name = "zhangsan";
System.out.println(student1==student2);//输出结果为false
System.out.println(student1.equals(student2));//若是不重写equals,还是false
}
}