πŸ”’ Private Site

This site is password-protected.

Unit V β€” Complete Solutions to All Practice Questions


Table of Contents


Practice Questions β€” BST


Q1. Construct a BST by inserting: 45, 15, 79, 90, 10, 55, 12, 20, 50

Insert each key β€” if less than current node go left, if greater go right.

Step-by-step:

  1. Insert 45 β†’ root
  2. Insert 15 β†’ 15 < 45, go left
  3. Insert 79 β†’ 79 > 45, go right
  4. Insert 90 β†’ 90 > 45 > 79, go right of 79
  5. Insert 10 β†’ 10 < 45 < 15, go left of 15
  6. Insert 55 β†’ 55 > 45, right; 55 < 79, left of 79
  7. Insert 12 β†’ 12 < 45, left; 12 < 15, left; 12 > 10, right of 10
  8. Insert 20 β†’ 20 < 45, left; 20 > 15, right of 15
  9. Insert 50 β†’ 50 > 45, right; 50 < 79, left; 50 < 55, left of 55

Final BST:

            45
           /  \
         15    79
        / \   /  \
      10  20 55   90
        \   /
        12 50

Q2. Delete nodes 15, 45, and 79 from the BST constructed in Q1.

Delete 15 (two children):

  • In-order successor of 15 is 20 (smallest in right subtree)
  • Replace 15 with 20, delete the original 20
            45
           /  \
         20    79
        /     /  \
      10     55   90
        \   /
        12 50

Delete 45 (two children β€” root):

  • In-order successor of 45 is 50 (smallest in right subtree)
  • Replace 45 with 50, delete the original 50
            50
           /  \
         20    79
        /     /  \
      10     55   90
        \
        12

Delete 79 (two children):

  • In-order successor of 79 is 90 (smallest in right subtree β€” only child)
  • Replace 79 with 90, remove original 90
            50
           /  \
         20    90
        /     /
      10     55
        \
        12

Three cases of BST deletion:

  1. Leaf node β€” simply remove
  2. One child β€” replace with child
  3. Two children β€” replace with in-order successor (or predecessor), then delete successor

Q3. What is the in-order, pre-order, and post-order traversal of the tree in Q1?

Tree from Q1:

            45
           /  \
         15    79
        / \   /  \
      10  20 55   90
        \   /
        12 50

In-order (Left, Root, Right): Visit left subtree, then root, then right subtree.

\[10, 12, 15, 20, 45, 50, 55, 79, 90\]

Note: In-order traversal of a BST always gives sorted order.

Pre-order (Root, Left, Right):

\[45, 15, 10, 12, 20, 79, 55, 50, 90\]

Post-order (Left, Right, Root):

\[12, 10, 20, 15, 50, 55, 90, 79, 45\]

Recursive:

TreeNode* searchRecursive(TreeNode* root, int key) {
    if (root == NULL || root->data == key)
        return root;

    if (key < root->data)
        return searchRecursive(root->left, key);
    else
        return searchRecursive(root->right, key);
}

Iterative:

TreeNode* searchIterative(TreeNode* root, int key) {
    while (root != NULL && root->data != key) {
        if (key < root->data)
            root = root->left;
        else
            root = root->right;
    }
    return root;  // NULL if not found, node if found
}
Aspect Recursive Iterative
Time $O(h)$ $O(h)$
Space $O(h)$ β€” stack frames $O(1)$ β€” no extra space
Best case $O(1)$ β€” root is key $O(1)$
Worst case $O(n)$ β€” skewed tree $O(n)$ β€” skewed tree
Balanced BST $O(\log n)$ $O(\log n)$

Q5. What happens if you insert a sorted array into a BST? How can you build a balanced BST from a sorted array?

Inserting sorted array [10, 20, 30, 40, 50]:

Each element is greater than all previous, so it always goes to the right:

10
  \
   20
     \
      30
        \
         40
           \
            50

This creates a skewed tree (essentially a linked list). Height = $n - 1 = 4$. All operations become $O(n)$ instead of $O(\log n)$.

Building a balanced BST from sorted array:

Use the middle element as root recursively:

TreeNode* sortedArrayToBST(int arr[], int left, int right) {
    if (left > right)
        return NULL;

    int mid = (left + right) / 2;
    TreeNode* root = createNode(arr[mid]);
    root->left = sortedArrayToBST(arr, left, mid - 1);
    root->right = sortedArrayToBST(arr, mid + 1, right);
    return root;
}

For [10, 20, 30, 40, 50]:

  • Mid = 30 (root)
  • Left: [10, 20] β†’ mid = 10 with right child 20
  • Right: [40, 50] β†’ mid = 40 with right child 50
       30
      /  \
    10    40
      \     \
      20    50

Height = 2 (balanced!). Operations: $O(\log n)$.


Practice Questions β€” AVL Tree


Q1. Insert 50, 20, 60, 10, 8, 15, 32, 46, 11, 48 into an AVL tree. Show rotations.

Insert 50: Root = 50

Insert 20: 20 < 50, left child. BFs: 50(+1), 20(0). OK.

Insert 60: 60 > 50, right child. BFs: 50(0), 20(0), 60(0). OK.

    50
   /  \
  20   60

Insert 10: 10 < 50, left; 10 < 20, left. BFs: 50(+2!) β†’ IMBALANCED!

Path: 50 β†’ 20 β†’ 10 (Left-Left) β†’ LL Rotation (Right Rotate at 50)

Before:     After LL rotation:
   50(+2)       20
  /  \          /  \
 20   60      10    50
 /                  /  \
10                (empty) 60

Wait β€” let me recalculate. BF(20) = 1, BF(50) = 2. LL case.

Right rotate at 50:

       20
      /  \
    10    50
           \
            60

BFs: 20(0), 10(0), 50(-1), 60(0). Balanced!

Insert 8: 8 < 20, left; 8 < 10, left.

       20
      /  \
    10    50
    /      \
   8       60

BFs: 8(0), 10(+1), 20(+1), 50(-1), 60(0). All OK.

Insert 15: 15 < 20, left; 15 > 10, right.

       20
      /  \
    10    50
    / \    \
   8  15   60

