优先级队列(堆)(java数据结构)

1. 概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。

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

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

JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。(也就是说,优先级队列的底层实现是一颗完全二叉树)

2.堆的概念 

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

堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。 


上面的概念就是说堆的底层是一颗完全二叉树,这里又把完全二叉树分类,分为大根堆和小根堆 .

这里我们就会疑惑,什么是大根堆,什么又是小根堆呢?

如上图:

在小根堆的示例中我们发现,他的根节点一定是小于左右孩子的所有节点 (如上图,根节点10,小于左子树15和右子树56,后面左子树右子树亦是如此),这里我们需要注意的是,左右孩子谁最小没有关系,我们只考虑根和左右孩子的关系.

在大根堆的示例中我们发现,他的根节点一定是大于左右孩子的所有节点 (如上图,根节点70,大于左子树56和右子树30,后面左子树右子树亦是如此),这里我们需要注意的是,不看左右孩子的大小,我们只考虑根和左右孩子的关系.

这里我们说的小根堆大根堆都是完全二叉树,这里存储的时候,就把他们存在了数组当中.

2.1堆的存储方式

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

注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。(就是说非完全二叉树就会浪费空间存在,如上一般二叉树的图)

讲到这里我们就要明白,我们说的PriorityQueue底层是一颗完全二叉树,而他要实现优先级队列一定要把这颗完全二叉树看成堆,堆又分为大根堆和小根堆.

2.2堆的创建 (此篇所有内容以大根堆为例)

所以接下来我们要做的就是如何自己实现这个优先级队列,用自己的方法把他创建成一个大根堆或者小根堆.

在这之前我们先来看一下如下性质:

将元素存储到数组中后,可以根据二叉树章节的性质5对树进行还原。假设i为节点在数组中的下标,则有:
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2 (这里0指的是下标,如下图)
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子 (就是说比如i是56,下标为2,2*i+1等于5,5小于节点个数6,则节点i的左孩子下标为5.比如i是25,下标为3,2*i+1等于7,不小于节点个数6,则25没有左孩子)
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

这里以大根堆为例:

以上图为例,目前上图只是一棵普通的二叉树,这里我们要做的是怎样把它变成大根堆?而大根堆意味着他的每颗子树都是大根49

这里我们要考虑的是,该如何找最大值呢?

这里让每颗子树变成大根堆的过程中,他的做法是找到左右孩子的最大值,然后让最大值和根节点交换(比如在18这棵树里面,找到左右孩子的最大值49,让他的最大值18和根节点18交换).

调整过程如下图:

总结一下调整过程:

1.这里是从子最后一棵子树开始调整的(如上我们是从下标4开始调整的)

2.找到左右孩子的最大值和根节点开始比较,如果根节点大,那么就交换

3.如果能够知道子树的根结点下标,那么下一棵子树就是当前根节点下标-1(如上我们先调节完     4下标,然后调节3下标,接下来调整2下标,然后调整1下标)

4.一直要调整到0下标这棵树就停止!

但是这里的问题是:

1.每颗子树调整的时候结束的位置怎么定?(怎么证明这棵树调整完了)

2.最后一颗子树的根节点下标怎么定?(这个简单,如上总共有10个节点,最后一个节点下标是9,而一直孩子节点推导父亲节点公式是(i-1)/2,而i又等于len-1 ((i-1)/2-->i=len-1) )

现在来探讨问题1,每颗子树结束的位置怎么定?  

其实很简单,我们直接给一个len就好,这里我们就要想,为什么每颗子树的结束位置都和len有关系呢?如上图的长度为10,(这里有没有左右孩子都和节点个数有关),他不管走到左边还是右边,这里不管算的(2*i)+1还是(2*i)+2,只要超过或者等于10个节点(这里只有10个节点,没有10下标),说明没有右孩子,表示这个子树就走完了.所以这里我们就找这棵树的节点个数,而节点个数就是数组长度.

2.3堆向下调整

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?

仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。 

向下过程(以小堆为例): 

下面我们来写代码:

public class TestHeap {

    public int[] elem;
    public int usedSize;//记录当前堆当中 有效的数据个数

    public TestHeap() {
        this.elem = new int[10];//这里只是代表数组的大小为10,所以我们先初始化数组
    }

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

