CSE408Semester 5 • 4 CreditsCore Subjects

Design And Analysis Of Algorithms

The course develops skills in designing and analyzing efficient algorithms using complexity analysis, divide-and-conquer, dynamic programming, greedy methods, graph algorithms, transform-and-conquer, backtracking, branch and bound, and approximation techniques. It also covers string matching, computational geometry, advanced sequence-processing techniques, and computational complexity classes.

6 Units • Notes • Model Answers • MCQs
Select Course Unit (6 Units)
Active: Unit 1
Unit 1:Unit 1: Analysis of Algorithms and Divide-and-Conquer
7 min read•High-Yield Unit Guide

Unit 1: Analysis of Algorithms and Divide-and-Conquer

Unit 1 Exam Focus & Core Takeaways

Review the core definitions, standard architectures, and syntax patterns below. Pay special attention to sections highlighted with bullet points for your mid-term and end-term subjective questions.

Unit 1: Analysis of Algorithms and Divide-and-Conquer

1. Foundations of Algorithm Analysis and Theoretical Frameworks

Algorithm analysis is the branch of computer science dedicated to estimating the resource utilization—primarily execution time and memory space—of an algorithm as a mathematical function of its input size nn. By characterizing resource growth independently of ephemeral physical hardware, compiler optimizations, or specific programming language runtimes, algorithm analysis establishes rigorous theoretical guarantees for scalability and algorithmic efficiency.

1.1 The Random Access Machine (RAM) Model of Computation

Theoretical algorithm analysis relies on an idealized computational abstraction termed the Random Access Machine (RAM) model:

  • Sequential Execution: Instructions execute sequentially one after another with no concurrent or parallel hardware multiprocessing unless explicitly modeled.
  • Uniform Memory Access: Accessing any memory cell takes constant time O(1)\mathcal{O}(1), regardless of physical hardware phenomena such as CPU L1/L2/L3 cache hierarchies, virtual memory paging, or TLB misses.
  • Unit-Cost Operations: Elementary operations—arithmetic calculations (addition, subtraction, multiplication, integer division), comparisons (<,≤,==,≥,><, \le, ==, \ge, >), logical operations (AND, OR, NOT), bitwise shifts, variable assignments, and pointer dereferences—are executed in constant time O(1)\mathcal{O}(1).
  • Bounded Word Size: Each memory word consists of clg⁡nc \lg n bits (typically 32 or 64 bits), ensuring that basic integers representing array indices and input sizes can be stored in a single machine word and manipulated in O(1)\mathcal{O}(1) time without requiring multi-precision arbitrary-length arithmetic.

1.2 Input Size and Problem Scaling

The input size nn characterizes the magnitude of the problem instance:

  • For linear sequences, arrays, and lists, nn denotes the total count of elements.
  • For graphs G=(V,E)G = (V, E), input complexity is bi-variate, parameterized by both the number of vertices ∣V∣|V| and the number of edges ∣E∣|E|.
  • For cryptographic and number-theoretic algorithms (e.g., primality testing, RSA factorization), input size is defined not by the numeric value NN itself, but by the number of bits β=⌈log⁡2(N+1)⌉\beta = \lceil \log_2(N + 1) \rceil required to represent NN in binary. An algorithm running in O(N)\mathcal{O}(N) arithmetic operations is therefore pseudo-polynomial, as its complexity is exponential O(2β)\mathcal{O}(2^\beta) relative to the true binary input length β\beta.

1.3 Asymptotic Notations and Formal Definitions

Asymptotic analysis examines the growth rate of an algorithm's resource function T(n)T(n) as n→∞n \to \infty, disregarding machine-dependent constant multipliers and asymptotically insignificant lower-order terms.