BFs: 20(+1), 10(0), 50(-1). OK.

Insert 32: 32 > 20, right; 32 < 50, left.

       20
      /  \
    10    50
    / \  /  \
   8  15 32  60

BFs: 20(0). OK.

Insert 46: 46 > 20, right; 46 < 50, left; 46 > 32, right.

       20
      /  \
    10    50
    / \  /  \
   8  15 32  60
           \
           46

BFs: 32(-1), 50(+1), 20(-1). OK.

Insert 11: 11 < 20, left; 11 > 10, right; 11 < 15, left.

       20
      /  \
    10    50
    / \  /  \
   8  15 32  60
      /    \
     11    46

BFs: 15(+1), 10(-1), 20(+1). OK.

Insert 48: 48 > 20, right; 48 < 50, left; 48 > 32, right; 48 > 46, right.

       20
      /  \
    10    50
    / \  /  \
   8  15 32  60
      /    \
     11    46
             \
             48

BFs: 46(-1), 32(-2!) β†’ IMBALANCED at 32!

Path: 32 β†’ 46 β†’ 48 (Right-Right) β†’ RR Rotation (Left Rotate at 32)

After RR rotation at 32:

       20
      /  \
    10    50
    / \  /  \
   8  15 46  60
      / /  \
     11 32  48

BFs: 46(0), 32(0), 48(0), 50(0). Balanced!

Final AVL Tree:

         20
        /  \
      10    50
      / \  /  \
     8  15 46  60
        / /  \
       11 32  48

Q2. What is a balance factor? For what values of BF is a tree AVL-balanced?

Balance Factor (BF) of a node:

\[BF = \text{Height(Left Subtree)} - \text{Height(Right Subtree)}\]

An AVL tree is balanced when every node has:

\[BF \in \{-1, 0, +1\}\]
BF Meaning
+1 Left subtree is 1 level taller (left-heavy)
0 Both subtrees have equal height (perfectly balanced)
-1 Right subtree is 1 level taller (right-heavy)
+2 or more Left-heavy imbalance β†’ needs rotation
-2 or less Right-heavy imbalance β†’ needs rotation

Q3. Explain all four types of AVL rotations with diagrams.

1. LL Rotation (Right Rotation):

Imbalance at node A, caused by insertion into the left subtree of A’s left child.

Before:        After (Right Rotate A):
    A (+2)          B (0)
   /               / \
  B (+1)          C    A (0)
 /
C

2. RR Rotation (Left Rotation):

Imbalance at node A, caused by insertion into the right subtree of A’s right child.

Before:        After (Left Rotate A):
  A (-2)          B (0)
   \             / \
    B (-1)      A    C
     \
      C

3. LR Rotation (Left-Right):

Imbalance at A, caused by insertion into the right subtree of A’s left child. Needs TWO rotations.

Before:           Step 1 (Left Rotate B):    Step 2 (Right Rotate A):
    A (+2)            A (+2)                     C (0)
   /                 /                          / \
  B (-1)            C (+1)                     B    A
   \               /
    C             B

4. RL Rotation (Right-Left):

Imbalance at A, caused by insertion into the left subtree of A’s right child. Needs TWO rotations.

Before:           Step 1 (Right Rotate B):    Step 2 (Left Rotate A):
  A (-2)            A (-2)                      C (0)
   \                 \                          / \
    B (+1)            C (-1)                   A    B
   /                   \
  C                     B

Summary:

Case BF(Node) BF(Child) Rotation
LL +2 +1 Right rotate at node
RR -2 -1 Left rotate at node
LR +2 -1 Left rotate child, then right rotate node
RL -2 +1 Right rotate child, then left rotate node

Q4. What is the maximum height of an AVL tree with $n$ nodes?

The maximum height of an AVL tree with $n$ nodes is:

\[h_{\max} \leq 1.44 \log_2(n + 2) - 0.328\]

Practically: $h \approx 1.44 \log_2 n$

This comes from the minimum number of nodes $N(h)$ in an AVL tree of height $h$:

\[N(h) = N(h-1) + N(h-2) + 1\]

Base cases: $N(0) = 1$, $N(1) = 2$

Height $h$ Min nodes $N(h)$
0 1
1 2
2 4
3 7
4 12
5 20

$N(h)$ grows like Fibonacci numbers: $N(h) \approx \phi^h$ where $\phi = 1.618…$

This means AVL trees guarantee $O(\log n)$ height, making all operations $O(\log n)$.


Q5. Compare BST and AVL tree. Why don’t we always use AVL trees?

Feature BST AVL Tree Β  Β 
Balance Not guaranteed Always balanced ($ BF \leq 1$)
Height $O(n)$ worst case (skewed) $O(\log n)$ always Β  Β 
Search $O(n)$ worst $O(\log n)$ worst Β  Β 
Insert $O(n)$ worst $O(\log n)$ + rotation cost Β  Β 
Delete $O(n)$ worst $O(\log n)$ + rotation cost Β  Β 
Rotations None Up to 2 per insert, $O(\log n)$ per delete Β  Β 
Implementation Simple Complex (balance tracking, rotations) Β  Β 

Why not always use AVL trees?

  1. Extra overhead: Each insertion/deletion must check balance and potentially perform rotations
  2. More storage: Each node needs to store height or balance factor
  3. Insert-heavy workloads: The rotation cost makes AVL slower for frequent insertions
  4. Simpler alternatives exist: For random data, a plain BST is often balanced enough
  5. Red-black trees offer a middle ground β€” less strictly balanced but fewer rotations

When to use AVL: When search-heavy workloads demand guaranteed $O(\log n)$ performance (e.g., databases, dictionaries).


Practice Questions β€” Graphs


Q1. Represent the graph using adjacency matrix and adjacency list.

Graph:

  A --- B
  |   / |
  |  /  |
  C --- D

Edges: (A,B), (A,C), (B,C), (B,D), (C,D)

Adjacency Matrix (0-indexed: A=0, B=1, C=2, D=3):

Β  A B C D
A 0 1 1 0
B 1 0 1 1
C 1 1 0 1
D 0 1 1 0

Adjacency List:

