Chapter1 - ArraySort-5
Chapter1 - ArraySort-5
Algorithms
Required Knowledge / Prerequisites
Programming Fundamentals
• Proficiency in at least one programming language (e.g., C, C++, Java, or
Python)
• Understanding of:
• Functions and recursion
• Pointers or references
• Basic memory concepts
• Ability to implement and test data structures
Basic Data Structures
• Arrays and dynamic arrays
• Linked lists (singly and doubly linked)
• Stacks and queues
• Basic hashing concepts
Fixed-size “array”
Dynamic array example
Show that a Python list grows by “resizing” (capacity idea)
Trees and Graph Fundamentals
• Binary trees and tree terminology (root, leaf, height, depth)
• Binary search trees (BST)
• Tree traversals (inorder, preorder, postorder)
• Basic understanding of graphs (nodes, edges, connected components)
Algorithm Analysis
• Asymptotic notation:
• Big-O, Big-Ω, Big-Θ
• Worst-case and average-case analysis
• Recurrence relations (basic)
• Introduction to amortized analysis (desirable but not mandatory)
Recurrence Relations
A recurrence relation is an equation that defines a sequence (or a function)
using its previous values.
So instead of giving 𝑎𝑛 directly, you say how to compute it from earlier terms like
𝑎𝑛−1 ,𝑎𝑛−2 ,etc.
Example 1: Arithmetic “+3”
𝑎𝑛 = 𝑎𝑛−1 + 3, 𝑎0 = 2
Meaning:
Start with 𝑎0 = 2
𝑎1 = 2 + 3 = 5
𝑎2 = 5 + 3 = 8
𝑎3 = 8 + 3 = 11
Another classic example (Fibonacci)
𝐹𝑛 = 𝐹𝑛−1 + 𝐹𝑛−2 , 𝐹0 = 0, 𝐹1 = 1
Compute:
𝐹2 = 1 + 0 = 1
𝐹3 = 1 + 1 = 2
𝐹4 = 2 + 1 = 3
Sequence: 0, 1, 1, 2, 3, 5, 8, ...
Why we use them in Computer Science
Recurrence relations are used a lot to describe algorithm time complexity,
especially for divide-and-conquer algorithms.
Example: Merge Sort
It splits the problem into 2 halves and merges them:
A
/\
B C
/\
D E
Binary Trees
Tree Terminology
Root
The topmost node of the tree
Has no parent
A is parent of B and C
B is child of A
Leaf (External Node)
A leaf is a node with no children
Internal Node
A node with at least one child
Binary Trees
Tree Terminology
Height
Height of a node:
Number of edges on the longest path from the node to a leaf
Height of D = 0
Height of B = 1
Height of A = 2
Binary Trees
Tree Terminology
Depth
Depth of a node:
Number of edges from the root to the node
Depth of A = 0
Depth of B = 1
Depth of D = 2
Binary Trees
Formal Properties
For a binary tree with height h:
Maximum number of nodes:
2ℎ+1 − 1
Minimum number of nodes:
ℎ+1
Binary Trees
class Node:
def __init__(self, value):
[Link] = value
[Link] = None
[Link] = None
Example Construction
root = Node('A')
[Link] = Node('B')
[Link] = Node('C')
[Link] = Node('D')
[Link] = Node('E')
Tree Traversals (Preorder, Inorder,
Postorder)
A tree traversal is a systematic way of visiting every node in a tree exactly
once.
Because a tree is non-linear, there is no single natural order like arrays or
lists.
Traversals define how recursion explores the structure.
Tree Traversals (Preorder, Inorder,
Postorder)
Depth-First Traversals
Preorder, Inorder, and Postorder are all Depth-First Search (DFS) traversals.
Each traversal differs in when the root node is processed.
Traversal Order
Preorder Root → Left → Right
Inorder Left → Root → Right
Postorder Left → Right → Root
Tree Traversals (Preorder, Inorder,
Postorder)
Example Tree
We use the same tree for all traversals:
Preorder
def preorder(node):
if node:
print([Link], end=' ')
preorder([Link])
preorder([Link])
Preorder
Iterative Preorder
def preorder_iterative(root):
if not root:
return
stack = [root]
while stack:
node = [Link]()
print([Link], end=' ')
if [Link]:
[Link]([Link])
if [Link]:
[Link]([Link])
Inorder
def inorder(node):
if node:
inorder([Link])
print([Link], end=' ')
inorder([Link])
Inorder
# 3) Then go right
cur = [Link]
Postorder
def postorder_iterative_two_stacks(root):
Iterative Postorder if root is None:
return
s1 = [root]
s2 = []
while s1:
node = [Link]()
[Link](node)
if [Link]:
[Link]([Link])
if [Link]:
[Link]([Link])
while s2:
node = [Link]()
print([Link], end=' ')
Binary Search Trees (BST)
A Binary Search Tree (BST) is a binary tree that satisfies an ordering property:
For every node N:
All keys in the left subtree of N are less than N
All keys in the right subtree of N are greater than N
Searching in a BST
At each node:
Compare the target value with the current node
Go left or right
Eliminate half the tree at each step
Insertion in a BST
Rule:
Insert new keys as leaves
Preserve the BST ordering property
return root
Deletion
BST Deletion
Deletion has three cases:
Leaf node → remove directly
One child → replace node with its child
Two children → replace with:
inorder predecessor (max of left subtree), or
inorder successor (min of right subtree)
BST Deletion — Inorder Predecessor
(Maximum in Left Subtree)
Visual Example: Delete 40:
Inorder predecessor of 40 = 30
Rule
Go to left child
Then go right as far as possible
Python Helper Function
def find_max(node):
current = node
while [Link]:
current = [Link]
return current
BST Deletion Using Inorder Predecessor
(Python)
else:
# Case 1: No child
if [Link] is None and [Link] is None:
return None
def delete(root, key):
if root is None:
# Case 2: One child
return root
if [Link] is None:
return [Link]
if key < [Link]:
if [Link] is None:
[Link] = delete([Link], key)
return [Link]
elif key > [Link]:
# Case 3: Two children
[Link] = delete([Link], key)
temp = find_max([Link]) # inorder predecessor
[Link] = [Link]
[Link] = delete([Link], [Link])
return root
BST Deletion Using Inorder Successor
Rule
Go to right child
Then go left as far as possible
Python Helper Function
def find_min(node):
current = node
while [Link]:
current = [Link]
return current
BST Deletion Using Inorder Successor
(Python) else:
# Case 1: no child
if [Link] is None and [Link] is None:
return None
def delete(root, key):
if root is None: # Case 2: one child
return root if [Link] is None:
return [Link]
if key < [Link]: if [Link] is None:
[Link] = delete([Link], key) return [Link]
return root
Introduction to AVL Trees
The Problem with BST. A BST does not control its height.
If keys are inserted in sorted or nearly sorted order, the BST becomes
degenerate:
Height = n
Operations degrade to O(n)
BST becomes equivalent to a linked list, This is unacceptable when
performance must be guaranteed.
Introduction to AVL Trees
Height Function
def height(node):
if node is None:
return 0
return [Link]
Balance Factor Computation (AVL
Trees)
Height computed recursively
def height(node):
if node is None:
return 0
return 1 + max(height([Link]), height([Link]))
Balance Factor Computation (AVL
Trees)
Balance Factor Function
def balance_factor(node):
if node is None:
return 0
return height([Link]) - height([Link])
LL (Left–Left) Rotation
An LL imbalance happens when:
1. A node N becomes left-heavy: BF(N)>1
2. The imbalance is caused by insertion into the left subtree of N’s left child.
𝐵𝐹 𝑁 = +2
𝐵𝐹 𝑁𝑙𝑒𝑓𝑡 ≥ 0
AVL Tree Rotations: LL (Left–Left) Rotation
Implementation
def right_rotate(N):
L = [Link]
T2 = [Link]
# Perform rotation
[Link] = N
[Link] = T2
# Update heights
[Link] = 1 + max(height([Link]), height([Link]))
[Link] = 1 + max(height([Link]), height([Link]))
return L
AVL Trees — RR (Right–Right) Rotation
def left_rotate(N):
R = [Link]
T2 = [Link]
# Perform rotation
[Link] = N
[Link] = T2
# Update heights
[Link] = 1 + max(height([Link]), height([Link]))
[Link] = 1 + max(height([Link]), height([Link]))
return R
AVL Trees — LR (Left–Right) Rotation
def lr_rotate(N):
[Link] = left_rotate([Link])
return right_rotate(N)
AVL Trees — RL (Right–Left) Rotation
def rl_rotate(N):
[Link] = right_rotate([Link])
return left_rotate(N)
AVL Tree Insertion
return root
AVL Tree Deletion
A Red-Black Tree (RBT) is a binary search tree that maintains balance using
coloring rules instead of explicit height constraints.
Each node is colored either: Red or Black
These colors impose structural constraints that indirectly control the height
of the tree.
Fundamental Red-Black Tree Rules
ℎ ≤ 2𝑙𝑜𝑔2 (𝑛 + 1)
Where n is the total number of nodes.
Therefore:
Search → O(log n)
Insertion → O(log n)
Deletion → O(log n)
All in the worst case.
Coloring Logic (Insertion Intuition)
Rule 4 violated
Fix (recoloring only):
P → Black
U → Black
G → Red
Then continue checking from G.
No rotations
Balance preserved
Violation may propagate upward
Coloring Logic (Insertion Intuition)
Case 3 — Parent Is Red and Uncle Is Black
LL (Left–Left)
Fix:
1. Right rotation on G
2. Recolor:
P → Black
G → Red
RR (Right–Right)
Fix:
1. Left rotation on G
2. Recolor:
P → Black
G → Red
Coloring Logic (Insertion Intuition)
Case 3 — Parent Is Red and Uncle Is Black
LR (Left–Right)
Fix:
1. Left rotation on P
2. Right rotation on G
3. Recolor:
N → Black
G → Red
RL (Right–Left)
Fix:
1. Right rotation on P
2. Left rotation on G
3. Recolor:
N → Black
G → Red
Coloring Logic (Insertion Intuition)
Situation Action
Parent black, Uncle any Do nothing
Parent red, Uncle red Recolor
LL Right rotation + recolor
RR Left rotation + recolor
LR Left + Right rotation + recolor
RL Right + Left rotation + recolor
Red-Black Tree Deletion
RED = "RED"
BLACK = "BLACK"
class Node:
def __init__(self, key):
[Link] = key
[Link] = RED # new nodes are RED
[Link] = None
[Link] = None
[Link] = None
Red-Black Tree Insertion
implementation-level
Checking a Coloring Violation:
In Red-Black insertion, the only initial violation is: A red node having a red
parent
def has_red_red_violation(node):
return (
node is not None and
[Link] is not None and
[Link] == RED and
[Link] == RED
)
Red-Black Tree Insertion
implementation-level
Fixing Coloring Violations (Insertion Fix-Up)
# Case 1: Uncle is RED → recolor
if uncle is not None and [Link] == RED:
[Link] = BLACK
[Link] = BLACK
def fix_insert(root, node): [Link] = RED
node = grandparent
while node != root and [Link] == RED: else:
# Case 2: LR → rotate left on parent
parent = [Link] if node == [Link]:
grandparent = [Link] node = parent
root = left_rotate(root, node)
# Parent is left child
if parent == [Link]: # Case 3: LL → rotate right on grandparent
uncle = [Link] [Link] = BLACK
[Link] = RED
root = right_rotate(root, grandparent)
Red-Black Tree Insertion
implementation-level
Fixing Coloring Violations (Insertion Fix-Up)
else:
# Case 2 (mirror): RL → rotate right on
# Parent is right child (mirror case) parent
else: if node == [Link]:
uncle = [Link] node = parent
root = right_rotate(root, node)
# Case 1 (mirror): Uncle is RED
if uncle is not None and [Link] == RED: # Case 3 (mirror): RR → rotate left on
[Link] = BLACK grandparent
[Link] = BLACK [Link] = BLACK
[Link] = RED [Link] = RED
node = grandparent root = left_rotate(root, grandparent)
return root
Red-Black Tree Deletion
Fix
1. Recolor: s → RED
2. Move the “extra black” up: set x = p
After
Now two subcases:
If original p was RED, then when it becomes DB you can simply make it BLACK
and STOP.
If original p was BLACK, DB continues upward (loop repeats).
Red-Black Tree Deletion
Case B — Deleting a BLACK node
Case 3 — Sibling s is BLACK, near child is RED, far child is BLACK
For x on the left:
near child = SL
far child = SR
Before
Fix (convert to Case 4)
1. Recolor: c → BLACK, s → RED
2. Rotate right at s
After
Now the new sibling of x is c(B), and it has a far red child (the red is on the
“outside” after conversion), so we are in Case 4 next.
(That “(.)” is the subtree that was between c and s; it doesn’t affect the
case logic.)
Red-Black Tree Deletion
Case B — Deleting a BLACK node
Case 4 — Sibling s is BLACK, far child is RED
This is the final resolving case (it eliminates DB).
Before
Fix
1. [Link] = [Link]
2. [Link] = BLACK
3. [Link] = BLACK
4. Rotate left at p
5. DB is removed (x becomes normal black / NIL)
After
DB is gone, tree satisfies all Red-Black properties again, STOP.
Splay Trees
A Splay Tree is a:
Binary Search Tree (BST)
Self-adjusting
Not explicitly balanced
Key idea:
Every time you access a node, you move it to the root
This operation is called splaying.
Splay Trees
Assume:
x = accessed node
p = parent
g = grandparent
Case 1: ZIG
x has no grandparent (parent is root)
Single rotation
Splaying cases
Case 2: ZIG–ZIG
x and p are both left children(or both right children)
Two rotations (same direction)
Splaying cases
Case 3: ZIG–ZAG
x is left child, p is right child (or vice versa)
Two rotations (opposite directions)
Splay Trees Search operation
Steps:
1. Perform normal BST search
2. Splay the accessed node to the root
Result:
Recently searched node becomes root
Frequently used nodes stay near top
Amortized time: O(log n)
Splay Trees Insert operation
Steps:
1. Insert like normal BST
2. Splay the inserted node to the root
Effect:
New elements become fast to access
Tree reorganizes automatically
Splay Trees Delete operation
Steps:
1. Splay the node to delete to the root
2. Remove the root
3. Join left and right subtrees:
Splay the maximum of left subtree
Attach right subtree
Splay Trees POS
Those keys act like separators that split the number line into ranges.
The fundamental separator rule: If a node contains k keys, then it has k + 1 children.
Example:
Let’s choose: t = 3
Then:
maxKeys = 2t − 1 = 5
minKeys = t − 1 = 2 (for non-root nodes)
maxChildren = 2t = 6
minChildren = t = 3 (for internal non-root nodes)
So each node can contain:
2 to 5 keys (except root can have 1 to 5 keys)
Example nodes:
Search in a B-Tree
So splitting:
promote 30
left: [10|20]
right: [40|50]
Full insertion example (t = 3)
We will insert these keys in order: 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
Step A: Insert first 5 keys (fit in root)
After inserting 10,20,30,40,50:
Step B: Insert 60 (root is full → split root first)
Split root: promote 30
Tree becomes:
Now insert 60:
60 > 30 → go right
right node [40|50] has space → insert into it:
Full insertion example (t = 3)
This is a valid B-Tree (t=3), height is small, leaves are same level.
B-Trees are always balanced
Rule 2: Borrow from LEFT sibling (before descending into a minimal child)
You want to descend into the right child (minimal) to delete ⟦7⟧.
Before (delete ⟦7⟧ is inside right child):
Borrow from left sibling:
parent key 6 goes down to the right child
left sibling max (5) goes up to parent
After borrow (now proceed to delete ⟦7⟧):
Now delete ⟦7⟧ in the leaf:
B-Tree deletion rules
Rule 3: Borrow from RIGHT sibling (before descending into a minimal child)
You want to descend into the left child (minimal) to delete ⟦4⟧.
Before:
Delete ⟦4⟧:
B-Tree deletion rules
Left child has 3 keys, so predecessor is 9. Replace ⟦10⟧ by 9, then delete 9 in left
leaf:
After:
B-Tree deletion rules
Right child has 3 keys, successor is 11. Replace ⟦10⟧ by 11, then delete 11 in right
leaf:
After:
B-Tree deletion rules
A treap (tree + heap) is a binary search tree (BST) where every node also
has a priority, and the tree is kept balanced by maintaining a heap
property on priorities
Treap rules
Each node stores: (key, priority)
BST property (by key)
Left subtree keys < key
Right subtree keys > key
Important: priorities are usually assigned randomly, which makes the treap
balanced on average.
Treaps
Insert 50(50)
Heap violation: 20(10) has smaller priority than parent 30(80) ⇒ rotate right
at 30:
Insert 40(30)
BST path: 20 → right to 50 → left to 30 → right (since 40 > 30)
Before fixing heap:
Heap violation again: 40(30) priority < 50(50) ⇒ rotate right at 50:
These are search trees that do not rebalance after every update (like AVL /
Red-Black). Instead, they:
allow the tree to become somewhat unbalanced, and
when imbalance becomes “too much,” they rebuild only a part of the tree (a
subtree) into a perfectly balanced form.
That’s why it’s called partial rebuilding: you rebuild a subtree, not the whole
tree, and not every time.
Trees with partial rebuilding:
Scapegoat Tree
The main example: Scapegoat Tree
A scapegoat tree is a BST that keeps balance using occasional subtree
rebuilds.
1
It chooses a constant α (alpha) with: <𝛼<1
2
2
(common: 𝛼 = 3)
buildBalancedBST(arr, l, r):
if l > r: return null
mid = (l + r) // 2
node = new Node(arr[mid])
[Link] = buildBalancedBST(arr, l, mid-1)
[Link] = buildBalancedBST(arr, mid+1, r)
return node
Example
5
/ \
2 8
/ \ / \
1 3 6 9
\ \ \
4 7 10
Amortized analysis