优先级队列

    • 优先级队列
    • 实现一个大堆
    • 建堆算法时间复杂度分析
    • 堆的插入与删除

优先级队列

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列;这就得使用到优先级队列

JDK1.8中的PriorityQueue底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整;一般分为最大堆和最小堆。(左右孩子之间没有大小关系,根跟左和根跟右才有关系)
最大堆:父亲节点的值大于孩子节点的值
最小堆:父亲节点的值小于孩子节点的值

如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子;因为的越界了。
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子;

优先级队列_第1张图片

实现一个大堆

堆通常是用数组作为底层数据结构来实现的;完全二叉树使用顺序结构非常适合;非完全二叉树,则不适合使用顺序方式进行存储;空间利用率低;因为要存空节点才能表示出这棵二叉树。

现在假设给你一串数字;如何变成一个堆?
优先级队列_第2张图片
让每棵子树成为大根堆
两种方法
方法一:插入一个数;每插一次就排一次。有几个节点插入进来就让这几个节点变成堆
方法二:整体看做是一棵树;从最后一棵子树开始调整;先28和37交换;再49和18交换。然后65和19交换;15和49交换了。注意:15和49交换后;15的子树还有个18呢;得交换一下。最后一步65和27交换;换完还有27比34小;再换这个
优先级队列_第3张图片
我们以方法二来实现试试:
最后一棵树位置:length-1。它的父亲:((length-1)-1)/2 ; 通过最后一个父亲树,然后一直–就能到0了
逻辑分析:总的来说,就是上面方法的循环决定你是从尾比较到根节点。 下面的方法循环就是,你的内部比较交换
注意:有时候需要你换完上面的,然后下面还得重新再换一次。
优先级队列_第4张图片

import java.util.Arrays;
    public class TestHeap {

        public int[] elem;

        public int usedSize;//有效的数据个数

        public static final int DEFAULT_SIZE = 10;

        public TestHeap() {
            elem = new int[DEFAULT_SIZE];
        }

        public void initElem(int[] array) {
            for (int i = 0; i < array.length; i++) {
                elem[i] = array[i];
                usedSize++;
            }
        }

        /**
         * 时间复杂度:O(n)
         */
        public void createHeap() {
            for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
                //统一的调整方案
                shiftDown(parent, usedSize);
            }
        }

        private void shiftDown(int parent, int len) {
            int child = 2 * parent + 1;
            //1. 必须保证有左孩子
            while (child < len) {
                //child+1 < len && 保证有右孩子
                if (child + 1 < len && elem[child] < elem[child + 1]) {
                    child++;
                }
                //child下标 一定是左右孩子 最大值的下标
           /* if(elem[child] < elem[child+1] && child+1 < len ) {
                child++;
            }*/
                if (elem[child] > elem[parent]) {
                    int tmp = elem[child];
                    elem[child] = elem[parent];
                    elem[parent] = tmp;
                    parent = child;
                    child = 2 * parent + 1;
                } else {
                    break;
                }
            }
        }

        
    }




建堆算法时间复杂度分析

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
优先级队列_第5张图片
优先级队列_第6张图片

建堆时间复杂度:O(N)

堆的插入与删除

向上调整:调整的时候是往上调;child往父母走
1:判满扩容
2:放进去elem[ usedsize];然后usedsize往后走。
3:放进去你还得再调整为大(小)堆
逻辑:通过这个位置找到它的父亲,看看要不要修改,不修改就没其他事了。如果要修改,修改完,还要再找修改后的父亲。直到0下标。

public boolean isFull() {//判满
        return usedSize == elem.length;
    }
 public void offer(int val) {
        if(isFull()) {
            //扩容
            elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[usedSize] = val;
        usedSize++;
        //想办法让他再次变成大根堆
        shiftUp(usedSize-1);//最后一个节点的下标;也就是插入节点的下标
    }

    private void shiftUp(int child) {
        int parent = (child-1) / 2;
        while (child > 0) {//parent >= 0    //向上调整,当到根节点的时候,child=0.结束
            if (elem[child] > elem[parent]) {//刚插入的孩子大于父母的情况,交换
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                
                child = parent;                  //这个是child往父亲的位置走。上一个写的是父亲往chlid位置走
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }

删除操作:一定是删除堆顶元素,不然没有意义(前提堆里得有元素)
往前移不现实,移完还要调整,而且调整的范围非常大。
所以我们所使用交换的方式:把最后一个节点跟根节点交换位置,然后我们只需要调整这边一棵树就好了
优先级队列_第7张图片


    public int pop() {//判空
        if(isEmpty()) {
            return -1;
        }
        int tmp = elem[0];
        elem[0] = elem[usedSize-1];
        elem[usedSize-1] = tmp;
        usedSize--;//交换后把最后一个删除掉。
        //保证他仍然是一棵大根堆
        shiftDown(0,usedSize);         //调用我们之前的调整方法,传根节点和树长度,就调整的是根节点的这棵树
        return tmp;                    //我们构建这个堆的时候,循环传入,从最后一个父亲一直往前调整
    }
       时间复杂度:O(logn)

你可能感兴趣的:(数据结构,数据结构,算法)