Bài 21: Greedy (Tham Lam) - Chọn Tốt Nhất Mỗi Bước!¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: VNOI Wiki - Thuật toán Tham lam
1. Chuyện gì đang xảy ra?¶
Bài toán: Đổi tiền¶
Bạn cần đổi 110 đồng bằng ít tờ tiền nhất. Các mệnh giá: 1, 5, 10, 50, 100.
Greedy: Mỗi bước chọn mệnh giá lớn nhất có thể!
- 110 → lấy 1 tờ 100 → còn 10
- 10 → lấy 1 tờ 10 → còn 0
- Tổng: 2 tờ! ✅
2. Greedy là gì?¶
Greedy (Tham lam) = Mỗi bước chọn lựa chọn tốt nhất tại thời điểm đó, không quan tâm tương lai.
Khi nào Greedy đúng?¶
Greedy không phải lúc nào cũng đúng! Chỉ đúng khi bài toán có:
- Tính tham lam (Greedy Choice Property): Lựa chọn tốt nhất cục bộ → tốt nhất toàn cục
- Tính tối ưu con (Optimal Substructure): Bài toán lớn = bài toán con + lựa chọn
Ví dụ Greedy SAI¶
Đổi tiền với mệnh giá {1, 3, 4}, cần đổi 6:
- Greedy: 4 + 1 + 1 = 3 tờ ← SAI!
- Đúng: 3 + 3 = 2 tờ
→ Greedy không phải lúc nào cũng tối ưu! (Nhưng với hệ tiền tệ chuẩn {1, 5, 10, 50, 100} thì Greedy luôn đúng.)
Thử tương tác
3. Các bài toán Greedy kinh điển¶
3.1. Activity Selection (Chọn hoạt động)¶
Có N hoạt động, mỗi hoạt động có thời gian bắt đầu và kết thúc. Chọn nhiều hoạt động nhất sao cho không trùng thời gian.
Greedy: Sắp xếp các hoạt động theo thời gian kết thúc tăng dần. Lần lượt chọn hoạt động đầu tiên kết thúc sớm nhất, sau đó chọn hoạt động tiếp theo bắt đầu sau khi hoạt động trước đó kết thúc.
Minh họa trực quan: Trong ví dụ dưới đây, các hoạt động màu xanh lá được thuật toán chọn (không giao nhau và tối ưu số lượng), các hoạt động màu đỏ bị loại bỏ do bị trùng chéo thời gian:

