PriorityQueue 优先级队列

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

猜你喜欢

转载自kainever7.iteye.com/blog/2202749