CODE
      Running Time T(n)
         ▲
         │                 c2 * g(n)  [Upper Bound]
         │              ..·'
         │           .·'    f(n)      [Exact Bound]
         │        .·'   ..·'
         │     .·'   .·'
         │  .·'   .·'      c1 * g(n)  [Lower Bound]
         │ '   .·'
         │  .·'
         │.·'
         └─────────────┼─────────────────────────► Input Size n
                      n0
  1. Big-O Notation (O\mathcal{O} - Asymptotic Upper Bound):

    • Definition: f(n)=O(g(n))f(n) = \mathcal{O}(g(n)) if there exist positive constants c>0c > 0 and n0≥1n_0 \ge 1 such that:
      0≤f(n)≤c⋅g(n)for all n≥n00 \le f(n) \le c \cdot g(n) \quad \text{for all } n \ge n_0
    • Significance: Represents the maximum upper envelope of growth. Guarantees that the algorithm will not take longer than c⋅g(n)c \cdot g(n) for sufficiently large inputs.
  2. Big-Omega Notation (Ω\Omega - Asymptotic Lower Bound):

    • Definition: f(n)=Ω(g(n))f(n) = \Omega(g(n)) if there exist positive constants c>0c > 0 and n0≥1n_0 \ge 1 such that:
      0≤c⋅g(n)≤f(n)for all n≥n00 \le c \cdot g(n) \le f(n) \quad \text{for all } n \ge n_0
    • Significance: Represents the minimum baseline resource requirement. Asserts that the algorithm requires at least c⋅g(n)c \cdot g(n) operations on large inputs.
  3. Big-Theta Notation (Θ\Theta - Asymptotically Tight Bound):

    • Definition: f(n)=Θ(g(n))f(n) = \Theta(g(n)) if and only if f(n)=O(g(n))f(n) = \mathcal{O}(g(n)) and f(n)=Ω(g(n))f(n) = \Omega(g(n)). Formally, there exist positive constants c1,c2>0c_1, c_2 > 0 and n0≥1n_0 \ge 1 such that:
      0≤c1⋅g(n)≤f(n)≤c2⋅g(n)for all n≥n00 \le c_1 \cdot g(n) \le f(n) \le c_2 \cdot g(n) \quad \text{for all } n \ge n_0
    • Significance: Sandwiches the running time within a constant-factor envelope. g(n)g(n) provides an asymptotically exact characterization.
  4. Little-o and Little-omega Notations (oo and ω\omega):

    • f(n)=o(g(n))f(n) = o(g(n)) represents an upper bound that is not asymptotically tight:
      lim⁡n→∞f(n)g(n)=0\lim_{n \to \infty} \frac{f(n)}{g(n)} = 0
    • f(n)=ω(g(n))f(n) = \omega(g(n)) represents a lower bound that is not asymptotically tight:
      lim⁡n→∞f(n)g(n)=∞\lim_{n \to \infty} \frac{f(n)}{g(n)} = \infty

1.4 Cases of Analysis: Worst-Case, Best-Case, and Average-Case

  • Worst-Case Complexity (Tworst(n)T_{\text{worst}}(n)): The maximum execution cost over all possible input instances of size nn:
    Tworst(n)=max⁡I∈DnCost(I)T_{\text{worst}}(n) = \max_{I \in \mathcal{D}_n} \text{Cost}(I)
    Serves as the primary metric in enterprise and critical systems because it provides an unconditional upper guarantee.
  • Best-Case Complexity (Tbest(n)T_{\text{best}}(n)): The minimum execution cost across all inputs of size nn:
    Tbest(n)=min⁡I∈DnCost(I)T_{\text{best}}(n) = \min_{I \in \mathcal{D}_n} \text{Cost}(I)
    Identifies optimal inputs (e.g., an already sorted array for Insertion Sort).
  • Average-Case Complexity (Tavg(n)T_{\text{avg}}(n)): The probabilistic expected value of cost across all possible input permutations under an assumed probability distribution P(I)P(I):
    Tavg(n)=∑I∈DnP(I)⋅Cost(I)T_{\text{avg}}(n) = \sum_{I \in \mathcal{D}_n} P(I) \cdot \text{Cost}(I)
    Often assumes a uniform distribution where each permutation is equally likely (P(I)=1/n!P(I) = 1 / n!).

2. Iterative and Incremental Sorting Analysis: Insertion Sort

Insertion Sort is an archetypal incremental comparison-based sorting algorithm. It constructs the final sorted array one element at a time by repeatedly taking the next unsorted element and inserting it into its correct position within the already sorted prefix.

2.1 Algorithmic Mechanics and Pseudocode

CODE
INSERTION-SORT(A, n)
1  for j = 2 to n
2      key = A[j]
3      // Insert A[j] into the sorted sequence A[1 .. j-1]
4      i = j - 1
5      while i > 0 and A[i] > key
6          A[i + 1] = A[i]
7          i = i - 1
8      A[i + 1] = key

2.2 Loop Invariant and Correctness Proof

