Design & Analysis of Algorithms

2023-24 Solved Paper · Detailed Marks-Wise Solutions

Group A — Very Short Answer Type Questions [ 10 x 1 = 10 Marks ]

1. (i) What is the appropriate data structure for Prim's Minimum Spanning Tree algorithm? [ 1 Mark ]

Answer: Min-Priority Queue. For optimal performance, it is typically implemented using a Binary Heap (gives \(O(E \log V)\) complexity) or a Fibonacci Heap (gives \(O(E + V \log V)\) complexity). It is used to quickly extract the vertex with the minimum key value among the unvisited vertices.

1. (ii) What is represented by the asymptotic notation O(1)? [ 1 Mark ]

Answer: Constant Time Complexity. It indicates that the execution time (or space) of the algorithm remains constant, bounded by a fixed upper limit, and does not grow or change regardless of the size of the input data \(n\).

1. (iii) Time complexity for recurrence relation T(n) = 2T(n/2) + n is: [ 1 Mark ]

Answer: \(O(n \log n)\). This perfectly matches Case 2 of Master's Theorem where \(a=2, b=2\). Since \(n^{\log_2 2} = n^1 = n\), and \(f(n) = n\), we have \(f(n) = \Theta(n^{\log_b a})\). This results in \(T(n) = \Theta(n \log n)\).

1. (iv) The notation \(\Omega(n)\) is the formal way to express the ______ bound of an algorithm's running time. [ 1 Mark ]

Answer: asymptotic lower bound. It guarantees that the algorithm will take at least this much time to execute for sufficiently large inputs.

1. (v) Fractional knapsack problem is solved most efficiently by which algorithm design technique? [ 1 Mark ]

Answer: Greedy method. By sorting the items by their profit-to-weight ratio in descending order and continuously picking the item with the highest ratio, an optimal solution is guaranteed.

1. (vi) What is the appropriate data structure for Depth First Search algorithm? [ 1 Mark ]

Answer: Stack. This can be implemented either implicitly using the system call stack via recursion, or explicitly using a LIFO (Last-In-First-Out) Stack data structure to track visited nodes.

1. (vii) Which algorithm is used to solve a maximum flow problem? [ 1 Mark ]

Answer: Ford-Fulkerson Algorithm. Specifically, its polynomial-time implementation, the Edmonds-Karp Algorithm, which uses Breadth-First Search (BFS) to find the shortest augmenting paths, is commonly used.

1. (viii) How many solutions are there for 8 queens problem on 8*8 board? [ 1 Mark ]

Answer: 92 total distinct solutions. Note that if we exclude solutions that are identical under rotational and reflective symmetry, there are 12 unique fundamental solutions.

1. (ix) Time complexity of Heap sort with n items is: [ 1 Mark ]

Answer: \(O(n \log n)\) in all cases (best, worst, and average cases). Building the max heap takes \(O(n)\), and extracting elements takes \(O(n \log n)\).

1. (x) The 0-1 Knapsack problem can be solved using Greedy algorithm - state True or False. [ 1 Mark ]

Answer: False. Greedy algorithms do not guarantee an optimal solution for 0-1 Knapsack because items cannot be divided to fill remaining capacity. Dynamic Programming or Branch and Bound must be used.

1. (xi) Which algorithm is used to solve the single source shortest path problem in a graph with negative edge weights? [ 1 Mark ]

Answer: Bellman-Ford Algorithm. It iteratively relaxes all edges \(|V|-1\) times, allowing it to accurately compute shortest paths with negative weights and detect negative weight cycles.

1. (xii) Given items as {value, weight} pairs {{40, 20}, {30, 10}, {20, 5}}. The capacity of knapsack is 20. Find the maximum value output assuming items to be divisible. [ 1 Mark ]

Answer: 60.

Detailed Calculation:
1. Calculate value/weight ratios:

  • Item 1: \(40/20 = 2\)
  • Item 2: \(30/10 = 3\)
  • Item 3: \(20/5 = 4\)
