Java笔记16——优先级队列(堆)

2023-11-10

目录

概念

基于二叉树的堆(二叉堆)

基于动态数组ArrayList实现的最大堆

向堆中添加元素 siftUp

在堆中取出最大值(最大堆)

3.heapify ——堆化

在Java中比较两个元素的大小关系:

基于堆的优先级队列到底如何实现,有啥应用

重点:优先级队列的应用问题

内部类

力扣堆的练习题

关于Map的部分相关知识

原地堆排序


概念

优先级队列(堆):按照优先级的大小动态出队(动态指的是元素个数动态变化,而非固定)

普通队列:FIFO按照元素的入队顺序出队,先入先出。
 

现实生活中的优先级队列 PriorityQueue

现实生活中举例:

1. 去医院医生根据你的病情安排做手术的时间。

比如说一个人是轻微的咳嗽,一个病人是流血过多。虽然是第一个病人先来的,但医院一般都是先处理时间紧迫,更加严重的病人。

病情相同的情况下,安装排序来安排时间。若病情严重者,优先安排手术。

提问:那么这和排序有啥区别呢?能否按照病情排序,然后排序后的病人数组安排手术?
 

有区别!排序是数组个数确定的情况。而优先级队列是动态变化的。病人的人数是在变动的。

比如在5.20安排了5.21号这天手术3人。结果到了5.21这天突然来了个病情很长严重的患者需要马上做手术,优先级队列就会安排他先做。人数数据在不停的变化。

排序指的是当前集合个数是确定的,不会发生变化。

2. 操作系统中的任务管理器

任务管理器应用的也是优先级队列。

一般来说 系统任务进程 比 普通的应用优先级更高。 

还有一种情况,CPU和内存在电脑中都是有限的,当资源不够用时,优先让优先级高的的应用获取资源。

优先级队列的核心思想:动态

时间复杂度对比:

原本队列的时间复杂度是O(n)级别,而优先级队列在这方法进行了优化,变成了O(logn)

注释: 在计算机领域,若见到logn时间复杂度,近乎一定和"树"结构相关(并非一定要构造一棵树结构,而是它的算法过程逻辑上一定是一棵树)

快速排序里面的:归并,快速递归过程也是一个"递归"树。时间复杂度:nlogn

基于二叉树的堆(二叉堆)


应用最广泛的堆: d插堆 索引堆

本章将的二叉树的特殊:

1.是一课完全二叉树,基于数组存储(元素都是靠左排列,数组中存储时不会浪费空间)

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。

一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。 这种方式的主要用法就是堆的表示。

 只有完全二叉树适合使用数组这种结构来存,储其他的二叉树都要用链式结构

2.关于节点值:

堆中根节点值 >= 子树节点中的值(最大堆,大根堆)


堆中根节点值 <= 子树中节点的值(最小堆,小根堆)

堆中树根 >=  或者 <= 子树中所有节点所有子树也仍然满足堆的定义

不管根要满足,子树中的根也要满足。

判断一个结构是否满足堆:

1. 必须要是一个完全二叉树

2.  根 >= 子树最大堆 或者 根 <= 子树最小堆(是所以节点)
 

比如:


提问:最大堆中是否越"高"的结点—定就比越"低"值大?

 答:不一定。节点的层次和节点的大小没有任何关系只能保证当前树中,树根是最大值。其他节点层次大小关系不确定

比如右子树的13虽然比最底层高,却小于左子树。

注释:JDK中的PriorityQueue默认是基于最小堆的实现。

3.因为堆是基于数组来存储的,节点的之间的关系通过数组下标来表示从0开始编号,因为数组下标也是从开始。

假设此时结点编号为i,且存在父子节点。

父节点编号      parent = (i - 1) /  2;


左子树的编号  left = 2 * i + 1;

右子树的编号  left = 2 * i +  2;

 举例:给上图完全二叉树编号,下列任意节点编号都满足上面编号的三点关系

 上面的所有节点都是按层序遍历的方式一个个存入数组中的。

 节点之间通过数组的索引下标来找到父子节点

基于动态数组ArrayList实现的最大堆
 

堆的基础表示:


注释:还没写添加功能。

/**
 * 基于动态数组实现的最大堆
 */
