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; }