Queue

What is Queue ?

Practical Understanding:
Difference b/w Stack & Queue:
Operations:
Time Complexities:
Implementation Ways:

There are two ways to implement a stack:

  1. Using array
  2. Using linked list


Array implementation of Queue

Pros: Easy to implement. Memory is saved as pointers are not involved.

Cons: It is not dynamic. It doesn’t grow and shrink depending on needs at runtime.

Implementation
class Queue: 
    def __init__(self): 
        self.queue = []


    def is_empty(self):
        return len(self.queue) == 0
    

    def enqueue(self, data):
        self.queue.append(data)
    

    def dequeue(self):
        if(self.is_empty()):
            return "Empty Queue"
        return self.queue.pop(0)
    

    def front(self):
        if(self.is_empty()):
            return "Empty Queue"
        return self.queue[0]
    

    def rear(self):
        if(self.is_empty()):
            return "Empty Queue"
        return self.queue[-1]
  

print("Example:- Array Implementation of Queue.")
my_queue = Queue()
print("Enqueue: 10, 20, 30, 40 to queue.")
my_queue.enqueue(10)
my_queue.enqueue(20)
my_queue.enqueue(30)
my_queue.enqueue(40)
print("Dequeue the first element: {}".format(my_queue.dequeue()))
print("Front element in queue: {}".format(my_queue.front()))
print("Rear element in queue: {}".format(my_queue.rear()))
print("Is queue empty? {}".format(my_queue.is_empty()))

Output:

queue_array_impl_output


Linked List Implementation of Queue

Pros: The linked list implementation of stack can grow and shrink according to the needs at runtime.

Cons: Requires extra memory due to involvement of pointers.

Implementation
class QueueNode:
    def __init__(self, data):
        self.data = data
        self.next = None


class Queue:
    def __init__(self):
        self.head = None
    

    def is_empty(self):
        return True if self.head is None else False


    def enqueue(self, data):
        new_queue_node = QueueNode(data)
        new_queue_node.next = self.head
        self.head = new_queue_node
    

    def dequeue(self):
        if(self.is_empty()):
            return "Empty Queue"
        
        if(self.head.next is None):
            dequeued_data = self.head.data
            self.head = None
            return dequeued_data
        
        temp = self.head
        while(temp.next.next):
            temp = temp.next
        
        dequeued_data = temp.next.data
        temp.next = None
        return dequeued_data
    

    def front(self):
        if(self.is_empty()):
            return "Empty Queue"

        temp = self.head
        while(temp.next):
            temp = temp.next
        
        return temp.data
    

    def rear(self):
        if(self.is_empty()):
            return "Empty Queue"
        return self.head.data



print("Example:- LinkedList Implementation of Queue.")
my_queue = Queue()
print("Enqueue: 10, 20, 30, 40 to queue.")
my_queue.enqueue(10)
my_queue.enqueue(20)
my_queue.enqueue(30)
my_queue.enqueue(40)
print("Dequeue the first element: {}".format(my_queue.dequeue()))
print("Front element in queue: {}".format(my_queue.front()))
print("Rear element in queue: {}".format(my_queue.rear()))
print("Is queue empty? {}".format(my_queue.is_empty()))

Output:

queue_linked_list_impl_output


Applications of Queue


Standard Queue Problems

1. Queue using Stacks***

Problem:

Given a stack data structure with push and pop operations.

The task is to implement a queue using instances of stack data structure and operations on them.

Approach:

Algorithm with costly Enqueue:
Implementation:
from queue import LifoQueue

class Queue:
    def __init__(self):
        self.stack1 = LifoQueue()
        self.stack2 = LifoQueue()
    

    def enqueue(self, data):
        # (1) Put all the data first to stack2
        while(not self.stack1.empty()):
            self.stack2.put(self.stack1.get())

        # (2) Put the current data also to stack2
        self.stack2.put(data)
        
        # (3) Move evrything back to stack1 from stack2
        while(not self.stack2.empty()):
            self.stack1.put(self.stack2.get())
    

    def dequeue(self):
        if(self.stack1.empty()):
            print("Empty Queue")
            return 
        else:
            return self.stack1.get()
    

    def front(self):
        if(self.stack1.empty()):
            print("Empty Queue")
            return 
        else:
            return self.stack1.queue[-1]
    

    def rear(self):
        if(self.stack1.empty()):
            print("Empty Queue")
            return 
        else:
            return self.stack1.queue[0]
    

    def is_empty(self):
        return self.stack1.empty()

    

print("Example:- Queue Using Stacks:")
my_queue = Queue()
print("Enqueue: 3, 1, 2, 4 to queue.")
my_queue.enqueue(3)
my_queue.enqueue(1)
my_queue.enqueue(2)
my_queue.enqueue(4)
my_queue.enqueue(6)

print("6 is enqueued to the queue.\n")
print("Dequeue the element: {}".format(my_queue.dequeue()))
print("Front element in queue: {}".format(my_queue.front()))
print("Rear element in queue: {}".format(my_queue.rear()))
print("Is queue empty? {}".format(my_queue.is_empty()))

Output:

queue_using_stacks_output



2. Stack using Queues***

Problem:

Given a queue data structure that supports standard operations like enqueue() and dequeue().

The task is to implement a stack data structure using only instances of queue and queue operations allowed on the instances.

Approach:

Algorithm with costly Push:
Implementation:
from queue import Queue

