Giao diện
Sắp xếp O(n²) — Bubble, Selection, Insertion
Trong một buổi phỏng vấn tại một công ty lớn, ứng viên được hỏi: "Dữ liệu gần như đã sắp xếp sẵn — bạn chọn thuật toán O(n²) nào?". Ứng viên lúng túng. Người phỏng vấn không cần bạn nói về Quick Sort hay Merge Sort — họ muốn kiểm tra liệu bạn có thật sự hiểu trade-offs giữa ba thuật toán cơ bản nhất hay không.
Ba thuật toán này không chỉ là bài tập cho người mới. Chúng dạy bạn ba khái niệm nền tảng mà mọi kỹ sư phải nắm: stability (tính ổn định), in-place sorting (sắp xếp tại chỗ), và adaptive behavior (hành vi thích nghi). Hiểu sâu ba thuật toán O(n²) chính là bước đệm vững chắc để hiểu tại sao O(n log n) lại quan trọng.
🎯 Mục tiêu
🎯 Mục tiêu bài học
- Hiểu cơ chế hoạt động của 3 thuật toán sắp xếp O(n²)
- Phân biệt stable vs unstable, adaptive vs non-adaptive
- Biết khi nào O(n²) thắng O(n log n) trong thực tế
- Đọc được visualizer state transitions cho từng thuật toán
- Tránh 5 sai lầm kinh điển trong phỏng vấn và code review
Bức tranh tư duy — 3 chiến thuật xếp bài
Hãy tưởng tượng bạn đang cầm một bộ bài lộn xộn và muốn xếp theo thứ tự tăng dần. Ba người chơi — mỗi người một chiến thuật:
Bộ bài ban đầu: [8] [3] [5] [1] [9]
┌─────────────────────────────────────────────────────────────┐
│ 🫧 Người chơi A — Bubble Sort │
│ "Mình so sánh hai lá kề nhau, đổi chỗ nếu sai thứ tự, │
│ lặp lại cho đến khi hết cần đổi." │
│ │
│ [8][3] → đổi! → [3][8][5][1][9] │
│ [8][5] → đổi! → [3][5][8][1][9] │
│ [8][1] → đổi! → [3][5][1][8][9] │
│ ... lá lớn nhất "nổi" lên cuối mỗi pass │
├─────────────────────────────────────────────────────────────┤
│ 🔍 Người chơi B — Selection Sort │
│ "Mình tìm lá nhỏ nhất, đặt vào vị trí đầu tiên, │
│ rồi tìm lá nhỏ nhất còn lại, đặt vào vị trí thứ hai." │
│ │
│ Tìm min = [1] → đổi với vị trí 0 → [1][3][5][8][9] │
│ Tìm min = [3] → đã đúng chỗ → [1][3][5][8][9] │
│ ... mỗi pass chỉ có ĐÚNG 1 lần swap │
├─────────────────────────────────────────────────────────────┤
│ 📥 Người chơi C — Insertion Sort │
│ "Mình cầm từng lá một, chèn vào đúng vị trí trong │
│ phần đã sắp xếp — giống xếp bài trên tay." │
│ │
│ Tay: [8] lấy [3] → chèn trước 8 → [3][8] │
│ Tay: [3][8] lấy [5] → chèn giữa → [3][5][8] │
│ Tay: [3][5][8] lấy [1] → chèn đầu → [1][3][5][8] │
│ Tay: [1][3][5][8] lấy [9] → chèn cuối → [1][3][5][8][9]│
└─────────────────────────────────────────────────────────────┘Cùng kết quả — nhưng cách làm, số lần swap, và hiệu quả khác nhau hoàn toàn.
Bảng so sánh tổng quan
| Tiêu chí | Bubble Sort | Selection Sort | Insertion Sort |
|---|---|---|---|
| Best case | O(n) ✅ | O(n²) | O(n) ✅ |
| Average case | O(n²) | O(n²) | O(n²) |
| Worst case | O(n²) | O(n²) | O(n²) |
| Space | O(1) | O(1) | O(1) |
| Stable | ✅ Có | ❌ Không | ✅ Có |
| Adaptive | ✅ Có (với cờ) | ❌ Không | ✅ Có |
| Số lần swap | O(n²) | O(n) ✅ | O(n²) |
| Số lần so sánh | O(n²) | O(n²) | O(n²) |
| Use case lý tưởng | Dạy thuật toán | Ghi ít (flash memory) | Dữ liệu gần sorted |
💡 HPN Pro Tip — Đọc bảng này
Khi phỏng vấn hỏi "so sánh 3 thuật toán O(n²)", đừng chỉ đọc từng dòng — hãy nêu điểm khác biệt nổi bật: Insertion Sort adaptive + stable, Selection Sort ít swap nhất, Bubble Sort chỉ tốt khi có swapped flag.
Cốt lõi kỹ thuật
🫧 Bubble Sort — Sắp xếp nổi bọt
Cơ chế: So sánh hai phần tử kề nhau, đổi chỗ nếu sai thứ tự. Lặp lại các pass cho đến khi mảng được sắp xếp. Phần tử lớn nhất "nổi bọt" lên cuối mảng sau mỗi pass.
Tối ưu then chốt: Cờ swapped — nếu một pass hoàn thành mà không có swap nào, mảng đã sắp xếp → dừng sớm → best case O(n).
Minh hoạ từng bước — [5, 1, 4, 2, 8]
Pass 1: [5, 1, 4, 2, 8]
[5,1] → swap → [1, 5, 4, 2, 8]
[5,4] → swap → [1, 4, 5, 2, 8]
[5,2] → swap → [1, 4, 2, 5, 8]
[5,8] → ok → [1, 4, 2, 5, 8] ← 8 settled ✅
Pass 2: [1, 4, 2, 5, 8]
[1,4] → ok → [1, 4, 2, 5, 8]
[4,2] → swap → [1, 2, 4, 5, 8]
[4,5] → ok → [1, 2, 4, 5, 8] ← 5 settled ✅
Pass 3: [1, 2, 4, 5, 8]
[1,2] → ok
[2,4] → ok
swapped = false → BREAK! 🎉 ← Early exitVisualizer State Transitions — Bubble Sort
VISUALIZER STATES — Bubble Sort:
State 1: COMPARE — highlight arr[j] và arr[j+1] màu vàng
State 2: SWAP — hoán đổi, cả hai phần tử chuyển cam
State 3: SETTLED — phần tử đã ở đúng vị trí cuối → xanh lá
State 4: PASS_COMPLETE — ranh giới biên dịch trái (n - i - 1)
State 5: DONE — tất cả phần tử xanh láTriển khai chuẩn
cpp
void bubbleSort(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
bool swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr[j], arr[j + 1]);
swapped = true;
}
}
if (!swapped) break; // Already sorted
}
}java
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}python
def bubble_sort(arr: list[int]) -> None:
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped:
break🔍 Selection Sort — Sắp xếp chọn
Cơ chế: Tìm phần tử nhỏ nhất trong phần chưa sắp xếp, đổi chỗ với phần tử đầu tiên của phần chưa sắp xếp. Lặp lại cho đến hết.
Đặc điểm quan trọng:
- ❌ NOT adaptive — luôn thực hiện O(n²) phép so sánh, kể cả khi mảng đã sắp xếp
- ❌ NOT stable — thao tác swap có thể phá vỡ thứ tự tương đối của các phần tử bằng nhau
- ✅ Minimum swaps — chỉ O(n) lần swap → lý tưởng khi ghi dữ liệu đắt (flash memory, EEPROM)
Tại sao Selection Sort không stable?
Ví dụ: [5a, 3, 5b, 2] (5a và 5b là hai phần tử giá trị 5)
Bước 1: min = 2 (index 3)
Swap arr[0] với arr[3] → [2, 3, 5b, 5a]
→ 5a nhảy ra sau 5b! Thứ tự tương đối bị phá vỡ.
Ban đầu: 5a trước 5b
Kết quả: 5b trước 5a ← NOT STABLE ❌Minh hoạ từng bước — [5, 1, 4, 2, 8]
Pass 0: [5, 1, 4, 2, 8] → min=1(idx 1) → swap(0,1) → [1, 5, 4, 2, 8] ✅
Pass 1: [1,|5, 4, 2, 8] → min=2(idx 3) → swap(1,3) → [1, 2, 4, 5, 8] ✅
Pass 2: [1, 2,|4, 5, 8] → min=4(idx 2) → no swap → [1, 2, 4, 5, 8] ✅
Pass 3: [1, 2, 4,|5, 8] → min=5(idx 3) → no swap → [1, 2, 4, 5, 8] ✅
Done! [1, 2, 4, 5, 8]
| = sorted boundaryVisualizer State Transitions — Selection Sort
VISUALIZER STATES — Selection Sort:
State 1: SCANNING — vị trí đang quét highlight vàng
State 2: NEW_MIN — tìm được min mới, highlight đỏ
State 3: SWAP — đổi min vào biên sorted, animation cam
State 4: SETTLED — phần tử ở vị trí sorted chuyển xanh lá
State 5: DONE — tất cả xanh láTriển khai chuẩn
cpp
void selectionSort(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
if (minIdx != i) {
swap(arr[i], arr[minIdx]);
}
}
}java
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
if (minIdx != i) {
int temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
}
}python
def selection_sort(arr: list[int]) -> None:
n = len(arr)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
if min_idx != i:
arr[i], arr[min_idx] = arr[min_idx], arr[i]📥 Insertion Sort — Sắp xếp chèn
Cơ chế: Lấy từng phần tử từ phần chưa sắp xếp, dịch các phần tử lớn hơn sang phải, chèn vào đúng vị trí trong phần đã sắp xếp.
Đặc điểm — Vũ khí bí mật:
- ✅ Adaptive — O(n) trên dữ liệu gần sorted (best case thực tế!)
- ✅ Stable — bảo toàn thứ tự tương đối của phần tử bằng nhau
- ✅ Online — có thể xử lý dữ liệu đến từng phần tử (streaming)
- 🏭 Dùng trong Timsort — Python/Java sort dùng Insertion Sort cho subarray < 64 phần tử
Minh hoạ từng bước — [5, 1, 4, 2, 8]
Sorted | Unsorted Action
─────────────────────────────────────────────────
[5] | [1, 4, 2, 8] key=1, shift 5→, insert 1 at [0]
[1, 5] | [4, 2, 8] key=4, shift 5→, insert 4 at [1]
[1,4,5]| [2, 8] key=2, shift 5→,4→, insert 2 at [1]
[1,2,4,5]| [8] key=8, no shift, insert 8 at [4]
[1, 2, 4, 5, 8] Done! ✅Tại sao Insertion Sort = O(n) trên dữ liệu gần sorted?
Mảng gần sorted: [1, 2, 3, 5, 4, 6, 7, 8]
↑ chỉ 1 phần tử sai vị trí
key=4: chỉ cần dịch [5] sang phải → insert 4 → xong
→ Mỗi phần tử chỉ cần 1-2 phép so sánh → tổng cộng ≈ O(n)Visualizer State Transitions — Insertion Sort
VISUALIZER STATES — Insertion Sort:
State 1: PICK — highlight phần tử đang chèn màu vàng
State 2: SHIFT — các phần tử dịch phải, animation mũi tên
State 3: INSERT — phần tử rơi vào gap đúng, chuyển xanh lá
State 4: SORTED_BOUNDARY — ranh giới sorted/unsorted dịch phải
State 5: DONE — tất cả xanh láTriển khai chuẩn
cpp
void insertionSort(vector<int>& arr) {
int n = arr.size();
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}java
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}python
def insertion_sort(arr: list[int]) -> None:
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = keyKhi nào O(n²) thắng O(n log n)?
💡 HPN Pro Tip — O(n²) không phải lúc nào cũng tệ
Nhiều anh em nghe "O(n²)" là nghĩ "chậm, tệ, không dùng". Sai lầm! Có những trường hợp O(n²) thắng O(n log n):
- Dữ liệu gần sorted → Insertion Sort chạy O(n), trong khi Quick Sort vẫn O(n log n)
- Mảng nhỏ (n < 20–50) → O(n²) có hằng số nhỏ hơn, ít overhead hơn (không recursion, không merge buffer)
- Cần stable + in-place → Insertion Sort là lựa chọn tự nhiên, không cần thêm bộ nhớ
- Tối thiểu ghi dữ liệu → Selection Sort chỉ O(n) swaps (tốt cho flash memory, EEPROM)
- Nền tảng tư duy → Hiểu O(n²) là điều kiện tiên quyết để hiểu tại sao O(n log n) quan trọng
Thực tế: Timsort (Python sort(), Java Arrays.sort()) dùng Insertion Sort cho các subarray nhỏ.
⚡ Operational Complexity — Con số thật
Đừng chỉ nói "O(n²)" — hãy hiểu nó nghĩa là gì với dữ liệu thực:
┌─────────────┬──────────────────────┬───────────────────────┐
│ n │ O(n²) operations │ Thời gian ước tính │
├─────────────┼──────────────────────┼───────────────────────┤
│ 100 │ 10,000 │ < 0.01 ms ✅ │
│ 1,000 │ 1,000,000 │ ~ 1 ms ✅ │
│ 10,000 │ 100,000,000 │ ~ 100 ms ⚠️ │
│ 100,000 │ 10,000,000,000 │ ~ 10 giây ❌ │
│ 1,000,000 │ 1,000,000,000,000 │ ~ timeout/crash 💀 │
└─────────────┴──────────────────────┴───────────────────────┘
So sánh với O(n log n):
┌─────────────┬──────────────────────┬───────────────────────┐
│ n │ O(n log n) operations│ Thời gian ước tính │
├─────────────┼──────────────────────┼───────────────────────┤
│ 1,000 │ 10,000 │ ~ 0.01 ms ✅ │
│ 10,000 │ 133,000 │ ~ 0.1 ms ✅ │
│ 100,000 │ 1,700,000 │ ~ 1.7 ms ✅ │
│ 1,000,000 │ 20,000,000 │ ~ 20 ms ✅ │
└─────────────┴──────────────────────┴───────────────────────┘⚠️ Quy tắc ngón tay cái
- n ≤ 1,000 → O(n²) hoàn toàn ổn, không cần tối ưu
- 1,000 < n ≤ 10,000 → O(n²) bắt đầu chậm, cân nhắc O(n log n)
- n > 10,000 → Bắt buộc dùng O(n log n) hoặc tốt hơn
- Trong API/backend → Luôn giả định n có thể lớn, dùng O(n log n) mặc định
Sai lầm điển hình
⚠️ Cạm bẫy
Sai lầm 1: "Bubble Sort luôn là O(n²)"
❌ Sai: Nhiều bạn nghĩ Bubble Sort không thể nhanh hơn O(n²)
✅ Đúng: Với cờ swapped, Bubble Sort đạt O(n) trên mảng đã sắp xếp
python
# ❌ Naive — luôn O(n²), kể cả mảng đã sorted
def bubble_naive(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
# ✅ Optimized — O(n) best case với early exit
def bubble_optimized(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped:
break # Không có swap → mảng đã sorted⚠️ Cạm bẫy
Sai lầm 2: "Selection Sort is stable"
❌ Sai: Nhiều bạn nhầm Selection Sort là stable vì "chỉ swap ít"
✅ Đúng: Selection Sort KHÔNG stable — swap phá vỡ thứ tự tương đối
Ví dụ: [3a, 2, 3b, 1]
Pass 0: min = 1 → swap(arr[0], arr[3]) → [1, 2, 3b, 3a]
→ 3a giờ đứng SAU 3b → thứ tự bị phá vỡ ❌⚠️ Cạm bẫy
Sai lầm 3: Off-by-one trong Bubble Sort
❌ Sai: Vòng lặp trong dùng j < n hoặc j < n - 1
✅ Đúng: Phải là j < n - 1 - i — vì sau mỗi pass, phần tử cuối đã settled
python
# ❌ Sai: truy cập out of bounds hoặc so sánh thừa
for j in range(n - 1): # Thiếu "- i" → so sánh phần tử đã settled
if arr[j] > arr[j + 1]: ...
# ✅ Đúng: thu hẹp biên sau mỗi pass
for j in range(n - 1 - i): # Đúng boundary
if arr[j] > arr[j + 1]: ...⚠️ Cạm bẫy
Sai lầm 4: Dùng >= thay vì > trong Insertion Sort
❌ Sai: while j >= 0 and arr[j] >= key — phá vỡ tính stable
✅ Đúng: while j >= 0 and arr[j] > key — bảo toàn thứ tự phần tử bằng nhau
python
# ❌ Sai: dùng >= phá vỡ stability
while j >= 0 and arr[j] >= key: # Phần tử bằng nhau cũng bị dịch
arr[j + 1] = arr[j]
j -= 1
# ✅ Đúng: dùng > để giữ stable
while j >= 0 and arr[j] > key: # Phần tử bằng nhau giữ nguyên vị trí
arr[j + 1] = arr[j]
j -= 1⚠️ Cạm bẫy
Sai lầm 5: "O(n²) luôn tệ, không bao giờ dùng"
❌ Sai: Loại bỏ hoàn toàn O(n²) khỏi đầu
✅ Đúng: O(n²) tối ưu cho n nhỏ, dữ liệu gần sorted, và khi cần stable + in-place. Timsort — thuật toán sort mặc định của Python và Java — dùng Insertion Sort cho subarray < 64 phần tử.
Bài tập luyện tập
🧠 Quiz — Kiểm tra hiểu biết
🧠 Quiz
Câu hỏi: Thuật toán nào vừa stable VÀ adaptive?
- [ ] A. Bubble Sort — stable nhưng chỉ adaptive khi có optimization flag
- [ ] B. Selection Sort — không stable, không adaptive
- [x] C. Insertion Sort — stable và adaptive tự nhiên, không cần flag
- [ ] D. Cả 3 đều stable
💡 Giải thích: Insertion Sort tự nhiên là adaptive — trên mảng gần sorted, vòng
whilegần như không chạy nên đạt O(n). Nó cũng stable vì chỉ dịch phần tử khiarr[j] > key(strict greater), giữ nguyên thứ tự phần tử bằng nhau. Bubble Sort cần thêm cờswappedmới adaptive, còn Selection Sort không bao giờ adaptive.
🐛 Spot the Bug — Tìm lỗi
Đoạn Insertion Sort bên dưới có một lỗi nghiêm trọng. Bạn tìm được không?
python
def insertion_sort_buggy(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i # 🐛 Bug ở đây!
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key💡 Gợi ý
Hãy trace qua mảng [3, 1, 2] với đoạn code trên. Khi i = 1, key = 1, j bắt đầu từ đâu? Phần tử arr[j] so sánh với key — có hợp lý không?
✅ Lời giải
Bug: j = i thay vì j = i - 1
Khi j = i, dòng arr[j] > key so sánh arr[i] > key, tức là key > key → luôn false → vòng while không bao giờ chạy → mảng không được sắp xếp!
Ngoài ra, arr[j + 1] = key sẽ ghi ra ngoài mảng khi j = n - 1.
python
# ✅ Fix: j phải bắt đầu từ i - 1
def insertion_sort_fixed(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1 # ✅ Bắt đầu từ phần tử TRƯỚC key
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = keyBài học: Luôn trace code bằng tay với mảng nhỏ trước khi tin rằng code đúng.
🧩 Parsons Problem — Sắp xếp dòng code
Sắp xếp các dòng code sau thành hàm Insertion Sort hoàn chỉnh:
Các dòng (đã xáo trộn):
A: arr[j + 1] = key
B: while j >= 0 and arr[j] > key:
C: for i in range(1, len(arr)):
D: j = i - 1
E: arr[j + 1] = arr[j]
F: key = arr[i]
G: j -= 1💡 Gợi ý
- Vòng
forđi trước mọi thứ bên trong - Trước khi dịch, cần biết
keylà gì vàjbắt đầu từ đâu - Vòng
whilechứa thao tác dịch phải - Cuối cùng là chèn
keyvào vị trí đúng
✅ Đáp án
python
# Thứ tự đúng: C → F → D → B → E → G → A
def insertion_sort(arr):
for i in range(1, len(arr)): # C
key = arr[i] # F
j = i - 1 # D
while j >= 0 and arr[j] > key: # B
arr[j + 1] = arr[j] # E
j -= 1 # G
arr[j + 1] = key # AGiải thích luồng: Vòng for (C) duyệt từ phần tử thứ 2. Lưu key (F) và bắt đầu j từ trước key (D). Vòng while (B) dịch phải (E, G) các phần tử lớn hơn key. Cuối cùng chèn key (A) vào gap.
Checklist ghi nhớ
✅ Checklist triển khai
Bubble Sort
- [ ] Adjacent swap — so sánh hai phần tử kề, đổi nếu sai thứ tự
- [ ] Cờ
swapped→ best case O(n), không có cờ → luôn O(n²) - [ ] Stable sort — phần tử bằng nhau giữ thứ tự ban đầu
- [ ] In-place — O(1) bộ nhớ phụ
Selection Sort
- [ ] Find-min + swap — tìm min trong phần unsorted, đổi vào đầu
- [ ] Luôn O(n²) so sánh — KHÔNG adaptive
- [ ] Chỉ O(n) swaps — tối ưu khi ghi dữ liệu đắt
- [ ] KHÔNG stable — swap phá vỡ thứ tự tương đối
Insertion Sort
- [ ] Shift-and-insert — dịch phải rồi chèn vào gap
- [ ] O(n) trên dữ liệu gần sorted — adaptive tự nhiên
- [ ] Stable — dùng
>(strict), không dùng>= - [ ] Được Timsort sử dụng cho subarray nhỏ (< 64 phần tử)
Quy tắc chung
- [ ] O(n²) ổn cho n < 50 và dữ liệu gần sorted
- [ ] Hiểu tại sao thuật toán là O(n²), không chỉ biết nó là O(n²)
- [ ] Nắm rõ stability, adaptivity, và số lần swap khi phỏng vấn
Liên kết học tiếp
📍 Trang tiếp theo
| Trang | Mô tả |
|---|---|
| Sắp Xếp Hiệu Quả | Merge Sort, Quick Sort, Heap Sort + Timsort |
| Bản Đồ Phase 1 | Quay lại lộ trình tổng quan |