# Data Structures and Algorithms - Stacks and Queues

### (2) Queue

#### definition

Similar to a stack, a queue is also a special linear table, and the difference from an array is also reflected in operations such as additions and deletions. The insertion operation of the queue can only be performed at the end of the queue, and the deletion operation of the queue can only be performed at the head of the queue. The queue is a first-in, first-out linear table. A queue implemented with an array is called a chained queue, and a queue implemented with a linked list is called a chained queue.

#### Basic operations of queues

Similar to the stack, for the queue, inserting data can only be done at the end of the queue, and deleting data can only be done at the head of the queue. When inserting data in the queue, we call it enqueue, and deleting data in the queue is called dequeue.

Similarly, we discuss their addition and deletion operations based on sequential queues and chained queues respectively.

##### chained queue

For a chained queue, it is also necessary to define two pointers during initialization, the front pointer and the rear pointer point to the tail of the queue. When the queue is empty, both the front pointer and the rear pointer point to the head of the empty linked list. When dequeuing, you only need to point the front pointer to the next node of the current node.

To join the chain queue, you only need to point the next pointer of the node pointed to by the rear pointer at the end of the queue to the new node to be inserted.

```class Node():
def __init__(self, val, next = None):
'''Node Node initialization method.
parameter:
val:stored data
'''
self.val = val
self.next = next

"""chained queue"""
def __init__(self):
"""Chained queue initialization"""

def enqueue(self, num):
"""join the team
parameter:
num: enqueued value
"""
node = Node(num)
if self.tail:
self.tail.next = node
else:
self.tail = node
return True

def dequeue(self):
"""out of the team
return:
"""
self.tail = None
return num
return None

def PrintAll(self):
"""print queue
return:
the entire queue
"""
return None
res = []
while node:
res.append(node.val)
node = node.next
return '->'.join('%s'%num for num in res)
```
##### sequential queue

For a sequential queue, you need to define two pointers and give the size of the queue when initializing, the front pointer points to the head of the queue, and the rear pointer points to the tail of the queue. When the queue is empty, both the front pointer and the rear pointer point to the position where the index of the array is 0. When dequeuing, you only need to move the front pointer one bit backward, so the dequeue time complexity of the sequential queue is O(1).

For the queue entry operation of the sequential queue, it is necessary to consider whether the queue is full. If the rear pointer has pointed to the last subscript position of the array, it is necessary to judge whether the front pointer has moved backward after the dequeue operation. If The front pointer does not point to the position where the subscript of the array is 0, then the data between the front and rear pointers are moved to the beginning of the array, and then the front pointer points to the position of the array subscript 0 again, and the rear pointer points to the rear-front position. If the front pointer points to the position where the index of the array is 0 and the rear pointer points to the last position of the array, then the queue is full, and the problem of expansion needs to be considered. If the queue is not full and the position pointed to by the rear pointer is not the end of the array, the time complexity of entering the queue is O(1); if the queue is not full and the rear pointer points to the end of the array, and the queue is full and needs to be expanded In these two cases, the time complexity is O(n) due to the data movement involved.

For the enqueue operation of the sequential queue, like the above false overflow situation where the rear pointer points to the end of the array but the front of the array is empty, there are usually two simple and crude solutions:

(1) The first is a solution like the above, which consumes O(n) time complexity for data movement

(2) The second is when initializing the queue, apply for a large enough memory space to ensure that the array does not exceed the bounds

```class ArrayQueue():
"""sequential queue"""
def __init__(self, capacity):
"""Initialization of a sequential queue
parameter:
capacity: the size of the sequence queue
"""
self.queue = []
self.capacity = capacity

def enqueue(self, num):
"""enqueue 1
parameter:
num: enqueued value
"""
if self.tail == self.capacity:
return False
self.queue.append(num)
self.tail += 1
return True

def NewEnqueue(self, num):
"""Entry 2: If the leader of the team has extra space and the team is full, move the data before entering the team
parameter:
num: enqueued value
"""
if self.tail == self.capacity:
return False
self.queue[self.tail] = num
self.tail += 1
return True

def dequeue(self):
"""out of the team
return:
"""
if not self.queue:
return False
num = self.queue[0]
return num

def PrintAll(self):
"""print queue
return:
the entire queue
"""
if not self.queue:
print(None)
return False
return True
```

For the first method, when rear==n-1, there will be data movement, which will affect the performance of the queue entry operation; for the second method, due to the continuous dequeuing, the front of the array will be dequeued. A lot of unused memory space is vacated, and these spaces are not released, which will cause a lot of wasted memory space.

So is there any good solution to the out-of-bounds problem of the array? The circular queue to be mentioned next is a solution to this problem.

##### circular queue

We connect the end of the sequence queue to form a ring. As shown in the figure below, we can see that the size of the queue is 8, front=4, rear=7. When we insert elements into the queue, the data is inserted into the position pointed by rear And rear moves one bit back. It should be noted here that the rear pointer has already pointed to the position with the subscript 7, so when we insert an element at the rear, the rear pointer will point to the position with the subscript 6 next.

The above is the situation when the queue is not full. If the queue is full, suppose the situation of the queue is as shown in the following figure.

We can see that the position pointed to by the rear pointer is empty, which means that our circular queue will waste a storage space when it is used.

So how do you judge whether the team is full or empty? The first is to judge whether the queue is empty, which is the same as the ordinary queue. When front==rear, the queue is empty; the condition for judging the queue full is different from other queues. We can use two methods to judge whether the circular queue is full.

The first one: When we initialize the circular queue, we set a flag bit and a count value count. At first, rear=front and flag=count=0. When we insert a data, we judge whether the count is equal to the queue size n-1 , if it is, then the team is full and cannot insert data and set flag=1, otherwise we insert the data into the current position of rear and move rear to the next bit, count++; also when deleting data, head points to his next position and count—.

The second: According to the summary of the predecessors, the condition for the team to be full is (rear + 1) % n == front, and this condition is directly used to judge whether the team is full.

```class CircularQueue():
"""Circular queue implemented with arrays"""
def __init__(self, capacity):
"""Initialization of a sequential queue
parameter:
capacity: the size of the sequence queue
"""
self.queue = []
self.capacity = capacity + 1

def enqueue(self, num):
"""enqueue 1
parameter:
num: enqueued value
"""
if (self.tail + 1) % self.capacity == self.head:
return False
self.queue.append(num)
self.tail = (self.tail + 1) % self.capacity
return True

def dequeue(self):
"""out of the team
return:
"""
return num

def PrintAll(self):
"""print queue
return:
the entire queue
"""