2. Sort items by ratio in descending order: Item 3 \(\to\) Item 2 \(\to\) Item 1.
3. Load knapsack (Capacity = 20):
  • Take all of Item 3 (Weight = 5, Value = 20). Remaining capacity = 15.
  • Take all of Item 2 (Weight = 10, Value = 30). Remaining capacity = 5.
  • Take fraction of Item 1: \(\frac{5}{20} = 0.25\) of Item 1 (Weight = 5, Value = \(40 \times 0.25 = 10\)). Remaining capacity = 0.
Total Value = \(20 + 30 + 10 = 60\).

Group B — Short Answer Type Questions [ 3 x 5 = 15 Marks ]

2. What are the differences between Dijkstra's algorithm and Bellman-Ford algorithm? Compare their time complexities. [ 5 Marks ]

Both Dijkstra's Algorithm and Bellman-Ford Algorithm solve the Single Source Shortest Path (SSSP) problem, but they utilize entirely different design paradigms and have different constraints regarding edge weights.

Feature Dijkstra's Algorithm Bellman-Ford Algorithm
Design Paradigm Greedy Approach (selects the nearest unvisited vertex). Dynamic Programming Approach (relaxes all edges iteratively).
Negative Edge Weights Cannot handle negative edge weights. Will produce incorrect results. Fully capable of handling graphs with negative edge weights.
Negative Cycle Detection Fails completely; cannot detect negative cycles and may infinite loop. Can successfully detect the presence of negative weight cycles.
Edge Relaxation Approach Relaxes edges emanating from a single selected vertex at a time. Relaxes all edges in the graph in every single iteration.
Time Complexity (Worst Case) \(O(E \log V)\) (using a Binary Min-Heap data structure). \(O(V \cdot E)\) (significantly slower than Dijkstra on dense graphs).

3. Analyze the time complexity of quick sort algorithm for best case and worst case using recurrence relations. [ 5 Marks ]

Quick Sort's performance is highly dependent on the partition step (how the pivot splits the array).

1. Best Case Time Complexity: \(O(n \log n)\)

The best case occurs when the pivot chosen perfectly splits the array into two equal halves of size \(n/2\) at each level of recursion. The recurrence relation is:

\[T(n) = 2T(n/2) + \Theta(n)\]

Applying Case 2 of Master's Theorem (\(a=2, b=2, f(n)=\Theta(n) \implies n^{\log_2 2} = n\)):

\(T(n) = \Theta(n \log n)\)

2. Worst Case Time Complexity: \(O(n^2)\)

The worst case occurs when the partition process always picks the greatest or smallest element as the pivot (e.g., array is already sorted, and we pick the last element). This creates highly skewed partitions of size \(0\) and \(n-1\). The recurrence relation is:

\[T(n) = T(n-1) + T(0) + \Theta(n) = T(n-1) + \Theta(n)\]

Expanding the recurrence via substitution:

  • \(T(n) = T(n-1) + cn\)
  • \(T(n) = T(n-2) + c(n-1) + cn\)
  • \(T(n) = T(1) + c \sum_{i=2}^{n} i = T(1) + c \left[\frac{n(n+1)}{2} - 1\right]\)
\(T(n) = O(n^2)\)

4. Write the Floyd-Warshal Algorithm for all-pairs shortest path problem. What will be the time complexity? [ 5 Marks ]

Floyd-Warshall Algorithm: A classical dynamic programming approach used to find the shortest path between all pairs of vertices in a weighted graph (which may contain negative weights but no negative cycles). It systematically updates shortest paths by considering intermediate vertices \(\{1..k\}\).

