Data Structure And AlgorithmsUnit 1012 min read
Advanced Data Structures: AVL Trees, B-Trees, Tries, Graph Algorithms & Applications
Unit 10 of Data Structure And Algorithms explores self-balancing trees (AVL, B-trees), tries, graph algorithms (Dijkstra, Prim, Kruskal), and real-world applications in databases, search engines, and routing systems—with visual step-by-step traces of operations and code implementations.
TAKEAWAYS:
- AVL trees maintain balance via rotations after insertions/deletions, ensuring operations for dynamic datasets.
- B-trees optimize disk-based storage (e.g., databases) by reducing node accesses via multi-way branching.
- Tries excel at prefix-based searches (e.g., autocomplete) with time per operation, where is key length.
- Graph algorithms (Dijkstra, Prim, Kruskal) solve shortest-path and minimum-spanning-tree problems critical for networks (e.g., NTC’s fiber-optic routes).
- Applications span databases (B-trees), search engines (tries), and logistics (graph routing).
- Trade-offs: Time/space complexity, memory locality, and hardware constraints (e.g., RAM vs. disk) dictate structure choice.
1. Self-Balancing Trees: AVL and B-Trees
1.1 AVL Trees: Balanced Binary Search Trees
Definition: An AVL tree is a self-balancing BST where the heights of the left and right subtrees of any node differ by at most 1 (balance factor ). Rotations restore balance after insertions/deletions.
Key Operations:
- Insertion:
- Insert as in BST, then traverse upward to check balance factors.
- Perform rotations if violates the AVL property.
- Rotations:
- Left Rotation (LL): Right child becomes root; original root becomes left child.
- Right Rotation (RR): Left child becomes root; original root becomes right child.
- Left-Right (LR) and Right-Left (RL): Combination of rotations for complex imbalances.
Example: Insertion Trace
Insert [14, 16, 22, 19, 15, 12, 21] into an empty AVL tree. Show balance factors and rotations after each step.
Step-by-Step Insertion:
| Step | Insert | Tree State (BF) | Rotation Needed? |
|---|---|---|---|
| 1 | 14 | 14 (BF=0) |
No |
| 2 | 16 | 14→16 (BF=0) |
No |
| 3 | 22 | 14→16→22 (BF=0) |
No |
| 4 | 19 | 14→16→22→19 (BF=1) |
LL on 16 |
| 5 | 15 | 14→15→16→22→19 (BF=0) |
No |
| 6 | 12 | 12→14→15→16→22→19 |
RR on 14 |
| 7 | 21 | Final AVL tree | No |
Visual After Step 4 (LL Rotation):
Code Implementation (Python):
class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1
def insert(root, key):
# BST insertion + AVL balancing logic
pass
Advantages/Disadvantages:
| AVL Trees | Pros | Cons |
|---|---|---|
| Time Complexity | for all operations | Higher overhead vs. BST |
| Use Case | Dynamic datasets (e.g., databases) | Slower than hash tables for exact matches |
1.2 B-Trees: Disk-Optimized Trees
Definition: A B-tree is a multi-way search tree where:
- Each node has to keys (degree ).
- All leaves are at the same level.
- Keys are stored in sorted order, with children pointers between keys.
Why B-Trees?
- Minimize disk I/O: Fewer node accesses due to high branching factor (e.g., for SSDs).
- Used in: Databases (e.g., MySQL’s
InnoDB), filesystems (e.g., NTFS), and NEPSE’s stock index storage.
Example: B-Tree Insertion ()
Insert [10, 20, 5, 6, 12, 30, 7, 17] into a B-tree.
Step-by-Step:
- Insert
10: Root node[10]. - Insert
20: Root becomes[10, 20]. - Insert
5: Split[5, 10]; promote10to root. - Insert
6: Merge with left child; split if overflow. - Final structure (after all inserts):
Code Snippet (Insertion Logic):
class BTreeNode:
def __init__(self, leaf=False):
self.keys = []
self.children = []
self.leaf = leaf
def insert_btree(root, key, m):
# Handle splitting and promotion
pass
Comparison: AVL vs. B-Tree
| Feature | AVL Tree | B-Tree |
|---|---|---|
| Branching | Binary ( children) | Multi-way ( children) |
| Use Case | RAM-based (e.g., C++ std::map) |
Disk-based (e.g., databases) |
| Balance | Strict () | Relaxed (height varies) |
| Insertion Cost | rotations | splits |
2. Tries: Prefix Trees for Strings
Definition: A trie (prefix tree) stores strings by shared prefixes in a tree structure:
- Root: Empty string.
- Edges: Characters.
- Terminal nodes: Mark end of a word.
Operations:
- Insert: Traverse/extend nodes for each character.
- Search: Traverse to the end of the string.
- Delete: Recursively remove nodes if no other strings use them.
Example: Autocomplete Trie
Insert ["car", "card", "care", "cat"] into a trie.
Applications in Nepal:
- eSewa/Khalti: Prefix searches for merchant names (e.g., typing "N" auto-completes to "Ncell Merchant").
- Nepali Language Processing: Storing complex scripts (Devanagari) efficiently.
Code Implementation:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
def insert_trie(root, word):
node = root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end = True
Advantages:
- Fast prefix searches: per operation ().
- Space-efficient for shared prefixes: E.g., "care" and "card" share
c-a-r.
3. Graph Algorithms: Shortest Paths and MSTs
3.1 Dijkstra’s Algorithm: Shortest Path
Problem: Find the shortest path from a source node to all other nodes in a weighted graph (non-negative edges).
Algorithm Steps:
- Initialize distances: , others .
- Use a priority queue to pick the node with the smallest .
- Relax edges: For each neighbor of , update .
- Repeat until all nodes are processed.
Example: NTC’s Fiber-Optic Network
Graph: Nodes = Kathmandu (K), Pokhara (P), Biratnagar (B), Dharan (D).
Edges: K-P (50), K-B (30), P-D (20), B-D (10).
graph TD
K["Kathmandu"] -->|"50"| P["Pokhara"]
K -->|"30"| B["Biratnagar"]
P -->|"20"| D["Dharan"]
B -->|"10"| DTrace (Source = K):
| Step | Node Processed | Priority Queue | ||||
|---|---|---|---|---|---|---|
| 1 | K | 0 | 50 | 30 | ∞ | P, B |
| 2 | B | 0 | 50 | 30 | 40 | P, D |
| 3 | D | 0 | 50 | 30 | 40 | P |
| 4 | P | 0 | 50 | 30 | 40 | - |
Shortest Paths:
- : Total cost = 40.
- : Total cost = 70.
Code (Python):
import heapq
def dijkstra(graph, source):
distances = {node: float('inf') for node in graph}
distances[source] = 0
heap = [(0, source)]
while heap:
current_dist, u = heapq.heappop(heap)
for v, weight in graph[u].items():
distance = current_dist + weight
if distance < distances[v]:
distances[v] = distance
heapq.heappush(heap, (distance, v))
return distances
3.2 Minimum Spanning Tree (MST): Prim’s and Kruskal’s
Problem: Find a subset of edges that connects all nodes with minimum total weight and no cycles.
Prim’s Algorithm:
- Start from an arbitrary node.
- Greedily add the cheapest edge connecting the current MST to a new node.
Kruskal’s Algorithm:
- Sort all edges by weight.
- Add edges one by one, skipping those that form cycles (use Union-Find).
Example: Pathao’s Bike-Sharing Routes
Graph: Nodes = Thamel (T), Koteshwor (K), Lazimpat (L), Jawalakhel (J).
Edges: T-K (4), T-L (2), K-J (3), L-J (1).
Prim’s Trace (Start at T):
- Add
T-L(cost=2). - Add
L-J(cost=1, total=3). - Add
T-K(cost=4, total=7). - Skip
K-J(cycle).
MST Edges: T-L, L-J, T-K (Total cost = 7).
Kruskal’s Trace:
- Sort edges:
L-J (1),T-L (2),K-J (3),T-K (4). - Add
L-J,T-L,T-K(skipK-Jas it forms a cycle).
Comparison:
| Algorithm | Time Complexity | Use Case |
|---|---|---|
| Prim’s | Dense graphs (e.g., social networks) | |
| Kruskal’s | Sparse graphs (e.g., NTC’s long-distance cables) |
## In the Real World
eSewa/Khalti’s Merchant Search:
- Trie: Stores merchant names (e.g., "Ncell Merchant", "F1 Plus") for instant autocomplete. Typing "N" narrows to names starting with "N" in time.
NTC’s Fiber-Optic Network:
- Dijkstra’s Algorithm: Computes the fastest route for data packets between cities (e.g., Kathmandu to Pokhara via Biratnagar if the direct link is congested).
Nepal Rastra Bank’s Loan Processing:
- B-Trees: Indexes customer accounts by loan ID for lookups, even with millions of records stored on disk.
Daraz’s Order Fulfillment:
- Graph (MST): Optimizes warehouse-to-delivery routes for multiple orders (e.g., Prim’s algorithm to minimize total delivery distance).
NEPSE’s Stock Trading System:
- AVL Trees: Maintains a balanced index of stock prices for buy/sell operations, ensuring fairness during high-volume trades.
## Exam Tip
AVL/B-Tree Questions:
- Must show: Balance factors, rotation types (LL/LR/RR/RL), and final tree structure after each operation.
- Common Pitfall: Forgetting to update heights/balance factors after rotations. Always recalculate them!
Trie Questions:
- Draw the trie step-by-step for insertions/searches. Label terminal nodes clearly.
- Shortcut: For autocomplete, highlight the longest prefix match.
Graph Algorithms:
- Dijkstra: Show the priority queue state after each extraction.
- MST: List edges in order of addition and justify why others are skipped (cycles).
- Real-World Tie-In: Relate to NTC/Pokhara University’s network or Daraz’s logistics (e.g., "Prim’s algorithm minimizes fuel costs for delivery vans").
Code Traces:
- Exams often ask for pseudocode + trace table. Include:
- Initialization step.
- Loop invariants (e.g., "priority queue always contains unprocessed nodes").
- Final output with justification.
- Exams often ask for pseudocode + trace table. Include:
Comparison Tables:
- Always include time/space complexity and use cases (e.g., AVL for RAM, B-tree for disk).
Visual Summary:
Based on the TU BITM syllabus for Data Structure And Algorithms (IT238), unit 10.
Discussion
Loading…