A β†’ [B, C]
B β†’ [A, C, D]
C β†’ [A, B, D]
D β†’ [B, C]
Representation Space Edge lookup Add edge List neighbors
Matrix $O(V^2)$ $O(1)$ $O(1)$ $O(V)$
List $O(V+E)$ $O(\deg(v))$ $O(1)$ $O(\deg(v))$

Q2. Perform BFS and DFS on the graph from Q1 starting at vertex A.

BFS (Breadth-First Search) β€” uses Queue:

Queue: [A]        Visit: A
Queue: [B, C]     Visit: B (neighbor of A), C (neighbor of A)
Queue: [C, D]     Visit: C already seen. Visit D (neighbor of B)
Queue: [D]        Visit: D already seen
Queue: []         Done!

BFS Order: A β†’ B β†’ C β†’ D

BFS Trace:

Step Queue Visited Current
0 [A] {A} β€”
1 [B, C] {A, B, C} A
2 [C, D] {A, B, C, D} B
3 [D] {A, B, C, D} C
4 [] {A, B, C, D} D

DFS (Depth-First Search) β€” uses Stack (or recursion):

Start at A β†’ go to first unvisited neighbor B
At B β†’ go to first unvisited neighbor C
At C β†’ go to first unvisited neighbor D
At D β†’ no unvisited neighbors, backtrack

DFS Order: A β†’ B β†’ C β†’ D

(Note: Order depends on the order neighbors are stored. If alphabetical: A→B→C→D)

DFS Trace (recursive):

Call Current Neighbors Visit
dfs(A) A B, C A
dfs(B) B Aβœ“, C, D B
dfs(C) C Aβœ“, Bβœ“, D C
dfs(D) D Bβœ“, Cβœ“ D
backtrack β€” β€” β€”

Q3. What is topological sorting? Can it be applied to undirected graphs?

Topological sorting is a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge $(u, v)$, vertex $u$ comes before vertex $v$ in the ordering.

Example:

A β†’ B β†’ D
|       ↑
β””β†’ C β”€β”€β”€β”˜

Topological orders: A, B, C, D or A, C, B, D (both valid)

Can it be applied to undirected graphs? NO.

Reasons:

  1. No direction — In undirected graphs, edge {A,B} means both A→B and B→A, creating the requirement that A comes before B AND B comes before A — a contradiction.
  2. Cycles everywhere — Every edge in an undirected graph forms a cycle of length 2 (A→B→A). Topological sort requires a DAG (no cycles).
  3. No partial order β€” Topological sort relies on a partial order defined by edge direction, which doesn’t exist in undirected graphs.

Algorithm (Kahn’s β€” BFS-based):

  1. Compute in-degree of all vertices
  2. Enqueue all vertices with in-degree 0
  3. While queue not empty: dequeue $u$, output $u$, reduce in-degree of all neighbors
  4. If output count β‰  $ V $, graph has a cycle

Q4. Run Dijkstra’s algorithm on a weighted graph.

Graph (adjacency matrix):

     0   1   2   3   4
0 [  0   4   0   0   8 ]
1 [  4   0   0   2   0 ]
2 [  0   0   0   1   7 ]
3 [  0   2   1   0   0 ]
4 [  8   0   7   0   0 ]

Source: vertex 0

Step Visited dist[0] dist[1] dist[2] dist[3] dist[4] Pick
Init {} 0 ∞ ∞ ∞ ∞ 0
1 {0} 0 4 ∞ ∞ 8 1 (dist=4)
2 {0,1} 0 4 ∞ 6 8 3 (dist=6)
3 {0,1,3} 0 4 7 6 8 2 (dist=7)
4 {0,1,3,2} 0 4 7 6 8 4 (dist=8)

Step 1: From 0: dist[1] = 0+4=4, dist[4] = 0+8=8

Step 2: From 1: dist[3] = min(∞, 4+2) = 6

Step 3: From 3: dist[2] = min(∞, 6+1) = 7, dist[1] = min(4, 6+2) = 4 (no change)

Step 4: From 2: dist[4] = min(8, 7+7) = 8 (no change)

Final shortest distances from 0: {0:0, 1:4, 2:7, 3:6, 4:8}

Shortest paths:

  • 0β†’1: 0β†’1 (cost 4)
  • 0β†’2: 0β†’1β†’3β†’2 (cost 7)
  • 0β†’3: 0β†’1β†’3 (cost 6)
  • 0β†’4: 0β†’4 (cost 8)

Q5. Compare BFS and DFS β€” data structures used, time complexity, and applications.

Feature BFS DFS
Data Structure Queue Stack (or recursion)
Strategy Level by level (breadth) As deep as possible first
Time $O(V + E)$ $O(V + E)$
Space $O(V)$ β€” queue stores a level $O(V)$ β€” stack stores a path
Shortest Path Yes (unweighted) No
Completeness Finds nearest solution first May explore far before near
Memory More (stores all nodes at current depth) Less (stores only one path)

BFS Applications:

  • Shortest path in unweighted graphs
  • Level-order traversal of trees
  • Web crawling (explore nearby pages first)
  • Social network β€” find people within $k$ connections

DFS Applications:

  • Topological sorting
  • Cycle detection
  • Finding connected components
  • Solving mazes/puzzles (backtracking)
  • Strongly connected components (Tarjan’s, Kosaraju’s)

Q6. What is a DAG? Give a real-life example of topological sorting.

DAG = Directed Acyclic Graph

A graph that is:

  • Directed β€” edges have direction (arrows)
  • Acyclic β€” no cycles (cannot follow edges in a loop back to the starting vertex)

Real-life example: Course Prerequisites

Math 101 β†’ Math 201 β†’ Math 301
  ↓                      ↑
CS 101 β†’ CS 201 β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ↓
CS 301

Topological sort: Math 101, CS 101, Math 201, CS 201, CS 301, Math 301

This ordering ensures you take prerequisites before any course that depends on them.

Other examples:

  • Build systems: Compile dependencies in order (Makefiles)
  • Task scheduling: Complete prerequisite tasks first
  • Package managers: Install dependencies before the package
  • Spreadsheet formulas: Evaluate cells that others depend on first