3.2. Fractional Knapsack (Cái túi phân số)¶
Có N đồ vật, mỗi vật có trọng lượng w[i] và giá trị v[i]. Túi chứa được W. Được phép chia nhỏ đồ vật. Tìm giá trị lớn nhất.
Greedy: Sắp xếp theo tỷ lệ giá trị/trọng lượng giảm dần, lấy hết vật nào được trước.
3.3. Job Sequencing (Xếp lịch công việc)¶
Mỗi công việc có deadline và lợi nhuận. Hoàn thành tối đa công việc trong deadline.
Greedy: Sắp xếp theo lợi nhuận giảm dần, chọn thời điểm trễ nhất trước deadline.
3.4. Minimum Number of Platforms (Sân ga)¶
Cho giờ đến và giờ đi của N chuyến tàu. Tìm số sân ga tối thiểu.
Greedy: Sắp xếp cả giờ đến và giờ đi. Duyệt, tăng số sân ga khi tàu đến, giảm khi tàu đi.
4. Proof of Greedy - Chứng minh tính đúng đắn¶
Để chứng minh Greedy đúng, ta thường dùng 2 phương pháp:
Phương pháp 1: Exchange Argument (Hoán đổi)¶
- Giả sử có nghiệm tối ưu O khác với nghiệm Greedy G
- Chỉ ra rằng ta có thể hoán đổi O để biến nó thành G mà không giảm chất lượng
- Kết luận: G cũng tối ưu
Ví dụ: Activity Selection
- Giả sử O chọn hoạt động kết thúc lúc t₁, nhưng G chọn hoạt động kết thúc sớm hơn lúc t₀ < t₁
- Hoán đổi: thay hoạt động t₁ bằng t₀ trong O
- Kết quả: O mới có cùng số hoạt động, nhưng hoạt động cuối kết thúc sớm hơn → có thể chọn thêm nhiều hơn
- → G không tệ hơn O
Phương pháp 2: Greedy Stays Ahead (Greedy luôn dẫn trước)¶
- Chứng minh rằng sau mỗi bước, nghiệm Greedy không tệ hơn nghiệm tối ưu
- Dùng quy nạp: bước đầu tiên Greedy chọn tốt nhất → các bước sau cũng vậy
Ví dụ: Đổi tiền {1, 5, 10, 50, 100}
- Chứng minh: nếu Greedy chọn tờ 100, thì mọi nghiệm tối ưu cũng phải dùng tờ 100
- Vì nếu không dùng tờ 100, cần ít nhất 10 tờ 10 → nhiều hơn 1 tờ 100
- → Greedy đúng
5. So sánh Greedy vs DP vs Quay lui¶
| Greedy | DP | Quay lui | |
|---|---|---|---|
| Lựa chọn | Tốt nhất cục bộ | Xét tất cả | Thử tất cả |
| Độ phức tạp | Thường O(N log N) | O(N²) hoặc O(NK) | O(2^N) hoặc O(N!) |
| Khi nào đúng? | Khi có tính tham lam | Luôn đúng | Luôn đúng |
| Ví dụ | Activity Selection | Knapsack 0/1 | N-Queens |
| Code | Ngắn, đơn giản | Trung bình | Dài hơn |
Khi nào dùng Greedy?¶
- Bài toán có tính tham lam (chứng minh được)
- Cần độ phức tạp tốt (O(N log N))
- Không cần xét tất cả khả năng
Khi nào KHÔNG dùng Greedy?¶
- Không chứng minh được tính tham lam
- Cần nghiệm chính xác tối ưu → dùng DP
- Bài toán có nhiều ràng buộc phức tạp → dùng DP hoặc Quay lui
6. Bài toán Greedy nâng cao¶
6.1. Huffman Coding (Nén dữ liệu)¶
Mỗi ký tự có tần suất xuất hiện. Mã hóa sao cho tổng độ dài mã là nhỏ nhất.
Greedy: Luôn gộp 2 node có tần suất nhỏ nhất.
6.2. Interval Partitioning (Phân đoạn)¶
Cho N hoạt động, mỗi hoạt động có bắt đầu và kết thúc. Tìm số phòng tối thiểu.
Greedy: Sắp xếp theo bắt đầu, gán phòng trống đầu tiên.
7. Lưu ý¶
- Greedy không phải lúc nào cũng đúng! Phải chứng minh tính đúng đắn
- Nếu không chắc Greedy đúng → dùng DP
- Greedy thường kết hợp với sắp xếp trước khi chọn
- Proof of Greedy là kỹ năng quan trọng trong thi đấu
8. Bài tập luyện tập¶
| Bài | Nền tảng | Độ khó | Chủ đề |
|---|---|---|---|
| CSES - Coin Piles | CSES | ⭐ | Greedy cơ bản |
| CSES - Tasks and Deadlines | CSES | ⭐⭐ | Activity selection |
| CSES - Stick Lengths | CSES | ⭐⭐ | Median |
| LeetCode - Jump Game | LC | ⭐⭐ | Greedy |
| LeetCode - Interval Scheduling | LC | ⭐⭐ | Activity selection |
| VNOJ - Atcoder DP Contest L - Deque | VNOJ | ⭐⭐⭐ | Game/Greedy |
| VNOJ - NKSGAME | VNOJ | ⭐⭐ | Two pointers |
| CSES - Room Allocation | CSES | ⭐⭐ | Interval scheduling |
Bài viết liên quan¶
Tài liệu tham khảo¶
- Topcoder - Greedy is Good
- VNOI Wiki - Thuật toán Tham lam
- GeeksforGeeks - Greedy Algorithms
- USACO Guide - Greedy Algorithms
Bài tiếp theo: Hình học cơ bản →