Asymptotic Analysis: Big-O, Θ, Ω

অ্যাসিম্পটোটিক বিশ্লেষণ — Big-O, Θ, Ω

Read: ~35 min Beginner 8 practice problems Live code runner

1. Why "Asymptotic" At All?

We never measure an algorithm's speed in seconds, because seconds depend on the CPU, the compiler, the operating system, even the room temperature. Instead, we measure how the number of operations grows as the input size n grows. That growth function is what asymptotic notation captures.

Algorithm-এর গতি আমরা সরাসরি সেকেন্ডে মাপি না — কারণ সেকেন্ড নির্ভর করে CPU, compiler, OS, এমনকি room temperature-এর উপরও। বরং আমরা মাপি ইনপুট n বাড়ার সাথে সাথে operation-এর সংখ্যা কীভাবে বাড়ে। সেই বৃদ্ধির function-ই asymptotic notation দিয়ে প্রকাশ করা হয়।
Key insight log n বার ভাগ করতে করতেই 109 → 30 হয়ে যায়।

That single fact is why binary search, balanced BSTs, segment trees and merge sort dominate modern computing.

2. Formal Definitions

Big-O (upper bound)
f(n) = O(g(n)) iff there exist constants c > 0 and n₀ ≥ 0 such that
0 ≤ f(n) ≤ c · g(n)  for all n ≥ n₀.
Big-Omega (lower bound)
f(n) = Ω(g(n)) iff ∃ c > 0, n₀ ≥ 0 such that 0 ≤ c · g(n) ≤ f(n) for all n ≥ n₀.
Big-Theta (tight bound)
f(n) = Θ(g(n)) iff f(n) = O(g(n)) and f(n) = Ω(g(n)).
Big-O হল উপরের সীমা — "f(n) সর্বোচ্চ এত দ্রুত বাড়বে।" Big-Ω হল নিচের সীমা — "f(n) অন্তত এত দ্রুত বাড়বে।" Big-Θ মানে দুটোই — "f(n) ঠিক এই হারেই বাড়ে।" n যথেষ্ট বড় হলেই (অর্থাৎ n ≥ n₀) এই সম্পর্কগুলো প্রযোজ্য — ছোট n-এ ব্যতিক্রম থাকতে পারে।
Common abuse — In daily contest talk we say "this is O(n log n)" but mean Θ(n log n). That's harmless as long as you know the difference when an interviewer asks.

3. The Growth Hierarchy

Memorise this order — bigger functions on the right dominate everything to their left for large n:

O(1) ⊂ O(log n) ⊂ O(√n) ⊂ O(n) ⊂ O(n log n) ⊂ O(n²) ⊂ O(n³) ⊂ O(2ⁿ) ⊂ O(n!)

ops n O(1) O(log n) O(n) O(n log n) O(n²) O(2ⁿ) Figure 2.1 — Growth rate of common complexity classes (smaller is better).
n বড় হলে কোন function আগে যাবে — সেটি নির্ণয়ের সহজ নিয়ম: এই সিরিজে যেটি ডানে আছে, সেটি বড় n-এ অন্যকে অনেক পেছনে ফেলে দেয়। যেমন n = 10⁶-এ O(n²) মানে 10¹² operation — অসম্ভব। অথচ O(n log n) মানে মাত্র ~2 × 10⁷ — পাশ করে যাবে।

4. Worst, Average, Best Case

For the same algorithm and the same n, runtime can vary wildly depending on the actual input. Linear search for example:

  • Best: target is the first element → 1 comparison → Θ(1)
  • Worst: target absent or last → n comparisons → Θ(n)
  • Average: uniformly random target → ~n/2 comparisons → Θ(n)
একই algorithm-এ একই n-এর জন্য input-ভেদে সময় ভিন্ন হতে পারে। যেমন linear search-এ: best case (টার্গেট প্রথমেই) Θ(1), worst case (টার্গেট নেই বা শেষে) Θ(n), average ≈ n/2 → Θ(n)।