    /**
     * 创建大根堆的代码
     * 如果 使用 向下调整创建大根堆  时间复杂度:O(N)
     */
    //最后一颗子树的下标是(i-1)/2-->i=len-1,所以usedSize-1-1指假如有大小为10,但是放了5个元素,注意的是这里不能是len-1,因为len包含了0,这里len-1=usedSize-1
    public void createHeap() {
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            siftDown(parent,usedSize);//这里放了两个参数指每颗子树和结束位置
        }
    }
    /**
     * 向下调整(每颗子树都是从根向下调整)
     * @param parent
     * @param len
     */
    private void siftDown(int parent,int len) {
        int child = 2*parent+1;
        while (child < len) {//防越界  (child < len说明 至少你有左孩子)
            //左孩子 和 右孩子 比较大小 如果右孩子的值大 就记录最大值
            if(child+1 < len && elem[child] < elem[child+1]) {
                child = child+1;
            }
            //走完上述if语句  证明 child下标 一定保存的是左右两个孩子最大值的下标
            if(elem[child] > elem[parent]) {
                //交换
                swap(child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }
}

其中:

注意:在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。
时间复杂度分析:

最坏的情况即上上图示的情况,从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为 O(log2n)

2.4建堆的时间复杂度

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

因此:建堆的时间复杂度为O(N)。

3. 堆的插入与删除

这里在堆中插入元素,插入之后,从下往上调整.删除堆中元素就从上往下调整 

3.1 堆的插入

堆的插入总共需要两个步骤:

1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
2. 将最后新插入的节点向上调整,直到满足堆的性质

代码如下: 

private void swap(int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

    public void push(int val) {
        //满了
        if(isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);
        //
        usedSize++;//11
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    public void siftUp(int child) {
        int parent = (child-1)/2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }





这里注意:向上调整的建堆复杂度比向下调整的建堆复杂度高一点

3.2 堆的删除

注意:堆的删除一定删除的是堆顶元素。具体如下:

1. 将堆顶元素(0下标)和堆中最后一个元素交换
2. 交换之后,将堆中有效数据个数减减,删除堆中最后一个元素
3. 只需要向下调整0下标这棵树,调整到满足堆特性为止

插入的时候要保证这个堆是一个大根堆,而删除的时候一定要删除优先级比较大的这个,如上的10,而且要删除的时候一定要保证他是大根堆

代码如下: 

 public int pop() {
        //判空
        if(empty()) {
            return -1;
        }
        int oldVal = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown(0,usedSize);
        return oldVal;
    }

    public boolean empty() {
        return usedSize == 0;
    }

完整代码如下:

import java.util.Arrays;

public class TestHeap {

    public int[] elem;
    public int usedSize;//记录当前堆当中 有效的数据个数

    public TestHeap() {
        this.elem = new int[10];//这里只是代表数组的大小为10,所以我们先初始化数组
    }

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

    /**
     * 创建大根堆的代码
     * 如果 使用 向下调整创建大根堆  时间复杂度:O(N)
     */
    //最后一颗子树的下标是(i-1)/2-->i=len-1,所以usedSize-1-1指假如有大小为10,但是放了5个元素,注意的是这里不能是len-1,因为len包含了0,这里len-1=usedSize-1
    public void createHeap() {
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            siftDown(parent,usedSize);//这里放了两个参数指每颗子树和结束位置
        }
    }
    /**
     * 向下调整(每颗子树都是从根向下调整)
     * @param parent
     * @param len
     */
    private void siftDown(int parent,int len) {
        int child = 2*parent+1;
        while (child < len) {//防越界  (child < len说明 至少你有左孩子)
            //左孩子 和 右孩子 比较大小 如果右孩子的值大 就记录最大值
            if(child+1 < len && elem[child] < elem[child+1]) {
                child = child+1;
            }
            //走完上述if语句  证明 child下标 一定保存的是左右两个孩子最大值的下标
            if(elem[child] > elem[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;
    }



    public void push(int val) {
        //满了
        if(isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);
        //
        usedSize++;//11
    }
    public boolean isFull() {

        return usedSize == elem.length;
    }
    public void siftUp(int child) {
        int parent = (child-1)/2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }


    public int pop() {
        //判空
        if(empty()) {
            return -1;
        }
        int oldVal = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown(0,usedSize);
        return oldVal;
    }

    public boolean empty() {

        return usedSize == 0;
    }


}
public class Text {
    public static void main(String[] args) {
        TestHeap testHeap = new TestHeap();
        int[] array = {27,15,19,18,28,34,65,49,25,37};
        testHeap.initElem(array);

        testHeap.createHeap();

        System.out.println(testHeap.pop());

        System.out.println("===");

    }
}

4.常见习题:

1.下列关键字序列为堆的是:(A)
A: 100,60,70,50,32,65   B: 60,70,65,50,32,100   C: 65,100,70,32,50,60
D: 70,65,100,32,50,60   E: 32,50,100,70,65,60   F: 50,100,70,65,60,32

这里没有告诉我们大堆小堆,自己动手


2.已知小根堆为8,15,10,21,34,16,12,删除关键字8之后需重建堆,在此过程中,关键字之间的比较次数是(C)
A: 1     B: 2     C: 3   D: 4


4.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是(C)
A: [3,2,5,7,4,6,8] B: [2,3,5,7,4,6,8]
C: [2,3,4,5,7,8,6] D: [2,3,4,5,6,7,8]

5.常用接口介绍(下面来看一下Java当中的堆是什么样子)

5.1 PriorityQueue的特性

Java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue。

下面我们来演示一下 PriorityQueue是什么样的:

这里我们往这个优先级队列里面放几个元素,这里没人告诉我们他是大根堆还是小根堆,这里我们peek一下这个元素,看我们拿到的是10(大根堆)还是5(小根堆).

结果如下:

如上结果意味着,当我们实例化一个PriorityQueue对象之后,其实默认是一个小根堆.

关于PriorityQueue的使用要注意:

1. 使用时必须导入PriorityQueue所在的包,即:

import java.util.PriorityQueue;

2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException异常

这里我们简单看个例子,如下代码:

import java.util.Comparator;
import java.util.PriorityQueue;
class Student {
    public int age;
    public String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}

public class Text {
    public static void main(String[] args) {


        PriorityQueue priorityQueue1=new PriorityQueue<>();
        priorityQueue1.offer(new Student(10,"张三"));
        priorityQueue1.offer(new Student(10,"王五"));
    }

如下结果:

这里我们发现运行结果报错了(类型转换错误),这里我们offer了两个学生,而push的时候这两个学生要进行比较,但是这里并没有告诉我们通过姓名比较还是通过年龄比较,所以他认为这里面是不能比较的.

这里如果我们屏蔽掉 priorityQueue1.offer(new Student(12,"王五")) 这段代码的时候就不会报错,原因是没有比较对象.这里该怎么做我们鼠标点击绿色画线.可以看到,自定义比较要加Comparable接口.


3. 不能插入null对象,否则会抛出NullPointerException


4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

堆相对于栈来说大一点


5. 插入和删除元素的时间复杂度均为o(log2N)
6. PriorityQueue底层使用了堆数据结构
7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

5.2 PriorityQueue常用接口介绍

这里我们先来适当的看一下 PriorityQueue这个类的源码:

我们先从这个类的构造方法入手:这里 PriorityQueue的继承关系我们先不看了.

以下是他的成员变量:

(默认初始容量)

数组引用,将来用来存储数据(默认为null)

当前队列当中的有效数据的个数

绿色部分就是比较器,它默认为null

不带参的构造方法,如上我们看到,这个构造方法调用了带两个参数的构造方法,一个默认的初始容量11和一个空的比较器,如下是带两个参数的构造方法(按住ctrl,点击上图this)

带一个参数的构造方法(直接给容量)

直接传一个比较器

如上就是他构造方法相关的一些源码.

下面来看一下offer的源码,拿上面这三个为例,他是一个小根堆,这里证明一下他是小根堆.

上面(return(x<y)?-1:(x==y)?0:1))中x<y代表小根堆,当把他改成x>y就是大根堆.

根据上面源码代码,我们来演示一下把PriorityQueue变成大根堆或小根堆. 

大根堆: 

import java.util.Comparator;
import java.util.PriorityQueue;
class Student {
    public int age;
    public String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        
        return o2.compareTo(o1);
    }
}

public static void main(String[] args) {
        Imp imp=new Imp();
        // 当我们实例化一个PriorityQueue对象之后,其实默认是一个小根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(imp);
        priorityQueue.offer(10);
        priorityQueue.offer(5);
        priorityQueue.offer(6);

//        System.out.println(priorityQueue.peek());

        System.out.println(priorityQueue.poll());//5
        System.out.println(priorityQueue.poll());//6
    }

小根堆:

import java.util.Comparator;
import java.util.PriorityQueue;
class Student {
    public int age;
    public String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        
        return o1.compareTo(o2);
    }
}

public static void main(String[] args) {
        Imp imp=new Imp();
        // 当我们实例化一个PriorityQueue对象之后,其实默认是一个小根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(imp);
        priorityQueue.offer(10);
        priorityQueue.offer(5);
        priorityQueue.offer(6);

//        System.out.println(priorityQueue.peek());

        System.out.println(priorityQueue.poll());//5
        System.out.println(priorityQueue.poll());//6
    }

上面我们说到了扩容,下面我们来看一下grow这个方法的源码.

6.OJ练习

top-k问题:最大或者最小的前k个数据。比如:世界前500强公司

top-k问题:最小的K个数(topK问题往往求得都是在大量的数据里面最小的K个值,这个k往往非常小)

意思就是:比如有5个数据,这里找到这5个数据中的最小的三个数.

刚开始看到这个题(最小的k个数),我们的第一反是:

1.整体建立一个小根堆

2.出k次数据就找到了

思路代码如下:

public class Text {
    public static void main(String[] args) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        int[] array = {10,3,15,7,19,9};

        //相当于是以向上调整的方式 建立小根堆 O(n*logn)
        for (int i = 0; i < array.length; i++) {
            priorityQueue.offer(array[i]);
        }

        int k = 3;
        int[] ret = new int[k];
        //k*logN
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }

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

代码结果:

 

如上代码可以通过,但是不是一个好的做法,因为速度比较慢(这里如果按照这种思路做,这里假如有一万个元素,他堆得大小就是一万个元素).而这里的解决思路就是,这里不要建这么大的堆.

举个例子:我们上面不是有27,15,19,18,28一共5个元素,如上图,这里我们的目标是找前k个最小的元素,刚刚上面我们建立的是一个小根堆,而现在要建立一个大根堆,这里不是整体建立一个大根堆,而是大小为k,这里我们先拿前三个建成一个大根堆,然后i走到3下标,然后从第k+1个元素开始比较,如果对顶元素小,则入堆.因为当前这个堆顶元素就不是我要的前K个最小的元素了(原理图如下),后面i++,原理一样.

下面我们来实现这个代码:

先来看一下IDEA上的:

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
class Student {
    public int age;
    public String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {

        return o2.compareTo(o1);
    }
}

public class Text {
    public static int[] smallestK(int[] array, int k) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Imp());
            //1、建立大小为k的大根堆 O(K*logK)
        for (int i = 0; i < k; i++) {
                priorityQueue.offer(array[i]);
            }
        //2、遍历剩下的元素 (N-K)*logK
        // (K*logK) + N*logK  - K*logK   =   N*logK
        for (int i = k; i < array.length; i++) {
            int top = priorityQueue.peek();//27
            if(array[i] < top) {
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        //下面这个不能算topK的复杂度 这个地方是整理数据
        //k*logK
        int[] ret=new int[k];
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

    public static void main(String[] args) {
        int[] array = {10,3,15,7,19,9};
        int[] ret=smallestK(array,3);
        System.out.println(Arrays.toString(ret));
    }




代码结果:9,7,3





这个代码和上面代码一样
class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {

        return o2.compareTo(o1);
    }

    public static int[] smallestK(int[] array, int k) {
        int[] ret = new int[k];
        if(array == null || k <= 0) {
            return ret;
        }
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });

        //1、建立大小为k的大根堆 O(K*logK)
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(array[i]);
        }

        //2、遍历剩下的元素 (N-K)*logK
        // (K*logK) + N*logK  - K*logK   =   N*logK
        for (int i = k; i < array.length; i++) {
            int top = priorityQueue.peek();//27
            if(array[i] < top) {
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        //下面这个不能算topK的复杂度 这个地方是整理数据
        //k*logK
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

再来看一下力扣上面的:

class Imp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {

        return o2.compareTo(o1);
    }
}

class Solution {
    public int[] smallestK(int[] array, int k) {
         int[] ret = new int[k];
        if(array==null || k<=0){
            return ret;

        }
         PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Imp());
        //相当于是以向上调整的方式 建立小根堆 O(n*logn)
       //1、建立大小为k的大根堆 O(K*logK)
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(array[i]);
        }

        //2、遍历剩下的元素 (N-K)*logK
        // (K*logK) + N*logK  - K*logK   =   N*logK
        for (int i = k; i < array.length; i++) {
            int top = priorityQueue.peek();//27
            if(array[i] < top) {
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        //下面这个不能算topK的复杂度 这个地方是整理数据
        //k*logK
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }
}

结果如下:

匿名内部类写法:

public class Text {
    public static int[] smallestK(int[] array, int k) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return 0;
            }
        });
            //1、建立大小为k的大根堆 O(K*logK)
        for (int i = 0; i < k; i++) {
                priorityQueue.offer(array[i]);
            }
        //2、遍历剩下的元素 (N-K)*logK
        // (K*logK) + N*logK  - K*logK   =   N*logK
        for (int i = k; i < array.length; i++) {
            int top = priorityQueue.peek();//27
            if(array[i] < top) {
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        //下面这个不能算topK的复杂度 这个地方是整理数据
        //k*logK
        int[] ret=new int[k];
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

7. 堆排序

这里我们先来思考一个问题:如果从小到大排序,应该建立大根堆还是小根堆?(应该建立大根堆)

举例:吧如上的的元素建成大根堆,建成大根堆之后,他把栈顶元素最大的元素和最后一个元素交换(如上第二幅图),也就是说最大的元素有序了,接着把这颗树进行调整为大根堆(如上第三幅图).再把49和15进行交换,接着把这颗树进行调整为大根堆.这里我们发现每次调整0下标就好了,所以我们这里定义一个end,每次让他和0下标交换,交换完之后end减减(只需要调整0下标,最后结束的位置就是end).

测试代码如下:

 public void heapSort() {
        int end = usedSize-1;
        while (end > 0) {
            swap(0,end);
            siftDown(0,end);
            end--;
        }


这段代码放在TestHeap这个类中

结果:

如上我们发现有序了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值