Practice Questions β€” Heaps


Q1. Build a max-heap from: [4, 10, 3, 5, 1, 8, 7]. Show each step.

Method: Build using bottom-up heapify. Start from the last non-leaf node (index $\lfloor n/2 \rfloor - 1 = 2$).

Initial array as tree:

        4
       / \
     10    3
    / \   / \
   5   1 8   7

Heapify index 2 (value 3): Children: 8, 7. Max child = 8 > 3 β†’ swap.

        4
       / \
     10    8
    / \   / \
   5   1 3   7

Heapify index 1 (value 10): Children: 5, 1. Max child = 5 < 10 β†’ no swap.

Heapify index 0 (value 4): Children: 10, 8. Max child = 10 > 4 β†’ swap.

       10
       / \
      4    8
    / \   / \
   5   1 3   7

Now heapify down index 1 (value 4): Children: 5, 1. Max child = 5 > 4 β†’ swap.

       10
       / \
      5    8
    / \   / \
   4   1 3   7

Final max-heap: [10, 5, 8, 4, 1, 3, 7]

Verification: Every parent β‰₯ its children. βœ“


Q2. Perform 3 extractMax operations on the heap from Q1.

Heap: [10, 5, 8, 4, 1, 3, 7]

Extract 1 β€” Remove 10: Swap root (10) with last element (7), remove 10, heapify down.

[7, 5, 8, 4, 1, 3]
β†’ 7 < 8 β†’ swap with 8
[8, 5, 7, 4, 1, 3]
β†’ 7 > 3 β†’ no more swaps

Extracted: 10. Heap: [8, 5, 7, 4, 1, 3]

Extract 2 β€” Remove 8: Swap root (8) with last element (3), remove 8, heapify down.

[3, 5, 7, 4, 1]
β†’ 3 < 7 β†’ swap with 7
[7, 5, 3, 4, 1]
β†’ 3 has no children below β†’ done

Extracted: 8. Heap: [7, 5, 3, 4, 1]

Extract 3 β€” Remove 7: Swap root (7) with last element (1), remove 7, heapify down.

[1, 5, 3, 4]
β†’ 1 < 5 β†’ swap with 5
[5, 1, 3, 4]
β†’ 1 < 4 β†’ swap with 4
[5, 4, 3, 1]

Extracted: 7. Heap: [5, 4, 3, 1]

Elements extracted in order: 10, 8, 7 (decreasing β€” this is Heap Sort!)


Q3. What is the difference between a max-heap and a min-heap?

Feature Max-Heap Min-Heap
Property Parent β‰₯ children Parent ≀ children
Root Maximum element Minimum element
Extract extractMax() extractMin()
Use case Find/remove largest Find/remove smallest
Priority Queue Highest priority = highest value Highest priority = lowest value
Heap Sort Produces ascending order Produces descending order

Max-Heap example:

       50
      /  \
    30    40
   / \
  10  20

Root = 50 (maximum)

Min-Heap example:

       10
      /  \
    20    30
   / \
  50  40

Root = 10 (minimum)


Q4. Why is a binary heap stored as an array instead of using pointers?

A binary heap is a complete binary tree β€” all levels are fully filled except possibly the last, which is filled left to right. This property makes array storage ideal:

Array index formulas (0-indexed):

  • Parent of node $i$: $\lfloor (i-1)/2 \rfloor$
  • Left child of node $i$: $2i + 1$
  • Right child of node $i$: $2i + 2$

Advantages of array storage:

Advantage Explanation
No pointer overhead Save 2 pointers per node (8-16 bytes each)
Cache-friendly Contiguous memory β†’ excellent cache locality
O(1) parent/child access Simple arithmetic instead of following pointers
No memory fragmentation Single array vs scattered heap allocations
Simple implementation No malloc/free for tree operations

Why it works: The complete tree property guarantees no β€œgaps” in the array. Every index from 0 to $n-1$ is used.

This would NOT work for BSTs or AVL trees because they are not necessarily complete.


Q5. Implement Heap Sort using a max-heap.