মনে রাখার নিয়ম — ICPC বা competitive programming-এ আমরা প্রায় সবসময় worst case ধরেই হিসাব করি, কারণ judge-এর adversarial input থাকে।

5. Live Demo — Timing Naive Sum vs Gauss Formula

We'll compute 1 + 2 + … + n two ways and time each with <chrono>. One is O(n), the other is O(1). Watch the difference for n = 100,000,000.

একই যোগফল দুইভাবে — একবার loop দিয়ে (O(n)), একবার Gauss-এর সূত্র দিয়ে (O(1)) — হিসাব করে সময় মাপব। n = 10৮-এ পার্থক্য চোখে পড়ার মতো।
timing.cpp
#include <bits/stdc++.h>
using namespace std;
using namespace chrono;

int main() {
    long long n = 100000000;        // 10^8

    auto t1 = high_resolution_clock::now();
    long long s1 = 0;
    for (long long i = 1; i <= n; i++) s1 += i;
    auto t2 = high_resolution_clock::now();

    auto t3 = high_resolution_clock::now();
    long long s2 = n * (n + 1) / 2;
    auto t4 = high_resolution_clock::now();

    auto ms_loop  = duration_cast<microseconds>(t2 - t1).count();
    auto ms_gauss = duration_cast<microseconds>(t4 - t3).count();

    cout << "loop  sum = " << s1 << "  in " << ms_loop  << " us\n";
    cout << "gauss sum = " << s2 << "  in " << ms_gauss << " us\n";
    return 0;
}

6. Pattern Recognition — Read Complexity by Inspection

The fastest way to estimate complexity is to recognise patterns. Here are the seven you must memorise:

PatternComplexityExample
Single loop 1..nO(n)for (i=0; i<n; i++)
Two nested loops 1..nO(n²)two-sum brute force
Loop halving each stepO(log n)for (i=n; i>0; i/=2)
Loop multiplying by 2O(log n)for (i=1; i<=n; i*=2)
Sort then linear scanO(n log n)sort + sweep
Recursion: T(n)=2T(n/2)+nO(n log n)merge sort
Recursion: T(n)=2T(n-1)+1O(2ⁿ)Tower of Hanoi
পেশাদাররা loop দেখেই দ্রুত বলে দিতে পারেন এটি O(n), O(n²) নাকি O(log n) — পদ্ধতি একটাই: কোন variable কীভাবে বাড়ছে/কমছে সেটি লক্ষ্য করা। যদি half হয় বা double হয় → log n; যদি +1 বা -1 → linear; nested হলে গুণ করুন।

7. Common Pitfalls

⚠️ Mistakes Beginners Make

  • Saying O(2n) — drop constants, it's O(n).
  • Saying O(n + n²) — keep the dominant term: O(n²).
  • Confusing O(log n) base — bases differ by a constant, dropped.
  • Ignoring hidden costs of string concat or vector copy.
  • Forgetting that set::insert is O(log n), not O(1).

✅ Pro Habits

  • Always state the complexity in your own head before you submit.
  • Plug n into the formula, compare with 10⁸.
  • Track both time and memory — both can TLE/MLE.
  • Use Θ when you can prove tight bounds; otherwise O.

8. Concrete Numbers For n

Functionn=10n=100n=10⁴n=10⁶
O(1)1111
O(log n)371420
O(n)1010010⁴10⁶
O(n log n)336641.3×10⁵2×10⁷
O(n²)10010⁴10⁸10¹² 💀
O(2ⁿ)102410³⁰ 💀——
O(n!)3.6×10⁶10¹⁵⁸ 💀——
উপরের টেবিল মুখস্থ থাকলে interview বা contest-এ একনজরে বলে দিতে পারবেন কোন approach পাশ করবে। যেমন n = 10⁵-এ O(n²) মানে 10¹⁰ — এক ঘণ্টার বেশি লাগবে; অর্থাৎ অন্য কৌশল লাগবে।

9. Practice Problems

