Big-O Notation
O(n!) — Factorial Time
เรียนรู้ O(n!) Factorial Time — operations ที่จำนวนขั้นตอนโตแบบ factorial (n × (n-1) × ... × 1) โตเร็วกว่า O(2ⁿ) ด้วยซ้ำ เกิดจากการสร้าง permutations ทั้งหมด พร้อม 6 Labs ฝึกเขียน factorial, countPermutations, swapElements, generatePermutations, permutationsOfString, และ nextPermutation
O(n!) คืออะไร
O(n!) หรือ **Factorial Time** หมายความว่า จำนวนขั้นตอนเท่ากับ **n! = n × (n-1) × (n-2) × ... × 1** — เป็นการเติบโตที่เร็วกว่า O(2ⁿ) ด้วยซ้ำ เมื่อ n โตขึ้นงานพุ่งกระฉูดในไม่กี่ค่า
ลองนึกภาพ: มีหนังสือ 3 เล่มจะเรียงบนชั้น — เรียงได้ 3! = 6 วิธี (ABC, ACB, BAC, BCA, CAB, CBA) ถ้ามี 5 เล่ม เรียงได้ 120 วิธี, 10 เล่ม เรียงได้ 3,628,800 วิธี — นี่คือ O(n!) ที่ใช้ในปัญหา permutations
ทำไม n! ถึงโตเร็วกว่า 2ⁿ
เพราะใน n! แต่ละขั้นตอนคูณด้วย n, n-1, n-2, ... ซึ่งเป็นตัวเลขที่ลดลงแต่ยังมากกว่า 2 (จนถึง n = 3) — ในขณะที่ 2ⁿ = 2 × 2 × 2 × ... (n ครั้ง) คูณด้วย 2 คงที่ทุกครั้ง
| n | O(n!) | O(2ⁿ) | O(n²) |
|---|---|---|---|
| 3 | 6 | 8 | 9 |
| 5 | 120 | 32 | 25 |
| 7 | 5,040 | 128 | 49 |
| 10 | 3,628,800 | 1,024 | 100 |
| 15 | ~1.3×10¹² | 32,768 | 225 |
สังเกต: ที่ n=7, n! แซง 2ⁿ ไปแล้ว (5,040 > 128) — และยิ่ง n ใหญ่ขึ้น n! จะยิ่งทิ้งห่าง 2ⁿ มากขึ้นเรื่อยๆ เพราะ n! คูณด้วยตัวเลขที่ใหญ่ขึ้นทุกรอบ
ตัวอย่าง O(n!): สร้าง permutations ทั้งหมด
การสร้าง **permutations** ทั้งหมดของ array — จำนวน permutations = n! — ต้องใช้เวลา O(n! × n) เพราะมี n! แบบ และแต่ละแบบใช้เวลา O(n) ในการสร้าง/คัดลอก
มี n! permutations — ถ้า n = 10, มี 3,628,800 permutations — ใหญ่พอที่จะทำให้เบราว์เซอร์ค้าง นี่คือเหตุผลที่เราไม่ใช้ O(n!) กับข้อมูลขนาดใหญ่
ตัวอย่าง O(n!): Traveling Salesman brute force
**Traveling Salesman Problem (TSP)** — หาเส้นทางที่สั้นที่สุดที่ผ่านทุกเมืองแล้วกลับจุดเริ่ม — ถ้าใช้ brute force ต้องลองทุก permutation ของลำดับเมือง = (n-1)! เส้นทาง
TSP brute force แสดงให้เห็นว่า O(n!) ไม่เหมาะกับ n > 10-12 — ในทางปฏิบัติเราใช้ heuristic หรือ dynamic programming แทน
ตัวอย่าง O(n!): จำนวน derangements
**Derangement** คือ permutation ที่ไม่มีสมาชิกตัวใดอยู่ตำแหน่งเดิม — จำนวน derangements = !n (subfactorial) ≈ n!/e — ยังเป็น O(n!) อยู่
ถึงฟังก์ชันนี้ใช้ recursion แต่จำนวน derangements เองเป็น O(n!) — และการหา derangements ทั้งหมดก็จะเป็น O(n!) เช่นกัน
สิ่งที่ **ไม่ใช่** O(n!)
เข้าใจผิดบ่อยที่สุดคือ คิดว่าฟังก์ชันที่ใช้ factorial เป็น O(n!) — การคำนวณ factorial(n) ใช้เวลา O(n) แต่ปัญหาที่สร้าง permutations ทั้งหมดเป็น O(n!)
กฎง่ายๆ: **O(n!) ต้อง "สร้างทุก permutation" หรือ "ลองทุกลำดับที่เป็นไปได้" — ไม่ใช่แค่คำนวณ factorial เฉยๆ**
กฎสำคัญ: O(n!) Patterns
O(n!) — Factorial Time
Operations ที่จำนวนขั้นตอนโตแบบ n × (n-1) × ... × 1 — โตเร็วกว่า 2ⁿ
สร้าง permutations ทั้งหมด
n! permutations — ต้องสร้างทุกแบบ = O(n! × n)
Traveling Salesman (brute force)
ลองทุกเส้นทาง = (n-1)! / 2 เส้นทาง — เป็น O(n!)
นับจำนวน permutations (n!)
คำนวณ n! ใช้เวลา O(n) — แต่จำนวนผลลัพธ์ = O(n!)
สร้าง derangements ทั้งหมด
จำนวน derangements ≈ n!/e — ยังเป็น O(n!)
คำนวณ factorial(n)
O(n) เท่านั้น — ไม่ใช่ O(n!) ในแง่ runtime
Nested loop / fibonacci
O(n²) หรือ O(2ⁿ) — ไม่ใช่ O(n!)
Lab 1: คำนวณค่า n!
Lab 2: นับจำนวน permutations
Lab 3: สลับค่าสองตำแหน่ง (helper)
Lab 4: สร้าง permutations ทั้งหมด
Lab 5: สร้าง permutations ของ string
Lab 6: หา next permutation (lexicographic order)
สรุป O(n!) Operations
| Operation | ตัวอย่าง | ความเร็ว |
|---|---|---|
| คำนวณ n! (runtime) | factorial(n) | O(n) |
| นับจำนวน permutations | countPermutations(arr) | O(n) |
| สลับค่าสองตำแหน่ง | swapElements(arr, i, j) | O(1) |
| สร้าง permutations ทั้งหมด | generatePermutations(arr) | O(n!) |
| Permutations ของ string | permutationsOfString(str) | O(n!) |
| Next permutation | nextPermutation(arr) | O(n) |
| Loop เดียว | for (const x of arr) | O(n) ❌ |
| Nested loop | for + for | O(n²) ❌ |
ทบทวนความเข้าใจ
O(n!) — Factorial Time
ทดสอบความเข้าใจเกี่ยวกับ O(n!) operations