Floyd-Warshall(W, V):
    // W is the initial adjacency/weight matrix of V vertices.
    1. Let D^(0) = W (Initialize shortest distances)
    
    // Core DP triple nested loops
    2. For k = 1 to V:          // Consider vertex k as an intermediate point
          For i = 1 to V:       // Source vertex
             For j = 1 to V:    // Destination vertex
                // If path via k is shorter, update it:
                D^(k)[i][j] = min( D^(k-1)[i][j], D^(k-1)[i][k] + D^(k-1)[k][j] )
                
    3. Return D^(V) // Contains shortest paths for all pairs

Time Complexity Analysis:

The algorithm consists of three nested loops, each iterating exactly \(V\) times (from 1 to \(V\)). Inside the innermost loop, the algorithm performs a constant-time \(O(1)\) addition and comparison operation. Thus, the total number of operations is proportional to \(V \times V \times V\).
Time Complexity = \(\Theta(V^3)\).
Space Complexity = \(\Theta(V^2)\) to store the distance matrix.

5. For the given set of 5 items and the knapsack capacity of 10 kg, find the maximum profit. You are allowed to take fractional amount of any item. Weights, Wi = {3, 3, 2, 5, 1}, Profits, Pi = {10, 15, 10, 12, 8}. [ 5 Marks ]

Given Knapsack Capacity \(M = 10\) kg. We will use the Greedy Method for the Fractional Knapsack Problem. First, we compute the profit-to-weight ratio for each item.

  1. Item 1: \(P_1 = 10, W_1 = 3 \implies \text{ratio} = \frac{10}{3} \approx 3.33\)
  2. Item 2: \(P_2 = 15, W_2 = 3 \implies \text{ratio} = \frac{15}{3} = 5.00\)
  3. Item 3: \(P_3 = 10, W_3 = 2 \implies \text{ratio} = \frac{10}{2} = 5.00\)
  4. Item 4: \(P_4 = 12, W_4 = 5 \implies \text{ratio} = \frac{12}{5} = 2.40\)
  5. Item 5: \(P_5 = 8, W_5 = 1 \implies \text{ratio} = \frac{8}{1} = 8.00\)

Step 1: Sort items in descending order of ratio:
Item 5 (8.0) \(\to\) Item 2 (5.0) \(\to\) Item 3 (5.0) \(\to\) Item 1 (3.33) \(\to\) Item 4 (2.40).

Step 2: Greedy Selection (Capacity = 10 kg):

  • Take all of Item 5: Weight = 1 kg, Profit = 8. (Remaining capacity = 9 kg).
  • Take all of Item 2: Weight = 3 kg, Profit = 15. (Remaining capacity = 6 kg).
  • Take all of Item 3: Weight = 2 kg, Profit = 10. (Remaining capacity = 4 kg).
  • Take all of Item 1: Weight = 3 kg, Profit = 10. (Remaining capacity = 1 kg).
  • Take fraction of Item 4: We only have 1 kg capacity left. We take \(\frac{1}{5} = 0.2\) fraction of Item 4.
    Weight taken = 1 kg, Profit = \(12 \times \frac{1}{5} = 2.4\). (Remaining capacity = 0 kg).
Maximum Total Profit: 8 + 15 + 10 + 10 + 2.4 = 45.4

6. Write short note on Randomized algorithms. [ 5 Marks ]

A Randomized Algorithm is an algorithm that incorporates a degree of randomness as part of its logic. It uses pseudo-random numbers to make decisions during execution, meaning its behavior and output (or execution time) can vary even for the exact same input on different runs. The main goal is to achieve better average-case performance or simpler implementation compared to deterministic algorithms.

Randomized algorithms are broadly classified into two major categories:

  1. Las Vegas Algorithms:
    These algorithms always produce the correct (exact) result or inform of failure, but their execution time is a random variable. The objective is to minimize the expected running time.
    Classic Example: Randomized Quick Sort. By choosing a random pivot instead of the last element, it virtually guarantees an expected \(O(n \log n)\) execution time, avoiding worst-case \(O(n^2)\) patterns.

  2. Monte Carlo Algorithms:
    These algorithms possess a deterministic, bounded execution time, but their output may have a small, bounded probability of being incorrect. The error probability can be exponentially reduced by running the algorithm multiple times.
    Classic Example: Miller-Rabin Primality Test. It checks if a large number is prime in polynomial time with a tiny error margin. If run \(k\) times, the error probability drops to \(4^{-k}\).