For each snippet, give the tightest Θ-bound. Try first, then check.

  1. for (i=1; i<=n; i++) for (j=1; j<=n; j++) sum++;
    উপরের nested loop-এর complexity কত?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(n²) — outer loop n times, inner loop n times each → n × n.

  2. for (i=1; i<=n; i++) for (j=i; j<=n; j++) sum++;
    দ্বিতীয় loop i থেকে শুরু — তখন complexity কত?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(n²). Total iterations = n + (n-1) + (n-2) + … + 1 = n(n+1)/2 ≈ n²/2. Constants are dropped.

    মোট iteration = n + (n-1) + … + 1 = n(n+1)/2। constant বাদ দিলে Θ(n²)।

  3. for (i=1; i<=n; i*=2) work();
    i প্রতিবার দ্বিগুণ — complexity কত?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(log n). i takes values 1, 2, 4, …, n — that's log₂ n values.

  4. for (i=1; i<=n; i++) for (j=1; j<=n; j*=2) work();
    বাইরের loop linear, ভেতরের loop double হচ্ছে — মোট কত?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(n log n). Outer = n, inner = log n. Multiply.

  5. for (i=1; i*i<=n; i++) work();
    i² ≤ n পর্যন্ত চলে — complexity?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(√n). The loop stops when i ≈ √n.

  6. Two-pointer technique: i moves only forward, j moves only forward, both bounded by n. Total work?
    Two-pointer পদ্ধতি — দুটি pointer-ই কেবল সামনে যায় এবং দুজনই সর্বোচ্চ n বার সরে। মোট কাজ?
    ✨ Show Answer (উত্তর দেখুন)

    Θ(n). Even though there are nested-looking loops, each pointer advances at most n times total — the work is amortised over both, giving 2n = Θ(n).

    দুটি pointer একসাথে সর্বোচ্চ 2n বার সরবে — তাই মোট Θ(n)।

  7. Predict whether 10⁸ operations fit inside a 1-second time limit.
    10⁸ operation কি ১ সেকেন্ডের time limit-এর মধ্যে শেষ হবে?
    ✨ Show Answer (উত্তর দেখুন)

    Borderline. A modern CPU executes ~10⁸ simple ops/sec. Tight loops with primitive ints — yes. Loops with string operations, map lookups, or modular arithmetic — usually no, expect TLE.

    CPU প্রতি সেকেন্ডে আনুমানিক 10⁸ অপারেশন করতে পারে। শুধু int দিয়ে tight loop হলে চলে যাবে; কিন্তু string বা map থাকলে TLE হবে।

  8. Live: count how many times the inner statement runs in the snippet below for n = 1000.
    নিচের কোডে n = 1000-এ inner statement কতবার চলে?
    ✨ Show Answer (উত্তর দেখুন)
    count.cpp
    #include <bits/stdc++.h>
    using namespace std;
    
    int main() {
        int n = 1000;
        long long cnt = 0;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j *= 2)
                cnt++;
        cout << "runs = " << cnt << "\n";
        cout << "theory n*log2(n) = " << n * (long long)ceil(log2(n + 1)) << "\n";
        return 0;
    }

    Approx 1000 × 10 = 10,000 iterations — Θ(n log n).

Summary — Module 02

Big-O upper-bounds growth, Big-Ω lower-bounds it, and Big-Θ pins it down. Drop constants and lower-order terms; keep only the dominant term. Recognise the seven canonical patterns (single loop, nested loop, halving loop, two-pointer, sort+sweep, divide-and-conquer recursion, exponential recursion) and you can call complexity by sight.

Big-O হল উপরের সীমা, Big-Ω নিচের, Big-Θ সঠিক বৃদ্ধি। constant ও lower-order term বাদ দিন, dominant term রাখুন। উপরের সাতটি প্যাটার্ন চিনে রাখলে কোড দেখেই complexity বলে দিতে পারবেন।

Next Module → Mathematical Tools: Logarithms, Recurrences, Summations — যেগুলো ছাড়া complexity-র formal proof লেখা যায় না।