public class MaxHeap {
    // 实际储存元素的数组
    private  List<Integer> elementData;
    // 当前堆中元素的个数
    private int size;

    //无参构造,默认创建数组大小为 10
    public MaxHeap(){
        this(10);
    }

    //有参构造--设置数组大小
    public MaxHeap(int n) {
        this.elementData = new ArrayList<>(size);
    }

    /**
     * 传入数组的索引,找到节点k对应的父类的索引
     */
    private int parent(int k){
        //规则:(k - 1) / 2 ,右移相当于除以 2
        return (k - 1) >> 1;
    }

    /**
     * 找对节点 k对应的 左子树的索引
     * (2 * k) + 1
     */
    public int leftChild(int k){
        return (k << 1) + 1;
    }

    /**
     * 找对节点 k对应的 右子树的索引
     * (2 * k) + 2
     */
    public int rightChild(int k){
        return (k << 1) + 2;
    }

    //返回当前数组的元素个数
    public int getSize(){
        return size;
    }

    //判断数组现在是否为 空
    public boolean isEmpty(){
        return size == 0;
    }


}

向堆中添加元素 siftUp


添加一个元素 52

堆是数组实现的,添加元素,数组咋添加元素?


 此时的这个堆仍然满足完全二叉树的性质

 但是此时这个完全二叉树就不再是一个最大堆了因此需要进行元素的上浮操作,让新添加的元素上浮到合适位置。

不断将此时索引k和父节点的索引 i 对应的元素进行大小关系比较,若大于父节点效换彼此的节点值,直 到当前节点 <= 父节点为止 或者(or)  走到树根
 

 

 

 代码实现:

  //添加元素
    public void add(int val){
        //1.直接向数组末尾添加元素
        elementData.add(val);
        size ++;
        //2. 进行元素的上浮操作,设计一个方法
        siftUp(size - 1);
    }

    //传入需要进行上浮操作元素的索引
    private void siftUp(int k) {
        //小于父类节点和已经是最大根节点就结
                          // 上浮元素节点值  跟它的 父类节点值比较
        while(k > 0 && elementData.get(k) > elementData.get(parent(k)) ){
            //进行节点值交换
            swap(k, parent(k));
            //进行交换后,k节点的父类节点就是新的上浮元素
            k = parent(k);//更新上浮元索引
        }

    }

    // 交换两个数字索引节点值
    private void swap(int k, int parent) {
        int child = elementData.get(k);//保存k节点的val
        int pareentVal = elementData.get(parent);//k的父类节点值val
        elementData.set(k,pareentVal);//k索引处更换为父类值
        elementData.set(parent, child);//父类索引处更换为 k的val值
    }

    //toStrng方法实现
    @Override
    public String toString() {
        return elementData.toString();
    }

在堆中取出最大值(最大堆)

堆中的最大值是谁?肯定是树的根节点吧

1.最大堆的最大值一定处在数根节点。直接取出树根即可.

取出最大值后

 

剩下来的左右子树怎么办? 

⒉需要融合左右两个子树,使得取出树根后这棵树仍然是最大堆

此时融合操作还是比较复杂的,因为左右子树大小关系不定,且节点的大小和层次没有必然联系。
 

怎么融合好呢?.

使用最低的成本来融合左右子树,使其仍然满足最大堆的性质。

比较和移动最少,删除元素  ——>  移除数组的末尾元素

融合方法:

1.直接取数根节点就是当前堆的最大值。
⒉将堆中最后一个元素顶到堆顶,然后进行元素的下沉操作

    siftDown===>使其仍然满足最大堆的性质


移除16之后这棵树仍然是完全二叉树,但此时不满足最大堆的性质。


解决办法:不断和左右子树的最大值进行交换直到16这个节点落在了最终位置。

