【Java】PriorityQueue优先级队列(堆)+比较器详解

目录

铺垫

建堆

基本公式

调整为大(小)根堆

堆的增加

堆的删除 

 正文:

比较

重写基类的equals()比较方法:

内部比较器——Comparable.compareTo(E o):

外部比较器——Comparator.compare(T o1, T o2):

源码粗略分析

常用构造方法

实现Comparable接口 

实现Comparator接口

改为大根堆

对于基本类型:

对于引用类型:

比较器快速写法


铺垫

优先级队列的底层是一个数组,但这个数组又比较特殊——二叉树通过层序遍历存放到该数组的数据。这样存放的方式就是这种数据结构。

大根堆:如果在堆中,每个根结点的值大于两个孩子结点的值。(对左右孩子无要求)

小根堆:如果在堆中,每个根结点的值小于两个孩子结点的值。(对左右孩子无要求)


建堆

基本公式

调整为大(小)根堆

在一个一维数组中:

1.对于每一个根结点都要调整为大小根堆。

2.调整时,先要找到较大的孩子

3.然后二者交换,继续向下调整该根结点下面的结点。

4.调整时可能会存在越界的情况,孩子结点可能会越界。

    public void createHeap() {
        //建大根堆
        //对每一个根节点使用向下调整算法
        //最后一个孩子 c = length - 1
        //c = 2 * p +1
        //p = (c - 1)/2
        for (int parent = (array.length - 1 -1) / 2; parent >= 0; parent--) {
            shiftDown(parent, usedSize);
        }
    }
    //向下调整
    private void shiftDown(int parent, int length) {
        int child = 2 * parent + 1;
        while (child < length) {
               //先判断孩子是否越界
            if (child < length - 1 && array[child] < array[child + 1]) {
                child++;
            }
            if (array[parent] < array[child]) {
                swap(array, parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

    private void swap(int[] array, int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

堆的增加

在堆里添加完数据后,应该还要保证堆为大(小)根堆。

1.判断堆是否满了,满了扩容。

2.把添加的数据放到数组的最后面。

3.找到最后一个结点的父亲结点,向上调整。

4.(建大堆)如果父亲结点比孩子结点小,就调整,然后,父亲结点指向孩子结点,重新计算父亲结点,直到最后一个孩子调整完。

5.useSized++

 

堆的删除 

优先级队列,是对于某些数据优先操作的结构,所以很自然的我们要优先对堆顶数据进行删除。

1.判断堆是否为空,为空抛出异常。

2.堆顶数据与最后一个结点进行交换。

3.对堆顶使用一次向下调整即可恢复成大(小)根堆。

4.useSized--

    public void pop() {
        if (isEmpty()) {
            return;
        }
        swap(array, 0, usedSize - 1);
        usedSize--;
        shiftDown(0, usedSize);
    }

 正文:

有了以上概念,我们就能更容易理解PriorityQueue的源码部分的实现。

比较

在生成大小根堆的时候,数据是通过比较大小来排序的。所以PriorityQueue底层必定有比较数据大小的东西。

重写基类的equals()比较方法

所有的类都是继承Object类的,Object类中就有equals()方法。

重写equals()方法用到这里显然不合适,因为该方法只能比较二者是否想等,无法比较出大小关系

内部比较器——Comparable<E>.compareTo(E o):

Comparable是JDK提供的泛型接口类,源码如下:

//Compareble是java.lang中的接口类,可以直接使用
public interface Comparable<E> {
    // 返回值:
    // < 0: 表示 this 指向的对象小于形参 o 指向的对象
    // == 0: 表示 this 指向的对象等于形参 o 指向的对象
    // > 0: 表示 this 指向的对象大于形参 o 指向的对象
    int compareTo(E o);
}

//对象支持自己与自己比较
//实现了该接口的类就可以自动进行排序
//集合通过Collection.sort()排序,数组通过Arrays.sort()进行排序

该方法可以比较出数据的大小,所以我们可以在定义类的时候实现该接口,重写方法就可以比较。

外部比较器——Comparator<T>.compare(T o1, T o2):

//Comparator是java.util 包中的泛型接口类,使用时必须导入对应的包

public interface Comparator<T> {
// 返回值:
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare(T o1, T o2);
}

//使用Comparator的情况:
//1.没有实现Comparable接口
//2.实现了Comparable接口,但是其中的compareTo方法不符合自己的预期


源码粗略分析

    public static void main(String[] args) {
        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>();
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);
    }

 

若是其他引用类型,就不可以这样offer()了,在offer()第二个数据的时候就会抛异常。

class Student {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }
}

public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>();
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);

        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>();
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
    }
}