void heapify(int arr[], int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;

    if (left < n && arr[left] > arr[largest])
        largest = left;
    if (right < n && arr[right] > arr[largest])
        largest = right;

    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

void heapSort(int arr[], int n) {
    // Step 1: Build max-heap (O(n))
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // Step 2: Extract elements one by one (O(n log n))
    for (int i = n - 1; i > 0; i--) {
        // Swap root (max) with last element
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        // Heapify reduced heap
        heapify(arr, i, 0);
    }
}

Trace for [4, 10, 3, 5, 1]:

Build heap: [10, 5, 3, 4, 1]

Step Swap Array Heap portion
1 10↔1 [1, 5, 3, 4, 10] heapify β†’ [5, 4, 3, 1, 10]
2 5↔1 [1, 4, 3, 5, 10] heapify β†’ [4, 1, 3, 5, 10]
3 4↔3 [3, 1, 4, 5, 10] heapify β†’ [3, 1, 4, 5, 10]
4 3↔1 [1, 3, 4, 5, 10] done

Sorted: [1, 3, 4, 5, 10]

Property Value
Time $O(n \log n)$ always
Space $O(1)$ β€” in-place
Stable No
Best for Guaranteed $O(n \log n)$ without extra space

Practice Questions β€” Hashing


Q1. What is hashing? Why is it useful?

Hashing is a technique that maps data (keys) to fixed-size values (hash codes) using a hash function, which determines the index/slot in a hash table where the data is stored.

\[h(key) = \text{hash function}(key) \rightarrow \text{index in table}\]

Why hashing is useful:

Operation Array BST Hash Table
Search $O(n)$ $O(\log n)$ $O(1)$ avg
Insert $O(1)$/$O(n)$ $O(\log n)$ $O(1)$ avg
Delete $O(n)$ $O(\log n)$ $O(1)$ avg

Applications:

  • Dictionaries / Symbol tables (compilers)
  • Database indexing
  • Caching (memoization)
  • Password storage (hash + salt)
  • Data deduplication
  • Sets (checking membership in $O(1)$)

Q2. Insert keys 25, 42, 96, 101, 57, 31, 67 into a hash table of size 7.

Hash function: $h(k) = k \mod 7$

Key $k \mod 7$
25 4
42 0
96 5
101 3
57 1
31 3 (collision with 101!)
67 4 (collision with 25!)

(a) Separate Chaining:

Each slot is a linked list:

Index Chain
0 42 β†’ NULL
1 57 β†’ NULL
2 NULL
3 101 β†’ 31 β†’ NULL
4 25 β†’ 67 β†’ NULL
5 96 β†’ NULL
6 NULL

(b) Linear Probing ($h(k, i) = (h(k) + i) \mod 7$):

Key Hash Probes Final Index
25 4 4 (empty) 4
42 0 0 (empty) 0
96 5 5 (empty) 5
101 3 3 (empty) 3
57 1 1 (empty) 1
31 3 3β†’4β†’5β†’6 6
67 4 4β†’5β†’6β†’(all full!)β†’ 2 2

Table: [42, 57, 67, 101, 25, 96, 31]

(c) Quadratic Probing ($h(k, i) = (h(k) + i^2) \mod 7$):

Key Hash Probes Final Index
25 4 4 4
42 0 0 0
96 5 5 5
101 3 3 3
57 1 1 1
31 3 3(full), 3+1=4(full), 3+4=0(full), 3+9=12%7=5(full), 3+16=19%7=5(full), 3+25=28%7=0(full)… Try: $(3+1)\%7=4$β†’full, $(3+4)\%7=0$β†’full, $(3+9)\%7=5$β†’full, $(3+16)\%7=5$β†’full. Hmm β€” let’s recheck. $i=1: (3+1)\%7=4$ full. $i=2: (3+4)\%7=0$ full. $i=3: (3+9)\%7=5$ full. $i=4: (3+16)\%7=5$ full. $i=5: (3+25)\%7=0$ full. $i=6: (3+36)\%7=4$ full. Cycle! Cannot insert.

With table size 7 (prime), quadratic probing can fail when table is > half full. Some keys may need rehashing.

31 goes to index 2 (trying alternate sequence) or requires table resize.

67: $h = 4$, $i=1: 5$ full, $i=2: (4+4)\%7=1$ full, $i=3: (4+9)\%7=6$β†’6.


Q3. What is a collision? Compare separate chaining and open addressing.

Collision: When two or more keys hash to the same index: $h(k_1) = h(k_2)$ but $k_1 \neq k_2$.

Feature Separate Chaining Open Addressing
Method Each slot has a linked list Find another empty slot in the table
Load factor Can exceed 1.0 ($\alpha > 1$) Must be $\alpha \leq 1$
Memory Extra for pointers No extra (all in array)
Cache Poor (linked list traversal) Better (array access)
Deletion Easy (remove from list) Tricky (need tombstone markers)
Clustering No clustering Primary/secondary clustering
Performance Graceful degradation Degrades sharply near $\alpha = 1$
Best when Many collisions expected Load factor stays < 0.75

Q4. Explain linear probing, quadratic probing, and double hashing with examples.

All are open addressing methods β€” when a collision occurs, probe for the next empty slot.

1. Linear Probing: $h(k, i) = (h(k) + i) \mod m$

Probe sequence: $h(k), h(k)+1, h(k)+2, \ldots$

Problem: Primary clustering β€” long clusters of occupied slots form, slowing searches.

2. Quadratic Probing: $h(k, i) = (h(k) + i^2) \mod m$

Probe sequence: $h(k), h(k)+1, h(k)+4, h(k)+9, \ldots$

Better than linear β€” jumps further, reduces primary clustering. Problem: Secondary clustering β€” keys with the same hash follow the same probe sequence.

3. Double Hashing: $h(k, i) = (h_1(k) + i \cdot h_2(k)) \mod m$

Uses a second hash function to determine the step size. Common choice: $h_2(k) = R - (k \mod R)$ where $R$ is a prime < $m$.

Example with m=7, keys: 10, 17, 24

$h(k) = k \mod 7$. All three hash to index 3!

Method 10β†’3 17 probes 24 probes
Linear 3 3β†’4 3β†’4β†’5
Quadratic 3 3β†’4 3β†’4β†’7%7=0
Double ($h_2 = 5-(k\%5)$) 3 3β†’3+3=6 3β†’3+1=4

Double hashing gives the most uniform distribution of probes.


Q5. What is the load factor $\alpha$? How does it affect performance?

\[\alpha = \frac{n}{m}\]

Where $n$ = number of keys stored, $m$ = table size.

Effect on performance (Open Addressing with Uniform Hashing):

$\alpha$ Avg. probes (unsuccessful) Avg. probes (successful)
0.25 1.33 1.15
0.50 2.0 1.39
0.75 4.0 1.85
0.90 10.0 2.56
0.99 100.0 4.65

Formulas:

  • Unsuccessful search: $\frac{1}{1-\alpha}$
  • Successful search: $\frac{1}{\alpha} \ln \frac{1}{1-\alpha}$

For Separate Chaining:

  • Average chain length = $\alpha$ (can exceed 1)
  • Unsuccessful search: $O(1 + \alpha)$
  • Successful search: $O(1 + \alpha/2)$

Best practice: Keep $\alpha < 0.75$ for open addressing. Rehash (double table size) when threshold is exceeded.


Q6. Why should the hash table size be a prime number?

Reason: A prime table size reduces the chance of patterns in key values causing clustering.

Example β€” Non-prime size (m=10): If keys are multiples of 5: 5, 10, 15, 20, 25…

  • $5 \mod 10 = 5$
  • $10 \mod 10 = 0$
  • $15 \mod 10 = 5$ β†’ collision!
  • $20 \mod 10 = 0$ β†’ collision!

Only slots 0 and 5 are used β€” terrible distribution.

Example β€” Prime size (m=7): Same keys: 5, 10, 15, 20, 25

  • $5 \mod 7 = 5$
  • $10 \mod 7 = 3$
  • $15 \mod 7 = 1$
  • $20 \mod 7 = 6$
  • $25 \mod 7 = 4$

All different slots β€” much better distribution!

Mathematical reason: When $m$ is prime, it shares no common factors with the key (unless the key is a multiple of $m$). This means the modulo operation distributes keys more uniformly. With composite $m$, keys that share a factor with $m$ tend to cluster.

Additional reasons:

  • Quadratic probing is guaranteed to visit all slots only when $m$ is prime
  • Double hashing works best with prime table sizes

Comprehensive Unit V Practice Set


1. Define: binary tree, BST, AVL tree, complete binary tree, full binary tree.

Term Definition
Binary Tree A tree where each node has at most 2 children (left and right)
BST A binary tree where left child < root < right child for every node
AVL Tree A BST that is self-balancing β€” balance factor of every node is in {-1, 0, +1}
Complete Binary Tree All levels fully filled except possibly the last, which is filled left to right
Full Binary Tree Every node has 0 or 2 children (no node has exactly one child)

Examples:

Complete:       Full:          Neither:
    1             1               1
   / \           / \             / \
  2   3         2   3           2   3
 / \           / \               \
4   5         4   5               5

2. What are the three depth-first traversals of a binary tree?

Sample tree:
      1
     / \
    2   3
   / \
  4   5
Traversal Order Result
In-order Left, Root, Right 4, 2, 5, 1, 3
Pre-order Root, Left, Right 1, 2, 4, 5, 3
Post-order Left, Right, Root 4, 5, 2, 3, 1

Mnemonics:

  • In-order: Root is in the middle
  • Pre-order: Root comes first (pre = before)
  • Post-order: Root comes last (post = after)

3. Why is in-order traversal of a BST always sorted?

In-order visits: left subtree β†’ root β†’ right subtree.

By the BST property:

  • All nodes in the left subtree have values less than the root
  • All nodes in the right subtree have values greater than the root

So in-order traversal visits all smaller values first (left subtree), then the current value (root), then all larger values (right subtree). Recursively, this produces values in ascending order.

Proof by induction: If in-order traversal of any BST subtree gives sorted output, then visiting left (sorted, all < root) + root + right (sorted, all > root) = sorted sequence for the whole subtree.


4. What is the balance factor in an AVL tree? When do rotations occur?

$BF = Height(Left) - Height(Right)$

Rotations occur when $ BF > 1$ (i.e., BF = +2 or -2) after an insertion or deletion.
BF at node BF at child Rotation
+2 +1 LL (single right rotation)
+2 -1 LR (left-right double rotation)
-2 -1 RR (single left rotation)
-2 +1 RL (right-left double rotation)

5. What is the difference between a graph and a tree?

Feature Tree Graph
Cycles No cycles (acyclic) May have cycles
Root Has a designated root No root (unless rooted graph)
Edges $n-1$ edges for $n$ nodes Any number of edges
Path Unique path between any two nodes Multiple paths possible
Connectivity Always connected Not necessarily
Direction Top-down (parent→child) Can be directed or undirected
Hierarchy Hierarchical Not necessarily

A tree is a special case of a graph: a connected, acyclic, undirected graph.


6. What is hashing? Name three hash functions.

Hashing maps keys to table indices using a hash function for $O(1)$ average-case access.

Three hash functions:

  1. Division Method: $h(k) = k \mod m$ β€” simple, effective when $m$ is prime.

  2. Mid-Square Method: Square the key, extract middle digits. $h(k) = \text{middle digits of } k^2$.

  3. Multiplication Method: $h(k) = \lfloor m \cdot (kA \mod 1) \rfloor$ where $A \approx 0.6180339887$ (Knuth’s suggestion).


7. Construct a BST by inserting 50, 30, 70, 20, 40, 60, 80. Write all three traversals.

         50
        /  \
      30    70
     / \   /  \
   20  40 60   80

In-order: 20, 30, 40, 50, 60, 70, 80 (sorted!)

Pre-order: 50, 30, 20, 40, 70, 60, 80

Post-order: 20, 40, 30, 60, 80, 70, 50


8. Insert 30, 20, 10, 25, 40, 35 into an AVL tree. Show all rotations.

Insert 30, 20: No imbalance.

    30
   /
  20

Insert 10: BF(30) = +2 β†’ LL Rotation (right rotate at 30)

Before:       After:
  30(+2)       20
 /            /  \
20(+1)      10    30
/
10

Insert 25: 25 > 20, right; 25 < 30, left.

    20
   /  \
  10   30
      /
     25

BFs: 30(+1), 20(-1). OK.

Insert 40: 40 > 20, right; 40 > 30, right.

    20
   /  \
  10   30
      /  \
     25   40

BFs: 30(0), 20(-1). OK.

Insert 35: 35 > 20, right; 35 > 30, right; 35 < 40, left.

    20
   /  \
  10   30
      /  \
     25   40
         /
        35

BFs: 40(+1), 30(-2!) β†’ RL Rotation at 30.

Step 1 β€” Right rotate at 40:

    20
   /  \
  10   30
      /  \
     25   35
            \
            40

Step 2 β€” Left rotate at 30:

    20
   /  \
  10   35
      /  \
     30   40
    /
   25

BFs: 30(+1), 35(+1), 20(-1). All OK!

Final AVL tree:

       20
      /  \
    10    35
         /  \
        30   40
       /
      25

9. Run BFS and DFS on the graph starting from vertex A.

Graph:

  A --- B --- E
  |         / |
  C --- D     F

Edges: (A,B), (A,C), (B,E), (C,D), (D,E), (E,F)

BFS (from A, using Queue):

Step Queue Visited Dequeue
0 [A] {A} β€”
1 [B, C] {A,B,C} A
2 [C, E] {A,B,C,E} B
3 [E, D] {A,B,C,E,D} C
4 [D, F] {A,B,C,E,D,F} E
5 [F] {A,B,C,E,D,F} D (no new neighbors)
6 [] {A,B,C,E,D,F} F

BFS Order: A, B, C, E, D, F

DFS (from A, recursive):

dfs(A) β†’ visit A, neighbors: B, C
  dfs(B) β†’ visit B, neighbors: Aβœ“, E
    dfs(E) β†’ visit E, neighbors: Bβœ“, D, F
      dfs(D) β†’ visit D, neighbors: C, Eβœ“
        dfs(C) β†’ visit C, neighbors: Aβœ“, Dβœ“ β†’ backtrack
      backtrack from D
      dfs(F) β†’ visit F, neighbors: Eβœ“ β†’ backtrack
    backtrack from E
  backtrack from B
backtrack from A

DFS Order: A, B, E, D, C, F


10. Run Dijkstra’s algorithm from vertex S.

Graph:

  S --2-- A --3-- D
  |       |       |
  6       1       5
  |       |       |
  B --4-- C --2-- E
Step Visited dist[S] dist[A] dist[B] dist[C] dist[D] dist[E] Pick
Init {} 0 ∞ ∞ ∞ ∞ ∞ S
1 {S} 0 2 6 ∞ ∞ ∞ A(2)
2 {S,A} 0 2 6 3 5 ∞ C(3)
3 {S,A,C} 0 2 6 3 5 5 D(5) or E(5)
4 {S,A,C,D} 0 2 6 3 5 5 E(5)
5 {S,A,C,D,E} 0 2 6 3 5 5 B(6)

Trace details:

  • From S: dist[A] = 2, dist[B] = 6
  • From A: dist[C] = min(∞, 2+1) = 3, dist[D] = min(∞, 2+3) = 5
  • From C: dist[B] = min(6, 3+4) = 6 (no change), dist[E] = min(∞, 3+2) = 5
  • From D: dist[E] = min(5, 5+5) = 5 (no change)
  • From E: no improvements

Shortest distances from S: S=0, A=2, B=6, C=3, D=5, E=5

Shortest paths:

  • Sβ†’A: Sβ†’A (2)
  • Sβ†’B: Sβ†’B (6)
  • Sβ†’C: Sβ†’Aβ†’C (3)
  • Sβ†’D: Sβ†’Aβ†’D (5)
  • Sβ†’E: Sβ†’Aβ†’Cβ†’E (5)

11. Build a max-heap from [3, 1, 6, 5, 2, 4]. Then extract max twice.

Build max-heap (bottom-up heapify):

Initial tree:

      3
     / \
    1   6
   / \ /
  5  2 4

Heapify index 2 (value 6): children: 4. 6 > 4 β†’ OK.

Heapify index 1 (value 1): children: 5, 2. Max = 5 > 1 β†’ swap.

      3
     / \
    5   6
   / \ /
  1  2 4

Heapify index 0 (value 3): children: 5, 6. Max = 6 > 3 β†’ swap.

      6
     / \
    5   3
   / \ /
  1  2 4

Continue heapifying 3: child 4 > 3 β†’ swap.

      6
     / \
    5   4
   / \ /
  1  2 3

Max-heap: [6, 5, 4, 1, 2, 3]

Extract max 1 β€” Remove 6: Swap 6 with last (3): [3, 5, 4, 1, 2]. Pop 6. Heapify: 3 < 5 β†’ swap. [5, 3, 4, 1, 2]. 3 > 1 and 2 β†’ OK.

Heap: [5, 3, 4, 1, 2]. Extracted: 6.

Extract max 2 β€” Remove 5: Swap 5 with last (2): [2, 3, 4, 1]. Pop 5. Heapify: 2 < 4 β†’ swap. [4, 3, 2, 1]. OK.

Heap: [4, 3, 2, 1]. Extracted: 5.


12. Insert keys 10, 22, 31, 4, 15, 28, 17, 88, 59 into a hash table of size 11.

Hash function: $h(k) = k \mod 11$

Key $k \mod 11$
10 10
22 0
31 9
4 4
15 4
28 6
17 6
88 0
59 4

(a) Separate Chaining:

Index Chain
0 22 β†’ 88 β†’ NULL
1 NULL
2 NULL
3 NULL
4 4 β†’ 15 β†’ 59 β†’ NULL
5 NULL
6 28 β†’ 17 β†’ NULL
7 NULL
8 NULL
9 31 β†’ NULL
10 10 β†’ NULL

(b) Linear Probing:

Key Hash Probes Index
10 10 10 βœ“ 10
22 0 0 βœ“ 0
31 9 9 βœ“ 9
4 4 4 βœ“ 4
15 4 4β†’5 βœ“ 5
28 6 6 βœ“ 6
17 6 6β†’7 βœ“ 7
88 0 0β†’1 βœ“ 1
59 4 4β†’5β†’6β†’7β†’8 βœ“ 8

Final table:

0 1 2 3 4 5 6 7 8 9 10
22 88 β€” β€” 4 15 28 17 59 31 10

13. Explain binary tree traversals with recursive code and detailed trace.

typedef struct TreeNode {
    int data;
    struct TreeNode *left, *right;
} TreeNode;

void inorder(TreeNode* root) {
    if (root == NULL) return;
    inorder(root->left);
    printf("%d ", root->data);
    inorder(root->right);
}

void preorder(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->data);
    preorder(root->left);
    preorder(root->right);
}

