~/victorpierre.dev $ cat learning/programming/data-structures/queues.md
Queues
Queues are a fundamental data structure used extensively in programming for managing data in a particular order. The order is typically First-In-First-Out (FIFO), but variations exist. Queues are used in various applications, including process scheduling, breadth-first search in graphs, and more.
graph LR;
A(Front) --> B[Element 1]
B --> C[Element 2]
C --> D[Element 3]
D --> E(Rear)
Overview of Queues
FIFO Principle: In a standard queue, the first element added to the queue will be the first one to be removed, much like a line of people waiting for a service.
Basic Operations:
- Enqueue (or offer): Adds an item to the end of the queue.
- Dequeue (or poll): Removes an item from the front of the queue.
- Peek (or front): Returns the item at the front of the queue without removing it.
Implementation: Queues can be implemented using arrays, linked lists, or specialized queue data structures provided by many programming languages.
Usage: Queues are used in scenarios like breadth-first search in graphs, CPU task scheduling, and handling requests in web servers.
Types of Queues
- Simple Queue: The standard FIFO queue.
- Circular Queue: A more efficient queue implementation using a fixed-size array and two pointers.
- Priority Queue: Elements are added based on their priority, and the highest-priority element is removed first.
- Double-Ended Queue (Deque): Allows insertions and deletions at both the front and the rear.
Common Operations and Their Big O Notation
| Operation | Time Complexity | Comments |
|---|---|---|
| Enqueue | O(1) | Adding an item to the rear is usually a constant time operation. |
| Dequeue | O(1) | Removing an item from the front is also a constant time operation. |
| Peek/Front | O(1) | Accessing the front element without removing it is constant time. |
| Size | O(1) | Keeping track of the queue’s size is typically done in constant time. |
Advantages of Queues
- Orderly Processing: Ensures that data is processed in the order it arrives, which is essential in many applications like BFS.
- Efficiency: Basic operations have constant time complexity.
- Versatility: Variants like priority queues and deques have a wide range of applications.
Disadvantages of Queues
- Limited Access: Only the front (and rear, in deques) is accessible, limiting certain operations.
- Fixed Capacity: In array-based implementations, the queue can run out of space (though this can be mitigated with dynamic arrays or circular queues).
Applications
- Breadth-First Search (BFS): In graph algorithms, queues are used to hold the vertices to be processed next.
- CPU Scheduling: In operating systems, queues manage the tasks to be executed by the CPU.
- Asynchronous Data Transfer: Queues are used in scenarios where data is produced and consumed at different rates (e.g., IO Buffers, pipes, file IO).
Code Representation
import (
"container/list"
"fmt"
)
// Queue represents a queue data structure.
type Queue struct {
items *list.List
}
// NewQueue creates a new empty queue.
func NewQueue() *Queue {
return &Queue{items: list.New()}
}
// Enqueue adds an item to the back of the queue.
func (q *Queue) Enqueue(item interface{}) {
q.items.PushBack(item)
}
// Dequeue removes and returns the item from the front of the queue.
func (q *Queue) Dequeue() (interface{}, error) {
if q.IsEmpty() {
return nil, fmt.Errorf("queue is empty")
}
item := q.items.Front()
q.items.Remove(item)
return item.Value, nil
}
// Peek returns the item at the front of the queue without removing it.
func (q *Queue) Peek() (interface{}, error) {
if q.IsEmpty() {
return nil, fmt.Errorf("queue is empty")
}
item := q.items.Front()
return item.Value, nil
}
// IsEmpty checks if the queue is empty.
func (q *Queue) IsEmpty() bool {
return q.items.Len() == 0
}
// Size returns the number of items in the queue.
func (q *Queue) Size() int {
return q.items.Len()
}