完成下层操作:

 代码实现:

    /**
     * 取中当前最大堆的最大值
     */
    public int extractMax(){
        //如果此时的数组为空,那么就报一个异常警告
        if(isEmpty()){
            throw new NoSuchElementException("heap is empty!cannot extract!");
        }
        //不为空,说明此时树根就是最大节点
        int max = elementData.get(0);
        //将数组末尾元素顶到堆顶
        elementData.set(0, elementData.get(size - 1));
        // 将数组的最后一位元素从堆中删除
        elementData.remove(size - 1);
        size --;
        //进行元素的下层操作,从索引为 0 的根开始
        siftDown(0);
        return max;
    }

    /**
     * 从索引k开始进行元素的下沉操作
     */
    private void siftDown(int k) {
        //因为这堆满足完全二叉树特性,不存在有右子树,没有左子树
        //循环条件表示:当前还存在有子树,可以循环判断是否能下层
        while(leftChild(k) < size){
            //获取左子树的索引
            int j = leftChild(k);//用来进行下层交换的索引值
            // 表示此时也存在右子树  左子树和右子树索引差 1
            if(j + 1 < size) {
                //判断此时的右子树是否大于左子树的值
                if (elementData.get(j + 1) > elementData.get(j)) {
                    //表示右子树大于左子树,更新 j 索引值,变成右子树的索引
                    j++;
                }
            }
            
            //此时的索引 j 一定对应了左右子树中的最大值索引
            //判断索引 k元素是否需要进行下层操作
            if (elementData.get(k) < elementData.get(j)) {
                //说明需要进行下层
                swap(k, j);
                //更新此时需要下层元素的索引
                k = j;
            } else {
                // 当前元素 >= 左右子树最大值,下沉结束,元素k落在了最终位置
                break;
            }



        }//while
}

 

如果此时一直出去最大值并打印会怎么样?

发现这就是个降序结果,如果想要升序,把结果逆序一下就好了

注释:堆排序就是建立最大堆,然后在最大堆的基础上进行调整操作就可以得到一个升序集合~~
 

3.heapify ——堆化
 

现在给你一个任意的整型数组  ===》都可以看做是一个完全二叉树,距离最大堆就差元素调整操作

 那么怎么进行元素调整,使这个二叉树变成最大堆呢?

方法1  最简单的方法:假设有一个最大堆,每次给这个最大堆添加一个新元素,然后进行下层,那么这个二叉树就任然保存 最大堆特性。

1.将这n个元素依次调用add方法添加到一个新的最大堆中
   遍历原数组,创建一个新的最大堆,调用最大堆的add方法即可。

那么这个操作是时间复杂度是多少?

堆优化的时间复杂度,遍历一次是 O(logN) 级别

那么进行 n 次遍历就是 N O(logN) 级别  空间复杂度 O(N)

方法2: 原地heapify(堆化)

它的时间复杂度可以优化到 O(n)

思想:从最后一个非叶子结点开始进行元素siftDown操作

从当前二叉树中最后一个小子树开始调整,不断向前,直到调
整到根节点。

过程:

 1. 最后一个 非叶子节点 就是 22 那么就从它开始调整

发现此时它的子树 62 比它自己大,那就进行换位子。

此时对于绿色框框圈起来的二叉树来说,它就已经是一个最大堆了 

 2. 此时继续往前走,再看上一个非叶子节点 13,如果13这个节点存在子树比他大的话,就跟最大值进行交换位置。

 此时对于橙色框框圈起来的二叉树来说,他们就可以看做是一个最大堆了

3. 再重复操作向上走。

 4. 关键点来了,此时17下面都是最大堆了,现在只需要对这个根 17 进行下层操作,这样就会形成最终的最大堆

 进行下层操作后,这个二叉树就是一个新的最大堆了。

最后树根节点的左右子树都是最大堆了,其需要最后进行一次下层操作就可以了。

不断将子树调整为最大堆时,最终走到树根时,左右子树已经全都是最大堆
只需最后下沉根节点就能得到最终的最大堆。

 

代码实现:

    /**
     * 原地堆化,创建一个构造方法。
     * 降任意的整形数组arr 调整了堆
     */
    public MaxHeap(int[] arr){
        this.elementData = new ArrayList<>(arr.length);//开辟等长的空间
        //1. 先将所有元素复制到 Data数组中
        for(int i : arr){
            elementData.add(i);
            size ++;
        }
        //2. 从最后一个非叶子节点不断进行siftDown操作
        // 最后一个非叶子节点就是最后一个元素的父节点
        for (int i = parent(size - 1); i >= 0 ; i--) {
            //不断下层操作
            siftDown(i);
        }
    }

在Java中比较两个元素的大小关系:
 

