原理
- 基于最小堆实现。当操作时,维持最小堆的性质(细节参照算法导论)
解读java.util.PriorityQueue
- 基础操作
优先队列,说穿了就是在维护一个最小堆,下面是几个重要的基础操作,看下优先队列是如何维护最小堆的。 - 队列的基础操作:add和poll,并且假设,现在已经是最小堆
- 1.add
- 在完全树的队尾加入加入的元素。此时,只有当前节点至根节点的这条路径不满足最小堆要求。因此只要维护下此路径至最小堆状态便可。因此,执行siftUp操作,类似于插于排序,将元素不停的与其父节点对比。如果比父节点小,则与父节点互换。保证路径从小到大排列。
- 2.poll
- 取出队列头的值(返回值),将最后一个节点的值赋给队列头,砍掉最后一个节点。这个时候,只有第一个节点的值会不符合最小堆。至需要使用siftDown操作,将这个节点down下去就可以。
- 分析
- 存储问题:静态数组存储,每次数组长度不够的时候,产生新的更长的数组。遵循Double size if small; else grow by 50%。这边界定的大小为64.
- 优点:纯粹靠数组的位置关系去构建完全二叉树,不需要额外的指针开销,非常的节省内存。java.util中,数组的第0位也完美的使用了起来。
parent = (child - 1) >>> 1 - childL = parent <<< 1 + 1
- childR = parent <<< 1 + 2
- 缺点:每次产生新数组时。需要从新申请空间,并且将数据拷贝至新数组。同时当优先队列使用长时间后。内存开销会一直保持在最大值。
- 个人觉得,可以根据业务场景,优化下内存分配策略。比如不在乎内存的,最大值确定的,可以直接生成最大值的数组。定时重置数组长度等。个人觉得缺点还是不明显的。
- 优点:纯粹靠数组的位置关系去构建完全二叉树,不需要额外的指针开销,非常的节省内存。java.util中,数组的第0位也完美的使用了起来。
- 元素比较问题
- 支持设置comparator比较器进行比较,或者元素本身实现Comparable接口
- JAVA泛型理解不好,需要补进 TODO
- 其他
- modCount,类似于版本号的概念,当优先队列发生一次改变后,modCount++。个人觉得应该用于高并发状态下的版本控制或者用户持久化相关。
- 存储问题:静态数组存储,每次数组长度不够的时候,产生新的更长的数组。遵循Double size if small; else grow by 50%。这边界定的大小为64.
简易实现
package Structure;
import java.util.*;
public class PriorityQueue<E>{
public static void main(String args[]){
PriorityQueue<Integer> priorityqueue= new PriorityQueue<Integer>();
int tmp = 0;
int leap =10;
Random rand =new Random(25);
for(int i=0;i<leap;i++) {
tmp = rand.nextInt(100);
System.out.println(tmp);
priorityqueue.add(new Integer(tmp));
priorityqueue.output();
}
System.out.println("********");
Integer ans = new Integer(0);
for(int i=0;i<leap;i++) {
ans = priorityqueue.poll();
System.out.println(ans);
priorityqueue.output();
}
}
private Object queue[];
private int size;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
public PriorityQueue(){
queue = new Object[DEFAULT_INITIAL_CAPACITY];
size = 0;
}
public boolean add(E e) {
if(e == null){
throw new NullPointerException();
}
size++;
if(queue.length <= size){
grow();
}
int place = size - 1;
upshift(place,e);
return true;
}
private void grow(){
int oldLength = queue.length;
int newLength;
if(oldLength < 64){
newLength = (oldLength << 2) +2 ;
}else {
newLength = oldLength + oldLength >> 2;
}
queue=Arrays.copyOf(queue,newLength);
}
public E poll() {
if( 0 == size) {
return null;
}
size--;
E result = (E) queue[0];
E e = (E) queue[size];
queue[size] = null;
if(0 < size){
downshift(0,e);
}
return result;
}
public int size() {
return size;
}
public boolean isEmpty() {
if (size == 0) {
return true;
}
return false;
}
public void clear() {
queue = new Object[DEFAULT_INITIAL_CAPACITY];
size = 0;
}
public void output(){
for(int i = 0 ;i<size; i++) {
System.out.print(((E) queue[i]).toString());
System.out.print(',');
}
System.out.println();
}
private void upshift(int place,E x){
Comparable<? super E> key = (Comparable<? super E>) x;
int parentPlace;
while(place > 0){
parentPlace = (place-1) >> 1;
Object e = queue[parentPlace];
if (key.compareTo((E) e) > 0)
break;
queue[place] = e;
place = parentPlace;
}
queue[place] = x;
}
private void downshift(int place,E x){
Object e = null;
int half = size >>> 1;
while(place < half) {
Comparable<? super E> key = (Comparable<? super E>) x;
int leftChild = (place << 1) + 1;
int rightChild = leftChild + 1;
int child = place;
e = queue[leftChild];
if(key.compareTo( (E) e) > 0){
child = leftChild;
key = (Comparable<? super E>) e;
}
if(rightChild < size){
e = queue[rightChild];
if(key.compareTo( (E) e) > 0){
child = rightChild;
}
}
if(child != place){
queue[place] = queue[child];
place = child;
}else {
break;
}
}
queue[place] = x;
}
}
后记
- 逻辑写的巨搓无比,后期版本可以优化
- 面向对象写的特别的不习惯,估计是面向过程写太多了,还是要抽空好好学习下java语法
- 越写越像java.util.PriorityQueue,下次一定要先写完了在看官方实现. 看看自己在看了算法实现后,能封装成什么样子