Group C — Long Answer Type Questions [ 3 x 15 = 45 Marks ]

7. Dynamic Programming and Shortest Paths [ 15 Marks ]

(a) Find the minimum number of multiplications required for the following matrix chain multiplication using Dynamic programming: A(10x20) * B(20x50) * C(50x1) * D(1x100). [ 7 Marks ]

Given 4 matrices with dimensions: \(A(10 \times 20)\), \(B(20 \times 50)\), \(C(50 \times 1)\), and \(D(1 \times 100)\). The dimension array is \(P = [10, 20, 50, 1, 100]\). The DP recurrence is:

\[m[i, j] = \min_{i \le k < j} \{m[i, k] + m[k+1, j] + P[i-1] \cdot P[k] \cdot P[j]\}\]

Step-by-Step DP Calculation Table:

  1. Chain Length 1 (Diagonal elements):
    \(m[1, 1] = m[2, 2] = m[3, 3] = m[4, 4] = 0\)
  2. Chain Length 2:
    • \(m[1, 2] = 10 \times 20 \times 50 = 10,000\) (Split at \(k=1\))
    • \(m[2, 3] = 20 \times 50 \times 1 = 1,000\) (Split at \(k=2\))
    • \(m[3, 4] = 50 \times 1 \times 100 = 5,000\) (Split at \(k=3\))
  3. Chain Length 3:
    • \(m[1, 3]\) (Multiplying \(A B C\)):
      • If \(k = 1\): \(m[1, 1] + m[2, 3] + P[0] \cdot P[1] \cdot P[3] = 0 + 1000 + 10 \times 20 \times 1 = 1,200\)
      • If \(k = 2\): \(m[1, 2] + m[3, 3] + P[0] \cdot P[2] \cdot P[3] = 10000 + 0 + 10 \times 50 \times 1 = 10,500\)
      Minimum \(m[1,3] = \) 1,200 at split \(k=1\).
    • \(m[2, 4]\) (Multiplying \(B C D\)):
      • If \(k = 2\): \(m[2, 2] + m[3, 4] + P[1] \cdot P[2] \cdot P[4] = 0 + 5000 + 20 \times 50 \times 100 = 105,000\)
      • If \(k = 3\): \(m[2, 3] + m[4, 4] + P[1] \cdot P[3] \cdot P[4] = 1000 + 0 + 20 \times 1 \times 100 = 3,000\)
      Minimum \(m[2,4] = \) 3,000 at split \(k=3\).
  4. Chain Length 4:
    • \(m[1, 4]\) (Multiplying \(A B C D\)):
      • If \(k = 1\): \(m[1, 1] + m[2, 4] + P[0] \cdot P[1] \cdot P[4] = 0 + 3000 + 10 \times 20 \times 100 = 23,000\)
      • If \(k = 2\): \(m[1, 2] + m[3, 4] + P[0] \cdot P[2] \cdot P[4] = 10000 + 5000 + 10 \times 50 \times 100 = 65,000\)
      • If \(k = 3\): \(m[1, 3] + m[4, 4] + P[0] \cdot P[3] \cdot P[4] = 1200 + 0 + 10 \times 1 \times 100 = 2,200\)
      Minimum \(m[1,4] = \) 2,200 at split \(k=3\).
Minimum Scalar Multiplications: 2,200
Optimal Parenthesization: \(((A \times B) \times C) \times D\)

(b) Write the algorithm for single source shortest path (-ve edge weights allowed). [ 5 Marks ]

The Bellman-Ford Algorithm computes shortest paths from a single source vertex to all other vertices. It is robust enough to handle negative edge weights.

