The queue is one of the most fundamental data structures.
A queue observes the First-In-First-Out (FIFO) principle, where the first element inserted is the first one to be removed. The structure can be visualized as in a line of people waiting for a service, where the person who enters first is served first.
The Queue Abstract Data Type
A Queue have two main operations, enqueue and dequeue. The enqueue operations adds an elements at the rear end of the queue, while the dequeue operation removes the element at the front of the Queue.
Altogether, a queue should support the following operations.
enqueue(e)
: Add element e
at the end of the queue.
dequeue()
: Remove and return the first element.
first()
: Return a reference to the first element in the queue without removing it.
is_empty()
: Check whether the queue is empty. True
if so, False
otherwise.
size()
: Return the number of elements in the queue.
List-based Queue implementation
We will first look on how to implement a Queue
class from scratch to better understand its mechanisms before exploring better built-in implementations.
We will implement the Queue
class with a list as the underlying structure for storing the queue elements.
Note: The implementation of the dequeue() method in the above class is very inefficient. Calling pop(-1)
of a lists always has a worst-case time complexity of O(n)
, where n is the size of the list. This is because after the first element is removed, each of the remaining elements will have to shift to the left, which takes O(n) time.
While the Queue
class above serves as a simple and intuitive example, it is not meant for use beyond simple learning projects due to the aforementioned inefficiency. In the following parts we will discuss more robust and efficient implementations of the queue data structure.
Using collections.deque
The collections module in the standard library defines the deque
class, pronounced as "deck" which offers very efficient insertion and deletion operations on either ends. We can use this class to create LIFO queues for use even in a production setup.
The deque
class have various methods, we will only discuss the ones that enables us to use it as a FIFO queue.
append(e) |
Add element e at the right end. |
popleft() |
Remove the item at the left end. |
len() |
get the size of the deque. |
Since deque
objects supports efficient insertions and deletions from either end, we can also add elements at the front(left) end and remove them from the back(right) end, without any change in functionality or performance. This way we can use the following methods:
appendleft(e) |
Add element e at the left end. |
pop() |
Remove an element from right. |
The queue module
The queue module in the standard library also provides an efficient implementation of the queue data structure. This implementation is optimized for performance and is thread-safe. The class is more suitable for use with multi-threaded applications, as it allows multiple threads to safely insert and remove items from the queue without causing any conflicts or errors.
The Queue
class in the module defines the following basic methods.
put(e) |
Adds element e at the end of the Queue. |
get() |
Removes and returns the first element. |
empty() |
Checks whether the queue is empty. True if so, False otherwise. |
qsize() |
Returns the size of the queue. |
Size: 4
10
20
30
40