Java集合数据结构——优先级队列 (堆PriorityQueue)

本文详细介绍了二叉树的顺序存储方法,包括完全二叉树的数组表示及父子节点下标关系。接着,深入探讨了堆的概念,包括大堆和小堆,并展示了Java中建堆、入队、出队操作的代码实现。同时,提到了Java的PriorityQueue及其默认的小堆特性。最后,简要提及了堆排序的原理和时间复杂度。
摘要由CSDN通过智能技术生成


一、二叉树的顺序存储

1. 存储方式

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。
一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。
这种方式的主要用法就是堆的表示

二叉树

2. 父亲和孩子的下标关系

已知父亲(parent)的下标

  • 左孩子(left)下标 = 2 * parent + 1;
  • 右孩子(right)下标 = 2 * parent + 2;

已知孩子(不区分左右)(child)下标

  • 父亲ji(parent)下标 = (child - 1) / 2;

二、堆(heap)

1. 堆的基本概念

  1. 堆逻辑上是一棵完全二叉树
  2. 堆物理上是保存在数组中
  3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
  4. 反之,则是小堆,或者小根堆,或者最小
  5. 堆的基本作用是,快速找集合中的最值

在这里插入图片描述

2. 堆的操作

(1) 建堆(向下调整)

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

向上调整的思路:从堆的最后一个元素的父亲。左右孩子比较大小后再和父亲比较如果大就交换。再更新父亲和孩子的下标,直到当所有孩子都没有父亲大时就是大根堆了。
在这里插入图片描述

代码示例:

public class TestHeap {
    public int[] elem;
    public int usedSize;

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

    /**
     * 建大堆
     * @param array
     */
    public void creteHeap(int[] array) {
        for (int i = 0; i < array.length; i++) {
            this.elem[i] = array[i];
            this.usedSize++;
        }
        for (int parent = (array.length-1-1)/2; parent >= 0; parent--) {
            adjustDown(parent,this.usedSize);//向上调整
        }
    }
    public void adjustDown(int root,int len) {
        int parent = root;
        int child = 2*parent+1;

        while (child < len) {
            //判断是否有右孩子
            if(child+1 < len && this.elem[child] < this.elem[child+1]) {
                child++;
            }
            //如果左右孩子的最大值大于父亲的值就交换
            if(this.elem[parent] < this.elem[child]) {
                int tmp = this.elem[parent];
                this.elem[parent] = this.elem[child];
                this.elem[child] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }
}

(2) 建堆的时间复杂度

通过公式一步计算得到: n - log₂(n + 1)
当n慢慢变大,时间复杂度就是 O(n)
建堆的时间复杂度
在这里插入图片描述

(3) 入队(向上调整)

在这里插入图片描述

//向上调整
public void adjustUp(int child) {
        int parent = (child-1)/2;
        while(parent >= 0) {
            if(this.elem[parent] < this.elem[child]) {
                int tmp = this.elem[parent];
                this.elem[parent] = this.elem[child];
                this.elem[child] = tmp;
                child = parent;
                parent = (child-1)/2;
            } else {
                break;
            }
        }
    }

    /**
     * 入队
     * @param val
     */
    public void push(int val) {
        //扩容
        if(this.isFull()) {
            this.elem = Arrays.copyOf(this.elem,this.elem.length*2);
        }
        this.elem[this.usedSize] = val;
        this.usedSize++;
        adjustUp(this.usedSize-1);
    }

(4) 出队(向下调整)

让堆顶元素和堆的最后一个元素交换,元素个数减1。再从0下标向下调整
在这里插入图片描述

	/**
     * 出队首元素
     */
    public void pop() {
        if(this.isEmpty()) return;

        int tmp = this.elem[0];
        this.elem[0] = this.elem[this.usedSize-1];
        this.elem[this.usedSize-1] = tmp;
        this.usedSize--;
        this.adjustDown(0,this.usedSize);//堆顶和堆尾下标
    }
    public void adjustDown(int root,int len) {
        int parent = root;
        int child = 2*parent+1;
        while(child < len) {
            //找到左右孩子的最大值
            //1、前提是你得有右孩子
            if(child+1 < len && this.elem[child] < this.elem[child+1]) {
                child++;
            }
            //保证,child下标的数据  一定是左右孩子的最大值的下标
            if(this.elem[child] > this.elem[parent]) {
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }
    public boolean isEmpty() {
        return this.usedSize == 0;
    }

三、Java中的优先级队列(PriorityQueue)

Java中的PriorityQueue默认是一个小堆,需要指定比较规则才能改为大堆。下篇博客会讲到。

 PriorityQueue<List<Integer>> heap = new PriorityQueue<>();

在这里插入图片描述

四、堆排序

从大到小:建一个小堆
从小到大:建一个大堆

时间复杂度:O(n*logN)
空间复杂度:O(1)
稳定性:不稳定

这里不细说,到后面常见排序算法中还会提到。
在这里插入图片描述

	/**
     * 堆排序
     * 先交换第一个和最后一个元素,再调整树
     * 从小到大排序
     */
    public void heapSort() {
        if(this.isEmpty()) return;

        int len = this.usedSize-1;
        while(len > 0) {
            int tmp = this.elem[len];
            this.elem[len] = this.elem[0];
            this.elem[0] = tmp;
            adjustDown(0,len);
            len--;
        }
    }
    public void adjustDown(int root,int len) {
        int parent = root;
        int child = 2*parent+1;

        while (child < len) {
            //判断是否有右孩子
            if(child+1 < len && this.elem[child] < this.elem[child+1]) {
                child++;
            }
            //如果左右孩子的最大值大于父亲的值就交换
            if(this.elem[parent] < this.elem[child]) {
                int tmp = this.elem[parent];
                this.elem[parent] = this.elem[child];
                this.elem[child] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }

完!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的三毛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值