在Java中比较两个元素相等equals


在比较两个自定义对象的大小关系,类覆写Comparable接口,实现compareTo方法
 

若一个类Student implements Comparable,这个类继承Comparable接口,则这个Student类具备可比较的能力

compareTo方法 具有三大返回值:

实现java.util.comparator接口   ====》 比较器接口

 自定义一个Student类,使它具备可比较的能力。

//Comparable接口

import java.util.Arrays;

public class Student implements Comparable<Student> {

    private int age;
    private String name;

    //构造方法
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override//需要覆写Comparable里面的 compareTo方法
    public int compareTo(Student o) {
        //比如说我们现在自定义认为,年龄越大,这个Student则越大
        return this.age - o.age;
    }

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

    public static void main(String[] args) {
        Student[] students = new Student[]{
                new Student(19,"小明"),
                new Student(17,"小红"),
                new Student(18,"小刚"),
        };
        //快速排序
        Arrays.sort(students);

        System.out.println(Arrays.toString(students));
    }

}

 原本我们自定义的这个类是不具备比较能力的,系统怎么知道你怎么样算大。
 原因:

1. 我们继续了Comparable接口,往里面穿了我们自定义的类Student

 2. 覆写了compareTo方法,定义了一个大小的规则。认为年龄大就算大 

提问:现在得到的结果是升序了过程,那怎么变成降序结果呢?

注释:快速排序: Arrays.sort(students); 这个是别人写好的东西,不能修改

很简单,把覆写方法结果反一下就好了。变成我们认为 年龄越大的,是 “小”

运行结果:就是降序 

 

 假设哪—天,我又想得到一个按照名字排序的数组,那么又得改compareTo方法。

 这样会显得很麻烦,而且非常不好!程序世界中有个说法叫 开闭原则。

开闭原则 :   一段程序应该对扩展开放,对修改关闭


注释:写好的代码别老改,要有新场景,就拓展新代码,不要影响老代码。

那么我们现在在原来的基础上做下修改。

这样Comparator接口就孕育而生了。 

 接口的语义:一个类若实现了这个接口,表示这个类天生就是为别的类大小关系服务的~

修改过程:

1. 先把原来的覆写方法删除。

 2. 现在也不需要继承原来的接口了,删除

 那么现在这个student 这个类就不具备了比较的能力

 3. 但是,我现在定义一个类,专门就是为了比较它Student这个类的大小。

StudentSec这个类天生就是为了Student对象的排序而存在

这个定义的类里面会覆写compare方法(o1,o2)   ====>  返回值 int 表示 o1 和 o2 的大小关系

 

4. 先在原来的Student里面写一个get方法,让外部可以拿到里面的数据

5.  现在就可以在StudentSec 里面覆写 compare 方法了

6. 使用比较强

 在快速排序里面传入一个该类的 比较器对象

 运行结果:使这个Student照样可以进行比较输出

 注释:虽然我自己比不成,但有一个类就是给我进行比较的。

7. 刚才我们定义的这个类是根据年龄越大就越大进行比较

    现在我再定义一个方法,此时又是根据年龄越小反而越大进行比较

 运行结果:

 注释:定义这个类对原来的代码没有任何影响,也没有对老代码进行修改。

现在我就可以根据我自己的需求对里面传入不同的比较器对象,这就满足了我们之前说的

程序的开闭原则:对拓展开发,对修改关闭。

注释:这种设计思想就是java中的:策略设计模式

结论:

当把Student类的大小关系比较从Student类中"解耦"此时的比较策略非常灵活,

需要哪种方式,只需要创新─一个新的类实现Comparator接口即可根据此时大小关系的需要传入比较器对象。

这就是:策略模式

解耦:就是从原来的程序中脱离开来

sort方法内部定义:调用了 Comparator

 推荐大家以后写代码用 比较器这种方法

基于堆的优先级队列到底如何实现,有啥应用

这就是经典的:TopK问题
 

优先级队列的实现:其实优先级队列的方法就是应用我们之前写的最大堆的方法,对它进行包装了一层而已。

import seqlist.queue.Queue;

/**
 * 基于最大堆的 优先级队列
 */