class Stack:
    def __init__(self):
        self.queue1 = Queue()
        self.queue2 = Queue()
    

    def push(self, data):
        # (1) Put the data first to queue2
        self.queue2.put(data)

        # (2) Put all the data from queue1 to queue2
        while(not self.queue1.empty()):
            self.queue2.put(self.queue1.get())
        
        # (3) Swap the names of two queues
        self.queue1, self.queue2 = self.queue2, self.queue1
    

    def pop(self):
        if(self.queue1.empty()):
            print("Stack Underflow")
            return 
        else:
            return self.queue1.get()
    

    def top(self):
        if(self.queue1.empty()):
            print("Stack Underflow")
            return 
        else:
            return self.queue1.queue[0]
    

    def is_empty(self):
        return self.queue1.empty()

    

print("Example:- Stack Using Queues:")
my_stack = Stack()
print("Pushed: 4, 2, 1, 3 to stack.")
my_stack.push(4)
my_stack.push(2)
my_stack.push(1)
my_stack.push(3)
my_stack.push(6)
print("6 is pushed to the stack.\n")

print("Pop the first element: {}".format(my_stack.pop()))
print("Top element in stack: {}".format(my_stack.top()))
print("Is stack empty? {}".format(my_stack.is_empty()))

Output:

stack_using_queus_output



3. Find first circular tour that visits all petrol pumps***

Problem:

Example:

Input: {4, 6}, {6, 5}, {7, 3} and {4, 5}. // 4 petrol pumps with amount of petrol and distance to next petrol pump

Output: 2 // The first point from where truck can make a circular tour is 2nd petrol pump.

Approach-1:
Approach-2:
Implementation:
def first_circular_tour_visiting_all_pumps(pumps):
    n = len(pumps) 
    # Consider first petrol pump as starting point 
    start = 0 
    end = 1 
      
    curr_petrol = pumps[start][0] - pumps[start][1]
      
    # Run a loop while all petrol pumps are not visited
    while(end != start): 
        # While curr_petrol is < 0, dequeue till it become positive.  
        while(curr_petrol<0 and start!=end): 
            # Remove starting petrol pump. Change start 
            curr_petrol -= pumps[start][0] - pumps[start][1]
            start = (start +1)%n 
              
            # If 0 is being considered as start again, then there is no possible solution 
            if start == 0: 
                return -1
  
        # Add a petrol pump to current tour 
        curr_petrol += pumps[end][0] - pumps[end][1] 
        end = (end +1) % n 
  
    return start+1  



print("Example-1: first_circular_tour_visiting_all_pumps()")
pumps = [(4, 6), (6, 5), (7, 3), (4, 5)]
print(first_circular_tour_visiting_all_pumps(pumps))

print("Example-2: first_circular_tour_visiting_all_pumps()")
pumps = [(4, 6), (6, 5), (3, 5), (10, 5)]
print(first_circular_tour_visiting_all_pumps(pumps))

Output:

first_circular_tour_output

Complexity:



4. Maximum of all subarrays of size k (Sliding Window Maximum)

Problem:

Given an array an integer k, find the maximum for each and every contiguous subarrays of size k.

Examples:

Input: 1, 2, 3, 1, 4, 5, 2, 3, 6 and k=3

Output: 3, 3, 4, 5, 5, 5, 6

Input: 8, 5, 10, 7, 9, 4, 15, 12, 90, 13 and k=4

Output: 10, 10, 10, 15, 15, 90, 90

Approach-1:
Approach-2:
Implementation
def max_of_all_subarrays_of_k_size(arr, k):
    n = len(arr)
    max_subarray = []

    # Initially current window
    current_window = arr[:k]

    # First get the initial current_window of of first k elements,
    # the first element of max_subarray is max of initial window.
    max_subarray.append(max(current_window))

    # Now start from k+1th element, and in current_window
    # dequeue the first element from current_window and enqueue the k+1th element
    # get max of current_window and put in max_subarray and continue till the last element.
    for i in range(k, n):
        # Dequeue the first element and enqueue the next element
        current_window.pop(0)
        current_window.append(arr[i])

        max_subarray.append(max(current_window))
    
    return max_subarray



print("Example-1: max_of_all_subarrays_of_k_size")
arr = [1, 2, 3, 1, 4, 5, 2, 3, 6]
print(max_of_all_subarrays_of_k_size(arr, 3))

print("\nExample-2: max_of_all_subarrays_of_k_size")
arr = [8,  5,  10, 7,  9,  4,  15, 12, 90, 13]
print(max_of_all_subarrays_of_k_size(arr, 4))

Output:

sliding_window_max

Complexity:



5. Generate Binary Numbers from 1 to N (An Interesting Method)

Problem:

Given a number n, write a function that generates and prints all binary numbers with decimal values from 1 to n.

Examples:

Input: n = 2

Output: 1, 10

Input: n = 5

Output: 1, 10, 11, 100, 101

Approach:
Algorithm:
Implementation:
def generate_1_to_n_binary_using_queue(n):
    binary = []
    queue = ["1"]

    while(n > 0):
        # Dequeue from the queue, add current to the list of binary numbers, then
        # append "0" to it and enqueue and again append "1" to it and enqueue to queue
        current = queue.pop(0)
        binary.append(current)

        queue.append(current+"0")
        queue.append(current+"1")
        
        n -= 1
    
    return binary



print("Example-1: generate_1_to_n_binary_using_queue(5)")
print(generate_1_to_n_binary_using_queue(5))
        
print("\nExample-2: generate_1_to_n_binary_using_queue(10)")
print(generate_1_to_n_binary_using_queue(10))

generate_binary_using_queue

Complexity:




← Previous:  Stack Next: Matrix →