void postorder(TreeNode* root) {
    if (root == NULL) return;
    postorder(root->left);
    postorder(root->right);
    printf("%d ", root->data);
}

Trace on tree:

      1
     / \
    2   3
   / \
  4   5

In-order trace:

inorder(1) β†’ inorder(2) β†’ inorder(4) β†’ inorder(NULL) β†’ return
                                       β†’ print 4
                                       β†’ inorder(NULL) β†’ return
                          β†’ print 2
                          β†’ inorder(5) β†’ inorder(NULL) β†’ print 5 β†’ inorder(NULL)
             β†’ print 1
             β†’ inorder(3) β†’ inorder(NULL) β†’ print 3 β†’ inorder(NULL)

Output: 4 2 5 1 3

Pre-order: 1 2 4 5 3 Post-order: 4 5 2 3 1

Time: $O(n)$, Space: $O(h)$ where $h$ is the tree height.


14. Explain BST insertion and deletion with all three cases.

Insertion: Compare key with root, go left if smaller, right if larger, insert when NULL is reached.

TreeNode* insert(TreeNode* root, int key) {
    if (root == NULL) return createNode(key);
    if (key < root->data)
        root->left = insert(root->left, key);
    else if (key > root->data)
        root->right = insert(root->right, key);
    return root;
}

