堆--优先级队列(直接揭露优先级队列的神秘的面纱)

本文围绕优先级队列展开,介绍其概念,指出在有优先级场景下的应用。阐述Java中PriorityQueue的使用及异常处理,说明其底层用堆实现,介绍堆的定义、性质和创建。还给出用堆模拟优先级队列的实现,最后针对一亿数据找1000个最大数字的top-K问题给出内存够和不够的两种方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.优先级队列(概念)

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。

1.1java中的优先级队列(PriorityQueue)

常用方法
在这里插入图片描述
通过代码来感受一下


import java.util.PriorityQueue;

public class Test0606_1 {
    public static void main(String[] args) {

        int[] arr={4,3,6,1,7,8};
        //构建一个优先级队列对象
        PriorityQueue<Integer> queue =new PriorityQueue<>();
        for(int x:arr){
            queue.offer(x);
        }
        while (!queue.isEmpty()){
            Integer a= queue.poll();
            System.out.println(a);
        }    
    }
}

运行结果:
在这里插入图片描述
从结果可以看出,它内部已经按照一定的优先级给元素排好序了。
再来看一段代码


import java.util.PriorityQueue;

class girl {
    String name;
    int age;
    int money;
    int face;

    public girl(String name, int age, int money, int face) {
        this.name = name;
        this.age = age;
        this.money = money;
        this.face = face;
    }

    @Override
    public String toString() {
        return "girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", money=" + money +
                ", face=" + face +
                '}';
    }
}


public class Test0606_1 {
    public static void main(String[] args) {
    girl[] girls={
            new girl("静静",18,15,100),
            new girl("甜甜",25,200,50),
            new girl("富婆",40,150,5)
    };
    PriorityQueue<girl> queue=new PriorityQueue<>();
        for (girl g:girls){
                queue.offer(g);
                }
                while (!queue.isEmpty()){
                girl g= queue.poll();
                System.out.println(g);
                }
    }
}

运行结果:
在这里插入图片描述
出现异常了,原因其实也不难发现,它内部是无法直接你传入的对象来进行比较的,可以看源码的出结论:
在这里插入图片描述
在第二次插入对象时,它会进行向上调整,此时可以从源代码中看出,插入的对象需要实现它的Comparable接口的,由于刚才没有实现,所以才会抛出异常!
修改改后的代码为


import java.util.PriorityQueue;

class girl implements Comparable<girl>{
    String name;
    int age;
    int money;
    int face;

    public girl(String name, int age, int money, int face) {
        this.name = name;
        this.age = age;
        this.money = money;
        this.face = face;
    }

    @Override
    public String toString() {
        return "girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", money=" + money +
                ", face=" + face +
                '}';
    }

    @Override
    public int compareTo(girl o) { //指定的排序规则
        return  o.money-this.money;
    }
}


public class Test0606_1 {
    public static void main(String[] args) {
    girl[] girls={
            new girl("静静",18,15,100),
            new girl("甜甜",25,200,50),
            new girl("富婆",40,150,5)
    };
    PriorityQueue<girl> queue=new PriorityQueue<>();
        for (girl g:girls){
                queue.offer(g);
                }
        System.out.println("通过钱来排序:");
                while (!queue.isEmpty()){
                girl g= queue.poll();
                System.out.println(g);
                }
    }
}

运行结果:
在这里插入图片描述
补充
除了实现compareble接口之外,还可以通过实现comparator接口来实现一些复杂的排序,在构造优先级队列时传入比较器实例对象即可(comparator的实现类!)代码如下:


import java.util.Comparator;
import java.util.PriorityQueue;

class girl {
    String name;
    int age;
    int money;
    int face;

    public girl(String name, int age, int money, int face) {
        this.name = name;
        this.age = age;
        this.money = money;
        this.face = face;
    }

    @Override
    public String toString() {
        return "girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", money=" + money +
                ", face=" + face +
                '}';
    }
}

