Big-O Notation
O(n log n) — Linearithmic Time
เรียนรู้ O(n log n) Linearithmic Time — operations ที่ทำ n × log n ขั้นตอน เกิดจาก divide and conquer หรือทำ O(log n) ซ้ำ n ครั้ง พร้อม 6 Labs ฝึกเขียน totalHalvings, mergeSorted, batchBinarySearch, sortAndGetRange, countUniqueSorted, และ mergeSort
O(n log n) คืออะไร
O(n log n) หรือ **Linearithmic Time** หมายความว่า จำนวนขั้นตอนเท่ากับ **n × log n** — คือ ทำ O(log n) งานซ้ำ n ครั้ง หรือ ทำ O(n) งานซ้ำ log n ระดับ (divide and conquer)
ลองนึกภาพ: ต้องจัดเรียงนามบัตร 1,000 ใบ ถ้าเทียบทีละคู่ทุกคู่ (bubble sort) ต้องเทียบ ~1,000,000 คู่ (O(n²)) แต่ถ้าแบ่งเป็นครึ่งๆ แล้วรวมกลับ (merge sort) จะมี ~10 ระดับ (log n) แต่ละระดับจัด 1,000 ใบ (n) = ~10,000 ขั้นตอน — นี่คือ O(n log n)
ทำไม O(n log n) ถึงเกิดจาก n × log n
O(n log n) = O(n) × O(log n) — ทำ O(log n) งาน **สำหรับทุก element** (n ตัว) หรือ ทำ O(n) งาน **log n ระดับ** (divide and conquer) — **คูณกัน ไม่ใช่บวกกัน**
| n | O(n) | O(n log n) | O(n²) |
|---|---|---|---|
| 10 | 10 | ~33 | 100 |
| 100 | 100 | ~664 | 10,000 |
| 1,000 | 1,000 | ~9,966 | 1,000,000 |
| 10,000 | 10,000 | ~132,877 | 100,000,000 |
สังเกต: O(n log n) ช้ากว่า O(n) แต่เร็วกว่า O(n²) **มาก** — เมื่อ n = 10,000, O(n²) ช้ากว่า O(n log n) ถึง ~750 เท่า
ตัวอย่าง O(n log n): การ sort ที่มีประสิทธิภาพ
Sorting algorithms ที่ดีที่สุด (merge sort, quick sort, heap sort) ทำงานใน O(n log n) — โดยใช้กลยุทธ์ **divide and conquer**: แบ่งข้อมูลเป็นครึ่งซ้ำๆ (log n ระดับ) แล้วรวมกลับ (ทำงาน n ตัวต่อระดับ)
JavaScript's `Array.sort()` ก็ใช้ O(n log n) เบื้องหลัง — เราจะเจาะจง merge sort และ quick sort เพิ่มเติมในบทเรียน phase Searching & Sorting
ตัวอย่าง O(n log n): ทำ O(log n) ซ้ำ n ครั้ง
อีกรูปแบบที่พบบ่อยคือ ทำ operation ที่เป็น O(log n) ซ้ำสำหรับทุก element ใน array — เช่น binary search ทีละตัว
กฎง่ายๆ: **ถ้ามี loop n รอบ และในแต่ละรอบทำงาน O(log n) — รวมเป็น O(n log n)**
ตัวอย่าง O(n log n): merge สอง sorted array
**Merge step** คือหัวใจของ merge sort — เอา sorted array สองอันมารวมเป็น sorted array เดียว โดยเปรียบเทียบตัวหน้าสุดของแต่ละ array ทีละครั้ง
Merge step เองใช้เวลา O(n) แต่ merge sort เรียก merge นี้ log n ระดับ (ระดับละ n ตัว) = **O(n log n) รวม**
สิ่งที่ **ไม่ใช่** O(n log n)
เข้าใจผิดบ่อยที่สุดคือ คิดว่า O(n log n) เกิดจาก n + log n ความจริงคือเป็น **n × log n** — คูณกัน ไม่ใช่บวกกัน
กฎง่ายๆ: **O(n log n) เกิดจาก "loop n รอบ × ทำ O(log n) ต่อรอบ" หรือ "log n ระดับ × ทำ O(n) ต่อระดับ"** — ไม่ใช่บวกกัน แต่คูณกัน
กฎสำคัญ: O(n log n) Patterns
O(n log n) — Linearithmic Time
Operations ที่ทำ n × log n ขั้นตอน — เร็วกว่า O(n²) มาก แต่ช้ากว่า O(n)
Merge sort / Quick sort / Heap sort
Divide and conquer sorting — log n ระดับ ระดับละ n งาน
Array.sort() ใน JavaScript
Engine ใช้ TimSort ซึ่งเป็น O(n log n) — ไม่ต้องเขียน sort เอง
ทำ binary search สำหรับทุก element
n binary searches × O(log n) each = O(n log n)
Sort แล้ว scan
O(n log n) sort + O(n) scan = O(n log n) เพราะ O(n log n) มากกว่า O(n)
Merge two sorted arrays
O(n) building block ของ merge sort — เรียก log n ระดับ = O(n log n)
Nested loop ซ้อน 2 ชั้น / Bubble / Selection sort
ซ้อน n × n = O(n²) ไม่ใช่ O(n log n)
n + log n
บวกกัน = O(n) ไม่ใช่ O(n log n) — ต้องคูณกัน
Lab 1: นับ total halvings ของทุกตัวใน array
Lab 2: Merge สอง sorted array
Lab 3: Binary search สำหรับทุก target
Lab 4: Sort แล้วหา range
Lab 5: นับจำนวนค่าที่ไม่ซ้ำ โดย sort ก่อน
Lab 6: Merge sort แบบเต็ม
สรุป O(n log n) Operations
| Operation | ตัวอย่าง | ความเร็ว |
|---|---|---|
| Merge sort / Quick sort / Heap sort | mergeSort(arr) | O(n log n) |
| Array.sort() | arr.sort((a,b) => a-b) | O(n log n) |
| Binary search n ครั้ง | batchBinarySearch(arr, targets) | O(n log n) |
| Sort แล้ว scan | sort แล้วนับ unique | O(n log n) |
| Merge two sorted arrays | merge(a, b) | O(n) |
| Bubble / Selection sort | nested loop sort | O(n²) ❌ |
| Single loop | for (const x of arr) | O(n) ❌ |
ทบทวนความเข้าใจ
O(n log n) — Linearithmic Time
ทดสอบความเข้าใจเกี่ยวกับ O(n log n) patterns