Priority Queue - Heap

foreword

This blog describes the following knowledge points

  1. Master the concept and implementation of the heap
  2. Master the use of PriorityQueue

priority queue

concept

The queue was introduced earlier. The queue is a first-in-first-out (FIFO) data structure, but in some cases, the data to be operated may have priority. Generally, when dequeuing, elements with higher priority may be required to be out of the queue first. In the medium scene, it is obviously inappropriate to use the queue. For example: when playing a game on a mobile phone, if there is an incoming call, the system should prioritize the incoming call; in this case, the data structure should provide two basic
operations , one is to return the highest priority object, and the other is to add a new object. This data structure is the Priority Queue (Priority Queue)

heap concept

If there is a set of key codes K = {k0, k1, k2, ..., kn-1}, store all its elements in a one-dimensional array in the order of a complete binary tree, and satisfy: Ki <= K2i +1 and Ki<= K2i+2 (Ki >= K2i+1 and Ki >= K2i+2) i = 0, 1, 2..., then it is called a small heap (or a large heap). The heap with the largest root node is called the largest heap or large root heap, and the heap with the smallest root node is called the smallest heap or small root heap.

Properties of the heap:

The value of a node in the heap is always not greater than or not less than the value of its parent node;
the heap is always a complete binary tree
insert image description here

heap storage

From the concept of the heap, it can be seen that the heap is a complete binary tree, so it can be efficiently stored in a sequential manner according to the rules of the sequence. Note:
for incomplete binary trees, it is not suitable to use sequential storage, because in order to restore the binary tree, the space Empty nodes must be stored in the node, which will lead to relatively low space utilization. After storing the elements in the array, the tree can be restored according to property 5 of the chapter on binary trees. Assuming that i is the subscript of the node in the array, then:

If i is 0, the node represented by i is the root node, otherwise the parent node of node i is (i - 1)/2
If 2 * i + 1 is less than the number of nodes, the subscript of the left child of node i is 2 * i + 1, otherwise there is no left child
If 2 * i + 2 is less than the number of nodes, the subscript of the right child of node i is 2 * i + 2, otherwise there is no right child

heap creation - adjust down (array initialization)

  1. Let the parent mark the node that needs to be adjusted, and the child mark the left child of the parent (note: if the parent has a child, it must have a left child first)
  2. If the left child of the parent exists, that is: child < size, perform the following operations until the left child of the parent does not exist, judge whether the right child of the parent exists, find the smallest child among the left and right children, let the child mark it, and compare the parent with the parent Smaller child child comparison, if:
    parent is smaller than the smaller child child, the adjustment is over
    Otherwise: exchange the parent and the smaller child child, after the exchange is completed, the larger elements in the parent move down, which may cause the subtree
    not to satisfy the right nature, so you need to continue to adjust downwards,
    that is, parent = child; child = parent*2+1; and then continue
public void shiftDown(int[] array, int parent) {
    
    
	// child先标记parent的左孩子,因为parent可能右左没有右
	int child = 2 * parent + 1;
	int size = array.length;
	while (child < size) {
    
    
	// 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记
		if(child+1 < size && array[child+1] < array[child]){
    
    
			child += 1;
		}
	// 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了
	if (array[parent] <= array[child]) {
    
    
		break;
	}else{
    
    
		// 将双亲与较小的孩子交换
		int t = array[parent];
		array[parent] = array[child];
		array[child] = t;
	// parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
		parent = child;
		child = parent * 2 + 1;
		}
	}
}

Note: When adjusting a binary tree rooted at the parent, it must be satisfied that the left and right subtrees of the parent are already heaps before they can be adjusted downwards.
Time complexity analysis:
The worst case is the situation shown in the illustration. From the root to the leaf, the number of comparisons is the height of the complete binary tree, that is, the time complexity is log(N) and the time complexity of
building a heap is O(N )

The time complexity of building a heap is O(N)

Heap insertion requires a total of two steps:

  1. Put the elements into the underlying space first (note: expansion is required when the space is not enough)
  2. Adjust the last newly inserted node up until the property of the heap is satisfied
public void shiftUp(int child) {
    
    
	// 找到child的双亲
	int parent = (child - 1) / 2;
	while (child > 0) {
    
    
	// 如果双亲比孩子大,parent满足堆的性质,调整结束
		if (array[parent] > array[child]) {
    
    
		break;
	}
	else{
    
    
		// 将双亲与孩子节点进行交换
		int t = array[parent];
		array[parent] = array[child];
		array[child] = t;
		// 小的元素向下移动,可能到值子树不满足对的性质,因此需要继续向上调增
		child = parent;
		parent = (child - 1) / 1;
	}
}
}

heap deletion

Note: The deletion of the heap must delete the top element of the heap. details as follows:

  1. swap the top element of the heap with the last element in the heap
  2. Reduce the number of valid data in the heap by one
  3. Adjusts the top element down
public class MyPriorityQueue {
    
    
	// 演示作用,不再考虑扩容部分的代码
	private int[] array = new int[100];
	private int size = 0;
	
	public void offer(int e) {
    
    
		array[size++] = e;
		shiftUp(size - 1);
	}
	public int poll() {
    
    
		int oldValue = array[0];
		array[0] = array[--size];
		shiftDown(0);
		return oldValue;
	}
	public int peek() {
    
    
		return array[0];
	}
}

common interface

The Java collection framework provides two types of priority queues, PriorityQueue and PriorityBlockingQueue. PriorityQueue is thread-unsafe, and PriorityBlockingQueue is thread-safe. This article mainly introduces PriorityQueue.

Note about the use of PriorityQueue:

  1. When using it, you must import the package where PriorityQueue is located, namely:
  2. The elements placed in the PriorityQueue must be able to compare in size, and objects that cannot be compared in size cannot be inserted, otherwise
    a ClassCastException will be thrown
  3. A null object cannot be inserted, otherwise a NullPointerException will be thrown
  4. There is no capacity limit, any number of elements can be inserted, and its internal capacity can be automatically expanded
  5. The time complexity of inserting and deleting elements is
  6. The bottom layer of PriorityQueue uses a heap data structure
  7. PriorityQueue is a small heap by default—that is, the elements obtained each time are the smallest elements
Function name explain
boolean offer(E e) Insert element e, return true if the insertion is successful, if the e object is empty, throw NullPointerException, time complexity, note: when the space is not enough, it will expand
E peek() Get the element with the highest priority, if the priority queue is empty, return null
E poll() Remove the element with the highest priority and return, or null if the priority queue is empty
int size() Get the number of valid elements
void clear() empty
boolean isEmpty() Check whether the priority queue is empty, return tru if empty

Guess you like

Origin blog.csdn.net/qq_56454895/article/details/131860247