The correctness of Insertion Sort is established using a formal Loop Invariant:

  • Invariant Statement: At the start of each iteration of the outer for loop (line 1), the subarray A[1…j−1]A[1 \dots j-1] consists of the elements originally in A[1…j−1]A[1 \dots j-1], but arranged in sorted order.
  • Initialization: Prior to the first iteration (j=2j = 2), the subarray is A[1…1]A[1 \dots 1], consisting of a single element. A single-element subarray is trivially sorted. The invariant holds.
  • Maintenance: During iteration jj, lines 4–7 shift elements A[j−1],A[j−2],…A[j-1], A[j-2], \dots that are strictly greater than key one position to the right until the correct insertion slot is located. Line 8 deposits key into that position. The resulting subarray A[1…j]A[1 \dots j] now contains the original elements in sorted order. Incrementing jj for the next loop iteration preserves the invariant.
  • Termination: The loop terminates when j=n+1j = n + 1. Substituting j=n+1j = n + 1 into the invariant yields: the subarray A[1…n]A[1 \dots n] consists of the elements originally in A[1…n]A[1 \dots n], arranged in sorted order. Hence, the entire array is sorted.

2.3 Mathematical Complexity Analysis

Let tjt_j represent the number of times the while loop condition (line 5) is evaluated for a given index jj:
T(n)=c1n+c2(n−1)+c4(n−1)+c5∑j=2ntj+c6∑j=2n(tj−1)+c7∑j=2n(tj−1)+c8(n−1)T(n) = c_1 n + c_2 (n - 1) + c_4 (n - 1) + c_5 \sum_{j=2}^n t_j + c_6 \sum_{j=2}^n (t_j - 1) + c_7 \sum_{j=2}^n (t_j - 1) + c_8 (n - 1)

  • Best-Case Analysis (Already Sorted Array):
    • If the array is already sorted in non-decreasing order, A[i]≤keyA[i] \le \text{key} on the very first comparison of line 5.
    • Thus, tj=1t_j = 1 for all j∈{2,…,n}j \in \{2, \dots, n\}.
    • The sums ∑j=2ntj=n−1\sum_{j=2}^n t_j = n - 1 and ∑j=2n(tj−1)=0\sum_{j=2}^n (t_j - 1) = 0.
    • Result: T(n)=an+b=Θ(n)T(n) = a n + b = \Theta(n) (linear time).
  • Worst-Case Analysis (Reverse Sorted Array):
    • If the array is sorted in strictly descending order, every element in A[1…j−1]A[1 \dots j-1] is greater than key.
    • The while loop must shift all j−1j-1 elements: tj=jt_j = j.
    • The sum evaluates to:
      ∑j=2nj=n(n+1)2−1=Θ(n2)\sum_{j=2}^n j = \frac{n(n + 1)}{2} - 1 = \Theta(n^2)
    • Result: T(n)=cn2+dn+e=Θ(n2)T(n) = c n^2 + d n + e = \Theta(n^2) (quadratic time).
  • Average-Case Analysis:
    • Assuming a uniform distribution of distinct keys, on average half of the elements in A[1…j−1]A[1 \dots j-1] are greater than key: tj≈j/2t_j \approx j/2.
    • The sum ∑j=2n(j/2)≈12⋅n22=Θ(n2)\sum_{j=2}^n (j/2) \approx \frac{1}{2} \cdot \frac{n^2}{2} = \Theta(n^2).
  • Space Complexity:
    • Insertion Sort sorts strictly in-place. It requires only a constant number of auxiliary scalar variables (key, i, j), yielding O(1)\mathcal{O}(1) auxiliary space complexity.

3. The Divide-and-Conquer Paradigm and Merge Sort

The Divide-and-Conquer algorithmic paradigm structures problem resolution into three recursive phases:

  1. Divide: Partition the problem instance into two or more disjoint, smaller subproblems of the same type.
  2. Conquer: Solve the subproblems recursively. If the subproblem size is sufficiently small (the base case), solve it directly using a primitive base routine.
  3. Combine: Merge the solutions of the subproblems into a unified solution for the original problem instance.

3.1 Merge Sort Mechanics and Recurrence Formulation

Merge Sort divides an array of size nn into two equal halves of size n/2n/2, recursively sorts each half, and combines the two sorted subarrays using a linear-time merging routine.

