1. 去哪儿面试的时候,被问到java源代码中有用到堆的地方吗?
我不假思索的回到,没有!因为当初压根就没有用到过Queue相关的类!
PriorityQueue就是通过Heap实现的。Heap通过数组模拟的!
分析下他维护堆的性质,以及删除首元素时,源代码中采用的手段:
因为PriorityQueue模拟的是队列,所以就必须遵循FIFO,所以这里存在两个维护堆的过程,
一个从下到上 == add
一个从上到下 == remove
还有一个地方,要注意的就是父子节点的小标处理,一定得注意数组是从0开始,不能简单 left = 2*i;
add() 插入元素 , // 通过调用offer()
public boolean offer(E e) {
if (e == null)
throw new NullPointerException(); // 不支持null
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1); // 数组扩容
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e); // 关键一步就在这了,维护堆的性质!在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; // 可以这样用,说明E类型实现了Comparable接口!像Integer,String 默认实现来的!
while (k > 0) {
int parent = (k - 1) >>> 1; // 相当于 (k - 1)/2; 取得父节点
Object e = queue[parent];
if (key.compareTo((E) e) >= 0) // 如果比父节点大,那么就不要比了,说明建的是一个最小堆!那么可以建最大堆吗?
break;
queue[k] = e;// 如果比父节点小,那么就一路递归上去,直到比父节点大或者k == 0!
k = parent;
}
queue[k] = key;
}
poll() 移除头结点 也就是最小的节点!然后再维护堆!
public E poll() {
if (size == 0)
return null;
int s = --size; // 注意这里,size 的大小 -1 了 , 不要在for循环里使用 i < queue.size()一边remove一边还想着输出所有的值了!
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;// 最后一个元素为空,在priorityQueue中实际帮我们存值的是elements数组,但是我们不会看到这个数组,我们只会看到存放元素的那一部分!
if (s != 0)
siftDown(0, x); // 这里就要把最后一个元素插入首部,因为你现在要移除它吗,总得有个来顶替他的位置!
return result;
}
private void siftDown(int k, E x) { // 注意到他与add时的差别吗?这也满足队列的性质,队尾进队首出!队尾进维护堆就得从下往上比较,而插入到对首就得从上往下来比较了!
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 size / 2
while (k < half) { // k如果等于half 那么接下来计算child > size 不就会数组越界了吗?当k = half - 1时,child = 2*half - 1 right = 2*half 注意2*half不一定==size
int child = (k << 1) + 1; // assume left child is least 相当于 k * 2 + 1 注意这里明明是左孩子,可是为什么还要+1了,要知道数组是从0开始的!
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;
}