class compareTest implements Comparator<girl>{

    @Override
    public int compare(girl o1, girl o2) { //性价比复合比较规则
        
        return (o2.face*6+o2.money*2+o2.age*2) -(o1.face*6+o2.age*2+o2.money*2);
    }
}


public class Test0606_1 {
    public static void main(String[] args) {
    girl[] girls={
            new girl("静静",18,15,100),
            new girl("甜甜",25,200,50),
            new girl("富婆",40,150,5)
    };
    PriorityQueue<girl> queue=new PriorityQueue<>(new compareTest());
        for (girl g:girls){
                queue.offer(g);
                }
        System.out.println("性价比最高的女朋友排序:");
                while (!queue.isEmpty()){
                girl g= queue.poll();
                System.out.println(g);
                }
    }
}

运行结果为:
在这里插入图片描述

1.2堆

JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式
存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

性质
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。

1.3堆的创建


import java.util.Arrays;

public class HeapTest {
    //向下调整(大堆)
    private static void shiftDown(int[] arr,int size,int index){

        int parent =index;
        int child=2*parent+1; //左孩子下标
        while (child<size){ //循环调整
            if(child+1<size && arr[child+1]>arr[child]){
                child=child+1;
            }
            if(arr[parent]<arr[child]){
                int tem=arr[parent];
                arr[parent]=arr[child];
                arr[child]=tem;
            }else {
                break;
            }
            parent=child; //更新引用的位置,进行下次循环
            child=2*parent+1;
        }
    }
    //向上调整(大堆)
    public static void shiftUp(int[] arr, int size, int index) {
        int child = index;
        int parent = (child - 1) / 2;
        // 如果 child 为 0, 说明已经调整到最上面了
        while (child > 0) {
            if (arr[parent] < arr[child]) {
                // 不符合大堆的要求
                // 交换两个元素
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
            } else {
                break;
            }
            child = parent;
            parent = (child - 1) / 2;
        }
    }

    public static void main(String[] args) {
        int[] arr={4,2,5,7,8,100};
//        向下调整建堆,只能从后往前遍历
//        for(int i=arr.length-1;i>=0;i--){
//            shiftDown(arr,arr.length,i);
//
//        }
//
        //向上调整建堆,需要从前往后遍历
        for(int i=0;i<arr.length;i++){
            shiftUp(arr,arr.length,i);
        }
        System.out.println(Arrays.toString(arr));

    }

}

运行结果:
在这里插入图片描述
从运行结果可知,完全符合建大堆的预期结果!
向下调整原理图
在这里插入图片描述

2.使用堆模拟优先级队列的实现(MyPriorityQueue)


class girl1 implements Comparable<girl1>{
    String name;
    int age;
    int money;
    int face;

    public girl1(String name, int age, int money, int face) {
        this.name = name;
        this.age = age;
        this.money = money;
        this.face = face;
    }

    @Override
    public String toString() {
        return "girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", money=" + money +
                ", face=" + face +
                '}';
    }

    @Override
    public int compareTo(girl1 o) {
        return this.money-o.money;
    }
}
public class MyPriorityQueue<T> {
    private T[] arr=null;
    private int size;
    private int capacity=10; //默认的最大容量