//其实所谓的优先级队列就是调用最大堆中的方法,包装了一层而已
public class PriorityQueue implements Queue<Integer> {
    private MaxHeap heap;

    //构造方法
    public PriorityQueue() {
        this.heap = new MaxHeap();
    }

    @Override
    public void offer(Integer val) {
        heap.add(val);
    }

    @Override
    public Integer poll() {
        return heap.extractMax();//取中当前最大堆的最大值
    }

    @Override
    public Integer peek() {
        return heap.peekMax();//查看当前数组的最大元素
    }

    @Override
    public boolean isEmpty() {
        return heap.isEmpty();
    }
}

重点:优先级队列的应用问题

以力扣的题目为场景:

这个题,很经典,非常爱考。 

面试题 17.14. 最小K个数 - 力扣(LeetCode)

TopK问题: 

 返回一组数里面最小的前几个数,比如一百万个数里面返回前100个最小的数

我们之前碰到这问题怎么做?是直接排序吧!然后输出数组前k元素。

最快的排序算法时间复杂度也要: O(nlogn) 

/**
 * 最简单的办法就是用快速排序,然后保存前k个
 */
class Solution {
    public int[] smallestK(int[] arr, int k) {
        Arrays.sort(arr); //快速排序
        int[] data = new int[k];
        for (int i = 0; i < k; i++) {
             data[i] = arr[i];
        }
    return data;
    }
}

如果此时题目要求时间复杂度优于  O(nlogn)  ,那么这个算法就不行了。

这种情况就可以去使用优先级队列

知识要点:

比如此时是取出前四个最小元素 ——》构造一个大小为 四 的最大堆 

 这个最大堆值保存4个元素。

规则:

 用的方法就是我们之前写的优先级队列,它是基于动态数组实现的最大堆上包装完成的。

 随着堆顶元素不断交换,会把堆顶元素不断变小,最终队列扫描结束就存放了最小的k个数。

这题用优先级队列的时间复杂度为: O(nlog k),k是题目要取的数量。当k远小于n时,

O(nlog k) 跟 O(nlog n) 的差距就越明显

注释:当k 远小于 n时,log k 就很小,那么时间复杂度可以近乎于 O(n)级别,但如果问你是什么复杂度的话还是 O(nlog k)的复杂度,因为 k 是个变量,不能省略。

思路二:用优先级队列解决

注释:此时我们只能用java提供的优先级队列

         但 JDK默认提供的是一个最小堆,我们需要的是大堆 就需要改造成大堆

 

怎么改造?用之前学的传入 比较器对象 或者传入 匿名内部类

1. 先说匿名内部类怎么弄:

 相当于传入了一个 匿名内部类

 然后开始在覆写方法里面设定。反过来就行

现在改造好了 

 2. 创建比较器类,传入对接比较器对象

在方法的外部创建

 在题目方法里面使用:

/**
 * 思路二:用优先级队列
 */

//创建一个类 比较器
class Integerreverse implements Comparator<Integer>{

    @Override
    public int compare(Integer o1, Integer o2) {
        //这样反过来写,就是降序,最大堆
        return o2 - o1;
    }
}

class Solution {

    public int[] smallestK(int[] arr, int k) {
       int[] ret = new int[k];

        //边界--因为就算不存在也要返回空数组
       if(arr.length == 0 || k == 0 ){
           return ret;
       }
        //此时我们只能用java提供的优先级队列
        //但 JDK默认提供的是一个最小堆,我们需要的是大堆
        //就需要改造成大堆
        //这是匿名内里类的创建方法
//        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                //这样反过来写,就是降序,最大堆
//                return o2 - o1;
//            }
//        });

        Queue<Integer> queue = new PriorityQueue<>(new Integerreverse());
        //遍历原数组集合,队列中保存k个元素
        for(int i : arr){

            //判断队列是有 k 个元素
            if(queue.size() < k){
                //说明此时队列没有k个元素,就添加
                queue.offer(i);
            }else{
                //说明此时队列不需要继续增加元素了
                int max = queue.peek();//查看树顶元素
                if(max > i){
                    queue.poll();//弹出树顶元素
                    queue.offer(i);//添加max,同时进行下层操作

                }
            }

        }
        //此时队列里面就保存了最小的k个元素,依次出队就行
        int t = 0;
        while(!queue.isEmpty()){
            ret[t ++] = queue.poll();
        }
        
        return ret;
    }

}

