Post

Array-based Sequences - Queue, Stack

Practice for Array-based Sequences such as

Array-based Sequences - Queue, Stack

Queue

A Queue ADT stores arbitrary objects and follows the First-In, First-Out (FIFO) principle.

The first element inserted into the queue is the first one removed.

Queue Discipline (FIFO)

TermMeaning
FrontPosition where elements are removed
Rear (Back)Position where elements are added

1.1 Abstract Data Type (ADT): Queue

OperationDescription
enqueue(x)Insert element x at the rear
dequeue()Remove and return the front element
peek() / front()Return the front element without removing it
is_empty()Check whether the queue is empty
size()Return the number of elements

Exceptions: Attempting the execution of dequeue or front on an empty queue throws an EmptyQueueException

1.2. Object-Oriented Design of a Queue Class

1.2.1. Attributes

items: a container to store queue elements

1.2.2. Methods

  • Constructor: init()
  • Core operations: enqueue, dequeue
  • Utility operations: peek, is_empty, size

1.3. Implementation 1: Queue Using a Python List

1.3.1. Array-Based Queue Implementation

A queue can be efficiently implemented using an array of fixed size N in a circular fashion.

Instead of shifting elements, the array is treated as circular, wrapping around when the end is reached.

Two integer variables are used to track the queue:

  • f: index of the front element
  • r: index immediately past the rear element

Important rule

  • Array location r is always kept empty
  • This helps distinguish between full and empty states
1
2
Array indices: 0  1  2  3  4  ...  N-1
Queue wraps around using modulo arithmetic

1.3.2. Size Operation

1
2
Algorithm size()
	return (N - f + r) mod N

Explanation:

  • Computes the number of elements stored
  • Works correctly even when indices wrap around

1.3.3. isEmpty Operation

1
2
Algorithm isEmpty()
    return (f = r)

Explanation:

  • When f == r, the queue contains no elements

1.3.4. Enqueue Operation (Full Queue Case)

The enqueue operation inserts an element at position r

After insertion: r <- (r + 1) mod N

1
2
3
4
5
6
Algorithm enqueue(o)
	if size() = N <- 1 then
		throw FullQueueException
	 else  
		Q[r] <- o
		r <- (r + 1) mod N

In this case:

  • enqueue throws an exception
  • The specific exception type is implementation-dependent

