优先级队列详解

时间:2024-10-10 07:56:53

一,优先级队列

什么是优先级队列呢,不知道大家了解过队列没有,队列是一种先进先出的数据结构,但是我们有时会想让优先级高的先出队列,所以我们出现了一种新的数据结构,我们实现两种主要功能得到优先级高的数据,和添加数据。

priorityQueue 就是我们的优先级队列,他实现了Queue接口,它底层使用了堆这种数据结构,而堆他是把一颗完全二叉树按顺序遍历的顺序把数据放到数组中,其中每个节点的父亲节点下标是 (i-1)/2,左子节点为2*i+1 ,右子节点为2*i+2。

我们来看priorityQueue中的主要方法

priority的构造方法。

优先级队列我们可以直接拿来创建大根堆和小根堆,那么什么是大根堆和小根堆呢

大根堆

小根堆

我们以完全二叉树形式,并且每个节点都比他两个子节点大,并且以顺序遍历的方式放到数组中就是我们的大根堆,反之就是小跟堆,那么怎么直接用priorityQueue创建大根堆呢。

public static void main(String[] args) {
        Queue<Integer> queue = new PriorityQueue<>();
        queue.offer()
    }

 我们如果直接用offer方法去添加的话是创建一个小根堆

public static void main(String[] args) {
        Queue<Integer> queue = new PriorityQueue<>();
        queue.offer(28);
        queue.offer(23);
        queue.offer(19);
        queue.offer(21);
        queue.offer(8);
        queue.offer(18);
        queue.offer(5);
    }

我们通过调试发现,建的堆确实是一个小根堆,priorityQueue底层实现建堆的时候是通过比较来处理要创建小根堆还是大根堆的,我们创建一个类来改变比较过程。

class IntCmp implements Comparator<Integer>{
    public int compare(Integer a,Integer b){
        return b-a;
    }
}
 Queue<Integer> queue = new PriorityQueue<>(new IntCmp());

我们把IntCmp对象传给优先级队的列构造方法。

这样我们就成功构建大根堆了。

二,优先级队列的模拟实现

下面我们来模拟下这个数据结构

在实现之前,我们来了解下什么是向上调整和向下调整,

向下调整

向下调整就是我们从一个节点开始,看当前节点的左节点和右节点,如果存在比当前节点大的节点的情况下,那么就是交换这两个节点

看这个图,24这个节点,左节点45比他大,就交换

因为交换的是24和45,所以我们不看12,看24这个节点,67大于24,交换。

到24节点和29比,29大交换,

这样我们就完成了一次向下调整。 

代码实现。

private void shiftDownBig(int root,int len) {
        int child = root*2+1;
        while (child<len){
            if(child+1<len && elem[child]<elem[child+1]){
                child++;
            }
            if (elem[child]>elem[root]){
                int tmp = elem[child];
                elem[child] = elem[root];
                elem[root] = tmp;
                root = child;
                child = root*2+1;
            }
            else {
                break;
            }
        }
    }
    private void shiftDownSmall(int root,int len) {
        int child = root*2+1;
        while (child<len){
            if(child+1<len && elem[child]>elem[child+1]){
                child++;
            }
            if (elem[child]<elem[root]){
                int tmp = elem[child];
                elem[child] = elem[root];
                elem[root] = tmp;
                root = child;
                child = root*2+1;
            }
            else {
                break;
            }
        }
    }

向上调整

向上调整也是差不多,从一个节点开始与父母节点比较,如果比父母节点大或者小就与它进行交换

我们找小的。

我们看24节点,24节点比自己的父亲节点小,交换, 

接下来24节点与其父亲节点67比较,24节点小,交换,

接下来24节点与45节点比较,24节点小,进行交换。

这下我们就完成了一次向上调整。

private void shiftUpBig(int child) {
        int parent = (child-1)/2;
        while (child>0){
            if(elem[parent]<elem[child]){
                int tmp = elem[parent];
                elem[parent] = elem[child];
                elem[child] = tmp;
                child = parent;
                parent = (child-1)/2;
            }
            else {
                break;
            }
        }
    }
    private void shiftUpSmall(int child) {
        int parent = (child-1)/2;
        while (child>0){
            if(elem[parent]>elem[child]){
                int tmp = elem[parent];
                elem[parent] = elem[child];
                elem[child] = tmp;
                child = parent;
                parent = (child-1)/2;
            }
            else {
                break;
            }
        }
    }

 接下来我们来创建大小根堆。

我们将会用两种方法分别创建大根堆和小根堆,所以我们在test创建4次数组,这样好看。

我们先来用向下调整实现大根堆和小根堆。 

用向下调整建大根堆其实就是多次利用向下调整去调整我们的二叉树,

还是这个图,如果我们要把它调整为大根堆,我们就要保证每个小数都是大根堆,我们从最后一个叶子节点看,找到它的父亲节点,例如67,再记录67的下标。

此时67的下标为3,我们让67向下调整,此时67这个子树是大根堆了,我让刚才记录的下标--,就来到了2下标,2再进行向下调整,此时2下标也为大根堆,2下标--,来到1,1向下调整,1下面也为大根堆,下标来到0,向下调整,此时全部都为大根堆,小根堆也一样,我们来看代码。

 public void createHeapBig1() {
        int c = elem.length-1;
        int p = (c-1)/2;
        while (p>=0){
            shiftDownBig(p,elem.length);
            p--;
        }
    }//向下调整建大根堆

    public void createHeapSmall1(){
        int c = elem.length-1;
        int p = (c-1)/2;
        while (p>=0){
            shiftDownSmall(p,elem.length);
            p--;
        }
    }//向下调整建小根堆

shiftDownSmall就是刚才我们实现的向下调整,只不过比较的时候大于号,小于号不同。

我们再用向上调整来实现大小根堆的创建。

这个是从最后一个数开始向上调整,调整好一个下标--,直到0下标,0下标不用向上调整。

    public void createHeapBig2(){
        int c = elem.length-1;
        while (c>0){
            shiftUpBig(c);
            c--;
        }

    }//向上调整建大根堆

    public void createHeapSmall2(){
        int c = elem.length-1;
        while (c>0){
            shiftUpSmall(c);
            c--;
        }
    }//向上调整建小根堆

 剩下的添加元素,获取元素就不难了

 public void push(int val) {
        elem = Arrays.copyOf(elem,elem.length+1);
        elem[elem.length-1] = val;
        shiftUpBig(elem.length-1);
    }


    /**
     * 出队【删除】:每次删除的都是优先级高的元素
     * 仍然要保持是大根堆
     */
    public void pollHeap() {
        elem[elem.length-1] = elem[0];
        elem = Arrays.copyOf(elem,elem.length-1);
        shiftDownBig(0,elem.length);
    }

    /**
     * 获取堆顶元素
     * @return
     */
    public int peekHeap() {
        int w = elem[0];
        elem[elem.length-1] = elem[0];
        elem = Arrays.copyOf(elem,elem.length-1);
        shiftDownBig(0,elem.length);
        return w;
    }

push一个新的元素的时候一定要向上调整,获取元素我们是把头和尾交换位置,把尾下标的访问权限封住,在从0进行向下调整即可