Understanding Priority Queue and Its Use Cases
A priority queue is an abstract data type that expands on the idea of a traditional queue. A priority queue manages elements according to their priority; higher priority elements are served before lower priority ones, regardless of their order of arrival, in contrast to a standard queue that operates on the First-In-First-Out (FIFO) principle.
Priority queues are very useful when some tasks or data pieces need more urgent attention than others because of this basic difference. Operating systems, networking, and algorithmic optimization are just a few of the computer science fields that use priority queues.
The hospital emergency queue is an ideal real-life example of a priority queue. In this queue of patients, the patient with the most critical situation is the first in the queue, and the patient who doesn’t need immediate medical attention will be the last. In this queue, the priority depends on the medical condition of the patients.
Creating Priority Queues
To create a priority queue, we must import the java.util.PriorityQueue package. After importing the package, follow these steps to create a Java priority queue.
PriorityQueue<Integer> digits = new PriorityQueue<>();
In this case, a priority queue has been established without any arguments. In this instance, the smallest item in the queue is at the top of the priority queue. Additionally, items are eliminated from the queue in ascending order. However, with the help of a comparator interface, we can easily customize the ordering of elements.
Working of Priority Queues
The working of priority queues consists of some of the basic commands of operations and methods of implementation.
Basic Operations of Priority Queue
Basic operations mainly consist of different operations, and it also supports the following core operations. Some of the basic core operations are:
IsEmpty: It is one of the basic operations of the priority queue, which aims to check if the given queue is empty or not.
Peek: In a priority queue, peek is called one of the most basic operations whose main work is to return the highly prioritized element without removing it from the queue.
Insert: Insert is one of the basic commands of the priority queue. Insert is also known as enqueue. The main aim of the insert operation is to add an element with an associated priority with the queue.
Size: The basic command of size operations is to return the number of elements in a queue.
Delete operation: Delete operation is also known as dequeue. The main task of this operation is to remove the elements from the queue and also return the elements with the highest priority in a queue.
Methods Implementation
The priority queues can be implemented with the help of various data structures, and each method consist of different performance characteristics. Some of these are given below:
Binary Search Tree
In the data structure named as binary search tree, after implementing the methods, complexity can be. Insertion is O(log n) on average, Deletion is O(log n) on average, and Peek can degrade to O(1) if unbalanced.
Array or Linked List (unsorted)
In the data structure named as Linked List for unsorted data. Insertion can be O(1), and it is simply added to the end. Deletion is O(n), and it must go for an element with the highest priority.
Array or Linked List (Sorted)
In the data structure named as Linked List for sorted data. Insertion must be O(n) and always maintain a sorted order. Deletion can be O(1), and it is always at the front with the highest priority.
Fibonacci Heap
In the data structure named as Fibonacci Heap. The complexity of the insertion operation is O(1). The complexity of the deletion operation is O(log n).
Binary Heap
A binary heap is a type of data structure, and it can be considered the most common implementation for operations such as insertion, deletion, and peek because it strikes a good balance between performance and implementation complexity. Here, Insertion gives O(log n), Deletion also gives O(log n), and peek produces O(1) as a result.
Understanding Priority Queue with the help of an example.
// Priority Queue implementation
import java.util.ArrayList;
class Heap {
// Function to heapify the tree
void heapify(ArrayList<Integer> hT, int i) {
int size = hT.size();
// Find the largest among root, left child, and right child
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && hT.get(l) > hT.get(largest))
largest = l;
if (r < size && hT.get(r) > hT.get(largest))
largest = r;
// Swap and continue heapifying if root is not largest
if (largest != i) {
int temp = hT.get(largest);
hT.set(largest, hT.get(i));
hT.set(i, temp);
heapify(hT, largest);
}
}
// Function to insert an element into the tree
void insert(ArrayList<Integer> hT, int newNum) {
int size = hT.size();
if (size == 0) {
hT.add(newNum);
} else {
hT.add(newNum);
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(hT, i);
}
}
}
// Function to delete an element from the tree
void deleteNode(ArrayList<Integer> hT, int num) {
int size = hT.size();
int i;
for (i = 0; i < size; i++) {
if (num == hT.get(i))
break;
}
int temp = hT.get(i);
hT.set(i, hT.get(size - 1));
hT.set(size - 1, temp);
hT.remove(size - 1);
for (int j = size / 2 - 1; j >= 0; j--) {
heapify(hT, j);
}
}
// Print the tree
void printArray(ArrayList<Integer> array, int size) {
for (Integer i : array) {
System.out.print(i + " ");
}
System.out.println();
}
// Driver code
public static void main(String args[]) {
ArrayList<Integer> array = new ArrayList<Integer>();
int size = array.size();
Heap n = new Heap();
n.insert(array, 3);
n.insert(array, 4);
n.insert(array, 9);
n.insert(array, 5);
n.insert(array, 2);
System.out.println("Max-Heap array: ");
n.printArray(array, size);
n.deleteNode(array, 4);
System.out.println("After deleting an element: ");
n.printArray(array, size);
}
}
Output:
Key Characteristics of Priority Queues
In a data structure, a priority queue is considered an extension of the linear queue, and it consists of certain kinds of features.
Mutable vs Immutable priorities: In a priority queue according to the mutable and immutable priorities, some of the implementations allow for priority updates and others don’t.
Element Priority: In a priority queue, every element consists of certain priorities that determine its position in the queue.
Same Priority Handling: According to the principle of First In First Out (FIFO), if different numbers of elements consist of the same priority, then it is removed from the queue.
Dynamic Ordering: In a priority queue, if new elements are inserted in a queue, then the order of the queue can be changed dynamically.
No Strict FIFO: In a priority queue, elements with the lowest priority can be jump behind the elements with the highest priority.
Advanced Variations of Priority Queue
Concurrent Priority Queues
Priority Queues with Priority Updates
Min-Max Heap
Double-ended priority queue
Applications of Priority Queues
Some of the common applications of priority queues are given below:
Operating System Scheduling
Operating system scheduling is one of the most common fundamental applications of priority scheduling. In this application operating system maintains processes that are already waiting in a queue and are ready for the execution done by the CPU.
An example of operating system scheduling is: In Linux, Completely Fair Scheduler (CFS) uses a variant of priority queue allocates the time to the CPU in a fair way to all the processes in a queue.
Dijkstra’s Shortest Path Algorithm
Dijkstra algorithm is another application of priority queues that is used to find the shortest path between the two nodes for a particular graph. This algorithm maintains a priority queue of nodes to explore in the graph. Priority is mostly based on the current shortest distance from the source node. In this algorithm, in every step, nodes having the smallest distance or nodes with the highest priority are processed next. Due to this, it becomes easy to find the shortest path. At last, we can say that without using a priority queue, the implementation of Dijkstra’s algorithm will be less efficient.
Huffman Coding (Data Compression)
Huffman coding is a crucial application of a priority algorithm. It is mainly used for data compression. Huffman coding is a lossless data compression algorithm that is used to build an optimal prefix code. In this algorithm, given characters are counted first for their frequency in the input. Using a min-heap, each character is placed in a priority queue along with its frequency as a priority. It repeatedly gives two lowest frequency nodes, combines them, and then inserts the new node again. This whole helps in the creation of a Huffman tree from the bottom up by ensuring optimal compression.