Bellman-Ford(Graph G, Source S):
    // 1. Initialization
    Initialize distance array dist[] of size |V| to Infinity
    dist[S] = 0
    
    // 2. Relax all edges |V| - 1 times
    For i = 1 to |V| - 1:
       For each edge (u, v) in G with weight w:
          If dist[u] != Infinity and dist[u] + w < dist[v]:
             dist[v] = dist[u] + w
             parent[v] = u
             
    // 3. Detect negative weight cycles
    For each edge (u, v) in G with weight w:
       If dist[u] != Infinity and dist[u] + w < dist[v]:
          Return Error "Negative weight cycle detected!"
          
    Return dist[], parent[]

(c) Discuss the advantages and drawbacks of Dynamic Programming. [ 3 Marks ]

Advantages:
Guaranteed Optimality: Unlike Greedy approaches, DP evaluates all possible subproblem states and combinations, guaranteeing a globally optimal solution.
Avoids Redundancy: By storing solutions to overlapping subproblems in a table (memoization/tabulation), it drastically cuts down exponential time complexities to polynomial time.

Drawbacks:
High Memory/Space Overhead: DP algorithms heavily rely on large multi-dimensional matrices or arrays to store intermediate states, demanding substantial RAM memory \(O(n^2)\) or worse.
Strict Constraints: It can only be applied to problems exhibiting Optimal Substructure and Overlapping Subproblems. Designing the transition relations can also be highly abstract and complex.

8. Algorithm Features & Complexity [ 15 Marks ]

(a) What are the features of any Algorithm? [ 3 Marks ]

According to Donald Knuth, a sequence of instructions must satisfy five essential characteristics to be considered a legitimate algorithm:

  1. Input: An algorithm must accept zero or more external values.
  2. Output: An algorithm must produce at least one output that aligns with the desired goal.
  3. Definiteness: Every instruction must be perfectly clear, unambiguous, and precise.
  4. Finiteness: The algorithm must strictly terminate and halt after a finite number of steps.
  5. Effectiveness: Every step must be basic enough that it can be carried out manually (with paper and pencil) in a finite amount of time.

(b) Discuss the different Asymptotic notations and their importance. [ 5 Marks ]

Asymptotic notations are mathematical tools used to describe the limiting behavior (time/space complexity) of a function when the input size approaches infinity.

  • Big-O Notation (\(O\)): Represents the Asymptotic Upper Bound. It describes the worst-case scenario. If \(f(n) = O(g(n))\), the algorithm will execute no slower than \(g(n)\). It is crucially important for ensuring software runs within strict time limits.
  • Big-Omega Notation (\(\Omega\)): Represents the Asymptotic Lower Bound. It describes the best-case scenario. If \(f(n) = \Omega(g(n))\), the algorithm takes at least \(g(n)\) time.
  • Big-Theta Notation (\(\Theta\)): Represents the Tight Bound. Used when the upper and lower bounds grow at the identical rate, indicating the exact order of growth.
  • Little-o (\(o\)): Represents a strictly loose upper bound. It means \(f(n)\) grows significantly slower than \(g(n)\) without ever catching up.
  • Little-omega (\(\omega\)): Represents a strictly loose lower bound. It means \(f(n)\) grows significantly faster than \(g(n)\).

(c) Write the recurrence relation for computing the time complexity of "Towers of Hanoi" problem. Also solve the recurrence relation. [ 7 Marks ]

Formulating the Recurrence Relation:
In the Towers of Hanoi puzzle, moving \(n\) disks from the Source peg to the Destination peg using an Auxiliary peg requires:
1. Moving \(n-1\) disks from Source to Auxiliary (Cost = \(T(n-1)\)).
2. Moving the largest \(n^{\text{th}}\) disk from Source to Destination (Cost = \(1\) operation).
3. Moving the \(n-1\) disks from Auxiliary to Destination (Cost = \(T(n-1)\)).

\(T(n) = 2T(n-1) + 1\)
\(T(1) = 1 \text{ (Base case)}\)

