【Java源码】PriorityQueue源码剖析及其应用
来源:互联网 发布:火影忍者ol先攻的算法 编辑:程序博客网 时间:2024/05/22 05:54
第0部分 本文概要
这一篇文章说说PriorityQueue, 之前只知道其底层维护着一个小根堆。
An unbounded priority queue based on a priority heap. The elements of the priority queue are ordered according to their natural ordering, or by a Comparator provided at queue construction time, depending on which constructor is used.
Java8 API 文档里说 PriorityQueue 是一个无界的优先堆结构。 元素的顺序是按照自然序, 或者通过构造时候设置Comparator比较器来决定是大顶堆还是小顶堆, 这个取决于构造函数的选择。
A priority queue does not permit null elements. A priority queue relying on natural ordering also does not permit insertion of non-comparable objects (doing so may result in ClassCastException).
此外, PriorityQueue
不允许 null 元素, 可能会抛出 ClassCastException
类转换异常的错误。
Note that this implementation is not synchronized. Multiple threads should not access a PriorityQueue instance concurrently if any of the threads modifies the queue. Instead, use the thread-safe PriorityBlockingQueue class.
记住:该实现是非线程安全的, 可以使用线程安全的实现类—— PriorityBlockingQueue
- 第0部分 本文概要
- 第1部分 类属性和方法
- 1 add方法
- 2 peek 出队不删除元素
- poll 出队 删除最小元素
- 4 removeAt 删除堆中某一个节点
- 5 clear 清除
- 6 contains 是否包含
- 7 indexOfObject o 查找
- 第2部分 TopK 应用
第1部分 类&属性和方法
//默认初始化大小 11private static final int DEFAULT_INITIAL_CAPACITY = 11; //堆 private transient Object[] queue; //当前大小 private int size = 0; //比较器 private final Comparator<? super E> comparator; //修改次数(增、删、改、查) private transient int modCount = 0; //增加 public boolean add(E e) //出队(不删除) public E peek() //出队(删除) public E poll() //删除 public boolean remove(Object o) //是否包含某元素 public boolean contains(Object o) //清空 public void clear() //扩容 private void grow(int minCapacity) //查找 private int indexOf(Object o)
1.1 add()方法
堆在增加元素后,需要进行调整才能维护其最大堆或者最小堆的性质,下面以最小堆为例:
- 增加元素26,默认是从队尾增加,即直接添加到数组最后。
- 下一步需要执行上滤。26首先和39比较,交换位置;再次和父节点30比较, 交换位置; 继续和父节点14比较, 发现比自己(26)小,停止比较和交换。
- 整个过程经过了3次比较。
贴出add()源码:
/** * Inserts the specified element into this priority *///可以看出,add方法实际上是全部委托给offer(E)public boolean add(E e) { return offer(e); }public boolean offer(E e) { // 第1步:判空 if (e == null) throw new NullPointerException(); // 第2步:改变大小和扩容 modCount++; int i = size; //检查容量(扩容) if (i >= queue.length) grow(i + 1); //改变size size = i + 1; // 第3步:添加元素并上滤 if (i == 0) queue[0] = e;//无父节点 ,直接赋值 else siftUp(i, e);//有父节点,需要上滤 return true; }
增加元素的关键的步骤是:grow()
和 siftUp()
,下面贴下代码:
/*** 1:扩容方式是:当前队列大小queue.length<64,则增加一倍容量;反之则增加一半容量。* 2:调用Arrays的copyOf函数 ,实际上调用了该函数*/ /** * Increases the capacity of the array. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { int oldCapacity = queue.length; // Double size if small; else grow by 50% int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); // overflow-conscious code if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); } // siftUp()://根据不同的比较方式,采取不同比较策略。下面以使用默认comparable的方式分析private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } // 经典的堆 shiftUp 操作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; }
1.2. peek() 出队(不删除元素)
// 这个很简单,只是取出了其中的首位元素,但是并没有删除,不需要调整堆。public E peek() { if (size == 0) return null; return (E) queue[0]; }
3. poll() 出队 删除最小元素
出队过程:
堆顶元素(队首元素)移出, 最后一个叶子节点39 交换到队首。
下滤过程: 将39的两个子节点 26 和 20 比较大小, 选择较小的 20 ,并与39进行交换;接着将39和22 和 21 比较, 发现39大于22 同时大于 21, 因此选取22 和 21 中的较小值 21 与 39 再次交换;
注意:如果孩子结点为null或者都比39大,则结束。
至此, 经过4次比较, 2次交换, 完成出队后再调整。
poll()源码如下:
public E poll() { // 优先队列为空,返回null if (size == 0) return null; int s = --size; modCount++; // 取出队首 E result = (E) queue[0]; E x = (E) queue[s]; // 队尾赋值为null queue[s] = null; // 判断是否执行下滤 if (s != 0) siftDown(0, x); //核心方法,这里把最后一个元素传进去了,而且添加位置是0 return result; }private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } /*** k = 0,所以相当于堆顶空降一个元素,然后执行经典的 ShiftDown*/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; }
1.4 removeAt() 删除堆中某一个节点
private E removeAt(int i) { assert i >= 0 && i < size; modCount++; int s = --size; // 如果删除的是最后一个元素,则将最后一个元素设为null if (s == i) queue[i] = null; else { // 如果删除的不是最后一个元素,取出最后一个元素,并将最后一个元素设为null。执行 ShiftUp E moved = (E) queue[s]; queue[s] = null; // 执行 ShiftDown siftDown(i, moved); // 如果下滤后元素位置没变,说明moved是该子树最小元素;之后需要执行上滤 // ShiftUp 和 ShiftDown 实际只会执行其中一个 if (queue[i] == moved) { siftUp(i, moved); if (queue[i] != moved)// iterator中会用到此处 return moved; } } return null; }
1. 5. clear() 清除
/** * Removes all of the elements from this priority queue. * The queue will be empty after this call returns. */ public void clear() { modCount++; for (int i = 0; i < size; i++) queue[i] = null;//将队列中的每一个元素置为null, 等待GC回收 size = 0; }
1.6 contains() 是否包含
实际上就是查找过程, 代码逻辑很简单,没得说。
public boolean contains(Object o) { return indexOf(o) != -1;//调用1.7 部分内容的indexOf(Object o) } //后面有详细注释 private int indexOf(Object o) { if (o != null) { for (int i = 0; i < size; i++) if (o.equals(queue[i])) return i; } return -1; }
1.7 indexOf(Object o) 查找
private int indexOf(Object o) { (o != null) { //遍历数组查询 for (int i = 0; i < size; i++) //如果是自定义的元素,重写equals方法是很有必要的 if (o.equals(queue[i])) return i; } return -1; }
第2部分 TopK 应用
应用部分已经在之前的【Top K 问题】[Leetcode-215] Kth Largest Element in an Array 数组中第K大的数 一文中已经说过了。
小顶堆解决 Top K 问题的思路:
小顶堆维护当前扫描到的最大100个数,其后每一次的扫描到的元素,若大于堆顶,则入堆,然后删除堆顶;依此往复,直至扫描完所有元素。
Java实现第K大整数代码如下:
public static int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> myQueue = new PriorityQueue<>(k, new Comparator<Integer>(){ public int compare(Integer o1, Integer o2){ return o1 - o2;//小顶堆// return o2 - o1;//大顶堆 } }); for(Integer num: nums){ //如果堆大小小于K, 直接入堆 或者 元素大于小顶堆的堆顶元素,也入堆 if(myQueue.size() < k || num > myQueue.peek() ){ myQueue.offer(num); } if(myQueue.size() > k){ myQueue.poll(); } } return myQueue.peek(); }
- 【Java源码】PriorityQueue源码剖析及其应用
- 《Java源码分析》:PriorityQueue
- Java源码阅读-PriorityQueue
- Java源码阅读之PriorityQueue
- java PriorityQueue 原理分析及源码解读
- 源码分析之PriorityQueue
- PriorityQueue源码及用法
- PriorityQueue类源码解析
- Java8 - PriorityQueue源码
- jdk源码分析PriorityQueue
- 源码分析-PriorityQueue
- PriorityQueue源码分析
- PriorityQueue源码阅读总结
- PriorityQueue 源码分析
- 【Java集合源码剖析】ArrayList源码剖析
- 【Java集合源码剖析】LinkedList源码剖析
- 【Java集合源码剖析】Vector源码剖析
- 【Java集合源码剖析】HashMap源码剖析
- 对于Javascript 执行上下文的理解
- 数据结构解析与归纳
- ssm框架mysql数据库报错,jdbc.properties数据库配置问题
- Android程序员学WEB前端(9)-CSS(4)-商城首页Demo-Sublime
- Swing用户界面组件 文本输入练习
- 【Java源码】PriorityQueue源码剖析及其应用
- R语言做岭回归
- 360浏览器,qq浏览器等登录页输入账号密码,在页面内再次遇见有密码框的地方会自动填充的解决办法。
- TCP之种种连接异常
- 可查询最值
- Unable to execute dex:Multiple dex files define XXXXX
- 在IoC容器中装配Bean(精通Spring+4.x++企业应用开发实战 四)
- Cookie和Session的区别与联系
- imageLoader