java中的堆——优先队列

目录

java中的堆——优先队列

215. 数组中的第K个最大元素

PriorityQueue的特点

源码解析

PriorityQueue常用方法


java中的堆——优先队列

215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

要找到第k个最大的元素,首先想到的就是对数组进行排序,然后输出第k个值。这样本质上是使用的快排,时间复杂度为O(ogn)

控件复杂度为O(1)。

另外,也可以考虑使用容量为k的小根堆,并且将所有数组元素依次进堆。最后堆顶元素就是所求的第k大的元素,整个堆中的元素就是前K个大的元素。

值得一提的是,在JAVA中堆的实现是通过PriorityQueue优先队列来实现的。

解题代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        // 定义小根堆
        PriorityQueue<Integer> heap = new PriorityQueue<Integer>((n1, n2) -> n1 - n2);

        // 控制堆的容量为k
        for (int n: nums) {
          heap.add(n);
          if (heap.size() > k)
            heap.poll();
        }

        // 输出
        return heap.poll();        
  }
}

PriorityQueue的特点

  • PriorityQueue 是基于优先级堆的无限优先级队列。
  • PriorityQueue是一种无界的,线程不安全的队列。
  • PriorityQueue通过数组实现的。
  • 优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。
  • 不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。
  • 队列的头部是相对于指定顺序的最小元素。 如果多个元素被绑定到最小值,那么头就是这些元素之一。
  • 优先级队列是无限制的,但是具有管理用于在队列上存储元素的数组的大小的内部容量 。 它始终至少与队列大小一样大。 当元素被添加到优先级队列中时,其容量会自动增长。

PriorityQueue优先级规则可以由我们根据具体需求而定制, 方式有2种:

  1. 添加元素自身实现了Comparable接口,确保元素是可排序的对象。
  2. 如果添加元素没有实现Comparable接口,可以在创建PriorityQueue队列时直接指定比较器。

eg:

public class Test {
    public static void main(String[] args) {
        //通过改造器指定排序规则
        PriorityQueue<Student> q = new PriorityQueue<Student>(new Comparator<Student>() {
            public int compare(Student o1, Student o2) {
                //按照分数低到高,分数相等按名字
                if(o1.getScore() == o2.getScore()){
                    return o1.getName().compareTo(o2.getName());
                }
                return o1.getScore() - o2.getScore();
            }
        });
        //入列
        q.offer(new Student("dafei", 20));
        q.offer(new Student("will", 17));
        q.offer(new Student("setf", 30));
        q.offer(new Student("bunny", 20));

        //出列
        System.out.println(q.poll());  //Student{name='will', score=17}
        System.out.println(q.poll());  //Student{name='bunny', score=20}
        System.out.println(q.poll());  //Student{name='dafei', score=20}
        System.out.println(q.poll());  //Student{name='setf', score=30}
    }
}

源码解析

1. 默认属性

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    transient Object[] queue;    //队列容器, 默认是11
    private int size = 0;  //队列长度
    private final Comparator<? super E> comparator;  //队列比较器, 为null使用自然排序
    //....
}

2.入堆操作

    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);      //当队列长度大于等于容量值时,自动拓展
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e); //
        return true;
    }
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);   //指定比较器
        else
            siftUpComparable(k, x);   //没有指定比较器,使用默认的自然比较器
    }
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

从源码上看PriorityQueue的入列操作并没对所有加入的元素进行优先级排序。仅仅保证数组第一个元素是最小。

3.出堆操作

    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);
        return result;
    }
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);  //指定比较器
        else
            siftDownComparable(k, x);    //默认比较器
    }
private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

    @SuppressWarnings("unchecked")
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

从源码可知,当第一个元素出堆之后,对剩下的元素进行再排序,挑选出最小的元素排在数组第一个位置,相当每次只排出最小的那个。

另外从出堆和进堆的操作可以看出来,PriorityQueue是线程不安全的,因为它的入堆和出堆都没有加锁。

PriorityQueue常用方法

PriorityQueue常用方法
Modifier and TypeMethod and Description
booleanadd(E e)

将指定的元素插入到此优先级队列中。

voidclear()

从此优先级队列中删除所有元素。

Comparator<? super E>comparator()

返回用于为了在这个队列中的元素,或比较null如果此队列根据所述排序natural ordering的元素。

booleancontains(Object o)

如果此队列包含指定的元素,则返回 true 。

Iterator<E>iterator()

返回此队列中的元素的迭代器。

booleanoffer(E e)

将指定的元素插入到此优先级队列中。

Epeek()

检索但不删除此队列的头,如果此队列为空,则返回 null 。

Epoll()

检索并删除此队列的头,如果此队列为空,则返回 null 。

booleanremove(Object o)

从该队列中删除指定元素的单个实例(如果存在)。

intsize()

返回此集合中的元素数。

Spliterator<E>spliterator()

在此队列中的元素上创建late-binding失败快速 Spliterator 。

Object[]toArray()

返回一个包含此队列中所有元素的数组。

<T> T[]toArray(T[] a)

返回一个包含此队列中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值