Solving the Recurrence via Back-Substitution:

  • \(T(n) = 2 \cdot T(n-1) + 1\)
  • Substitute \(T(n-1) = 2 \cdot T(n-2) + 1\):
    \(T(n) = 2[2 \cdot T(n-2) + 1] + 1 = 2^2 \cdot T(n-2) + 2 + 1\)
  • Substitute \(T(n-2) = 2 \cdot T(n-3) + 1\):
    \(T(n) = 2^2[2 \cdot T(n-3) + 1] + 2 + 1 = 2^3 \cdot T(n-3) + 2^2 + 2^1 + 2^0\)
  • Continuing this expansion for \(k\) steps yields:
    \(T(n) = 2^k \cdot T(n-k) + \sum_{i=0}^{k-1} 2^i\)
  • The base case is reached when \(n-k = 1 \implies k = n-1\). Substituting \(k\):
    \(T(n) = 2^{n-1} \cdot T(1) + \sum_{i=0}^{n-2} 2^i\)
  • Since \(T(1) = 1\) and the sum of a geometric series is \(\sum_{i=0}^{m} 2^i = 2^{m+1} - 1\):
    \(T(n) = 2^{n-1} + (2^{n-1} - 1)\)
    \(T(n) = 2^n - 1\)
Time Complexity: \(\Theta(2^n)\) (Exponential Time)

9. Greedy Algorithms and Spanning Trees [ 15 Marks ]

(a) Write the greedy algorithm for job sequencing with deadline. [ 4 Marks ]

The goal is to schedule jobs within their deadlines to maximize total profit on a single processor. Each job takes 1 unit of time.

JobSequencing(Jobs array J, Integer n):
    // 1. Sort jobs by profit in descending order
    Sort J in descending order based on profit p_i
    
    // 2. Find maximum deadline to create timeslots
    Max_D = 0
    For i = 1 to n:
       Max_D = max(Max_D, J[i].deadline)
       
    // 3. Initialize slots array
    Array slots[1...Max_D] initialized to NULL
    
    // 4. Greedily schedule jobs
    For i = 1 to n:
       // Try placing job in the latest possible slot before deadline
       For j = min(Max_D, J[i].deadline) down to 1:
          If slots[j] is NULL:
             slots[j] = J[i]    // Assign job
             Break              // Move to next job
             
    Return slots array

(b) Find an optimal solution to the problem of job sequencing with deadline where n=4, profits=(100,10,5,27) and deadlines=(2,1,2,1). [ 5 Marks ]

Jobs Data: \(J_1(p=100, d=2)\), \(J_2(p=10, d=1)\), \(J_3(p=5, d=2)\), \(J_4(p=27, d=1)\).

  1. Sort by Profits (Descending):
    \(J_1 (100)\) \(\to\) \(J_4 (27)\) \(\to\) \(J_2 (10)\) \(\to\) \(J_3 (5)\).
  2. Initialize Time Slots: Max deadline is 2. Create Slot 1 (0-1) and Slot 2 (1-2).
  3. Schedule \(J_1\) (Deadline 2): Latest available slot before 2 is Slot 2.
    State: Slot 1 [Free], Slot 2 [\(J_1\)]
  4. Schedule \(J_4\) (Deadline 1): Latest available slot before 1 is Slot 1.
    State: Slot 1 [\(J_4\)], Slot 2 [\(J_1\)]
  5. Schedule \(J_2\) (Deadline 1): Needs Slot 1, but Slot 1 is full. Slot 2 is also full. Job is Rejected.
  6. Schedule \(J_3\) (Deadline 2): Needs Slot 2 or 1, both are full. Job is Rejected.
Optimal Job Schedule: Slot 1 \(\to J_4\), Slot 2 \(\to J_1\)
Total Optimal Profit: 27 + 100 = 127

(c) Define spanning tree. [ 2 Marks ]