内部类

所谓的内部类,就是一个类嵌套到另一个类的内部的操作。
有四种内部类

匿名内部类:

这个等价于,我们上一题在方法外部写的 比价器,不过我们现在new的这个类是没有名字的,

而且我们直接在方法里面写了,很方便

 

 优化上面题目的代码:

/**
 * 思路三:优化,使用匿名内部类
 */


class Solution {
        public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];

        //边界--因为就算不存在也要返回空数组
        if(arr.length == 0 || k == 0){
            return ret;
        }

        //这是匿名内里类的创建方法 跟上面写的 比较器是等价的,不过这个是没有类名的
        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //这样反过来写,就是降序,最大堆
                return o2 - o1;
            }
        });


        //遍历原数组集合,队列中保存k个元素
        for(int i : arr){

            //判断队列是有 k 个元素
            if(queue.size() < k){
                //说明此时队列没有k个元素,就添加
                queue.offer(i);
            }else{
                //说明此时队列不需要继续增加元素了
                int max = queue.peek();//查看树顶元素
                if(max > i){
                    queue.poll();//弹出树顶元素
                    queue.offer(i);//添加max,同时进行下层操作

                }
            }

        }
        //此时队列里面就保存了最小的k个元素,依次出队就行
        int t = 0;
        while(!queue.isEmpty()){
            ret[t ++] = queue.poll();
        }

        return ret;
    }

}

小知识:函数式编程

力扣堆的练习题

做题小知识:

在LeetCode和牛客网中,若碰到某些问题,现有的类型无法解决,自定义一个类。借助自定义对象辅助解决问题

347. 前 K 个高频元素 - 力扣(LeetCode)

求前k个出现次数最多的。

 1. 求大用小,所以现在用最小堆实现,JDK默认的就是最小堆。

 2. 难点在于:如何让编译器知道,出现次数多的为 " 大 " 呢?

之前我们是值比较 》《 一下就判断出来了,现在要让编译器知道出现次数多的才算 大

Map接口——存储一个键值对数据

映射结构

key = Value

这题里面 key :不重复的元素

            Value :数组元素出现的次数

那么现在优先级队列要保存元素,保存 key? 保存 Value ? 都不合适。

需要保存键值对:出现的 元素以及其 频次

所以现在定义一个类,保存类的对象即可。 

 实现步骤:

1. 

 创建Map集合的方法:里面键值对保存的类型的 int 

  HashMap :哈希Map

 2.扫描原数组,保存map

 3.扫描 Map集合,将出现频次最高的前k个元素添加到优先级队列中

4.  把列表元素放进数组里面

 这样就完成了

关于Map的部分相关知识

原地堆排序

给你一个任意的数组,如何就在这个数组的基础上进行堆排序,不创建任何额外空间。
比如:

 1. 先把这个数组画成二叉树,层序遍历的方法,上到下,左到右。

 2. 然后进行heapify过程,堆化。变成一个最大堆。就是上面讲的,从最后一个非叶子节点开始进行 siftDown 操作。最后元素的父类就是最后一个非叶子节点,上面都讲了,就是先把这个任意二叉树变成一个最大堆。 

这就是跟根据上面给的数组排成的最大堆       

 3. .不断交换堆顶元素最后一个元素的位置,

最大堆什么特性?树根值最大,那么想将这个二叉树排序,把最大值跟最大堆的末尾元素交换位置,这样就把数组内的最大值放在了最终的位置上。然后将堆顶元素再进行siftDown  操作,让堆保持还是最大堆

然后开始倒数第二个元素跟树顶元交换位置。。。。重复此操作,每次确定一个最大值位置。

 此时就确定了两个最大值的位置。

终止条件:直到数组剩下—个未排序的元素为止。

按照上面的思路,代码实现:原地排序实现

package sort;

import java.util.Arrays;

/**
 * 七大排序
 */
//注释:arr. length - 1是最后一个元素的索引
//而 (arr.length - 1 - 1) / 2 是找最后一个元素的父类索引

public class SevenSort {