CODE
MERGE-SORT(A, p, r)
1  if p < r
2      q = floor((p + r) / 2)       // Divide: O(1)
3      MERGE-SORT(A, p, q)          // Conquer: T(n/2)
4      MERGE-SORT(A, q + 1, r)      // Conquer: T(n/2)
5      MERGE(A, p, q, r)            // Combine: Theta(n)

The merging procedure MERGE(A, p, q, r) allocates two auxiliary buffers LL and RR to copy the left and right sorted segments, uses two pointers to compare their front elements, and writes the smaller element back into array AA. Because each comparison places at least one element into its final sorted destination, MERGE executes in exactly Θ(n)\Theta(n) time, where n=r−p+1n = r - p + 1.

The overall running time is characterized by the recurrence relation:
T(n)={Θ(1)if n=12T(n/2)+Θ(n)if n>1T(n) = \begin{cases} \Theta(1) & \text{if } n = 1 \\ 2 T(n/2) + \Theta(n) & \text{if } n > 1 \end{cases}

3.2 Complexity and Stability Properties of Merge Sort

  • Time Complexity: As solved via the Master Theorem or Recursion Tree, T(n)=Θ(nlg⁡n)T(n) = \Theta(n \lg n) across all cases (best-case, average-case, and worst-case). Unlike Quick Sort, Merge Sort guarantees O(nlg⁡n)\mathcal{O}(n \lg n) worst-case time unconditionally.
  • Space Complexity: Standard array-based Merge Sort requires allocating auxiliary arrays of size n/2n/2 during merges. The maximum auxiliary space allocated at any point in the call tree is Θ(n)\Theta(n). Thus, standard Merge Sort is not an in-place sort (O(n)\mathcal{O}(n) auxiliary space).
  • Stability: A sorting algorithm is stable if elements with identical comparison keys retain their relative original input order. Merge Sort is inherently stable provided the merge step resolves equal keys by selecting from the left buffer first (L[i] <= R[j]).

4. Analytical Methods for Solving Recurrences

Divide-and-conquer recurrences take the general form T(n)=aT(n/b)+f(n)T(n) = a T(n/b) + f(n). Three primary mathematical techniques exist to solve such recurrences:

4.1 The Substitution Method

The Substitution Method employs a two-step process: (1) guess the form of the mathematical bound, and (2) apply mathematical induction to determine the constants and prove correctness.

Example: Solve T(n)=2T(⌊n/2⌋)+nT(n) = 2 T(\lfloor n/2 \rfloor) + n.

  • Inductive Hypothesis: Guess T(n)≤cnlg⁡nT(n) \le c n \lg n for some constant c>0c > 0.
  • Inductive Step: Assume the bound holds for all integers m<nm < n, specifically for m=⌊n/2⌋m = \lfloor n/2 \rfloor:
    T(⌊n/2⌋)≤c⌊n/2⌋lg⁡(⌊n/2⌋)T(\lfloor n/2 \rfloor) \le c \lfloor n/2 \rfloor \lg(\lfloor n/2 \rfloor)
  • Substituting into the recurrence:
    T(n)≤2(c⌊n/2⌋lg⁡(⌊n/2⌋))+n≤2(cn2lg⁡n2)+n=cn(lg⁡n−lg⁡2)+n=cnlg⁡n−cn+nT(n) \le 2 \left(c \lfloor n/2 \rfloor \lg(\lfloor n/2 \rfloor)\right) + n \le 2 \left(c \frac{n}{2} \lg\frac{n}{2}\right) + n = c n (\lg n - \lg 2) + n = c n \lg n - c n + n
  • To satisfy T(n)≤cnlg⁡nT(n) \le c n \lg n, we must ensure:
    cnlg⁡n−cn+n≤cnlg⁡n  ⟺  −cn+n≤0  ⟺  c≥1c n \lg n - c n + n \le c n \lg n \iff -c n + n \le 0 \iff c \ge 1
  • Choosing c≥1c \ge 1 completes the inductive step. Base cases n=2,3n = 2, 3 confirm the hypothesis, proving T(n)=O(nlg⁡n)T(n) = \mathcal{O}(n \lg n).

4.2 The Recursion Tree Method

The Recursion Tree method models the recursive unfolding of the algorithm as a rooted tree, calculating the cost incurred at each level and summing across all levels:

CODE
Level 0:                         f(n)                          Cost = f(n)
                               /      \
Level 1:                 f(n/b)        f(n/b)                  Cost = a * f(n/b)
                        /      \      /      \
Level 2:             f(n/b^2) ........... f(n/b^2)             Cost = a^2 * f(n/b^2)
                       ...                  ...
Level log_b(n):        T(1) T(1) T(1) ... T(1) T(1)           Cost = a^(log_b n) * T(1)
                                                                    = n^(log_b a) * Theta(1)
  1. Tree Height: At depth ii, the subproblem size is n/bin / b^i. The subproblem reaches the base case n/bi=1n / b^i = 1 at depth i=log⁡bni = \log_b n. The tree depth is log⁡bn\log_b n.
  2. Leaf Node Count: The branching factor is aa. At depth h=log⁡bnh = \log_b n, the number of leaves is:
    Leaves=alog⁡bn=nlog⁡ba\text{Leaves} = a^{\log_b n} = n^{\log_b a}
  3. Total Work: Total cost is the sum of the non-recursive work f(n/bi)f(n/b^i) at all internal levels plus the cost of the leaves:
    T(n)=∑i=0log⁡bn−1aif(n/bi)+Θ(nlog⁡ba)T(n) = \sum_{i=0}^{\log_b n - 1} a^i f(n / b^i) + \Theta(n^{\log_b a})

4.3 The Master Method

The Master Theorem provides a cookbook solution for recurrences of the standard form:
T(n)=aT(n/b)+f(n)T(n) = a T(n/b) + f(n)
where a≥1a \ge 1 is the number of subproblems, b>1b > 1 is the subproblem reduction factor, and f(n)f(n) is an asymptotically positive function representing division and recombination cost.

The behavior is determined by comparing f(n)f(n) against the watershed function nlog⁡ban^{\log_b a}:

CaseConditionIntuitionSolution
Case 1f(n)=O(nlog⁡ba−ϵ)f(n) = \mathcal{O}(n^{\log_b a - \epsilon}) for some constant ϵ>0\epsilon > 0Leaf costs dominate. Internal node work is asymptotically smaller.T(n)=Θ(nlog⁡ba)T(n) = \Theta(n^{\log_b a})
Case 2f(n)=Θ(nlog⁡balg⁡kn)f(n) = \Theta(n^{\log_b a} \lg^k n) for some constant k≥0k \ge 0Costs are distributed evenly across all log⁡bn\log_b n levels.T(n)=Θ(nlog⁡balg⁡k+1n)T(n) = \Theta(n^{\log_b a} \lg^{k+1} n)
Case 3f(n)=Ω(nlog⁡ba+ϵ)f(n) = \Omega(n^{\log_b a + \epsilon}) for some ϵ>0\epsilon > 0, AND satisfies the Regularity Condition: af(n/b)≤cf(n)a f(n/b) \le c f(n) for some constant c<1c < 1 and large nnRoot cost dominates. Work decreases geometrically down the tree.T(n)=Θ(f(n))T(n) = \Theta(f(n))

Non-Applicability and Limitations of the Master Theorem

The Master Theorem cannot be applied if:

  • aa is not a constant (e.g., T(n)=nT(n/2)+nT(n) = n T(n/2) + n).
  • bb is non-constant or represents subtraction rather than division (e.g., T(n)=T(n−1)+nT(n) = T(n-1) + n).
  • The gap between f(n)f(n) and nlog⁡ban^{\log_b a} is not polynomial. For example, in T(n)=2T(n/2)+n/lg⁡nT(n) = 2T(n/2) + n / \lg n, nlog⁡22=nn^{\log_2 2} = n. The ratio f(n)/nlog⁡ba=1/lg⁡nf(n) / n^{\log_b a} = 1 / \lg n is smaller than 1, but not by a polynomial factor nϵn^\epsilon. Hence, Case 1 does not apply, and the recurrence must be solved using recursion trees or substitution (T(n)=Θ(nlg⁡lg⁡n)T(n) = \Theta(n \lg \lg n)).

5. Sub-Cubic Matrix Multiplication: Strassen's Algorithm

Multiplying two n×nn \times n dense matrices AA and BB to produce C=A×BC = A \times B is a fundamental computational primitive:
Cij=∑k=1nAikBkjC_{ij} = \sum_{k=1}^n A_{ik} B_{kj}
Standard triple-nested loop multiplication performs nn multiplications and n−1n-1 additions for each of the n2n^2 entries, yielding Θ(n3)\Theta(n^3) operations.

5.1 Naive Divide-and-Conquer Matrix Multiplication

Partitioning AA, BB, and CC into four (n/2)×(n/2)(n/2) \times (n/2) submatrices:
(C11C12C21C22)=(A11A12A21A22)(B11B12B21B22)\begin{pmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{pmatrix} = \begin{pmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{pmatrix} \begin{pmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{pmatrix}
yields four submatrix equations:

  • C11=A11B11+A12B21C_{11} = A_{11} B_{11} + A_{12} B_{21}
  • C12=A11B12+A12B22C_{12} = A_{11} B_{12} + A_{12} B_{22}
  • C21=A21B11+A22B21C_{21} = A_{21} B_{11} + A_{22} B_{21}
  • C22=A21B12+A22B22C_{22} = A_{21} B_{12} + A_{22} B_{22}

This naive divide-and-conquer formulation requires:

  • a=8a = 8 recursive matrix multiplications of size n/2n/2.
  • 4 matrix additions of size n/2n/2, costing Θ(n2)\Theta(n^2) time.
    Recurrence: T(n)=8T(n/2)+Θ(n2)T(n) = 8 T(n/2) + \Theta(n^2).
    By Master Theorem Case 1 (nlog⁡28=n3n^{\log_2 8} = n^3): T(n)=Θ(n3)T(n) = \Theta(n^3). This achieves zero asymptotic improvement over the naive iterative approach.

5.2 Strassen's Algebraic Insight

In 1969, Volker Strassen published a breakthrough method that reduced the number of recursive multiplications from 8 to 7, at the cost of 18 matrix additions/subtractions:

  1. Step 1: Divide matrices AA and BB into submatrices of size (n/2)×(n/2)(n/2) \times (n/2) in Θ(1)\Theta(1) index time.
  2. Step 2: Create 10 additive submatrices S1,S2,…,S10S_1, S_2, \dots, S_{10} using matrix additions and subtractions:
    • S1=B12−B22S_1 = B_{12} - B_{22}
    • S2=A11+A12S_2 = A_{11} + A_{12}
    • S3=A21+A22S_3 = A_{21} + A_{22}
    • S4=B21−B11S_4 = B_{21} - B_{11}
    • S5=A11+A22S_5 = A_{11} + A_{22}
    • S6=B11+B22S_6 = B_{11} + B_{22}
    • S7=A12−A22S_7 = A_{12} - A_{22}
    • S8=B21+B22S_8 = B_{21} + B_{22}
    • S9=A11−A21S_9 = A_{11} - A_{21}
    • S10=B11+B12S_{10} = B_{11} + B_{12}
  3. Step 3: Recursively compute 7 matrix products P1,P2,…,P7P_1, P_2, \dots, P_7:
    • P1=A11⋅S1=A11B12−A11B22P_1 = A_{11} \cdot S_1 = A_{11} B_{12} - A_{11} B_{22}
    • P2=S2⋅B22=A11B22+A12B22P_2 = S_2 \cdot B_{22} = A_{11} B_{22} + A_{12} B_{22}
    • P3=S3⋅B11=A21B11+A22B11P_3 = S_3 \cdot B_{11} = A_{21} B_{11} + A_{22} B_{11}
    • P4=A22⋅S4=A22B21−A22B11P_4 = A_{22} \cdot S_4 = A_{22} B_{21} - A_{22} B_{11}
    • P5=S5⋅S6=(A11+A22)(B11+B22)P_5 = S_5 \cdot S_6 = (A_{11} + A_{22})(B_{11} + B_{22})
    • P6=S7⋅S8=(A12−A22)(B21+B22)P_6 = S_7 \cdot S_8 = (A_{12} - A_{22})(B_{21} + B_{22})
    • P7=S9⋅S10=(A11−A21)(B11+B12)P_7 = S_9 \cdot S_{10} = (A_{11} - A_{21})(B_{11} + B_{12})
  4. Step 4: Combine the products to construct the quadrants of CC:
    • C11=P5+P4−P2+P6C_{11} = P_5 + P_4 - P_2 + P_6
    • C12=P1+P2C_{12} = P_1 + P_2
    • C21=P3+P4C_{21} = P_3 + P_4
    • C22=P5+P1−P3−P7C_{22} = P_5 + P_1 - P_3 - P_7

5.3 Complexity Analysis and Practical Engineering Trade-offs

  • Recurrence Relation:
    T(n)=7T(n/2)+Θ(n2)T(n) = 7 T(n/2) + \Theta(n^2)
  • Solving via Master Theorem:
    • a=7,b=2  ⟹  nlog⁡ba=nlog⁡27≈n2.807a = 7, b = 2 \implies n^{\log_b a} = n^{\log_2 7} \approx n^{2.807}.
    • f(n)=Θ(n2)f(n) = \Theta(n^2).
    • Since 2<2.8072 < 2.807, f(n)=O(nlog⁡27−ϵ)f(n) = \mathcal{O}(n^{\log_2 7 - \epsilon}) where ϵ≈0.807\epsilon \approx 0.807.
    • By Master Theorem Case 1:
      T(n)=Θ(nlog⁡27)≈Θ(n2.807)T(n) = \Theta(n^{\log_2 7}) \approx \Theta(n^{2.807})
  • Practical Engineering Limitations:
    • Crossover Threshold: The large constant factors introduced by the 18 matrix additions mean Strassen's algorithm is slower than naive multiplication for small matrices. Practical implementations switch to naive multiplication below a cutoff threshold (typically n∈[64,128]n \in [64, 128]).
    • Numerical Stability: Strassen's algorithm exhibits poorer numerical precision and accumulated floating-point rounding errors compared to naive multiplication.
    • Memory Overhead: Creating intermediate matrices consumes additional working memory.

6. Order Statistics and Selection Algorithms

The ii-th Order Statistic of a set of nn elements is the ii-th smallest element.

  • Minimum element: i=1i = 1
  • Maximum element: i=ni = n
  • Median element: i=⌊(n+1)/2⌋i = \lfloor (n + 1) / 2 \rfloor (lower median) or ⌈(n+1)/2⌉\lceil (n + 1) / 2 \rceil (upper median)

6.1 Simultaneous Minimum and Maximum

Finding the minimum of an array requires n−1n - 1 comparisons; finding the maximum independently requires another n−1n - 1 comparisons, totaling 2n−22n - 2 comparisons.
By processing elements in pairs, both minimum and maximum can be determined simultaneously in at most ⌊3n/2⌋\mathbf{\lfloor 3n/2 \rfloor} comparisons:

  1. Initialize min and max using the first 1 or 2 elements (1 comparison).
  2. For each remaining pair of elements (A[j],A[j+1])(A[j], A[j+1]):
    • Compare A[j]A[j] and A[j+1]A[j+1] (1 comparison).
    • Compare the smaller element against the global min (1 comparison).
    • Compare the larger element against the global max (1 comparison).
  3. Total comparisons: 33 comparisons for every 2 elements   ⟹  ≈32n\implies \approx \frac{3}{2} n comparisons, representing a 25% efficiency gain over independent scans.

6.2 Randomized Selection (Quickselect)

Quickselect, developed by Tony Hoare, adapts the partitioning principle of Quick Sort to locate the kk-th smallest element without fully sorting the array.

CODE
QUICKSELECT(A, p, r, k)
1  if p == r
2      return A[p]
3  q = RANDOMIZED-PARTITION(A, p, r)
4  i = q - p + 1                    // Number of elements in low partition
5  if k == i
6      return A[q]                  // Pivot is the k-th smallest!
7  else if k < i
8      return QUICKSELECT(A, p, q - 1, k)
9  else
10     return QUICKSELECT(A, q + 1, r, k - i)
  • Fundamental Distinction from Quick Sort: While Quick Sort recursively conquers both partitions (2T(n/2)2 T(n/2)), Quickselect recurses into only the single partition containing the kk-th element, discarding the other half.
  • Complexity:
    • Average-Case: Under balanced partitions, T(n)=T(n/2)+Θ(n)T(n) = T(n/2) + \Theta(n). By Master Theorem Case 3, T(n)=Θ(n)T(n) = \Theta(n) (linear time).
    • Worst-Case: If partitions are maximally unbalanced (00 and n−1n-1), the recurrence degenerates to T(n)=T(n−1)+Θ(n)=Θ(n2)T(n) = T(n-1) + \Theta(n) = \Theta(n^2).

6.3 Deterministic Linear-Time Selection: Median-of-Medians (BFPRT Algorithm)

In 1973, Blum, Floyd, Pratt, Rivest, and Tarjan (BFPRT) designed a deterministic selection algorithm that guarantees O(n)\mathcal{O}(n) running time in the worst case by ensuring a well-balanced pivot.

CODE
               Array of n elements partitioned into groups of 5
       ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
Row 1  │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │
Row 2  │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │
Row 3  │ m │ │ m │ │ m │ │ m │ │ M │ │ m │ │ m │ │ m │ │ m │ │ m │ ◄── Median-of-Medians (M)
Row 4  │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │
Row 5  │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │ │   │
       └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘

Step-by-Step Algorithmic Execution:

  1. Partition into Groups of 5: Divide the nn elements into ⌈n/5⌉\lceil n/5 \rceil groups of 5 elements each (with at most one final group containing the remaining n mod 5n \bmod 5 elements).
  2. Find Median of Each Group: Sort each 5-element group (taking O(1)\mathcal{O}(1) time per group) to determine its median. This produces a sequence of ⌈n/5⌉\lceil n/5 \rceil medians. Total cost for this step: O(n)\mathcal{O}(n).
  3. Find the Median-of-Medians (xx): Recursively call SELECT on the ⌈n/5⌉\lceil n/5 \rceil medians to find their median xx:
    Cost=T(⌈n/5⌉)\text{Cost} = T(\lceil n/5 \rceil)
  4. Partition Around Pivot xx: Partition the original array around pivot xx using standard two-way partitioning. Let kk be the rank of xx.
  5. Recurse: If i=ki = k, return xx. If i<ki < k, recurse into the low side; otherwise, recurse into the high side for rank i−ki - k.

Guaranteeing Balanced Partitions:

At least half of the ⌈n/5⌉\lceil n/5 \rceil medians are greater than or equal to xx. Each such median represents a group of 5 where at least 3 elements are ≥\ge the group median, and hence ≥x\ge x.
Excluding the group containing xx and the short group, the number of elements guaranteed to be strictly greater than xx is at least:
3(⌈12⌈n5⌉⌉−2)≥3n10−63 \left( \left\lceil \frac{1}{2} \left\lceil \frac{n}{5} \right\rceil \right\rceil - 2 \right) \ge \frac{3n}{10} - 6
By symmetry, at least 3n10−6\frac{3n}{10} - 6 elements are smaller than xx.
Consequently, in the worst case, the recursive call in Step 5 executes on at most:
n−(3n10−6)=7n10+6elementsn - \left(\frac{3n}{10} - 6\right) = \frac{7n}{10} + 6 \quad \text{elements}

Recurrence Relation and Proof of Linear Time:

T(n)≤T(⌈n/5⌉)+T(7n/10+6)+O(n)T(n) \le T(\lceil n/5 \rceil) + T(7n/10 + 6) + \mathcal{O}(n)

Using substitution, we prove T(n)≤cnT(n) \le c n for some constant cc:
T(n)≤c(n5+1)+c(7n10+6)+an=c9n10+7c+an=cn+(−cn10+7c+an)T(n) \le c \left(\frac{n}{5} + 1\right) + c \left(\frac{7n}{10} + 6\right) + a n = c \frac{9n}{10} + 7c + a n = c n + \left(-\frac{c n}{10} + 7c + a n\right)
To satisfy T(n)≤cnT(n) \le c n, we require:
−cn10+7c+an≤0  ⟺  c(n10−7)≥an-\frac{c n}{10} + 7c + a n \le 0 \iff c \left(\frac{n}{10} - 7\right) \ge a n
Assuming n≥140n \ge 140 (so that n/10−7≥n/20n/10 - 7 \ge n/20):
cn20≥an  ⟹  c≥20a\frac{c n}{20} \ge a n \implies c \ge 20 a
Thus, choosing c≥20ac \ge 20a establishes that T(n)=O(n)T(n) = \mathcal{O}(n) unconditionally in the worst case.

Note on Group Size: Why groups of 5? If groups of 3 were chosen, the recurrence would be T(n)≤T(n/3)+T(2n/3)+O(n)T(n) \le T(n/3) + T(2n/3) + \mathcal{O}(n). The subproblem fractions sum to 1/3+2/3=11/3 + 2/3 = 1, leading to T(n)=Θ(nlg⁡n)T(n) = \Theta(n \lg n) rather than linear time. Groups of 5 yield 1/5+7/10=9/10<11/5 + 7/10 = 9/10 < 1, ensuring geometric decay and guaranteed O(n)\mathcal{O}(n) linear performance.