A Spanning Tree of a connected, undirected graph \(G=(V, E)\) is a subgraph that is a tree (connected and acyclic) and includes absolutely all the vertices \(V\) of the original graph, utilizing exactly \(|V| - 1\) edges.


(d) Using Prim's algorithm generate the MST from the given graph. [ 4 Marks ]

Note: Based on the provided 7-node graph. We trace Prim's algorithm maintaining a set of visited vertices and growing the tree using minimum outgoing edges.

  • Step 1: Start at vertex 1. Visited = {1}.
    Outgoing edges: (1,2) wt 2, (1,3) wt 4, (1,4) wt 1.
    Select Minimum: Edge (1, 4) with weight 1. Add vertex 4. Visited = {1, 4}.
  • Step 2: Outgoing from {1, 4}: (1,2) wt 2, (1,3) wt 4, (4,2) wt 3, (4,3) wt 2, (4,5) wt 7, (4,6) wt 8, (4,7) wt 4.
    Select Minimum: Edge (1, 2) with weight 2. Add vertex 2. Visited = {1, 4, 2}.
  • Step 3: Outgoing from {1, 4, 2}: (4,3)[2], (4,7)[4], (2,5)[10], etc.
    Select Minimum: Edge (4, 3) with weight 2. Add vertex 3. Visited = {1, 4, 2, 3}.
  • Step 4: Outgoing from {1, 4, 2, 3}: (4,7)[4], (3,6)[5], (4,5)[7], (2,5)[10].
    Select Minimum: Edge (4, 7) with weight 4. Add vertex 7. Visited = {1, 4, 2, 3, 7}.
  • Step 5: Outgoing from {1, 4, 2, 3, 7}: (7,6)[1], (3,6)[5], (7,5)[6].
    Select Minimum: Edge (7, 6) with weight 1. Add vertex 6. Visited = {1, 4, 2, 3, 7, 6}.
  • Step 6: Outgoing from {1, 4, 2, 3, 7, 6}: (7,5)[6], (4,5)[7], (2,5)[10].
    Select Minimum: Edge (7, 5) with weight 6. Add vertex 5. Visited = {1, 4, 2, 3, 7, 6, 5}.
MST Edges: (1,4)[wt 1], (1,2)[wt 2], (4,3)[wt 2], (4,7)[wt 4], (7,6)[wt 1], (7,5)[wt 6]
Total Minimum Cost: 1 + 2 + 2 + 4 + 1 + 6 = 16

10. Complexity Classes and Reducibility [ 15 Marks ]

(a) Write and explain the classes P, NP, NP-hard and NP-complete. [ 7 Marks ]
  • P (Polynomial Time): The set of all decision problems that can be fundamentally solved in polynomial time (\(O(n^k)\)) by a deterministic Turing machine. These are considered "easy" and tractable problems (e.g., Sorting, BFS).
  • NP (Non-deterministic Polynomial Time): The set of decision problems where, if the answer is "Yes", a proposed solution (certificate) can be verified as correct in polynomial time by a deterministic Turing machine. P is a subset of NP.
  • NP-Hard: The class of problems that are intuitively "at least as hard as the hardest problems in NP". A problem \(H\) is NP-Hard if every problem \(L \in NP\) can be reduced to \(H\) in polynomial time (\(L \le_p H\)). They do not have to be in NP and can be optimization problems.
  • NP-Complete (NPC): The intersection of NP and NP-Hard. These are the absolute hardest problems inside NP. To prove a problem is NPC, it must be proven to belong to NP, and an existing NPC problem must be reduced to it in polynomial time (e.g., SAT, 3-Coloring).

(b) What is reducibility? [ 3 Marks ]

Reducibility is a theoretical mechanism used to convert an instance of problem \(A\) into an instance of problem \(B\) using a computable function. Specifically, polynomial-time reducibility (\(A \le_p B\)) means that there exists an algorithm operating in polynomial time that can map any input of \(A\) to a valid input of \(B\). If \(A \le_p B\), it implies that solving \(B\) is at least as hard as solving \(A\), because an algorithm for \(B\) can automatically be used as a subroutine to solve \(A\).


