Collection series Queue (nine): PriorityQueue

PriorityQueue is a priority queue, the underlying principle implemented using binary heap. Let's take a look at its class declaration:

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable

PriorityQueue inherited AbstractQueue abstract class, with basic features queue.

Binary heap

Because the underlying PriorityQueue using binary heap to achieve, so we need to introduce binary heap.

Binary heap is actually a complete binary tree Structurally, or nearly complete binary tree. Each binary heap left subtree and right subtree is a binary heap. Called "maximum heap" when the parent node is always greater than or equal to key a child node, referred to as "minimum heap" when the parent node is always less than or equal to any key a child node.

        最小堆                               最大堆
             1                                11                          
         /        \                        /        \ 
       2           3                    9           10
    /     \      /     \             /     \      /     \ 
   4      5     6       7           5      6     7      8
  / \     / \                      / \     / \
 8  9 10 11                       1   2   3   4 

Heap common operations in binary are: insert, delete, we will introduce two operations in detail.

insert

In the binary heap insertion node idea is: to insert a new node at the end of the array, and then continue to go up compared to the parent node until you find the right place, in line with the current sub-tree binary heap nature. Binary heap inserting operation in the worst case the root node needs to move from the leaves, so its time complexity is O (logN).

For example, we have the following minimum heap, when we insert a value of node 6, the adjustment process is as follows:

        最小堆                              
             1                                                       
         /        \                       
       5           7                  
    /     \      /     \             
   8      10   48     55         
  / \     / \                     
 11 9   15                      
  • 6 new node is inserted at the end of the array.
        最小堆                              
             1                                                       
         /        \                       
       5           7                  
    /     \      /     \             
   8      10   48     55         
  / \     / \                     
 11 9   15   6                   
  • Doing floating operation constantly compared with the parent node, the parent node until it is greater than or equal. First, 6 <10, the exchange position.
        最小堆                              
             1                                                       
         /        \                       
       5           7                  
    /     \      /     \             
   8     → 6   48     55         
  / \     / \                     
 11 9   15   10                   
  • Compared with the parent node continues, 6> 5 with the nature of a binary tree ends.

delete

Binary heap ideas for deleting nodes:

  1. First, if the end node is removed, then delete it directly, you do not need to be adjusted.
  2. Next, delete nodes and end nodes exchange data, after deleting the end node, and then to delete nodes continue to do the sinking operation.
  3. Finally, we continue to do operations on floating delete nodes.

For example, we have the following minimum heap, a node 7 when we delete the value, the adjustment process is as follows:

             1                                                       
         /        \                       
       5           7                  
    /     \      /     \             
   8      10   48     55         
  / \     / \   / \                   
 11 9   15  16 50 52                
  • First, delete nodes and end nodes exchange data, and delete end nodes.
             1                                                       
         /        \                       
       5           52                  
    /     \      /   \             
   8      10   48     55         
  / \     / \   / \                   
 11 9   15  16 50                      
  • Next, delete nodes (52) continue to make sinking operation. 52 first compares the size of 48 and 55, 52 and 48 will be exchanged. 52 are then compared with the size of 50, 52 and 50 will be exchanged. The results are:
             1                                                       
         /        \                       
       5           48                  
    /     \      /   \             
   8      10    50     55         
  / \     / \   / \                   
 11 9   15  16 52  
  • Finally, to remove a node (15) continues to go up to do the operation, the result is:
             1                                                       
         /        \                       
       5          15                  
    /     \      /     \             
   8      10   48     55         
  / \     / \                     
 11 9 

Here's a detail, Why do after sinking operation, does it need to do a float? This is because we can not determine the size relationship between the parent node and delete node value of the end node.

In the example above, we remove the node 7, delete parent node is 1, the end node is 52. Since the end nodes and delete nodes in the same sub-tree, so we can determine the parent node to delete nodes must be smaller than the end node, that is, 1 must be smaller than 52. So we do not need to go up operation.

But if the end node and remove nodes in a sub-tree is not it? At this point we can not determine the size relationship between the end node and delete the parent node, then the following scenario may occur:

             1                                                       
         /        \                       
       5           230                  
    /     \      /   \             
   8      10   240     255         
  / \     / \   / \      / \           
 11 9   15  16 241 242 256 260 
/ \
27 33

If we remove the node 255 at this time, then the parent node of the deletion node 230, end node 33. At this end of the node to delete nodes smaller than the parent node, you need to do floating operations.

principle

Complete understanding of the binary tree insertion, deletion principle, let us look at PriorityQueue source code is very simple.

Class member variables

// 队列数据
transient Object[] queue;  
// 大小
private int size = 0;
// 比较器
private final Comparator<? super E> comparator;

