Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Hai đoạn code cùng cho kết quả đúng, nhưng một đoạn chạy trong chớp mắt còn đoạn kia làm máy treo khi dữ liệu lớn. Big O là ngôn ngữ để nói về sự khác biệt đó mà không cần đo đồng hồ.
Big O mô tả tốc độ tăng của số bước thực hiện khi kích thước dữ liệu n tăng. Nó không đo giây cụ thể, vì thời gian phụ thuộc máy móc. Thay vào đó, ta xem khi dữ liệu gấp đôi, gấp mười thì công việc tăng bao nhiêu. Có hai loại: độ phức tạp thời gian và độ phức tạp bộ nhớ.
| Ký hiệu | Tên | Ví dụ |
|---|---|---|
| O(1) | Hằng số | Lấy phần tử mảng theo chỉ số |
| O(log n) | Logarit | Tìm kiếm nhị phân |
| O(n) | Tuyến tính | Duyệt hết mảng một lần |
| O(n log n) | Gần tuyến tính | Sắp xếp hiệu quả (merge sort) |
| O(n^2) | Bình phương | Hai vòng lặp lồng nhau |
| O(2^n) | Mũ | Thử mọi tập con |
Thứ tự từ nhanh đến chậm cũng chính là thứ tự trong bảng. O(1) và O(log n) gần như không đáng lo; từ O(n^2) trở đi dữ liệu lớn sẽ gây vấn đề.
# O(1): số bước không đổi
def phan_tu_dau(ds):
return ds[0]
# O(n): duyệt một lần
def tong(ds):
t = 0
for x in ds:
t += x
return t
# O(n^2): hai vòng lồng nhau
def co_trung(ds):
for i in range(len(ds)):
for j in range(i + 1, len(ds)):
if ds[i] == ds[j]:
return True
return False
# O(n): cải tiến bằng set
def co_trung_nhanh(ds):
da_thay = set()
for x in ds:
if x in da_thay:
return True
da_thay.add(x)
return FalseHai hàm cuối cùng làm cùng một việc. Với 1.000 phần tử, bản đầu có thể cần cỡ nửa triệu phép so sánh, bản sau chỉ cỡ một nghìn bước. Với một triệu phần tử, chênh lệch là khổng lồ. Đây chính là giá trị thực tế của việc hiểu Big O, kết hợp với cấu trúc dữ liệu đúng.
Thường bạn có thể đổi bộ nhớ lấy tốc độ. Hàm co_trung_nhanh dùng thêm một set, tốn O(n) bộ nhớ nhưng nhanh hơn nhiều. Kỹ thuật ghi nhớ kết quả (memoization) trong đệ quy cũng là ví dụ khác. Không có lựa chọn tốt tuyệt đối, bạn cân nhắc theo bài toán.
Big O không có nghĩa là lúc nào cũng phải viết thuật toán tối ưu nhất. Với danh sách vài chục phần tử, O(n^2) vẫn chạy tức thì và code đơn giản dễ đọc hơn. Hãy viết cho đúng và rõ ràng trước; khi đo thấy chậm, hoặc khi biết chắc dữ liệu sẽ lớn, mới tối ưu đúng chỗ nghẽn.
Một quy tắc kinh nghiệm: máy tính thông thường xử lý được vào cỡ hàng chục triệu phép tính đơn giản mỗi giây trong ngôn ngữ thông dịch, nhiều hơn trong ngôn ngữ biên dịch. Con số này chỉ là ước lượng thô nhưng đủ để định hướng:
| Độ phức tạp | n khoảng 1.000 | n khoảng 1.000.000 |
|---|---|---|
| O(n) | Tức thì | Vẫn ổn |
| O(n log n) | Tức thì | Vẫn ổn |
| O(n^2) | Ổn | Rất chậm, có thể hàng giờ |
| O(2^n) | Không khả thi | Không khả thi |
Vì vậy trong các bài thi lập trình, người ta thường nhìn giới hạn của n để đoán thuật toán cần dùng. Trong công việc, bạn nên đo thực tế bằng công cụ đo thời gian trước khi tối ưu:
import time
t = time.perf_counter()
co_trung(list(range(3000)))
print(round(time.perf_counter() - t, 3), "giây")Thử với n tăng dần để tận mắt thấy đường cong O(n^2) dựng đứng như thế nào.
Xác định độ phức tạp của: tìm phần tử lớn nhất (O(n)), kiểm tra danh sách có trùng bằng hai vòng lặp (O(n^2)), in mọi cặp (a, b) trong hai danh sách (O(n x m)), và đếm số bit của n bằng cách chia đôi liên tục (O(log n)).
Big O cho bạn một cách nhanh để ước lượng code có "chịu được" dữ liệu lớn hay không. Hãy tập thói quen nhìn vòng lặp và tự hỏi: số bước phụ thuộc n như thế nào? Chỉ cần nhận ra các mức O(1), O(log n), O(n), O(n^2) là bạn đã đi được một nửa đường.
Bạn có thể tiếp tục hành trình với những khóa học lập trình trên codebem.com, học theo tốc độ của riêng mình.
Nguyễn Đình Cường
0 comment