    public static void main(String[] args) {
        int[] arr = {19,27,13,22,3,1,6,5,4,2,110,65,70,98,72};
        //调用刚刚写的原地排序方法
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    
    //原地排序
    public static void heapSort(int[] arr){
        //1.先将任意数组进行heapify调整为最大堆
        for(int i = (arr.length - 1 - 1) / 2 ; i >= 0; i--){
            //不断进行着 siftDown 操作
            siftDown(arr, i, arr.length);
        }
        //2 不断交换堆顶元素到数组末尾,每交换一个元素,就有一个元落在了最终位置
        for (int i = arr.length - 1; i > 0 ; i--) {
            // arr[i] 就是未排序数组的最大值,交换末尾
            swap(arr,0, i);
            siftDown(arr,0, i);//这里数组长度必须传 i 因为每成功一次相当于
            //末尾位置确定了,就不需要被下层操作的影响。逻辑上相当于数组没有了这个元素位置。
        }

    }

    /**
     * 交换两个元素的位置
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     *
     * @param arr
     * @param i  //当前哪个位置的元素需要跟树顶换位置,进行下层操作
     * @param length //左子树大于总长度就不用下层了
     */
    private static void siftDown(int[] arr, int i, int length) {
        //终止条件,不存在左子树索引就结束
        while(2 * i + 1 < length){
            int j = 2 * i + 1;
            if(j + 1 < length && arr[j + 1] > arr[j] ){
                j = j + 1;
            }
            //此时 j索引就是左右子树中最大值的索引
            if(arr[i] > arr[j]){
                break;
            }else{
                swap(arr, i,j);
                i = j;
            }
            
        }
    }
}

注释:这个执行速度是非常快的,是一个稳定了O(nlongn) 级别

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java笔记16——优先级队列(堆) 的相关文章

随机推荐

  • DRM驱动代码分析:色彩管理

    高通PQ有哪些子模块 DSPP sub blocks SDE DSPP IGC DSPP Inverse gamma correction block SDE DSPP PCC Panel color correction block SD
  • Linux Ubuntu安装多个cuda版本

    因为pytorch版本与cuda版本有一定的对应要求 服务器上的cuda是不能自己随便动的 所以需要在自己账户中安装其他版本的cuda 而不能影响其他账户中已安装的cuda 这里参考了多篇博文总结出以下要点 1 nvcc和nvidia sm
  • 攻防世界 shrine 详解

    打开题目 整理源码 代码审计 目标 config FLAG 过滤了 config self 这两个函数的过滤没看懂 总之好像也没过滤掉 应该是过滤了后面的变量 圆括号是彻底的被过滤掉了 URL编码都没用 刚开始想测试XSS来着 做完后 拿编
  • JS数组过滤 简单------->多条件筛选

    在前端部分完成筛选功能 一次拿到所有数据 然后根据条件筛选 通常情况下筛选是后台给接口 在数据量不大的情况下 也有人可能会遇到前端筛选这样的情况 这个是例子中的被筛选数组 var aim name Anne age 23 gender fe
  • 最强大脑(9-10)

    目录 第九季 团队冲击赛 乾坤魔方 运Q帷幄 光影残卷 光柱霓虹 六宫数局 双面拼图 索玛秘图 康斯迭代 第九季 淘汰赛 慧眼识金 连杆曲线 光点密钥 希尔伯特旋涡 移星掠形 星阵潜袭 明灯谜局 彩虹雪花 光图谜笼 战旗阵地 时间旅人 数字
  • [开发] 认证的几种方式简介

    LDAP 认证 LDAP 轻量级目录访问协议 是一种用于访问和维护分布式目录信息的开放标准协议 它最初由电子数据系统公司 Netscape 开发 现在被广泛用于企业和组织中的身份认证和授权管理 LDAP的目标是为不同类型的应用程序 如电子邮
  • 创作灵感打卡

    打卡 打卡 打卡 重要的事情说三遍 作为一个CSDN新手 目标就是 坚持下来 每日分享关于C语言知识 希望在CSDN平台上可以走的更远 今日刚刚发布几篇博客 兴趣大发 希望同僚可以给以鼓励 使得坚持下来
  • RPNet 分割

    46m https github com ooooverflow BiSeNet 网络好像比较大 无模型 https github com superlxt RPNet Pytorch solov2 还未开源 yolact map不到30
  • VS Code 配置C/C++环境 出现问题 could not find the task 'g++' / 'gcc'

