Big-O Notation
O(2ⁿ) — Exponential Time
เรียนรู้ O(2ⁿ) Exponential Time — operations ที่จำนวนขั้นตอนเพิ่มเป็นสองเท่าทุกครั้งที่ n เพิ่มขึ้น 1 เกิดจาก recursion แตกกิ่งและการค้นหาทุก subset พร้อม 6 Labs ฝึกเขียน powerOfTwo, countFibCalls, generateBinaryStrings, countSubsets, hanoiMoves, และ powerSet
O(2ⁿ) คืออะไร
O(2ⁿ) หรือ **Exponential Time** หมายความว่า จำนวนขั้นตอน **เพิ่มเป็นสองเท่า** ทุกครั้งที่ n เพิ่มขึ้น 1 — เมื่อ n จาก 5 เป็น 6 งานจะโต 2 เท่า, จาก 10 เป็น 11 งานโต 2 เท่า, จาก 20 เป็น 21 งานโต 2 เท่า — แต่ละ step เพิ่มเท่าตัว
ลองนึกภาพ: คุณพับกระดาษ 1 แผ่น ความหนาเป็น 2 ชั้น, พับอีกทีเป็น 4 ชั้น, พับอีกทีเป็น 8 ชั้น — พับ n ครั้ง = 2ⁿ ชั้น — พับ 10 ครั้งได้ 1,024 ชั้น, พับ 30 ครั้งได้ ~1,000,000,000 ชั้น (หนาเกินโลก!) — นี่คือการเติบโตแบบเอ็กซ์โปเนนเชียล
ทำไม O(2ⁿ) ถึงระเบิดเร็ว
เพราะทุกขั้นตอนของ recursion แตกเป็น 2 กิ่ง และแต่ละกิ่งก็แตกต่ออีก — รวมเป็น 2 × 2 × 2 × ... (n ครั้ง) = 2ⁿ ครั้งของการเรียกฟังก์ชัน
| n | O(2ⁿ) ขั้นตอน | O(n²) ขั้นตอน | O(n log n) ขั้นตอน |
|---|---|---|---|
| 5 | 32 | 25 | ~12 |
| 10 | 1,024 | 100 | ~33 |
| 20 | ~1,000,000 | 400 | ~86 |
| 30 | ~1,000,000,000 | 900 | ~148 |
สังเกต: เมื่อ n = 20, O(2ⁿ) = ~1,000,000 ขั้นตอน — มากกว่า O(n²) = 400 ขั้นตอนถึง 2,500 เท่า O(2ⁿ) เร็วมากจนแทบใช้งานไม่ได้เมื่อ n > 30-40
ตัวอย่าง O(2ⁿ): Fibonacci แบบ naive recursion
Fibonacci แบบ naive recursion เป็นตัวอย่างคลาสสิกของ O(2ⁿ) — ฟังก์ชันเรียกตัวเอง 2 ครั้งต่อ 1 ครั้ง สร้างเป็น tree ของการเรียกที่โตแบบเอ็กซ์โปเนนเชียล
ถ้าใช้ memoization เก็บผลลัพธ์ที่คำนวณแล้ว จะลดเหลือ O(n) — แต่วิธี naive แสดงให้เห็นว่า recursion แตกกิ่งเปลืองทรัพยากรแค่ไหน
ตัวอย่าง O(2ⁿ): สร้าง binary string ทั้งหมด
การสร้างสตริงไบนารีความยาว n ทั้งหมด (เช่น n=3 → "000", "001", "010", ... , "111") — มีทั้งหมด 2ⁿ แบบ นี่คือ O(2ⁿ) เพราะจำนวนผลลัพธ์ = 2ⁿ
ทุกระดับของ recursion แตกเป็น 2 ทาง (0 หรือ 1) — ระดับลึก n = 2ⁿ เส้นทางรวม
ตัวอย่าง O(2ⁿ): Power set (ทุก subset)
**Power set** คือ set ของ subset ทั้งหมด — array ที่มี n ตัว จะมี subset ทั้งหมด 2ⁿ แบบ (นับ subset เปล่าด้วย)
จำนวน subset = 2ⁿ — ถ้าต้องการสร้าง subset ทั้งหมด (ไม่ใช่แค่นับ) เวลาที่ใช้ก็จะเป็น O(2ⁿ × n) ต้องคูณ n เพราะแต่ละ subset ใช้เวลา O(n) ในการสร้าง
สิ่งที่ **ไม่ใช่** O(2ⁿ)
เข้าใจผิดบ่อยที่สุดคือ คิดว่า loop คูณ 2 เป็น O(2ⁿ) ความจริงคือ **คูณ 2 ใน loop = O(log n)** (inverse), recursion ทุกแบบ = O(2ⁿ) (ไม่จริง — ถ้าแตกกิ่งเดียว = O(n))
กฎง่ายๆ: **O(2ⁿ) ต้องมี recursion แตก 2 กิ่งโดยไม่เก็บผลลัพธ์ซ้ำ — หรือต้องสร้างผลลัพธ์ 2ⁿ ชุด**
กฎสำคัญ: O(2ⁿ) Patterns
O(2ⁿ) — Exponential Time
Operations ที่จำนวนขั้นตอนเพิ่มเป็นสองเท่าทุกครั้งที่ n เพิ่ม 1
Recursion แตกสองกิ่ง (ไม่มี memo)
เรียกตัวเอง 2 ครั้งต่อครั้ง → จำนวนเรียก = 2ⁿ+¹ - 1
สร้าง subset ทั้งหมด (power set)
Array n ตัว → 2ⁿ subsets — ต้องสร้างทุก subset = O(2ⁿ)
สร้าง binary string ความยาว n ทั้งหมด
n ตำแหน่ง แต่ละตำแหน่งเลือกได้ 2 แบบ = 2ⁿ แบบ
Tower of Hanoi (จำนวนย้าย)
ย้ายจาน n ใบ = 2ⁿ - 1 ครั้ง — งานเป็น O(2ⁿ)
Fibonacci with memoization / DP
เก็บผลลัพธ์ซ้ำ → ลดเหลือ O(n) — ไม่ใช่ O(2ⁿ)
คูณ 2 ใน loop (doubling)
i *= 2 → O(log n) — ไม่ใช่ O(2ⁿ)
Lab 1: คำนวณค่า 2ⁿ
Lab 2: นับจำนวนครั้งที่เรียก naive fib
Lab 3: สร้าง binary string ความยาว n ทั้งหมด
Lab 4: นับจำนวน subset ทั้งหมด
Lab 5: นับจำนวนย้าย Tower of Hanoi
Lab 6: สร้าง power set (ทุก subset)
สรุป O(2ⁿ) Operations
| Operation | ตัวอย่าง | ความเร็ว |
|---|---|---|
| คำนวณ 2ⁿ | powerOfTwo(n) | O(n) / O(log n) |
| Naive Fibonacci | fib(n) recursive | O(2ⁿ) |
| สร้าง binary strings ทั้งหมด | generateBinaryStrings(n) | O(2ⁿ) |
| นับจำนวน subsets | countSubsets(arr) | O(1) |
| Tower of Hanoi (จำนวนย้าย) | hanoiMoves(n) | O(2ⁿ) |
| สร้าง power set | powerSet(arr) | O(2ⁿ) |
| Fibonacci with memoization | fibMemo(n) | O(n) ❌ |
| คูณ 2 ใน loop | i *= 2 | O(log n) ❌ |
ทบทวนความเข้าใจ
O(2ⁿ) — Exponential Time
ทดสอบความเข้าใจเกี่ยวกับ O(2ⁿ) operations