1.3.5. Code Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Queue:
    def __init__(self):
        self.items = []

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

    def enqueue(self, item):
        self.items.append(item)

    def dequeue(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items.pop(0)

    def peek(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items[0]

    def size(self):
        return len(self.items)

1.3.6. Example Usage

OperationReturn ValueQueue (first <- Q <- last)
Q.enqueue(5)[5]
Q.enqueue(3)[5, 3]
len(Q)2[5, 3]
Q.dequeue()5[3]
Q.is_empty()False[3]
Q.dequeue()3[]
Q.is_empty()True[]
Q.dequeue()“error”[]
Q.enqueue(7)[7]
Q.enqueue(9)[7, 9]
Q.first()7[7, 9]
Q.enqueue(4)[7, 9, 4]
len(Q)3[7, 9, 4]
Q.dequeue()7[9, 4]

Examples:

1
2
3
4
5
6
7
8
9
q = Queue()

q.enqueue(5)
q.enqueue(10)
q.enqueue(15)

print(q.dequeue())  # Output: 5
print(q.peek())     # Output: 10
print(q.size())     # Output: 2

Direct applications

  • Waiting lists, bureaucracy
  • Access to shared resources (e.g., printer)
  • Multiprogramming

Indirect applications

  • Auxiliary data structure for algorithms
  • Component of other data structures

Array-based Queue

  • Use an array of size N in a circular fashion
  • Two variables keep track of the front and rear
    • f index of the front element
    • r index immediately past the rear element Array location r is kept empty

Queue Operations

  • We use the modulo operator (remainder of division)
1
2
3
4
5
Algorithm size()
	return (N - f + r) mod N

Algorithm isEmpty()
	return (f = r)
  • Operation enqueue throws an exception if the array is full
  • This exception is implementation-dependent
1
2
Operation enqueue throws an exception if the array is full
This exception is implementation-dependent

1.3.7. Time Complexity Analysis (List-Based Queue)

OperationTime ComplexityExplanation
enqueueO(1)Append to end of list
dequeueO(n)All elements shift left
peekO(1)Direct index access
is_emptyO(1)Length check

Using list.pop(0) is inefficient for large queues.

1.4. Implementation 2: Efficient Queue Using collections.deque

Python provides deque (double-ended queue), optimized for FIFO operations.

1.4.1 Code Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from collections import deque

class Queue:
    def __init__(self):
        self.items = deque()

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

    def enqueue(self, item):
        self.items.append(item)

    def dequeue(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items.popleft()

    def peek(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items[0]

    def size(self):
        return len(self.items)

1.4.2. Time Complexity (Deque-Based Queue)

OperationTime Complexity
enqueueO(1)
dequeueO(1)
peekO(1)

1.5 Comparison of Queue Implementations

ImplementationProsCons
List-basedSimple, intuitiveSlow dequeue()
Deque-basedFast, scalableRequires import
Circular QueueMemory efficientMore complex logic

2. Stack

An Abstract Data Type (ADT) is an abstraction of a data structure. It defines what operations are supported and how the data behaves, without specifying how the data is implemented.

A Stack is a linear data structure that follows the LIFO principle: \(LIFO:Last-In, First-Out\)

This means:

  • The last element inserted
  • is the first element removed

An ADT specifies:

  • Data stored
  • Operations on the data
  • Error conditions associated with operations

Example: ADT for a Stock Trading System

Data Stored: Buy and sell orders

Supported Operations

1
2
3
order buy(stock, shares, price)
order sell(stock, shares, price)
void cancel(order)

Error Conditions

  • Buying or selling a nonexistent stock
  • Canceling a nonexistent order

2.1 Basic Terminologies of Queue

A stack has one accessible end only, called the Top.

TermMeaning
TopPosition where elements are added and removed
BottomThe first element added

2.2 Abstract Data Type (ADT)

An Abstract Data Type (ADT) defines the behavior of a stack, independent of implementation.

OperationDescription
push(x)Insert element x onto the top
pop()Remove and return the top element
peek() / top()Return the top element without removing it
is_empty()Check if the stack is empty
size()Return the number of elements

2.3. Object-Oriented Design of a Stack Class

2.3.1. Attributes

  • items: container that stores stack elements

2.3.2 Methods

  • Constructor: init()
  • Core operations: push, pop
  • Utility operations: peek, is_empty, size

2.4. Implementation 1: Stack Using a Python List

2.4.1. Code Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Stack:
    def __init__(self):
        self.items = []

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

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items.pop()

    def peek(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[-1]

    def size(self):
        return len(self.items)

2.4.2. Example Usage

1
2
3
4
5
6
7
8
9
s = Stack()

s.push(10)
s.push(20)
s.push(30)

print(s.pop())    # Output: 30
print(s.peek())   # Output: 20
print(s.size())   # Output: 2

2.4.3. Time Complexity Analysis (List-Based Stack)

OperationTime ComplexityExplanation
pushO(1)Append to end of list
popO(1)Remove from end
peekO(1)Direct index access
is_emptyO(1)Length check

Very efficient using Python lists.

What’s happen? When:

  • Occurs when pushing onto a full stack (bounded stack)
  • Occurs when popping from an empty stack

2.5. Implementation 2: Bounded Stack (Optional Extension)

ApplicationDescription
Function callsCall stack in memory
Undo / RedoText editors
Expression evaluationPostfix / Prefix
Syntax checkingParentheses matching
DFSGraph traversal

2.5.1 Implement Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class BoundedStack:
    def __init__(self, capacity):
        if capacity <= 0:
            raise ValueError("Capacity must be a positive integer")

        self.capacity = capacity
        self.items = []

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

    def is_full(self):
        return len(self.items) == self.capacity

    def push(self, item):
        if self.is_full():
            raise OverflowError("Stack overflow: cannot push onto a full stack")
        self.items.append(item)

    def pop(self):
        if self.is_empty():
            raise IndexError("Stack underflow: cannot pop from an empty stack")
        return self.items.pop()

    def peek(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[-1]

    def size(self):
        return len(self.items)

    def __str__(self):
        return f"Stack(bottom → top): {self.items}"

2.5.2. Using

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
stack = BoundedStack(3)

stack.push(10)
stack.push(20)
stack.push(30)

print(stack)        # Stack(bottom → top): [10, 20, 30]
print(stack.peek()) # 30

# Stack overflow
# stack.push(40)    # OverflowError

print(stack.pop())  # 30
print(stack.pop())  # 20
print(stack.size()) # 1

# Stack underflow
# stack.pop()
# stack.pop()       # IndexError

2.5.3. Time & Space Complexity

OperationTime Complexity
pushO(1)
popO(1)
peekO(1)
is_emptyO(1)
is_fullO(1)

3. Excercises

3.1. Applying the Queue Data Structure – Service Queue Management System

Problems

In many real-world service systems such as banks, public service centers, hospitals, and customer support offices, service requests are typically processed in a First In – First Out (FIFO) order.

Without a proper queue management mechanism:

  • Customers may be served in the wrong order
  • The waiting process becomes difficult to track
  • It is impossible to evaluate system performance through statistics

Therefore, the problem is to design and implement a queue-based service management system that:

  • Manages customers in the correct service order
  • Simulates the service process
  • Provides basic operational statistics

The system must operate in a console-based environment and must not use any database.

System Requirements

Simulate a service queue system that processes customers in First-In-First-Out (FIFO) order.

The system must support the following operations:

  • ADD id name service_type arrival_time: add a customer to the queue
  • SERVE: remove and process the front customer
  • NEXT: view the front customer (do not remove)
  • COUNT: print number of customers currently waiting
  • SERVED: print total number of customers served

Data Requirements

AttributeData TypeDescription
idintCustomer identifier
namestringCustomer name
service_typestringType of service
arrival_timeint or floatArrival time

Technical Requirements

  • The Queue data structure must be used collections.deque
  • No database is allowed
  • The program must run in the terminal
  • The code must be well-structured and commented

Input / Output Specification

Input (input.txt)

  • Line 1: integer N – number of operations
  • Next N lines: each line is a command

Output (output.txt)

CommandConditionOutput Format
ADDAlways(No output)
SERVEQueue not emptyID: <id> Name: <name> Service: <service_type>
SERVEQueue is emptyEMPTY
NEXTQueue not emptyID: <id> Name: <name> Service: <service_type>
NEXTQueue is emptyEMPTY
COUNTAlways<number_of_waiting_customers>
SERVEDAlways<total_customers_served>

Examples:

Input:

1
2
3
4
5
6
7
8
9
10
11
10
ADD 101 A Payment 12.5
ADD 102 B Registration 13.0
NEXT
SERVE
NEXT
SERVE
SERVE
COUNT
SERVED
NEXT

Output:

1
2
3
4
5
6
7
8
ID: 101 Name: A Service: Payment
ID: 101 Name: A Service: Payment
ID: 102 Name: B Service: Registration
ID: 102 Name: B Service: Registration
EMPTY
0
2
EMPTY

Manual Code:

Step 1: Queue.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Queue:
    def __init__(self):
        self.items = []

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

    def enqueue(self, item):
        self.items.append(item)

    def dequeue(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items.pop(0)

    def peek(self):
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items[0]

    def size(self):
        return len(self.items)

Step 2: Import Queue into templates

1
from queue import Queue

Step 3: Initialize in solve()

1
2
3
n = int(all_lines_of_input[0])
countServed = 0
queueTmp = Queue()
  1. Loop through input and Handle each command
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def solve():
    ...
    for i in range(1, n + 1):
        words = all_lines_of_input[i].rstrip("\n").split(" ")
        if words[0] == 'ADD':
            queueTmp.enqueue(ServeModel(words[1], words[2], words[3], words[4]))
        elif words[0] == 'NEXT':
            if queueTmp.is_empty():
                result_for_output += "Empty\n"
            else:
                result_for_output += queueTmp.peek().print() + "\n"
        elif words[0] == 'SERVE':
            if queueTmp.is_empty():
                result_for_output += "Empty\n"
            else:
                countServed += 1
                result_for_output += queueTmp.dequeue().print() + "\n"
        elif words[0] == 'COUNT':
            result_for_output += str(queueTmp.size()) + "\n"
        elif words[0] == 'SERVED':
            result_for_output += str(countServed) + "\n"

3.2. Applying the Stack Data Structure – Expression Processing System

Problems

In many areas of computer science such as compilers, calculators, and programming language interpreters, expressions (arithmetic or logical) must be processed in a specific order.

However, expressions written in infix notation (e.g. 3 + 4 * 2) are not easy for computers to evaluate directly because operator precedence and parentheses must be handled correctly.

The problem is to design a system that:

  • Converts expressions from infix notation to postfix notation
  • Evaluates postfix expressions correctly
  • Uses the Stack data structure as the core mechanism

The system must be implemented as a console-based program without using any external libraries for expression evaluation.

Requirements

Given several infix expressions, perform the following tasks for each expression:

  1. Convert the infix expression to postfix notation
  2. Evaluate the postfix expression

The program must use the Stack data structure for both conversion and evaluation.

Expressions may contain:

  • Integers
  • Operators: + - * /
  • Parentheses: ( )

Input / Output Specification

Input (input.txt)

  • Line 1: integer T – number of test cases
  • Next T lines: each line is an infix expression

Output (output.txt)

For each test case:

  • If the expression is valid:
    • Line 1: postfix expression
    • Line 2: evaluation result
  • If the expression is invalid:
    • Print: Error: Mismatched parentheses.
  • If division by zero occurs:
    • Print: Error: Division by zero.

Example:

Input:

1
2
3
4
3
3 + (4 * 5)
10 + 2 * 6
(1 + 2

Output:

1
2
3
4
5
3 4 5 * +
23
10 2 6 * +
22
Error: Mismatched parentheses.

Manual Code:

Step 1: Using stack.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Stack:
    def __init__(self):
        self.items = []

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

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items.pop()

    def peek(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[-1]

    def size(self):
        return len(self.items)

Step 2: Update stack.py (make it safer & easier)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Stack:
    def __init__(self):
        self.items = []

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

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop() if not self.is_empty() else None

    def peek(self):
        return self.items[-1] if not self.is_empty() else None

    def size(self):
        return len(self.items)

Step 3: Write a function Simple Infix as Postfix in templates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def precedence(op):
    if op == '^':
        return 3
    elif op in ('*', '/'):
        return 2
    elif op in ('+', '-'):
        return 1
    return 0


def tokenize(expression):
    tokens = []
    i = 0
    n = len(expression)

    while i < n:
        if expression[i] == ' ':
            i += 1
            continue

        if expression[i].isdigit():
            num = []
            while i < n and expression[i].isdigit():
                num.append(expression[i])
                i += 1
            tokens.append(''.join(num))
            continue

        tokens.append(expression[i])
        i += 1

    return tokens


def infixToPostfix(expression):
    stack = Stack()
    output = []
    tokens = tokenize(expression)

    for token in tokens:
        if token.isdigit():
            output.append(token)

        elif token == '(':
            stack.push(token)

        elif token == ')':
            while not stack.is_empty() and stack.peek() != '(':
                output.append(stack.pop())
            stack.pop()  # bỏ '('

        else:  # operator
            while (not stack.is_empty() and
                   precedence(stack.peek()) >= precedence(token)):
                output.append(stack.pop())
            stack.push(token)

    while not stack.is_empty():
        output.append(stack.pop())

    return ' '.join(output)

Step 4: Simple Evaluate Postfix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def evaluatePostfix(expression):
    stack = Stack()
    tokens = expression.split()

    for token in tokens:
        if token.lstrip('-').isdigit():
            stack.push(int(token))
        else:
            b = stack.pop()
            a = stack.pop()

            if token == '+':
                stack.push(a + b)
            elif token == '-':
                stack.push(a - b)
            elif token == '*':
                stack.push(a * b)
            elif token == '/':
                stack.push(int(a / b))
            elif token == '^':
                stack.push(a ** b)

    return stack.pop()

Step 5. Very Clean solve()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def solve():
    ...
    n = int(all_lines_of_input[0])
    res = []

    for i in range(1, n + 1):
        words = all_lines_of_input[i].rstrip("\n")

        try:
            postfix = infixToPostfix(words)
            result = evaluatePostfix(postfix)

            if isinstance(result, str):
                res.append(result)
            else:
                res.append(postfix)
                res.append(str(result))

        except:
            res.append("Error: Mismatched parentheses.")

        result_for_output = "\n".join(res)
This post is licensed under CC BY 4.0 by the author.