From the class member variables we can know PriorityQueue underlying the use of arrays to store data, comparator determines in fact the realization of a maximum or minimum heap heap. By default PriorityQueue a minimum heap.

Construction method

PriorityQueue a total of seven constructors.

public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}
    
public PriorityQueue(int initialCapacity) {
    this(initialCapacity, null);
}
    
public PriorityQueue(Comparator<? super E> comparator) {
    this(DEFAULT_INITIAL_CAPACITY, comparator);
}
    
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) { 
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}
// 传入集合初始值
public PriorityQueue(Collection<? extends E> c) {
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        initElementsFromCollection(ss);
    }
    else if (c instanceof PriorityQueue<?>) {
        PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        initFromPriorityQueue(pq);
    }
    else {
        this.comparator = null;
        initFromCollection(c);
    }
}
// 传入PriorityQueue初始值
public PriorityQueue(PriorityQueue<? extends E> c) {
    this.comparator = (Comparator<? super E>) c.comparator();
    initFromPriorityQueue(c);
}
// 传入SortedSet初始值
public PriorityQueue(SortedSet<? extends E> c) {
    this.comparator = (Comparator<? super E>) c.comparator();
    initElementsFromCollection(c);
}

PriorityQueue constructor more, but their functions are similar. If the incoming ordinary collection, then it will copy the data, and finally call heapify method binary heap initialization. But if the incoming data is SortedSet or PriorityQueue these data have been ordered, then the data can be copied directly to the order.

The core method

For PriorityQueue, its core methods are: get, insert, delete, expansion.

Obtain

PriorityQueue no query methods, replaced by a peek method to get the data.

public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

If the queue is empty, then return null value, otherwise the first element of the queue (i.e. maximum or minimum).

insert

PriorityQueue data insertion process, in fact, the process of inserting data into binary heap.

public boolean add(E e) {
    return offer(e);
}
    
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    // 1.容量不够,进行扩容
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;
    // 2.如果队列为空那么直接插入第一个节点
    // 否则插入末尾节点后进行上浮操作
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}

private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        // 3.采用默认的比较器
        siftUpComparable(k, x);
}
    
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        // 4.将插入节点与父节点比较
        // 如果插入节点大于等于父节点,那么说明符合最小堆性质
        // 否则交换插入节点与父节点的值,一直到堆顶
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

The final logic of the code is inserted in siftUpComparable method, and the method we said above is actually a binary heap insertion implement logic.

delete

PriorityQueue data removal process, in fact, the data from the binary heap when it is deleted.

public boolean remove(Object o) {
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
        removeAt(i);
        return true;
    }
}
    
private E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    // 1.删除的是末尾节点,那么直接删除即可
    if (s == i) // removed last element
        queue[i] = null;
    else {
        E moved = (E) queue[s];
        queue[s] = null;
        // 2.对删除节点做下沉操作
        siftDown(i, moved);
        if (queue[i] == moved) {
            // 3.queue[i] == moved 表示删除节点根本没下沉
            // 意思是其就是该子树最小的节点
            // 这种情况下就需要进行上浮操作
            // 因为可能出现删除节点父节点大于删除节点的情况
            siftUp(i, moved);
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}
    
private void siftDown(int k, E x) {
    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
    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;
}

PriorityQueue deletion point to note is after its sinking, you also need to do a floating operation according to the conditions. Why do operations on floating explain binary heaps of time already mentioned above.

offer

Because the queue is PriorityQueue, so there offer operations.

For operations offer, in fact, is equivalent to an array of data is not inserted, insert it in a logical add details of our approach it has been said.

poll

Because the queue is PriorityQueue, will poll the same operation. The poll operation is actually eject head of the queue node, the same as dropping the head node.

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    // 弹出头结点
    E result = (E) queue[0];
    E x = (E) queue[s];
    queue[s] = null;
    // 做下沉操作
    if (s != 0)
        siftDown(0, x);
    return result;
}

We said before the tombstone node, a node that is to take the end of the replacement value of the node is removed and then do the sinking operation. But here because the deletion node is the root node, so do not need to go up operation.

Expansion

When data is inserted to the queue, if the queue capacity is enough for expansion will operate.

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

PriorityQueue expansion is very simple. If the original capacity is less than 64, then it doubled for the expansion, or expansion to 1.5 times the original.

to sum up

PriorityQueue realization is based on the binary heap, so understand binary heap is equivalent to understand the PriorityQueue. Under PriorityQueue default is the smallest heap, we can change the incoming comparator, making it the largest heap.

Guess you like

Origin www.cnblogs.com/chanshuyi/p/java_collection_09_priority_queue.html