常用构造方法

第一种:什么都不传,那么默认空间大小为11,无比较器

第二种:只传空间大小,此时空间大小为所传的值

第三种:只传比较器,下面详解比较器作用


实现Comparable接口 

class Student implements Comparable<Student> {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }

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

public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>();
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);

        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>();
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
        priorityQueue2.offer(new Student(23));
        System.out.println(priorityQueue2);
    }
}

 当实现了Comparable接口后,重写CompareTo方法后,此时就可以比较引用类型了。

此时它应该执行到一下源码部分。

实现Comparator接口

class Student {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class StuCompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.age - o1.age;
    }
}

public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>();
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);

        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>(new StuCompare());
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
        priorityQueue2.offer(new Student(23));
        System.out.println(priorityQueue2);
    }
}

通过自定义的比较器,并且通过传入比较器的构造方法后,我们也可以比较。

此时它应该执行到下面的源码部分。


改为大根堆

因为源码默认建小根堆,如果我们想建大根堆,有了以上源码分析,我们不难发现,关键就在比较器上,我们只需要改变比较规则,就可以建立大根堆。

对于基本类型:

比如Integer,我们无法到源码中改变比较规则,所以只能传比较器,这样会优先使用有比较器的方法。

class IntCompare implements Comparator<Integer> {
    @Override
    public int compare (Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}
public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>(new IntCompare());
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);
    }
}

 

对于引用类型:

若该引用类型实现了Comparable接口,我们直接修改compareTo

若没有,则修改比较器中的compare

    class Student implements Comparable<Student> {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        return o.age - this.age;
    }

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



public class Test {
    public static void main(String[] args) {
        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>();
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
        priorityQueue2.offer(new Student(23));
        System.out.println(priorityQueue2);
    }
}

 ​​​​​

 

class Student {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }


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

class StuCompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.age - o1.age;
    }
}

public class Test {
    public static void main(String[] args) {
        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>(new StuCompare());
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
        priorityQueue2.offer(new Student(23));
        System.out.println(priorityQueue2);
    }
}


比较器快速写法

class Student {
    public String name;
    public int age;

    public Student (int age) {
        this.age = age;
    }

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

public class Test {
    public static void main(String[] args) {
        //这里直接new了一个比较器传过去了
        PriorityQueue<Student>  priorityQueue2 = new PriorityQueue<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.age - o2.age;
            }
        });
        priorityQueue2.offer(new Student(20));
        priorityQueue2.offer(new Student(22));
        priorityQueue2.offer(new Student(23));
        System.out.println(priorityQueue2);
    }

    public static void main1(String[] args) {
        MyPriorityQueue heap = new MyPriorityQueue();
        int[] arr = {27, 15, 19, 18, 28, 34, 65, 49, 25, 37};
        heap.initArray(arr);
        heap.createHeap();
        heap.push(100);
        //heap.pop();

        PriorityQueue<Integer>  priorityQueue1= new PriorityQueue<>();
        priorityQueue1.offer(4);
        priorityQueue1.offer(6);
        priorityQueue1.offer(3);
        System.out.println(priorityQueue1);

    }
}

有什么错误评论区指出,希望可以帮到你。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值