    前言 由于新电脑未装VSCode C C 配置环境 刚好手头有些东西想在上面验证 于是开启安装之旅 耗时大概4h 最后还是拷了旧电脑的配置 修改过后才解决的问题 如果你是被标题 骗 进来的 请直接跳转到tasks json部分 推荐先序阅读
  • csu 1803 2016 2016湖南省赛 A

    Problem acm csu edu cn csuoj problemset problem pid 1803 vjudge net contest 161962 problem A Reference www cnblogs com w
  • Day【3】设计一个支持增量功能的栈

    原题链接 文章目录 思路 代码 用数组来模拟栈 思路 题目中已经确切的告诉了我们 数组中会放入多少个元素 这种情况并且只有添加操作 这种情况之下 使用数组模拟效率会更高一点 代码 用数组模拟栈 击败100 class CustomStack
  • 多链生态中的跨链桥是如何运行的?

    在以太坊升级之前 它网络拥堵 手续费高昂等问题逐渐难以满足人们的需求 因此 市场中出现了许多以太坊之外的公链 其中甚至不乏有一些号称 以太坊杀手 项目 尽管以太坊很快反应过来了 并开始对其自身进行升级优化 但一个多链的生态已然形成 在多链态
  • 牛客网 之 数列还原(数列的全排列算法)

    题目描述 牛牛的作业薄上有一个长度为 n 的排列 A 这个排列包含了从1到n的n个数 但是因为一些原因 其中有一些位置 不超过 10 个 看不清了 但是牛牛记得这个数列顺序对的数量是 k 顺序对是指满足 i lt j 且 A i lt A
  • 基于yolov5的物流托盘实时检测方法研究

    摘要 传统物流作业主要依靠人工操作叉车对托盘进行搬运 自动化程度低 工厂实际环境复杂多样 导致现有的托盘检测算法的模型复杂 耗时较长 无法同时达到准确性和实时性要求 难以实际运用 针对浙江某机械 搬运 设备有限公司下属的电动车工业园实际工厂
  • 基于Web日志挖掘的个性化推荐系统(附源码)

    个性化推荐系统 实现该系统主要是使用的编程语言主要是R 然后配合css在样式上进行一定优化 使用shiny开发的一款web程序 主要实现的核心功能是基于spark的ALS算法的课程个性化推荐系统 首页界面如下图所示 该系统中的所有课程名称
  • 低代码点亮普惠数字化转型之路,助力企业数字化转型实践

    在国家大力提倡下 新一代信息技术持续迭代 企业数字化转型被推到风口 现在步入了信息化时代 尤其是2022年 在十四五的政策规划下更是有利好前景 对于企业来说 低代码开发平台是一种通用的解决方案 成为了企业进行信息化管理的首选 现在 越来越多
  • 鸡啄米VS2010/MFC编程入门教程——学习2关于VS2010/MFC/VC/C++

    2015年5月17日15 21 11 VS2010 MFC编程入门之前言 http www jizhuomi com software 137 html 在大学里 有面向对象程序设计这门课程 主要介绍的是C 基础编程 那时候大家虽然都学习了
  • Freemarker中文指南

    序言 目录 一 什么是FreeMarker 二 阅读指南 三 文档约定 四 联系方式 五 关于本文档 一 什么是FreeeMarker FreeMarker是一个模板引擎 是一个基于模板生成文本的通用工具 任何文件都可以基于html自动产生
  • node调用谷歌翻译Api,实现自动国际化

    原因 项目国际化过程繁琐 每次都需要人工去google翻译 导致工作效率不高 需求 1 减少人工的重复劳动 提高工作效率 2 使用脚本调用谷歌翻译接口自动化翻译 3 free 作为程序员肯定接受不了付费服务 找方法解决限制 前期准备 1 谷
  • Java笔记16——优先级队列(堆)

    目录 概念 基于二叉树的堆 二叉堆 基于动态数组ArrayList实现的最大堆 向堆中添加元素 siftUp 在堆中取出最大值 最大堆 3 heapify 堆化 在Java中比较两个元素的大小关系 基于堆的优先级队列到底如何实现 有啥应用