Deletion β€” Three cases:

Case 1: Leaf node β€” Simply remove it.

Delete 4:
    5          5
   / \   β†’    / \
  3   7      3   7
 /
4

Case 2: One child β€” Replace node with its child.

Delete 3 (has left child 2):
    5          5
   / \   β†’   / \
  3   7     2   7
 /
2

Case 3: Two children β€” Replace with in-order successor (smallest in right subtree) or predecessor.

Delete 5 (root, has two children):
  In-order successor of 5 = 7
    5          7
   / \   β†’   /
  3   7     3
TreeNode* deleteNode(TreeNode* root, int key) {
    if (root == NULL) return NULL;
    if (key < root->data)
        root->left = deleteNode(root->left, key);
    else if (key > root->data)
        root->right = deleteNode(root->right, key);
    else {
        // Node found
        if (root->left == NULL) {       // Case 1 & 2
            TreeNode* temp = root->right;
            free(root);
            return temp;
        } else if (root->right == NULL) { // Case 2
            TreeNode* temp = root->left;
            free(root);
            return temp;
        }
        // Case 3: Two children
        TreeNode* succ = root->right;
        while (succ->left != NULL) succ = succ->left;
        root->data = succ->data;
        root->right = deleteNode(root->right, succ->data);
    }
    return root;
}

