UNIT 1: Fundamental Data Structures and Algorithms
1. Introduction to Data Structures
Data Structure is a systematic way to organize and store data to enable efficient access and modification.
Importance: Optimizes time/space complexity, forms basis for complex algorithms, and is essential for problem-solving.
Classification:
| Basis | Types | Examples |
|---|---|---|
| Linear vs Non-linear | Linear (sequential) | Array, Stack, Queue, Linked List |
| Non-linear (hierarchical/graph) | Tree, Graph | |
| Primitive vs Non-primitive | Primitive (basic) | int, char, float |
| Non-primitive (derived) | Array, Structure, Class | |
| Static vs Dynamic | Static (fixed size) | Array |
| Dynamic (resizable) | Linked List, Tree |
Abstract Data Type (ADT): A theoretical model defining data and operations without implementation details.
Examples:
-
Stack: LIFO; operations:
push,pop,peek. -
Queue: FIFO; operations:
enqueue,dequeue. -
List: Ordered collection; operations:
insert,delete,search.
Algorithm Characteristics:
-
Input: Zero or more inputs.
-
Output: At least one output.
-
Definiteness: Each step precisely defined.
-
Finiteness: Terminates after finite steps.
-
Effectiveness: Each step is basic and feasible.
Asymptotic Notations:
-
Big O (Upper bound): $$\displaystyle f(n) = O(g(n)) $$ if $$\displaystyle \exists c, n_0 $$ such that $0 \leq f(n) \leq c \cdot g(n)$ for $$\displaystyle n \geq n_0 $$.
-
Omega (Lower bound): $$\displaystyle f(n) = \Omega(g(n)) $$ if $$\displaystyle \exists c, n_0 $$ such that $0 \leq c \cdot g(n) \leq f(n)$ for $$\displaystyle n \geq n_0 $$.
-
Theta (Tight bound): $$\displaystyle f(n) = \Theta(g(n)) $$ if $$\displaystyle f(n) = O(g(n)) $$ and $$\displaystyle f(n) = \Omega(g(n)) $$.
Examples:
-
$$\displaystyle 3n^2 + 2n + 1 = O(n^2) $$
-
$$\displaystyle 5n^3 = \Omega(n^2) $$
-
$$\displaystyle 4n^2 + 6n = \Theta(n^2) $$
Time & Space Complexity:
-
Time Complexity: Number of primitive operations as function of input size $n$.
-
Space Complexity: Amount of memory used.
Solving Recurrences:
-
Substitution Method: Guess solution, verify by induction.
-
Master Theorem: For $$\displaystyle T(n) = aT(n/b) + f(n) $$:
-
If $$\displaystyle f(n) = O(n^{\log_b a - \epsilon}) $$, then $$\displaystyle T(n) = \Theta(n^{\log_b a}) $$.
-
If $$\displaystyle f(n) = \Theta(n^{\log_b a}) $$, then $$\displaystyle T(n) = \Theta(n^{\log_b a} \log n) $$.
-
If $$\displaystyle f(n) = \Omega(n^{\log_b a + \epsilon}) $$, then $$\displaystyle T(n) = \Theta(f(n)) $$.
-
[!TIP]
- In exams, always state the recurrence form before applying Master Theorem.
- For $$\displaystyle T(n)=2T(n/2)+n\log n $$, compare $$\displaystyle f(n)=n\log n $$ with $$\displaystyle n^{\log_2 2}=n $$. Since $f(n)$ is larger by a factor of $\log n$, use Case 3? Actually, $$\displaystyle f(n)=\Omega(n^{1+\epsilon}) $$? No, $\log n$ grows slower than $$\displaystyle n^\epsilon $$. So it’s Case 2? Wait, $$\displaystyle f(n)=\Theta(n^{\log_b a} \log^k n) $$ with $$\displaystyle k=1 $$, so $$\displaystyle T(n)=\Theta(n \log^2 n) $$. Box this.
2. Linear Data Structures
2.1 Arrays
-
One-dimensional: Contiguous memory, index-based access $O(1)$.
-
Multi-dimensional: Row-major (C, Java) vs Column-major (Fortran, MATLAB).
Address Calculation:
-
Row-major: $$\displaystyle \text{Address}(A[i][j]) = \text{Base} + ((i \times \text{COLS}) + j) \times \text{size} $$
-
Column-major: $$\displaystyle \text{Address}(A[i][j]) = \text{Base} + ((j \times \text{ROWS}) + i) \times \text{size} $$
Sparse Matrices: Matrices with mostly zeros.
-
Triplet Representation: Store only non-zero elements as $(row, col, value)$.
-
Upper/Lower Triangular: Store only elements above/below diagonal.
-
Space-efficient: For $n \times n$ triangular matrix, store $$\displaystyle \frac{n(n+1)}{2} $$ elements.
-
Address Formula (lower triangular, row-major):
$$ \text{Address}(A[i][j]) = \text{Base} + \left( \frac{i(i-1)}{2} + j \right) \times \text{size} \quad (j \leq i) $$
Operations & Limitations:
-
Compaction: Shifting elements to remove gaps (costly, $O(n)$).
-
Overflow: Insertion when array full.
-
Underflow: Deletion from empty array.
2.2 Comparison of Arrays and Linked Lists
| Aspect | Array | Linked List |
|---|---|---|
| Memory | Contiguous | Dynamic (non-contiguous) |
| Access Time | $O(1)$ (direct) | $O(n)$ (sequential) |
| Insertion/Deletion | $O(n)$ (shift required) | $O(1)$ at head, $O(n)$ elsewhere |
| Size | Fixed (static) | Flexible (dynamic) |
| Memory Overhead | None | Extra pointer per node |
2.3 Linked Lists
Singly Linked List:
-
Node:
data+nextpointer. -
Operations:
-
Insertion at head:
new->next = head; head = new;$O(1)$. -
Insertion after node
p:new->next = p->next; p->next = new;$O(1)$ givenp. -
Deletion of node
p(given previousprev):prev->next = p->next; free(p);$O(1)$. -
Traversal: Start from head, follow
nextuntilNULL.
-
-
Drawbacks: No backward traversal, extra memory for pointers, slower access.
Doubly Linked List:
-
Node:
prev+data+next. -
Search/Modify: Traverse from head/tail, $O(n)$.
-
Insertion (after node
p):new->next = p->next; new->prev = p; if (p->next) p->next->prev = new; p->next = new; -
Deletion (node
p):p->prev->next = p->next; if (p->next) p->next->prev = p->prev; free(p);
Circular Linked List:
-
Last node points to head.
-
Insertion at head:
if (head == NULL) { new->next = new; head = new; } else { new->next = head->next; head->next = new; swap(new->data, head->data); // if inserting before head } -
Deletion of head:
if (head->next == head) { free(head); head = NULL; } else { p = head; while (p->next != head) p = p->next; p->next = head->next; free(head); head = p->next; } -
Application: Josephus problem — $n$ people in circle, eliminate every $k$-th until one remains.
Applications:
-
Polynomial Representation: Each node stores
(coefficient, exponent); sorted by exponent. -
Merging Sorted Lists: Merge two sorted linked lists in $O(n+m)$.
-
Finding Merging Point (intersecting lists):
-
Compute lengths $$\displaystyle L_1, L_2 $$.
-
Advance longer list by $$\displaystyle |L_1 - L_2| $$ nodes.
-
Traverse both together until nodes match.
-
Time: $O(n+m)$, Space: $O(1)$.
-
[!TIP]
- For merging point, ensure lists actually intersect (same tail node).
- In circular list deletion, careful with single node case.
3. Tree Data Structures
3.1 Binary Trees
Terminology:
-
Node: Element.
-
Root: Top node (no parent).
-
Leaf: Node with no children.
-
Internal Node: Non-leaf.
-
Height: Longest path from root to leaf (edges).
-
Depth: Path length from root to node.
-
Level: Depth + 1 (root at level 1).
-
Degree: Number of children.
-
Order: Maximum children per node (binary = 2).
Properties:
-
Max nodes at height $h$: $$\displaystyle 2^{h+1} - 1 $$.
Proof: Levels 0 to $h$: sum $$\displaystyle 2^0 + 2^1 + ... + 2^h = 2^{h+1} - 1 $$.
-
Min nodes at height $h$: $h+1$ (skewed tree).
-
Relation: For $n$ nodes, height $$\displaystyle h \geq \lfloor \log_2 n \rfloor $$.
Traversals:
-
Inorder (Left-Root-Right): Gives sorted order for BST.
-
Preorder (Root-Left-Right): Used to copy tree.
-
Postorder (Left-Right-Root): Used to delete tree.
-
Level-order (BFS): Use queue.
Example (tree: root A, left B, right C, B has left D, right E):
-
Inorder: D, B, E, A, C
-
Preorder: A, B, D, E, C
-
Postorder: D, E, B, C, A
-
Level-order: A, B, C, D, E
Construction from Traversals:
-
Given inorder and preorder: First element of preorder is root. Find root in inorder; left part is left subtree, right part is right subtree. Recurse.
-
Given inorder and postorder: Last element of postorder is root. Split inorder, recurse.
3.2 Binary Search Trees (BST)
Definition: For any node, left subtree values < node value < right subtree values.
Operations:
-
Search:
while (current != NULL) { if (key == current->data) return current; else if (key < current->data) current = current->left; else current = current->right; } return NULL;Time: Average $O(\log n)$, worst $O(n)$ (skewed).
-
Insertion:
if (root == NULL) root = new Node(key); else { current = root; while (true) { if (key < current->data) { if (current->left == NULL) { current->left = new Node(key); break; } else current = current->left; } else { if (current->right == NULL) { current->right = new Node(key); break; } else current = current->right; } } } -
Deletion (three cases):
-
Leaf: Simply remove.
-
One child: Replace node with child.
-
Two children: Find inorder successor (smallest in right subtree), copy its value to node, delete successor (which has at most one child).
-
Example: Insert [45, 26, 10, 60, 70, 30, 40] → BST.
Delete 10 (leaf), then 60 (one child: 70), then 45 (two children: successor 70? Actually after deleting 60, 70 becomes child of 45? Step-by-step required in exam).
3.3 Balanced Binary Search Trees
AVL Trees:
-
Balance Factor (BF): $$\displaystyle BF = \text{height(left)} - \text{height(right)} $$. Must be -1, 0, or 1.
-
Rotations (single):
-
LL (left-left): Right rotate at unbalanced node.
-
RR (right-right): Left rotate.
-
LR (left-right): Left rotate on left child, then right rotate on node.
-
RL (right-left): Right rotate on right child, then left rotate on node.
-
-
Insertion: Insert as BST, update heights, check BF from bottom up, rotate if $$\displaystyle |BF|>1 $$.
-
Deletion: Delete as BST, update heights, rebalance up to root.
Example: Insert 485, 575, 655, 745, 830, 910, 100, 110, 520, 130, 340, 450, 365, 525, 204, 155, 130, 35 (Jun 2024). Show rotations step-by-step.
Red-Black Trees:
-
Properties:
-
Every node is red or black.
-
Root is black.
-
All leaves (NIL) are black.
-
Red node’s children are black.
-
Every path from node to leaves has same black-height.
-
-
Comparison: AVL more strictly balanced (better for search), Red-Black fewer rotations (better for frequent insertions/deletions).
B+ Trees:
-
All leaves at same level, linked list at leaves.
-
Internal nodes store keys for routing.
-
Applications: Databases (indexing), file systems.
3.4 Expression Trees
-
Construction: From infix (using stack for operators), prefix (scan right to left), postfix (scan left to right).
-
Traversals:
-
Inorder: Infix (add parentheses).
-
Preorder: Prefix.
-
Postorder: Postfix.
-
Example: $(a + b \times c) + (d \times e + f \times g)$
Tree: root +, left subtree + (a, *(b,c)), right subtree + (*(d,e), *(f,g)).
Traversals:
-
Inorder:
(a + (b * c)) + ((d * e) + (f * g)) -
Preorder:
+ + a * b c * * d e f g -
Postorder:
a b c * + d e * f g * +
[!TIP]
- Inorder traversal of expression tree requires parentheses to preserve order.
- For construction from infix, use operator precedence and stack.
4. Graph Data Structures
4.1 Graph Fundamentals
-
Vertices (V), Edges (E).
-
Directed (digraph): Edges have direction.
-
Undirected: Edges bidirectional.
-
Weighted: Edges have weights.
-
Connected: Path between every pair (undirected).
-
Cyclic: Contains cycle.
Representations:
| Adjacency Matrix | Adjacency List |
|---|---|
| $V \times V$ array | Array of lists (each vertex list) |
| Space: $$\displaystyle O(V^2) $$ | Space: $O(V + E)$ |
| Fast edge check: $O(1)$ | Fast for sparse graphs |
| Slow for sparse | Iterate neighbors quickly |
Example:
Matrix:
V0 V1 V2 V3
V0 0 1 1 0
V1 0 0 1 1
V2 0 0 0 1
V3 1 0 0 0
Graph: V0→V1, V0→V2, V1→V2, V1→V3, V2→V3, V3→V0.
Graph vs Tree Traversal:
-
Graph: May have cycles → need
visitedarray. -
Multiple components: Loop over all vertices.
-
Tree: No cycles, single component.
4.2 Graph Traversal Algorithms
Breadth-First Search (BFS):
-
Algorithm:
-
Mark start visited, enqueue.
-
While queue not empty: dequeue
u, for each neighborvofu: if not visited, mark visited, enqueuev.
-
-
Example: Graph above from V0:
Queue: [V0] → dequeue V0, enqueue V1,V2 → [V1,V2] → dequeue V1, enqueue V3 → [V2,V3] → dequeue V2 (no new) → dequeue V3 (no new). Order: V0,V1,V2,V3.
-
Applications: Shortest path in unweighted graphs, web crawling.
Depth-First Search (DFS):
-
Algorithm (recursive):
DFS(u): visited[u] = true; for each neighbor v of u: if (!visited[v]) DFS(v);Or iterative with stack.
-
Example: Same graph from V0:
Stack: [V0] → pop V0, push V1,V2 → [V1,V2] → pop V2, push V3 → [V1,V3] → pop V3, push V0 (visited) → [V1] → pop V1, push V2 (visited), V3 (visited). Order: V0,V2,V3,V1.
-
Applications: Topological sort, cycle detection, connected components.
Comparison:
| BFS | DFS |
|---|---|
| Queue (FIFO) | Stack (LIFO) / recursion |
| Finds shortest path | Not necessarily |
| More memory (queue) | Less memory (stack) |
| Complete (finds all) | May get stuck in deep path |
Valid DFS Sequences (Dec 2023 question): Given graph, check sequences by simulating stack and adjacency list order.
4.3 Spanning Trees and MST
-
Spanning Tree: Subgraph connecting all vertices, no cycles, $V-1$ edges.
-
Minimum Spanning Tree (MST): Spanning tree with minimum total edge weight.
Prim's Algorithm:
-
Steps:
-
Start with arbitrary vertex.
-
Grow tree by adding minimum-weight edge connecting tree to outside vertex.
-
Repeat until all vertices included.
-
-
Example: Use adjacency matrix/list, update
keyarray (min edge to tree). -
Time:
-
Matrix: $$\displaystyle O(V^2) $$.
-
Binary heap: $O(E \log V)$.
-
Kruskal's Algorithm:
-
Steps:
-
Sort all edges by weight.
-
Initialize disjoint sets (union-find).
-
For each edge in order: if endpoints in different sets, add edge, union sets.
-
-
Time: $O(E \log E)$ (sorting dominates) = $O(E \log V)$.
Comparison:
-
Prim's: Dense graphs ($$\displaystyle E \approx V^2 $$), $$\displaystyle O(V^2) $$ better than $O(E \log V)$.
-
Kruskal's: Sparse graphs ($$\displaystyle E \ll V^2 $$), easy to parallelize.
4.4 Shortest Path Algorithms
Dijkstra's Algorithm (non-negative weights):
-
Steps:
-
dist[source]=0, others=∞. -
Set
S= empty (vertices with final shortest path). -
While not all vertices in
S:-
Pick
unot inSwith mindist[u]. -
Add
utoS. -
For each neighbor
vofu: ifdist[u] + weight(u,v) < dist[v], updatedist[v].
-
-
-
Time: $$\displaystyle O(V^2) $$ (simple), $O(E \log V)$ with priority queue.
-
Example: Step-by-step distance updates.
4.5 Connected Components
-
Algorithm (undirected graph):
count = 0; for each vertex v: if (!visited[v]) { count++; DFS(v) or BFS(v); } -
Time: $O(V + E)$.
5. Sorting Algorithms
5.1 Comparison-Based Sorts
Selection Sort:
-
Algorithm:
for i=0 to n-2: min = i; for j=i+1 to n-1: if (A[j] < A[min]) min = j; swap(A[i], A[min]); -
Example: [29,10,14,37,13] → pass1: min=10 at index1, swap with 29 → [10,29,14,37,13]...
-
Complexity: Always $$\displaystyle O(n^2) $$ (comparisons: $$\displaystyle \frac{n(n-1)}{2} $$, swaps: $n-1$).
Insertion Sort:
-
Algorithm:
for i=1 to n-1: key = A[i]; j = i-1; while (j>=0 && A[j] > key) { A[j+1] = A[j]; j--; } A[j+1] = key; -
Example: [29,10,14,37,13] → i=1: key=10, shift 29 → [10,29,14,37,13]...
-
Complexity:
-
Best: $O(n)$ (already sorted).
-
Worst: $$\displaystyle O(n^2) $$ (reverse sorted).
-
Adaptive, stable.
-
Merge Sort (Divide & Conquer):
-
Recursive:
mergeSort(A, l, r): if (l < r) { mid = (l+r)/2; mergeSort(A, l, mid); mergeSort(A, mid+1, r); merge(A, l, mid, r); }mergeuses temporary array, $O(n)$. -
Iterative (bottom-up): Merge subarrays of size 1, then 2, etc.
-
Complexity: All cases $O(n \log n)$, space $O(n)$.
-
Example: [38,27,43,3,9,82,10] → split, merge step-by-step.
Quick Sort:
-
Partition (Lomuto, pivot=last):
partition(A, low, high): pivot = A[high]; i = low-1; for j=low to high-1: if (A[j] <= pivot) { i++; swap(A[i], A[j]); } swap(A[i+1], A[high]); return i+1; -
Algorithm:
quickSort(A, low, high): if (low < high) { pi = partition(A, low, high); quickSort(A, low, pi-1); quickSort(A, pi+1, high); } -
Example: [26,56,47,36,13,95,85,32] (pivot=32). Partition: elements ≤32 to left. Final pivot position? Show steps.
-
Complexity:
-
Best/Average: $O(n \log n)$.
-
Worst: $$\displaystyle O(n^2) $$ (sorted input with bad pivot).
-
-
Improvements: Random pivot, median-of-three.
Sorting Summary:
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Selection | $$\displaystyle O(n^2) $$ | $$\displaystyle O(n^2) $$ | $$\displaystyle O(n^2) $$ | $O(1)$ | No |
| Insertion | $O(n)$ | $$\displaystyle O(n^2) $$ | $$\displaystyle O(n^2) $$ | $O(1)$ | Yes |
| Merge | $O(n \log n)$ | $O(n \log n)$ | $O(n \log n)$ | $O(n)$ | Yes |
| Quick | $O(n \log n)$ | $O(n \log n)$ | $$\displaystyle O(n^2) $$ | $O(\log n)$ | No |
5.2 Internal vs External Sorting
-
Internal: Data fits in main memory (all above).
-
External: Data too large; use multi-way merge sort, replacement selection (create initial runs longer than memory).
5.3 Sorting in Practice
-
Small $n$: Insertion sort (low overhead).
-
General purpose: Quick sort (average fast, in-place).
-
Stable required: Merge sort.
-
Nearly sorted: Insertion/bubble sort.
-
Worst-case guarantee: Merge sort or heap sort.
6. Searching Algorithms
6.1 Linear Search
-
Algorithm: Scan array sequentially until key found or end.
-
Complexity: $O(n)$ worst/average, $O(1)$ best (first element).
-
Applications: Unsorted lists, small arrays.
6.2 Binary Search
-
Precondition: Sorted array.
-
Algorithm (iterative):
low=0, high=n-1; while (low <= high) { mid = (low+high)/2; if (A[mid] == key) return mid; else if (A[mid] < key) low = mid+1; else high = mid-1; } return -1; -
Complexity: $O(\log n)$.
-
Applications: Dictionary lookup, database indexing.
6.3 Fibonacci Search
-
Technique: Use Fibonacci numbers to divide array. Mid =
low + F_{k-2}where $$\displaystyle F_k $$ is largest Fibonacci ≤ n. -
Steps:
-
Find smallest Fibonacci $\geq n$.
-
Compare key with element at
offset + F_{k-2}. -
If key smaller, search left subarray of size $$\displaystyle F_{k-2} $$.
-
If key larger, search right subarray of size $$\displaystyle F_{k-3} $$, update offset.
-
-
Comparison: Fewer comparisons than binary search in some cases (uses only addition/subtraction).
7. Hashing
7.1 Hash Tables
-
Concept: Direct access via hash function $h(key)$ → index in table.
-
Hash Functions:
-
Division: $$\displaystyle h(key) = key \mod m $$ (m = table size, prime preferred).
-
Mid-square: Square key, extract middle digits.
-
Folding:
-
Shift-fold: Split key into parts, sum them.
-
Boundary-fold: Reverse alternate parts before sum.
-
-
-
Properties: Uniform distribution, simple computation, minimize collisions.
7.2 Collision Resolution
-
Collision: Two keys hash to same index.
-
Separate Chaining:
-
Array of linked lists.
-
Insert:
table[h(key)]->insert(key). -
Example: Insert [32,50,700,140,76,85,46,92,70,73,101] with
key mod 7:-
32 mod 7 = 4 → bucket4: 32
-
50 mod 7 = 1 → bucket1: 50
-
700 mod 7 = 0 → bucket0: 700
-
140 mod 7 = 0 → bucket0: 700→140
-
... continue.
-
-
-
Open Addressing (all in array):
-
Linear probing: $$\displaystyle h_i(key) = (h(key) + i) \mod m $$.
-
Quadratic probing: $$\displaystyle h_i(key) = (h(key) + c_1 i + c_2 i^2) \mod m $$.
-
Double hashing: $$\displaystyle h_i(key) = (h_1(key) + i \cdot h_2(key)) \mod m $$.
-
7.3 Hash Table Implementation
Java HashMap:
-
put(key, value): Compute hash, handle collisions (separate chaining with linked lists, treeify if list > 8 in JDK8). -
get(key): Compute hash, traverse chain. -
remove(key): Remove from chain.
C Implementation (separate chaining):
#define TABLE_SIZE 7
typedef struct Node {
int key;
struct Node* next;
} Node;
Node* table[TABLE_SIZE];
int hash(int key) { return key % TABLE_SIZE; }
void insert(int key) {
int idx = hash(key);
Node* new = malloc(sizeof(Node));
new->key = key; new->next = table[idx];
table[idx] = new;
}
7.4 Applications of Hashing
-
Compiler Symbol Table: Store variable names (keys) and attributes (type, scope). Fast lookup for compilation.
-
Database Indexing: Hash indexes for equality searches (e.g.,
WHERE id = 5). -
Caches: Web cache (URL → content), CPU cache (memory addresses).
-
Deduplication: Hash files to find duplicates.
-
Password Storage: Store salted hashes (not plain passwords).
Detailed Example 1: Symbol Table:
-
Compiler uses hash table to store identifiers.
-
On encountering
int x;, hash"x"→ bucket, insert entry{name:"x", type:int, scope:current}. -
On
x = 5;, hash"x"→ find entry, check type compatibility.
Detailed Example 2: LRU Cache:
-
Policy: Evict least recently used item when full.
-
Implementation: Hash map (key → node) + doubly linked list (access order).
-
Operations:
-
get(key): If exists, move node to head (most recent), return value. -
put(key, value): If exists, update, move to head; else create node, add to head. If full, remove tail (least recent) from list and map.
-
-
Time: $O(1)$ for both.
7.5 LRU Cache
-
Data Structures:
-
Doubly linked list: Head = most recent, tail = least recent.
-
Hash map:
key → pointer to list node.
-
-
Algorithm:
-
get(key):if (map.contains(key)) { node = map[key]; moveToHead(node); // remove from current position, add after head return node->value; } return -1; -
put(key, value):if (map.contains(key)) { node = map[key]; node->value = value; moveToHead(node); } else { node = new Node(key, value); map[key] = node; addToHead(node); if (size > capacity) { tail = removeTail(); map.erase(tail->key); free(tail); } }
-
[!TIP]
- In separate chaining, load factor $$\displaystyle \alpha = n/m $$ (n=elements, m=buckets). High $\alpha$ → longer chains.
- For open addressing, table should not be too full ($$\displaystyle \alpha < 0.7 $$) to avoid clustering.
8. Advanced and Additional Topics
8.1 Priority Queues
-
Definition: Queue where each element has priority; dequeue highest priority first.
-
Operations:
insert,deleteMax/deleteMin,peek. -
Implementation: Binary heap (array-based).
-
Heap property: Parent ≥ children (max-heap).
-
Insert: Add at end, heapify-up.
-
DeleteMax: Replace root with last element, heapify-down.
-
-
Example: Insert [10,20,15,12,40,25,18] into max-heap, show array after each insert.
8.2 Hybrid Data Structures
Mixed Representation (Jun 2023, Dec 2023):
-
Sequentially map $n$ data objects into array $[1,n]$.
-
First $$\displaystyle n_1 $$ objects are stacks, rest are queues.
-
Implementation:
-
Use array
A[1..n]. -
For stacks: maintain
top[i]for stack $i$ ($$\displaystyle 1 \leq i \leq n_1 $$). -
For queues: maintain
front[i],rear[i]for queue $i$ ($$\displaystyle n_1+1 \leq i \leq n $$).
-
-
Algorithms:
-
add(obj, value):if (obj <= n1) { // stack if (top[obj] == capacity) overflow; top[obj]++; A[top[obj]] = value; } else { // queue if (rear[obj] == capacity) overflow; rear[obj]++; A[rear[obj]] = value; if (front[obj] == -1) front[obj] = rear[obj]; } -
delete(obj):if (obj <= n1) { // stack if (top[obj] == -1) underflow; value = A[top[obj]]; top[obj]--; } else { // queue if (front[obj] == -1) underflow; value = A[front[obj]]; front[obj]++; if (front[obj] > rear[obj]) front[obj]=rear[obj]=-1; // empty }
-
8.3 Recursion in Data Structures
-
Role: Natural for tree traversals, DFS, merge sort, expression evaluation.
-
Design: Base case + recursive case.
-
Stack Usage: Each call pushes frame; can cause stack overflow for deep recursion (e.g., skewed tree).
8.4 Expression Conversion and Evaluation
Infix to Postfix (using stack):
-
Algorithm:
-
Initialize empty stack.
-
Scan infix left to right:
-
Operand → output.
-
'(' → push.
-
')' → pop and output until '('.
-
Operator: while stack top has higher/equal precedence (and not '('), pop to output; then push current.
-
-
Pop all remaining operators.
-
-
Precedence:
^(right assoc) >*,/>+,-(left assoc). -
Example:
a+b*c→ postfix:abc*+.
Postfix Evaluation:
-
Algorithm:
Initialize empty stack. For each token in postfix: If operand: push. If operator: pop two operands (b = pop(), a = pop()), compute a op b, push result. Result = pop(); -
Example:
2 3 9 * + 2 3 ^ - 6 2 / +Steps:
-
Push 2,3,9 → stack [2,3,9]
-
*: pop 9,3 → 3*9=27, push → [2,27] -
+: pop 27,2 → 2+27=29, push → [29] -
Push 2,3 → [29,2,3]
-
^: pop 3,2 → 2^3=8, push → [29,8] -
-: pop 8,29 → 29-8=21, push → [21] -
Push 6,2 → [21,6,2]
-
/: pop 2,6 → 6/2=3, push → [21,3] -
+: pop 3,21 → 21+3=24 → result 24.
-
Prefix Conversion: Reverse infix, convert to postfix (adjust parentheses), reverse result.
[!TIP]
- In infix to postfix, for equal precedence, pop if left-associative (e.g.,
-), don't pop if right-associative (e.g.,^).
- Postfix evaluation: order of operands matters for non-commutative operators (subtraction, division).