Unit 3
Unit 3
Data Structures
UNIT-III: Queue and Linked List
Page 1
Unit-III | Queue & Linked List
Python Basics
Variables
A variable is a name that holds a value.
x = 10
This means: create a box called x, and put the number 10 inside it. Later, when you write x, Python will
give you 10 back.
Lists
A list is like a row of boxes, each holding a value. We will use lists as our 'array'.
numbers = [10, 20, 30, 40]
print(numbers[0]) # prints 10 (first box)
print(numbers[1]) # prints 20 (second box)
print(numbers[-1]) # prints 40 (last box)
[Link](50) # adds 50 at the end → [10,20,30,40,50]
[Link](0) # removes first item → [20,30,40,50]
print(len(numbers)) # prints 4 (how many items)
REMEMBER: Python counts from 0, not 1. First item is numbers[0].
Dictionaries
A dictionary holds pairs: a NAME (called a key) and a VALUE. Think of it like a tiny form with labeled
fields.
student = {'name': 'Riya', 'marks': 85}
print(student['name']) # prints Riya
print(student['marks']) # prints 85
student['marks'] = 90 # change a value
print(student['marks']) # prints 90
WHY WE USE THIS: In this unit, every 'node' of a linked list will be a dictionary with two fields — 'data'
(the value) and 'next' (a link to the next node).
None
None is Python's way of saying "nothing" or "empty". We use it to say "this link points to nowhere."
x = None
print(x) # prints None
Page 2
Unit-III | Queue & Linked List
if x is None:
print("x is empty")
global keyword
Normally, a variable created inside a function lives only inside that function. If we want to change a
variable that lives OUTSIDE the function, we must say 'global' first.
count = 0
def increase():
global count # tells Python: I want to change the outer count
count = count + 1
increase()
increase()
print(count) # prints 2
WHY THIS MATTERS: Our queues and linked lists use global variables (front, rear, head) so that all
functions can see and update them. Every function that changes them must start with 'global'.
Page 3
Unit-III | Queue & Linked List
PART A — QUEUES
What is a Queue?
Imagine it's Monday morning at the Bengaluru Metro station. The ticket counter opens at 8:45 AM.
Twenty people are standing in a line. Who gets their ticket first? The one who came first. The one who
came last waits the longest. This is called FIFO — First In, First Out. That line is a QUEUE.
• PEEK — look at the front person, but don't remove them. Like checking "who's next" without
pushing them forward.
• IS_EMPTY — is the line empty? Cashier can rest.
• IS_FULL — is the counter full? Turn new people away.
Page 4
Unit-III | Queue & Linked List
Line of code
queue = [None] * SIZE Create a list of 5 empty boxes. 'None' means empty. So
queue = [None, None, None, None, None]. Our storage is
ready.
WHY -1 AND NOT 0? Because 0 is a valid position in the list (it's the first slot). We need a special value
that means 'NO position yet.' -1 is not a valid list position, so we use it as our 'empty' signal.
Line of code
Page 5
Unit-III | Queue & Linked List
Line of code
global front, rear Tell Python: I am going to change the outer variables
front and rear. Without this, Python would create new
local copies.
if rear == SIZE - 1: Check: is rear pointing to the very last box? SIZE is 5, so
SIZE-1 is 4 (the last index). If yes, the queue is full.
if front == -1: Check: is the queue empty right now? We use front == -1
as our 'empty' signal.
front = 0 If it was empty, set front to 0 — the first item will go into
box 0, so front now points there.
rear = rear + 1 Move the rear marker one step to the right. Now it
points to the next empty box.
Page 6
Unit-III | Queue & Linked List
Line of code
global front, rear We will read and maybe change the outer front and
rear.
if front == -1 or front > rear: Check if queue is empty. Two cases: (1) front is -1 →
never had items. (2) front has moved past rear → all
items were removed.
value = queue[front] Remember the value at the front. We will return this to
the user.
front = front + 1 Move the front marker one step to the right. The old
front slot is now considered 'removed'.
Run
Starting state: front=0, rear=4, queue=[10,20,30,40,50]
Call dequeue():
• Is queue empty? No — front=0, rear=4.
• value = queue[0] → value = 10.
• front = front + 1 → front = 1.
• Return 10. The user gets 10.
• State: front=1, rear=4, queue=[10,20,30,40,50] — notice 10 is STILL in the list physically, but
front has moved past it, so we treat it as gone.
NOTICE THIS PROBLEM: After a few dequeues, the front slots are 'wasted' — they still exist in the list
but we can never use them again. When rear hits 4, we say 'overflow' even though slots 0,1,2 may be
empty. This is called FALSE OVERFLOW. We will fix it with a Circular Queue.
Page 7
Unit-III | Queue & Linked List
The Code
SIZE = 5
cq = [None] * SIZE
front = -1
rear = -1
Page 8
Unit-III | Queue & Linked List
def enqueue(x):
global front, rear
if (rear + 1) % SIZE == front:
print("Queue is Full")
return
if front == -1:
front = 0
rear = (rear + 1) % SIZE
cq[rear] = x
print("Added", x)
def dequeue():
global front, rear
if front == -1:
print("Queue is Empty")
return None
value = cq[front]
if front == rear:
front = -1
rear = -1
else:
front = (front + 1) % SIZE
return value
global front, rear We will change the outer front and rear.
if (rear + 1) % SIZE == front: Check if queue is full. If the NEXT slot is where front sits,
the circle is full.
return Stop.
rear = (rear + 1) % SIZE Move rear forward by 1, but wrap around if needed. This
Page 9
Unit-III | Queue & Linked List
Line of code
print("Added", x) Confirm.
if front == rear: Special case: only ONE item left. After removing it, the
queue will be empty.
Run
SIZE=5, starting empty. front=-1, rear=-1.
• enqueue(10), (20), (30), (40), (50) → front=0, rear=4, cq=[10,20,30,40,50]
• dequeue() → returns 10. front moves from 0 to 1. cq=[10,20,30,40,50] (slot 0 ignored).
• dequeue() → returns 20. front moves to 2.
• enqueue(60) → rear was 4. (4+1)%5 = 0. Rear becomes 0. cq[0] = 60. Now cq=[60,20,30,40,50].
We reused slot 0!
• enqueue(70) → (0+1)%5 = 1. rear=1. cq=[60,70,30,40,50].
• enqueue(80) → check (1+1)%5 == front (2==2) → YES, queue is FULL. Rejected.
NO MORE FALSE OVERFLOW: We filled exactly 5 slots and then said full — which is correct. No
memory is wasted anymore.
Page 10
Unit-III | Queue & Linked List
Page 11
Unit-III | Queue & Linked List
Four Actions
• insert_front(x) — add x at the front end.
• insert_rear(x) — add x at the back end.
• delete_front() — remove from the front end.
• delete_rear() — remove from the back end.
The Code
dq = []
def insert_front(x):
[Link](0, x)
print("Inserted", x, "at front")
def insert_rear(x):
[Link](x)
print("Inserted", x, "at rear")
def delete_front():
if len(dq) == 0:
print("Deque is empty")
return None
return [Link](0)
def delete_rear():
if len(dq) == 0:
print("Deque is empty")
return None
return [Link]()
def display():
print("Deque:", dq)
Page 12
Unit-III | Queue & Linked List
Line-by-Line
Line of code
return [Link](0) pop(0) removes AND returns the item at position 0. The
user gets it.
return [Link]() pop() with no number means pop from the end. Returns
and removes the last item.
Run
insert_rear(10) # dq = [10]
insert_rear(20) # dq = [10, 20]
insert_front(5) # dq = [5, 10, 20]
insert_front(1) # dq = [1, 5, 10, 20]
print(delete_rear()) # prints 20, dq = [1, 5, 10]
print(delete_front())# prints 1, dq = [5, 10]
Page 13
Unit-III | Queue & Linked List
Priority Queue
In our code we use: smaller number = higher priority. So priority 1 beats priority 2, priority 2 beats
priority 3. You could do it the other way — just be consistent.
The Code
We will store each item as a pair: (priority, value). A pair in Python is called a tuple — written in round
brackets.
pq = []
def enqueue(value, priority):
[Link]((priority, value))
print("Added", value, "with priority", priority)
def dequeue():
if len(pq) == 0:
print("Priority queue is empty")
return None # find the index with the smallest priority
min_index = 0
for i in range(1, len(pq)):
if pq[i][0] < pq[min_index][0]:
min_index = i
item = [Link](min_index)
return item[1] # return just the value
def display():
print("Priority queue:", pq)
Page 14
Unit-III | Queue & Linked List
Line of code
def enqueue(value, priority): Two inputs now: the value and its priority number.
[Link]((priority, value)) Add the pair (priority, value) at the end. The round
brackets make it a tuple.
min_index = 0 Start by assuming the first item has the best priority.
for i in range(1, len(pq)): Loop through every other item, starting from index 1.
if pq[i][0] < pq[min_index][0]: pq[i][0] is the priority of item i. If this item's priority is
smaller (better) than our current best, update.
return item[1] Return just the value — item[1] is the second part of the
pair.
Run
enqueue("BrokenArm", 3)
enqueue("HeartAttack", 1)
enqueue("HighFever", 2)
print(dequeue()) # prints HeartAttack (priority 1 = highest)
print(dequeue()) # prints HighFever (priority 2)
print(dequeue()) # prints BrokenArm (priority 3 = lowest)
Notice: HeartAttack came in the MIDDLE but got served FIRST because its priority was the smallest
number. The order of insertion did not matter.
Page 15
Unit-III | Queue & Linked List
Page 16
Unit-III | Queue & Linked List
[ 10 | → ] → [ 20 | → ] → [ 30 | → ] → [ 40 | None ]
Each box has two halves: the data (left) and the pointer to the next box (right). The last box points to
None.
A linked list is like a TREASURE HUNT. You are given the first clue. It tells you where the second clue is.
The second clue tells you where the third clue is. Each clue leads to the next. The last clue says STOP.
You cannot jump to clue 5 directly — you have to follow the chain from clue 1.
Page 17
Unit-III | Queue & Linked List
Line of code
'data': 10 The first field, called 'data', holds the value 10.
'next': None The second field, called 'next', is supposed to hold the
next node. We set it to None for now — it points to
nothing yet.
Page 18
Unit-III | Queue & Linked List
Step 1 — Set Up
head = None
Line of code
head = None Create the head variable and set it to None. This means:
the list is empty — there is no first node yet.
Line of code
return {'data': x, 'next': None} Build and return a dictionary with two fields: data=x and
next=None. That's our new node, ready to be linked.
Line of code
def insert_at_beginning(x): Define the function with input x — the value to add.
new_node = create_node(x) Build a new node holding x. Its 'next' is currently None.
new_node['next'] = head Make the new node point to whatever head currently
points to. If head was None, new_node['next'] becomes
None — fine, new_node will be the only item. If head
Page 19
Unit-III | Queue & Linked List
Line of code
head = new_node Move head so it now points to the new node. The new
node is the first node.
Run
Before inserting: head → None (empty list)
Call insert_at_beginning(10):
• new_node = {'data': 10, 'next': None}
• new_node['next'] = head → new_node['next'] = None (head was None)
• head = new_node
• Now: head → [10 | None]
Call insert_at_beginning(20):
• new_node = {'data': 20, 'next': None}
• new_node['next'] = head → new_node's next now points to the node holding 10.
• head = new_node → head now points to the 20 node.
• Now: head → [20 | →] → [10 | None]
COMMON MISTAKE: Students often do head = new_node FIRST, then try new_node['next'] = head.
But now head already points to new_node, so new_node['next'] points to new_node itself — an
infinite loop! Always point the new node forward FIRST, then move head.
Line of code
global head We may need to change head (only if the list is empty).
Page 20
Unit-III | Queue & Linked List
Line of code
head = new_node Yes — the new node is the first AND last. head points to
it.
return Done.
while t['next'] is not None: Keep walking as long as there is a next node.
t['next'] = new_node When the loop exits, t is at the LAST node. Set its next to
the new node. Done.
Run
Starting list: head → [10|→] → [20|None]
Call insert_at_end(30):
• new_node = {data:30, next:None}
• Is head None? No.
• t = head → t points to [10|→]
• Check t['next'] is not None? [10]'s next is [20] — not None. Enter loop.
• t = t['next'] → t now points to [20|None]
• Check t['next'] is not None? [20]'s next is None. Exit loop.
• t['next'] = new_node → [20]'s next now points to the new [30] node.
• Final: head → [10|→] → [20|→] → [30|None]
Page 21
Unit-III | Queue & Linked List
Line of code
return Done.
for i in range(pos - 1): Walk pos-1 steps. We want to stop at the node JUST
BEFORE position pos.
if t is None: If we fell off the end before reaching position pos, bad
input.
return Exit.
Page 22
Unit-III | Queue & Linked List
Line of code
WHY THIS ORDER MATTERS: Imagine A → B and you want to insert N between them. If you FIRST set
A→N, then try to set N→B, you have already lost the original link from A to B! Always save the
forward link FIRST (new_node['next'] = t['next']), THEN change t ('s next.
Line of code
head = head['next'] Move head to the second node. The old first node has
no more references — Python will automatically clean it
up from memory.
Page 23
Unit-III | Queue & Linked List
t = head
while t['next']['next'] is not None:
t = t['next']
t['next'] = None
Line of code
if head['next'] is None: Only ONE node in the list? Then deleting from end
means the list becomes empty.
while t['next']['next'] is not None: Keep walking as long as there are TWO more nodes
ahead. When this is False, t is second-last.
t['next'] = None Cut off the last node by setting the second-last's next to
None. The last node is now unreachable — Python
cleans it up.
Line of code
Page 24
Unit-III | Queue & Linked List
Line of code
if head['data'] == x: If the head node has the value, just move head forward.
while t['next'] is not None and Keep walking until either we fall off the end OR we find a
t['next']['data'] != x: node whose NEXT has the value x.
if t['next'] is None: We fell off the end — value was not in the list.
Line of code
print(t['data'], end=" → ") Print its data, followed by an arrow. end=' → ' tells print
NOT to go to a new line.
print("None") After all nodes, print None to show the list ends.
Page 25
Unit-III | Queue & Linked List
Node Structure
# A doubly-linked-list node has three fields
def create_dnode(x):
return {'data': x, 'prev': None, 'next': None}
Visual
None ← [prev | 10 | next] ⇄ [prev | 20 | next] ⇄ [prev | 30 | next] → None
The arrows go both ways. The first node's 'prev' is None (nothing before it). The last node's 'next' is
None (nothing after it).
Line of code
Page 26
Unit-III | Queue & Linked List
Line of code
new_node['next'] = head New node points forward to the current first node.
if head is not None: If the list already had nodes, the old first node's prev
must now point BACK to the new node.
Line of code
new_node['prev'] = t Connect new node's prev BACK to the old last node.
Both directions now linked.
Page 27
Unit-III | Queue & Linked List
Line of code
target['prev']['next'] = Yes — make the previous node's next skip over target.
target['next']
target['next']['prev'] = Yes — make the next node's prev skip over target
target['prev'] backward.
Line of code
while t['next'] is not None: First, walk to the very last node.
Page 28
Unit-III | Queue & Linked List
Line of code
while t is not None: Now walk BACK using the prev pointers.
Page 29
Unit-III | Queue & Linked List
Visual
┌────────────────────────────────────────┐ ↓
│ [ 10 | → ] → [ 20 | → ] → [ 30 | → ] → [ 40 | ↑ ]
The last node's arrow swings back to the first. There is no end.
Remember the round-robin CPU scheduler? Processes A, B, C, D each get a time slice, and after D, the
scheduler comes back to A again. It never 'ends' as long as there are processes. That is exactly a
circular linked list. Another example — Ludo or any turn-based game. Player 1, 2, 3, 4, back to 1, 2, 3,
4... turns cycle forever.
Line of code
while t['next'] != head: In a normal list we check for None. Here we check 'have I
come back to head?' — that means I'm at the last node.
Page 30
Unit-III | Queue & Linked List
Line of code
t['next'] = new_node The old last's next now points to the new node.
new_node['next'] = head New node's next points back to head — closing the
circle.
Display
def display():
if head is None:
print("List is empty")
return
t = head
while True:
print(t['data'], end=" → ")
t = t['next']
if t == head:
break
print("(back to head)")
while True: Start an infinite loop — we'll break out manually when
we come back to head.
INFINITE LOOP WARNING: A student might write 'while t is not None' for a circular list. But t will
NEVER be None! The program runs forever and the terminal freezes. ALWAYS stop when you come
back to head.
Page 31
Unit-III | Queue & Linked List
Comparing Everything
Access item at index i Very fast — direct access Slow — must walk from head
Insert at beginning Slow — all items shift right Very fast — change one pointer
Insert at end Fast (append) Slow — must walk to the last node
Best used for Random reading, known size Lots of insert / delete at start or
middle
Page 32
Unit-III | Queue & Linked List
Practice Questions
Page 33