15. Explain AVL tree rotations. Why are AVL trees preferred over plain BSTs?

See Q3 of AVL Tree section for detailed rotation diagrams.

Why AVL over BST:

  • BST can degrade to $O(n)$ for sorted/nearly-sorted input (skewed tree)
  • AVL guarantees $O(\log n)$ for ALL operations
  • Critical for databases and real-time systems where worst-case performance matters

Trade-off: AVL has rotation overhead per insert/delete β€” worth it only when search frequency significantly exceeds modification frequency.


16. Compare BFS and DFS.

See Q5 of Graphs section for the detailed comparison table.

Key difference in behavior:

Graph:  A - B - E
        |       |
        C - D - F

BFS from A: A, B, C, E, D, F  (level by level)
DFS from A: A, B, E, D, C, F  (deep first, then backtrack)

BFS explores all neighbors before going deeper. DFS goes as deep as possible before backtracking.


17. Explain Dijkstra’s shortest path algorithm.

See Q4 of Graphs section for a complete trace, or Q10 of Comprehensive section for another example.

Algorithm summary:

  1. Initialize: dist[source] = 0, all others = ∞
  2. Repeat V times: Pick unvisited vertex $u$ with minimum dist
  3. For each neighbor $v$ of $u$: if $dist[u] + weight(u,v) < dist[v]$, update $dist[v]$
  4. Mark $u$ as visited

Time: $O(V^2)$ with array, $O((V+E)\log V)$ with min-heap

Limitation: Does NOT work with negative edge weights (use Bellman-Ford instead).


18. Explain hashing with collision resolution techniques.

See Q3 and Q4 of the Hashing section for detailed explanations.

Summary comparison:

Method Pros Cons
Separate Chaining Simple, no clustering, $\alpha > 1$ OK Extra memory for pointers
Linear Probing Simple, cache-friendly Primary clustering
Quadratic Probing Reduces clustering Secondary clustering, may not find empty slot
Double Hashing Best distribution, no clustering Needs second hash function

19. Explain priority queues and binary heaps.

Priority Queue: An ADT where elements have priorities. The highest-priority element is served first regardless of insertion order.

Binary Heap is the most common implementation:

Operation Time
Insert $O(\log n)$
Extract Max/Min $O(\log n)$
Peek $O(1)$
Build heap $O(n)$

Insert (Heap Up / Sift Up):

void insert(int heap[], int* n, int value) {
    heap[*n] = value;
    int i = *n;
    (*n)++;
    while (i > 0 && heap[i] > heap[(i-1)/2]) {
        int temp = heap[i];
        heap[i] = heap[(i-1)/2];
        heap[(i-1)/2] = temp;
        i = (i - 1) / 2;
    }
}

Extract Max (Heap Down / Sift Down):

int extractMax(int heap[], int* n) {
    int max = heap[0];
    (*n)--;
    heap[0] = heap[*n];
    heapify(heap, *n, 0);
    return max;
}

Example: Insert 15 into max-heap [20, 10, 8, 5, 3]:

[20, 10, 8, 5, 3, 15]
15 > parent(8) β†’ swap: [20, 10, 15, 5, 3, 8]
15 > parent(20)? No β†’ done.

20. Explain topological sorting. When is it applicable? Give an algorithm and trace on a DAG.

Topological sorting produces a linear ordering of vertices in a DAG such that for every directed edge $u \to v$, $u$ appears before $v$.

Applicable only to DAGs (Directed Acyclic Graphs). Cannot be applied to:

  • Undirected graphs
  • Graphs with cycles

Kahn’s Algorithm (BFS-based):

1. Compute in-degree of all vertices
2. Enqueue all vertices with in-degree 0
3. While queue not empty:
   a. Dequeue vertex u, add to result
   b. For each neighbor v of u:
      - Decrease in-degree[v] by 1
      - If in-degree[v] == 0, enqueue v
4. If result size β‰  |V|, graph has a cycle

Trace on DAG:

  A β†’ B β†’ D
  ↓       ↑
  C β”€β”€β”€β”€β”€β”€β”˜

Edges: A→B, A→C, B→D, C→D

In-degrees: A=0, B=1, C=1, D=2

Step Queue Dequeue Update in-degrees Result
Init [A] β€” A:0, B:1, C:1, D:2 []
1 [B, C] A B:0, C:0, D:2 [A]
2 [C, D] B C:0, D:1 [A, B]
3 [D] C D:0 [A, B, C]
4 [] D β€” [A, B, C, D]

Topological Order: A, B, C, D βœ“

Also valid: A, C, B, D (multiple valid orderings exist).

DFS-based approach: Run DFS and push each vertex to a stack after all its descendants are processed. Pop the stack for topological order.

Time: $O(V + E)$ for both algorithms.


πŸ“ Back to Unit V Notes β†’

← Back to DSA Index