    public MyPriorityQueue() {
       arr=(T[])new Object[capacity];
       size=0;
    }
    //向下调整维护堆(大堆)
    private  void shiftDown(int k, T x){
        Comparable<? super T> key = (Comparable<? super T>)x;
        int parent=k;
        int child=2*parent+1; //左孩子下标
        while (child<size){ //循环调整
            Object c=arr[child]; //取左孩子
            int right=child+1;  //右孩子坐标
            //如果右孩子的大小小于队列的大小,说明有右孩子,否就没有
            //左右孩子取大的一个,移动下标
            if(right<size && ((Comparable<? super T>) c).compareTo((T)arr[right])<0){
                child=child+1;
            }
            //若父亲的大小比孩子小,不满足大堆的要求,就交换
            if(key.compareTo((arr[child]))<0){
                Object tem=arr[parent];
                arr[parent]=arr[child];
                arr[child]=(T)tem;
            }else {
                break; //堆调整完毕
            }
            parent=child; //更新引用的位置,进行下次循环
            child=2*parent+1;
        }
    }
    //向上调整建堆(大堆)
    public  void shiftUp(int k, T x) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        int child = k;
        int parent = (child - 1) / 2;
        // 如果 child 为 0, 说明已经调整到最上面了
        while (child > 0) {
            Object e = arr[parent];
            //如果比父亲还大,交换父亲与孩子
            if (key.compareTo((T)e)>=0) {
                // 不符合大堆的要求
                // 交换两个元素
                Object tmp = e;
                arr[parent] = arr[child];
                arr[child] = (T)tmp;
            } else {
                break;
            }
            //更新引用的位置,进行下次循环
            child = parent;
            parent = (child - 1) / 2;
        }
    }
   //入优先级队列
    public void offer(T var){
        if(var==null){
            throw new NullPointerException();
        }
        if(size>=capacity){ //扩容
            T[] newArr=(T[]) new Object[capacity*2];
            for(int i=0;i<arr.length;i++){
                newArr[i]=arr[i];
            }
            arr=newArr; //更新引用的位置,维护原来的数组
            arr[size++]=var;
            shiftUp(size-1,arr[size-1]);
             return;
        }
        arr[size++]=var;
        shiftUp(size-1,arr[size-1]);
        return;

    }
    //出优先级队列
    public T  poll(){
       if(size==0){
           return null;
       }
       Object result=arr[0];

        // 交换 0 号元素和 size - 1 号元素
        Object tem=arr[0];
        arr[0]=arr[size-1];
        arr[size-1]=(T) tem;
        // size--, 把最后的元素干掉
        size--;

        // 从 0 号元素开始, 往下进行向下调整
        shiftDown(0,arr[0]);
        return (T)result;
    }
  //查看优先级队列的队首元素
    public T peek(){
        if(size==0){
           return null;
        }
        return arr[0];
    }
    //判断优先级队列是否为空
    public boolean isEmpty(){
        return size<=0;
    }

    public static void main(String[] args) {
        MyPriorityQueue<girl1> queue = new MyPriorityQueue<>();
        girl1[] girls={
                new girl1("静静",18,15,100),
                new girl1("甜甜",25,200,50),
                new girl1("富婆",40,150,5)
        };
        for (girl1 g:girls){
            queue.offer(g);
        }
        System.out.println("向钱看起:");
        while (!queue.isEmpty()){
            girl1 g= queue.poll();
            System.out.println(g);
        }
        System.out.println("分界线=====================");
        MyPriorityQueue<Integer> queue1 = new MyPriorityQueue<>();
        int[] arr1={5,2,6,8,1,9,10,16,12,11,30,90};
        for(int i=0;i<arr1.length;i++){
            queue1.offer(arr1[i]);
        }
        System.out.println("Integer底层自己的排序规则:");
        while (!queue1.isEmpty()){
         int result =queue1.poll();
         System.out.print(result+"  ");
     }
    }

}

运行结果:
在这里插入图片描述

3.top-K问题

假设有一亿个数据(int),==》400M要求找出其中1000个最大的数字?
方案一:在内存够的情况下,我们可以直接针对这一亿个数据建大堆,接下来循环1000次取堆顶元素和删除堆顶元素操作。

方案二:假设内存不够的话,我们就在内存中建立一个1000的小堆,将数据从磁盘中读取出来和堆顶元素比较,若比堆顶元素还小,直接抛弃,若 比堆顶元素大,就替换堆顶元素,重复遍历完一亿数据后,内存里的数据就是其中1000个最大的数字。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值