Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Tìm một thứ trong một tập hợp là bài toán xuất hiện ở khắp mọi nơi. Hai cách tiếp cận cơ bản nhất là tìm từng phần tử một, hoặc tận dụng thứ tự để loại bỏ một nửa mỗi bước. Khác biệt về tốc độ giữa chúng thật đáng kinh ngạc.
Ý tưởng: xem từng phần tử từ đầu đến cuối cho đến khi gặp thứ cần tìm.
def tim_tuan_tu(ds, can_tim):
for i, x in enumerate(ds):
if x == can_tim:
return i
return -1
print(tim_tuan_tu([7, 3, 9, 1], 9)) # 2
print(tim_tuan_tu([7, 3, 9, 1], 5)) # -1Ưu điểm là đơn giản và chạy được trên danh sách bất kỳ, không cần sắp xếp. Nhược điểm: trường hợp xấu nhất phải xem cả n phần tử, tức độ phức tạp O(n).
Nếu danh sách đã sắp xếp, bạn có thể làm tốt hơn nhiều. Hãy so sánh với phần tử ở giữa:
Mỗi bước loại bỏ một nửa, giống trò chơi đoán số từ 1 đến 100: hỏi "lớn hơn 50 không?" rồi tiếp tục chia đôi.
def tim_nhi_phan(ds, can_tim):
trai, phai = 0, len(ds) - 1
while trai <= phai:
giua = (trai + phai) // 2
if ds[giua] == can_tim:
return giua
if ds[giua] < can_tim:
trai = giua + 1
else:
phai = giua - 1
return -1
ds = [1, 3, 5, 7, 9, 11, 13]
print(tim_nhi_phan(ds, 9)) # 4
print(tim_nhi_phan(ds, 4)) # -1Tìm nhị phân có độ phức tạp O(log n). Với khoảng một triệu phần tử, tìm tuần tự có thể cần đến một triệu bước, trong khi tìm nhị phân chỉ cần chừng 20 bước, vì 2 mũ 20 xấp xỉ một triệu. Khi dữ liệu tăng gấp đôi, tìm nhị phân chỉ thêm đúng một bước.
| Tiêu chí | Tuần tự | Nhị phân |
|---|---|---|
| Yêu cầu dữ liệu | Không | Phải sắp xếp |
| Độ phức tạp | O(n) | O(log n) |
| Độ khó viết | Rất dễ | Dễ sai chi tiết |
trai <= phai chứ không phải <, nếu không sẽ bỏ sót trường hợp còn đúng một phần tử.giua + 1 và giua - 1, nếu chỉ gán giua có thể lặp vô hạn.(trai + phai) / 2 với mảng rất lớn; cách an toàn là trai + (phai - trai) / 2.Nếu chỉ tìm một lần trên dữ liệu chưa sắp xếp, tìm tuần tự thường hợp lý vì sắp xếp tốn nhiều công hơn. Nếu bạn tìm nhiều lần trên cùng một tập dữ liệu, sắp xếp một lần rồi tìm nhị phân sẽ có lợi. Còn nếu cần tra cứu theo khóa liên tục, bảng băm thường nhanh hơn nữa. Thư viện chuẩn của Python có sẵn module bisect cho tìm nhị phân, nên ngoài bài tập, bạn không cần tự viết lại.
Trong thực tế, ta thường không chỉ muốn biết "có hay không" mà còn cần biết vị trí để chèn một giá trị vào danh sách đã sắp xếp. Chỉ cần đổi giá trị trả về:
def vi_tri_chen(ds, gia_tri):
trai, phai = 0, len(ds)
while trai < phai:
giua = (trai + phai) // 2
if ds[giua] < gia_tri:
trai = giua + 1
else:
phai = giua
return trai
print(vi_tri_chen([1, 3, 5, 7], 4)) # 2
print(vi_tri_chen([1, 3, 5, 7], 9)) # 4Ở biến thể này khoảng tìm là nửa mở [trai, phai), nên điều kiện dừng là trai < phai. Đây cũng là cách module bisect của Python hoạt động. Một ứng dụng thú vị khác của ý tưởng nhị phân là "tìm nhị phân trên đáp án": nếu bạn có thể kiểm tra một giá trị có thỏa điều kiện hay không và tính chất đơn điệu (nếu x thỏa thì mọi giá trị lớn hơn cũng thỏa), bạn có thể nhị phân để tìm giá trị nhỏ nhất thỏa. Hãy thử với bài tìm căn bậc hai nguyên của một số.
Tìm kiếm nhị phân là ví dụ đẹp nhất của tư duy "chia để trị": dùng thứ tự có sẵn để loại bỏ một nửa không gian tìm kiếm mỗi bước. Hãy tự cài đặt nó mà không nhìn tài liệu, rồi kiểm thử với mảng rỗng, một phần tử, và giá trị không tồn tại.
Muốn học bài bản và có người hướng dẫn? Hãy xem các khóa học trên codebem.com để đi từ nền tảng đến dự án thực tế.
Nguyễn Đình Cường
0 comment