(c) "If any NP-complete problem can be solved in polynomial time then P=NP" explain. [ 5 Marks ]

This statement is the cornerstone of complexity theory. By definition, a problem \(L\) is NP-Complete if every single problem \(X \in NP\) can be reduced to \(L\) in polynomial time (\(X \le_p L\)).

Suppose someone discovers a polynomial-time algorithm to solve \(L\) (meaning \(L \in P\)). Then, to solve any problem \(X \in NP\), we can:

  1. Use the polynomial-time reduction to convert the input of \(X\) into an input for \(L\).
  2. Feed this converted input into the polynomial-time algorithm that solves \(L\).

Since both steps take polynomial time, their composition is also polynomial time. Thus, the problem \(X\) is solved in polynomial time. Because this applies to every \(X \in NP\), it would conclusively prove that all NP problems are in P, resulting in the monumental proof that P = NP.

11. Maximum Flow & Networks [ 15 Marks ]

(a) What is a Flow Network? What are the constraints of flow in a network? [ 5 Marks ]

A Flow Network is a directed graph \(G=(V, E)\) where each directed edge \((u, v)\) has a non-negative capacity \(c(u, v) \ge 0\). The network contains two special distinguished vertices: a source node \(S\) (produces flow) and a sink node \(T\) (consumes flow).

Mathematical Constraints: A valid flow \(f\) must adhere to three fundamental rules:

  1. Capacity Constraint: The net flow along any given edge cannot exceed its defined capacity: \[0 \le f(u, v) \le c(u, v)\]
  2. Flow Conservation: For every intermediate vertex (excluding \(S\) and \(T\)), the total amount of flow entering the vertex must exactly equal the total amount of flow leaving it: \[\sum_{u \in V} f(u, v) = \sum_{w \in V} f(v, w) \quad \text{for all } v \in V - \{S, T\}\]
  3. Skew Symmetry: The flow passing in one direction is mathematically considered the negative of the flow passing in the opposite direction: \[f(u, v) = -f(v, u)\]

(b) What is an augmenting path in a flow network? Explain residual capacity. [ 5 Marks ]

An Augmenting Path is a simple path from the source \(S\) to the sink \(T\) within the residual graph \(G_f\), such that every edge along this path has a strictly positive residual capacity (\(c_f(u,v) > 0\)). Finding one indicates that the total flow in the network can still be increased.

The Residual Capacity \(c_f(u, v)\) represents the amount of additional flow that an edge can still accommodate. It is dynamically calculated based on current flows:

For forward edges: \(c_f(u, v) = c(u, v) - f(u, v)\) (Remaining empty capacity)
For reverse edges: \(c_f(v, u) = f(u, v)\) (Capacity to cancel/push back existing flow)

(c) Write the Ford-Fulkerson Algorithm for finding maximum flow. [ 5 Marks ]
Ford-Fulkerson(Graph G, Source S, Sink T):
    // 1. Initialize empty flows
    For each edge (u, v) in G.E:
       f(u, v) = 0
       f(v, u) = 0
       
    // 2. Iteratively augment flow as long as a path exists
    While there exists an augmenting path P from S to T in residual graph G_f:
       // Find bottleneck edge capacity on the path
       c_f(P) = min { c_f(u, v) : (u, v) is in path P }
       
       // Update flows along the path
       For each edge (u, v) in path P:
          If (u, v) is a forward edge in original graph:
             f(u, v) = f(u, v) + c_f(P)  // Increase flow
             f(v, u) = -f(u, v)          // Maintain skew symmetry
          Else (it is a reverse edge):
             f(v, u) = f(v, u) - c_f(P)  // Cancel flow
             f(u, v) = -f(v, u)
             
    // 3. Return total flow emerging from Source S
    Return SUM(f(S, v)) for all v