数据结构-优先级队列/堆 (Java实现)

1. 优先级队列

1.1 概念

        优先级队列是一种特殊的队列,其中每个元素都有一个优先级。在优先级队列中,具有最高优先级的元素首先出列/(移除)。这与普通队列不同,普通队列是先进先出(FIFO)的,而优先级队列则是按照优先级来确定元素的出队顺序。

        在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。

2. 优先级队列的模拟实现

2.1 堆的概念

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

2.2 堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值

  • 堆总是一棵完全二叉树

每一颗子树 根节点都是小于孩子结点的(小根堆)

每一棵子树 根节点都是大于孩子节点的(大根堆)

2.3 堆的存储方式

堆是一棵完全二叉树,因此可以用层序遍历的规则采用顺序的方式来高效存储。

注意: 对于非完全二叉树,则不适合采用顺序的方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。如下图所示:

将元素存储到数组中后,可以根据二叉树的性质对树进行还原。假设 i 为结点在数组中的下标,则有:

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

2.4 堆的创建

2.4.1 堆的向下调整

把数组变成大根堆有2种方式: 

1. 把整个数组看成一棵树,从最后一棵子树开始 向下调整。

2. 一个元素一个元素的进行插入。

2.4.2 大根堆的创建

1. 左右孩子找最大值 和 根节点大小进行比较

2. 交换完成后,需要继续向下执行,直到下标child的指向 >= 数组的长度

堆的初始化

    public int[] elem;  //一个数组
    public int usedSize;  //元素个数

    public TestHep(){
        this.elem = new int[10];
    }

    //初始化elem数组的
    public void initElem(int[] array){
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }

创建大根堆

    //创建大根堆
    public void createBigHeap(){
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) { //从树最下面开始循环
            siftDown(parent,usedSize);  //调节树的父亲结点是谁(变化的),还有结束结点是谁
        }
    }

    private void siftDown(int parent,int end){
        int child = 2*parent+1;  //树的根节点以0为下标
        while(child < end){ //小于数组长度说明没调完
            if(child+1 < usedSize && elem[child] < elem[child+1]){ 
                //child+1小于usedsize说明没有越界
 //判断左孩子如果比右孩子小的话,child就要指向右孩子,因为下一步要用最大的孩子结点和父亲结点交换
                child++;
            }
            //程序执行到这一步就说明child一定是左右孩子最大值的下标
            if(elem[child]>elem[parent]) {  //child和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.4.3 堆元素的删除(堆顶)

  1. 将堆栈元素和最后一个元素交换
  2. 将堆中有效数据减少一个
  3. 对堆顶元素向下调整
    //堆删除元素

    public int poll(){
        int tmp = elem[0];  
        swap(0,usedSize-1);  //堆顶元素和最后一个元素交换
        usedSize--;  //usedSize减一,元素还保留在数组,下次元素插入的时候直接覆盖
        siftDown(0,usedSize);  //最后一个元素在堆顶,直接向下调整堆顶元素即可
        return tmp;
    }

    private void siftDown(int parent,int end){
        int child = 2*parent+1;  //树的根节点以0为下标
        while(child < end){ //小于数组长度说明没调完
            if(child+1 < usedSize && elem[child] < elem[child+1]){ //child+1小于usedsize说明没有越界
                //判断左孩子如果比右孩子小的话,child就要指向右孩子,因为下一步要用最大的孩子结点和父亲结点交换
                child++;
            }
            //程序执行到这一步就说明child一定是左右孩子最大值的下标
            if(elem[child]>elem[parent]) {  //child和parent找出最大值进行交换
                //交换
                swap(child,parent);
                parent = child;
                child = 2*parent+1;
            }else{
                break;  //不用交换
            }
        }
    }

2.4.4 堆元素的插入

  1. 判断堆数组满不满,不满则扩容
  2. 堆数组不满可以执行插入操作
  3. 向上调整(调成大/小根堆)
    //堆的插入
    public void offer(int val) {
        //1.判断数组满不满,满则扩容
        if(isFull()){
            //扩容
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }
        //2. 容量不满,可以插入元素
        elem[usedSize] = val;  //插入数组的最后
        usedSize++;

        //3.开始向上调整
        siftUp(usedSize-1);  //根节点从0开始,上一步加加,找到最后一个结点下标必须要元素个数减1
    }

    private void siftUp(int child) {
        int parent = (child - 1)/2; //根节点下标是0
        while(child > 0) {
            if(elem[child] > elem[parent]) {
                //如果孩子结点比父亲结点大的话,就交换
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;

                //孩子结点要走到父亲节点位置,父亲结点也要向上走
                child = parent;
                parent = (child - 1)/2;
            }else {
                break;
            }
        }
    }
    public boolean isFull() {
        return usedSize == elem.length;  //等于数组长度就说明满了
    }

  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值