Bạn đã từng nghe đến từ "thuật toán (algorithm)" nhưng không hiểu chính xác nó là gì? Nghe có vẻ phức tạp và liên quan đến toán học? Thực ra, thuật toán đang hiện diện trong cuộc sống hằng ngày của bạn. Đọc bài này, bạn sẽ hiểu thuật toán là gì, tại sao nó quan trọng trong lập trình, và bắt đầu học từ đâu.
Thuật toán là gì? Định nghĩa dễ hiểu nhất
Thuật toán là một tập hợp các bước rõ ràng để giải quyết một vấn đề cụ thể. Các bước này được thực hiện theo một thứ tự nhất định và luôn trả về kết quả. Trong lập trình, thuật toán giống như "bản thiết kế" trước khi viết code.
Nói đơn giản hơn, thuật toán chính là một công thức nấu ăn. Nó quy định bạn làm gì, theo thứ tự nào, để ra được kết quả mong muốn.
Ví dụ 1 — Công thức nấu phở
Hãy nghĩ đến các bước nấu một tô phở.
- Đầu vào (Input): Xương bò, bánh phở, rau thơm...
- Các bước (thuật toán):
- Bước 1: Ninh xương bò từ 3 đến 4 tiếng
- Bước 2: Lọc nước dùng để có nước lèo trong
- Bước 3: Thêm bánh phở, thịt và rau thơm để hoàn thiện
- Đầu ra (Output): Một tô phở hoàn chỉnh
Điều gì xảy ra nếu bỏ qua Bước 1? Nước dùng sẽ đục và tô phở sẽ không ngon. Thuật toán coi trọng thứ tự thực hiện.
Trong lập trình cũng vậy. Máy tính hoạt động theo từng bước theo thứ tự "sau bước này là bước kia". Cấu trúc này hoàn toàn giống với công thức nấu phở.
Ví dụ 2 — Tìm đường trên Google Maps
Khi bạn nhập "từ Hà Nội đến TP. Hồ Chí Minh" vào Google Maps, tuyến đường ngắn nhất hiện ra ngay lập tức.
- Đầu vào: Điểm xuất phát (Hà Nội) và điểm đến (TP. Hồ Chí Minh)
- Xử lý: Tính toán và so sánh hàng triệu tuyến đường
- Đầu ra: Hiển thị tuyến đường ngắn nhất, nhanh nhất
Mỗi lần bạn mở bản đồ, có một thuật toán đang chạy phía sau. Ở đây người ta dùng các thuật toán như Dijkstra (đọc là "đai-xtra"). Thuật toán thực ra rất gần gũi với cuộc sống hằng ngày.
4 điều kiện của một thuật toán tốt
Lấy ví dụ công thức nấu phở để kiểm tra, một thuật toán cần có 4 đặc điểm sau.
- Đầu vào rõ ràng (Input): Nguyên liệu được xác định. Ví dụ: danh sách điểm thi
- Đầu ra rõ ràng (Output): Luôn trả về kết quả. Ví dụ: điểm cao nhất trong danh sách
- Hữu hạn (Finiteness): Kết thúc sau một số bước nhất định. Không chạy mãi mãi
- Rõ ràng (Definiteness): Mỗi bước không được mơ hồ. "Ninh cho đến khi ngon" là chưa đủ rõ
Công thức nấu phở đáp ứng cả 4 điều kiện trên. Vì vậy nó hoàn toàn có thể gọi là một "thuật toán".
Thuật toán trong lập trình là gì?
Trong lập trình, trước khi viết code bạn cần suy nghĩ "sẽ làm gì và theo thứ tự nào". Giai đoạn thiết kế này chính là thuật toán. Trình tự đúng là nghĩ ra logic trước, rồi mới chuyển thành code.
Mã giả (Pseudocode) — Viết thuật toán bằng ngôn ngữ tự nhiên
Mã giả (pseudocode) là cách viết các bước của thuật toán bằng ngôn ngữ tự nhiên, không phụ thuộc vào ngôn ngữ lập trình cụ thể nào. Đây là công cụ giúp bạn tạo thói quen "suy nghĩ trước, code sau".
# Bài toán: Tìm điểm cao nhất trong danh sách
# Bước 1: Lấy phần tử đầu tiên làm "điểm cao nhất tạm thời"
# Bước 2: Duyệt qua từng phần tử còn lại
# Bước 3: Nếu phần tử hiện tại lớn hơn điểm cao nhất tạm thời thì cập nhật
# Bước 4: Sau khi duyệt hết danh sách, trả về điểm cao nhất
Mã giả có thể viết bằng tiếng Việt hay bất kỳ ngôn ngữ nào. Điều quan trọng là không bị ràng buộc vào một ngôn ngữ lập trình cụ thể.
Viết thực tế bằng Python
Nếu bạn chưa đọc Python là gì?, hãy xem qua trước. Sẽ giúp bạn hiểu nội dung code dễ hơn.
Chuyển mã giả ở trên sang Python sẽ trông như thế này.
# Tìm điểm cao nhất trong danh sách
def tim_diem_cao_nhat(danh_sach):
cao_nhat = danh_sach[0] # Lấy phần tử đầu tiên làm điểm cao nhất tạm thời
for diem in danh_sach:
if diem > cao_nhat:
cao_nhat = diem # Cập nhật nếu tìm thấy giá trị lớn hơn
return cao_nhat
# Ví dụ thực tế
diem_thi = [72, 88, 65, 94, 77]
print(f"Điểm cao nhất: {tim_diem_cao_nhat(diem_thi)}") # → 94
Thuật toán này duyệt qua từng phần tử của danh sách đúng một lần. Đơn giản và dễ hiểu theo trực giác.
Nếu bạn đang dùng JavaScript, logic cũng hoàn toàn giống nhau. Hãy thử so sánh tại JavaScript là gì?
Các loại thuật toán thông dụng và tầm quan trọng của chúng
Thuật toán tìm kiếm (Search)
Là phương pháp tìm một giá trị cụ thể trong dữ liệu. Hãy so sánh hai loại phổ biến nhất.
Giả sử bạn cần tìm từ "python" trong từ điển.
- Tìm kiếm tuyến tính (Linear Search): Xem từng trang từ đầu đến cuối. Trong trường hợp xấu nhất, phải đọc cả 1.000 trang
- Tìm kiếm nhị phân (Binary Search): Mở trang giữa rồi phán đoán "nửa đầu hay nửa sau" để thu hẹp phạm vi. Trong trường hợp xấu nhất, chỉ cần khoảng 10 lần
Hãy xem code tìm kiếm tuyến tính thực tế.
# Tìm kiếm tuyến tính (Linear Search)
# Duyệt danh sách từ đầu đến cuối theo thứ tự
def tim_kiem_tuyen_tinh(danh_sach, gia_tri_can_tim):
for i in range(len(danh_sach)):
if danh_sach[i] == gia_tri_can_tim:
return i # Trả về vị trí tìm thấy
return -1 # Trả về -1 nếu không tìm thấy
# Ví dụ
diem_thi = [65, 78, 90, 55, 88, 72]
vi_tri = tim_kiem_tuyen_tinh(diem_thi, 90)
print(f"Tìm thấy tại vị trí: {vi_tri}") # → 2
Đơn giản, nhưng hoạt động được ngay cả khi danh sách chưa được sắp xếp. Cách triển khai tìm kiếm nhị phân sẽ được đề cập chi tiết trong bài viết riêng.
Thuật toán sắp xếp (Sort)
Là thuật toán sắp xếp dữ liệu theo một thứ tự nhất định. Dưới đây là các loại tiêu biểu.
- Bubble Sort (Sắp xếp nổi bọt): So sánh các phần tử kề nhau và hoán đổi. Dễ hiểu cơ chế, thích hợp để học
- Merge Sort (Sắp xếp trộn): Chia danh sách thành hai nửa, sắp xếp rồi ghép lại. Hiệu quả với dữ liệu lớn
- Quick Sort (Sắp xếp nhanh): Chọn một phần tử trục rồi liên tục chia thành hai phần. Nhanh và thực dụng
Hàm sorted() tích hợp của Python sử dụng thuật toán Timsort bên trong. Trong phát triển thực tế, dùng hàm tích hợp sẵn là đủ. Cách triển khai chi tiết từng thuật toán sẽ được đề cập trong các bài viết riêng.
Đệ quy (Recursion)
Là kỹ thuật trong đó hàm (function) tự gọi lại chính nó. Hình ảnh dễ hiểu nhất là búp bê Matryoshka (búp bê Nga) — mở ra lại thấy một con búp bê giống hệt bên trong. Đó chính là cấu trúc của đệ quy.
Đệ quy là kỹ thuật mạnh mẽ, nhưng bắt buộc phải đặt điều kiện dừng (base case). Nếu không có điều kiện dừng, chương trình sẽ chạy mãi mãi. Cách triển khai đệ quy cho dãy số Fibonacci sẽ được đề cập chi tiết trong bài viết về Big O.
Lựa chọn thuật toán quyết định tốc độ
"Code chạy được" và "code chạy nhanh" là hai chuyện khác nhau. Hãy so sánh bằng ví dụ tìm kiếm trong dữ liệu điểm thi.
| Số lượng dữ liệu | Tìm kiếm tuyến tính | Tìm kiếm nhị phân |
|---|---|---|
| 100 bản ghi | Tệ nhất: 100 lần | Tệ nhất: 7 lần |
| 10.000 bản ghi | Tệ nhất: 10.000 lần | Tệ nhất: 13 lần |
| 1.000.000 bản ghi | Tệ nhất: 1.000.000 lần | Tệ nhất: 20 lần |
Dù dữ liệu có đến 1 triệu bản ghi, tìm kiếm nhị phân vẫn tìm được trong vòng 20 lần. Theo phép so sánh phở, tìm kiếm tuyến tính giống như "dùng đũa khuấy nồi từng sợi một để tìm sợi phở". Còn tìm kiếm nhị phân là "thu hẹp phạm vi từng nửa một". Cách làm khác nhau, thời gian cũng khác nhau hoàn toàn.
Đừng bỏ qua mối liên hệ với các công ty IT Việt Nam. Các công ty IT lớn như FPT Software, VNG, Tiki, Shopee Vietnam đều ra bài toán thuật toán trong phỏng vấn kỹ thuật. Việc luyện tập trước trên LeetCode hay HackerRank là thông lệ phổ biến. Học thuật toán gắn trực tiếp với cơ hội xin việc và thăng tiến sự nghiệp.
Độ phức tạp thuật toán và ký hiệu Big O
Để đo "tốc độ" của thuật toán, người ta dùng khái niệm độ phức tạp thời gian (time complexity) — một chỉ số đo mức độ nhanh chậm. Cách biểu diễn đó chính là ký hiệu Big O.
Hãy ghi nhớ cảm nhận về 3 loại sau.
- O(n): Dữ liệu tăng gấp đôi thì thời gian xử lý cũng tăng gấp đôi. Tìm kiếm tuyến tính thuộc loại này
- O(log n): Dữ liệu tăng gấp đôi nhưng thời gian xử lý hầu như không tăng. Tìm kiếm nhị phân thuộc loại này
- O(n²): Dữ liệu tăng gấp đôi thì thời gian xử lý tăng gấp bốn. Bubble Sort thuộc loại này. Cần lưu ý
Nhìn qua con số, sự chênh lệch rất rõ ràng.
| Số lượng dữ liệu | O(n) — Tìm kiếm tuyến tính | O(log n) — Tìm kiếm nhị phân |
|---|---|---|
| 1.000 bản ghi | 1.000 lần | 10 lần |
| 1.000.000 bản ghi | 1.000.000 lần | 20 lần |
| 1 tỷ bản ghi | 1.000.000.000 lần | 30 lần |
Để hiểu sâu hơn về ký hiệu Big O, hãy đọc bài tiếp theo: Big O Notation là gì?
Lộ trình học thuật toán (dành cho người mới bắt đầu)
Dành cho những bạn không biết bắt đầu từ đâu, đây là lộ trình chia làm 3 giai đoạn.
Giai đoạn 1 — Nền tảng (1 đến 2 tháng)
- Hiểu về mảng và danh sách (Mảng / List) và vòng lặp
- Triển khai tìm kiếm tuyến tính (Linear Search)
- Nắm khái niệm Bubble Sort
- Hiểu cơ bản về Big O Notation (cảm nhận O(n) và O(n²))
Giai đoạn 2 — Trung cấp (2 đến 3 tháng)
- Triển khai tìm kiếm nhị phân (Binary Search)
- Hiểu Merge Sort và Quick Sort
- Nắm cơ chế và ứng dụng của Stack và Queue
- Nhập môn DFS và BFS (duyệt đồ thị và cây)
Giai đoạn 3 — Thực chiến (liên tục)
- Giải dần từ bài Easy đến Medium trên LeetCode
- Hiểu Hash Table (bảng băm)
- Nhập môn Dynamic Programming (quy hoạch động)
Tài nguyên học tập được khuyến nghị
- LeetCode — Bộ bài tập sát với đề phỏng vấn thực tế (miễn phí)
- HackerRank — Nhiều bài tập phù hợp cho người mới (miễn phí)
- Visualgo.net — Xem thuật toán hoạt động qua animation (miễn phí)
- CS50 (Harvard) — Bài giảng tiếng Anh nhưng trực quan, dễ hiểu (miễn phí)
Nếu bạn muốn học có hệ thống qua video và bài tập thực hành, các khóa học thuật toán trên Udemy là lựa chọn đáng cân nhắc. Khi có đợt sale, giá thường rất hợp lý và phù hợp với nhiều mức ngân sách.
Câu hỏi thường gặp (FAQ)
H: Học thuật toán có khó không?
Tùy vào cách tiếp cận. Nếu bắt đầu từ lý thuyết và toán học ngay, bạn sẽ thấy khó. Nhưng nếu đi theo hướng như bài viết này — từ ví dụ thực tế rồi chuyển sang code — bạn hoàn toàn có thể nắm vững nền tảng trong 1 đến 2 tháng. Vấn đề không phải là "khó", mà thường là "bước vào sai cửa".
H: Học thuật toán có cần kiến thức toán học không?
Ở mức cơ bản, không cần. Tìm kiếm tuyến tính hay Bubble Sort chỉ cần hiểu vòng lặp và phép so sánh. Các chủ đề nâng cao như lý thuyết đồ thị hay quy hoạch động thì toán học có ích, nhưng đó là chuyện về sau. Hiện tại, chỉ cần tư duy được "bước tiếp theo là gì" là đủ.
H: Nên học framework hay thuật toán trước?
Nếu bạn mới bắt đầu lập trình, học framework trước để tạo ra sản phẩm sẽ giúp bạn có thêm động lực. Tuy nhiên, nếu bạn nhắm đến các công ty IT lớn như FPT Software hay VNG, thuật toán là điều không thể bỏ qua. Lý tưởng nhất là học song song — buổi sáng học framework, buổi tối giải 1 đến 2 bài Easy trên LeetCode. Đây là thói quen rất hiệu quả.
Tổng kết
Hãy cùng nhìn lại những gì bạn đã học trong bài viết này.
- Thuật toán là "tập hợp các bước rõ ràng" để giải quyết một vấn đề
- Công thức nấu phở và tìm đường trên Google Maps là những ví dụ thực tế điển hình
- Một thuật toán tốt cần có 4 điều kiện: Input, Output, tính hữu hạn và tính rõ ràng
- Tìm kiếm tuyến tính và tìm kiếm nhị phân là hai thuật toán tìm kiếm đầu tiên cần học
- Ký hiệu Big O là công cụ đo tốc độ thuật toán — đây là bước tiếp theo của bạn
Bước tiếp theo, hãy học cách đo hiệu quả của thuật toán: Big O Notation là gì?
Nếu bạn muốn mở rộng thêm kiến thức lập trình cơ bản, hãy tham khảo API là gì? — một khái niệm quan trọng khác trong lập trình hiện đại.