Divide & Conquer: The Core Paradigm

Divide and Conquer is one of the most powerful algorithm design paradigms. It breaks a problem into smaller subproblems of the same type, solves each recursively, and combines the results. The Karatsuba algorithm (1960, Anatoly Karatsuba) is a landmark example — it multiplies two \(n\)-digit numbers in \(O(n^{\log_2 3}) \approx O(n^{1.585})\) time, beating the classical \(O(n^2)\) grade-school method.

The Three Steps of Divide & Conquer

Original Problem size n Subproblem 1 size n/b Subproblem 2 size n/b Subproblem 3 size n/b Combine Results → Solution ① DIVIDE ② CONQUER ③ COMBINE

Why Karatsuba? The Naive Problem

Grade-school multiplication of two \(n\)-digit numbers needs \(n^2\) single-digit multiplications. For RSA cryptography, \(n\) can be 2048+ bits — that's ~4 million operations per multiply. Karatsuba reduces it to ~3 million using a clever algebraic trick.

Key Insight (Gauss's trick applied to polynomials):

To compute \((a+bi)(c+di)\), naively you need 4 real multiplications: \(ac, ad, bc, bd\). But notice \(ad + bc = (a+b)(c+d) - ac - bd\). That's only 3 multiplications instead of 4!

Mathematical Foundation

The Split

Given two \(n\)-digit numbers \(x\) and \(y\), let \(m = \lfloor n/2 \rfloor\). Split each number:

\[x = a \cdot 10^m + b\] \[y = c \cdot 10^m + d\]

where \(a = \lfloor x / 10^m \rfloor\), \(b = x \bmod 10^m\), etc.

The Naive Expansion

\[x \cdot y = (a \cdot 10^m + b)(c \cdot 10^m + d)\] \[= ac \cdot 10^{2m} + (ad + bc) \cdot 10^m + bd\]

This requires 4 recursive multiplications: \(ac,\; ad,\; bc,\; bd\)

Recurrence: \(T(n) = 4T(n/2) + O(n) \Rightarrow T(n) = O(n^2)\) — no improvement!

Karatsuba's Trick: Only 3 Multiplications

Define three products:

\[z_0 = a \cdot c\] \[z_2 = b \cdot d\] \[z_1 = (a + b)(c + d)\]

Then observe:

\[ad + bc = z_1 - z_0 - z_2\]

Final formula:

\[x \cdot y = z_0 \cdot 10^{2m} + (z_1 - z_0 - z_2) \cdot 10^m + z_2\]

Recurrence & Master Theorem

Recurrence: \(T(n) = 3T(n/2) + O(n)\)

Applying Master Theorem with \(a=3,\; b=2,\; f(n)=O(n)\):

\[n^{\log_b a} = n^{\log_2 3} \approx n^{1.585}\]

Since \(f(n) = O(n) = O(n^{1.585 - \varepsilon})\) → Case 1 of Master Theorem:

\[T(n) = \Theta(n^{\log_2 3}) \approx \Theta(n^{1.585})\]

Complexity Comparison

AlgorithmComplexityn=100 digitsn=1000 digitsUse case
Grade-school\(O(n^2)\)10,000 ops1,000,000 opsSmall n only
Karatsuba\(O(n^{1.585})\)~1,995 ops~33,600 ops100–10,000 digits
Toom-Cook 3\(O(n^{1.465})\)~1,200 ops~15,000 opsMedium-large n
Schönhage–Strassen\(O(n \log n \log\log n)\)~460 ops~6,900 opsVery large n

Visual Walkthroughs

Step-by-Step: 1234 × 5678

Karatsuba: 1234 × 5678 STEP 1 — SPLIT: x=1234, y=5678, m=2 x = 1234 = 12×10² + 34 y = 5678 = 56×10² + 78 a=12 b=34 c=56 d=78 STEP 2 — COMPUTE 3 PRODUCTS z₀ = a × c = ac = 12 × 56 = 672 z₀ = 672 z₂ = b × d = bd = 34 × 78 = 2652 z₂ = 2652 z₁ = (a+b)(c+d) = (12+34)(56+78) = 46×134 = 6164 z₁ = 6164 STEP 3 — MIDDLE TERM: ad+bc = z₁ − z₀ − z₂ 6164 − 672 − 2652 = 2840 = ad + bc (This replaces two separate multiplications ad and bc!) STEP 4 — COMBINE: x×y = z₀·10⁴ + (z₁−z₀−z₂)·10² + z₂ z₀ × 10⁴ = 6,720,000 + 2840 × 10² = 284,000 + z₂ = 2,652 Result = 6,720,000 + 284,000 + 2,652 = 7,006,652 ✓ Verification: 1234 × 5678 = 7,006,652

Recursion Tree Visualization

Karatsuba Recursion Tree — 3 branches per level 1234 × 5678 n=4, 3 calls on n=2 z₀: 12 × 56 n=2, 3 calls on n=1 z₁: 46 × 134 n=2/3, 3 calls on n=1 z₂: 34 × 78 n=2, 3 calls on n=1 1×5=5 base n=1 3×11=33 base n=1 2×6=12 base n=1 ... 3 base calls ... ... 3 base calls ... Recursion Tree Analysis • Level 0: 1 call (size n) → work = O(n) • Level 1: 3 calls (size n/2) → work = 3·O(n/2) = O(n) • Level 2: 9 calls (size n/4) → work = 9·O(n/4) = O(n) ... each level costs O(n) • Tree depth: log₂ n • Leaf nodes: 3^(log₂ n) = n^(log₂ 3) • Total: O(n^(log₂ 3)) ≈ O(n^1.585)

Interactive Simulation

Enter two integers and step through the Karatsuba algorithm. Watch each recursive split and combination.

// Enter x and y above and click "Run Karatsuba" to trace the algorithm...

C++ Implementations

Full Arbitrary-Precision Karatsuba (String-Based)

#include <iostream> #include <string> #include <algorithm> using namespace std; // ── Helpers ─────────────────────────────────────────────────────────── string addStr(string a, string b) { // Elementary addition of two non-negative digit strings int i = a.size()-1, j = b.size()-1, carry = 0; string res; while (i>=0 || j>=0 || carry) { int s = carry; if (i>=0) s += a[i--]-'0'; if (j>=0) s += b[j--]-'0'; res += char('0' + s%10); carry = s/10; } reverse(res.begin(), res.end()); return res.empty() ? "0" : res; } string subStr(string a, string b) { // a - b, assumes a >= b int i=a.size()-1, j=b.size()-1, borrow=0; string res; while (i>=0) { int d = (a[i--]-'0') - borrow - (j>=0 ? b[j--]-'0' : 0); if (d < 0) { d += 10; borrow=1; } else borrow=0; res += char('0'+d); } while (res.size()>1 && res.back()=='0') res.pop_back(); reverse(res.begin(), res.end()); return res; } string shift(string a, int zeros) { // multiply by 10^zeros return a=="0" ? "0" : a + string(zeros,'0'); } void pad(string &a, string &b) { // Left-pad shorter string with '0' to equal length while (a.size() < b.size()) a = "0"+a; while (b.size() < a.size()) b = "0"+b; } // ── Karatsuba Core ──────────────────────────────────────────────────── string karatsuba(string x, string y) { pad(x, y); int n = x.size(); // Base case: single digit if (n == 1) return to_string((x[0]-'0') * (y[0]-'0')); int m = n / 2; // split point string a = x.substr(0, n-m); // high half of x string b = x.substr(n-m); // low half of x string c = y.substr(0, n-m); // high half of y string d = y.substr(n-m); // low half of y string z0 = karatsuba(a, c); // ac string z2 = karatsuba(b, d); // bd string z1 = karatsuba(addStr(a,b), addStr(c,d)); // (a+b)(c+d) // ad+bc = z1 - z0 - z2 string mid = subStr(subStr(z1, z0), z2); // Combine: z0*10^(2m) + mid*10^m + z2 string result = addStr(addStr(shift(z0,2*m), shift(mid,m)), z2); // Remove leading zeros size_t pos = result.find_first_not_of('0'); return (pos==string::npos) ? "0" : result.substr(pos); } int main() { // Small example cout << karatsuba("1234","5678") << "\n"; // 7006652 // Large numbers (e.g., 60-digit numbers) string pi60 = "314159265358979323846264338327950288419716939937510"; string e60 = "271828182845904523536028747135266249775724709369995"; string big = karatsuba(pi60, e60); cout << "Product length: " << big.size() << " digits\n"; cout << "First 20 digits: " << big.substr(0,20) << "...\n"; return 0; }

Long Long Version (for numbers fitting in 64-bit integers)

#include <iostream> #include <cmath> using namespace std; long long karatsuba(long long x, long long y) { if (x < 10 || y < 10) return x * y; // base case int n = max((int)log10(x)+1, (int)log10(y)+1); long long m = (long long)pow(10, n/2); long long a = x/m, b = x%m; long long c = y/m, d = y%m; long long z0 = karatsuba(a, c); long long z2 = karatsuba(b, d); long long z1 = karatsuba(a+b, c+d); return z0*m*m + (z1-z0-z2)*m + z2; } int main() { cout << karatsuba(1234, 5678) << "\n"; // 7006652 cout << karatsuba(99999, 99999) << "\n"; // 9999800001 return 0; }

Real-World Applications

1. Cryptography (RSA / Diffie-Hellman)

RSA uses 2048–4096-bit keys. Every encryption/decryption involves modular exponentiation with 2048-bit numbers. Karatsuba is used in the modular multiplication step inside libraries like OpenSSL's BN_mul() and GMP.

Impact: RSA-2048 encryption ≈ 30% faster with Karatsuba vs grade-school.

2. Arbitrary Precision Libraries

GMP (GNU Multiple Precision) switches from grade-school to Karatsuba at around 70 digits, and to Toom-Cook at ~200 digits. Python's int type automatically uses Karatsuba for large integers.

3. Polynomial Multiplication

Multiplying two polynomials of degree \(n\) is equivalent to big-number multiplication. Computer Algebra Systems (Mathematica, Maple, SageMath) use Karatsuba for polynomial products up to moderate degrees.

4. Digital Signal Processing

Karatsuba underlies some hardware multiplier designs where reducing the number of partial products saves transistors and power. FPGA implementations of 64×64-bit multipliers use Karatsuba decomposition.

Algorithm Variants: D&C Family Tree

Grade-School O(n²) Ancient Karatsuba O(n^1.585) 1960 Toom-Cook 3 O(n^1.465) 1963 Schönhage-Strassen O(n log n log log n) 1971 Harvey-Hoeven O(n log n) 2019