Menu
CoddyTech

Tìm kiếm nhị phân

Cập nhật lần cuối

Tìm kiếm nhị phân (binary search) tìm một giá trị đích trong mảng **đã sắp xếp** bằng cách liên tục chia đôi vùng tìm kiếm. Thuật toán so sánh phần tử ở giữa với giá trị đích: nếu khớp thì dừng; nếu không, nửa không thể chứa giá trị đích bị loại bỏ và vùng tìm kiếm thu hẹp lại còn nửa kia. Mỗi lần so sánh loại đi một nửa số phần tử còn lại, đó là lý do thuật toán chạy trong O(log n): tìm trong một triệu giá trị đã sắp xếp chỉ cần tối đa khoảng 20 lần so sánh.

Hoạt ảnh ở trên hiển thị các con trỏ lo, mid và hi, đồng thời làm mờ nửa bị loại sau mỗi lần so sánh. Điều kiện tiên quyết bắt buộc: mảng phải được sắp xếp sẵn. Với dữ liệu chưa sắp xếp, bạn cần dùng tìm kiếm tuyến tính hoặc sắp xếp trước (xem sắp xếp trộn). Chính ý tưởng chia đôi này cũng là nền tảng của cây tìm kiếm nhị phân.

Độ phức tạp thời gian và không gian

Trường hợpĐộ phức tạpGhi chú
Tốt nhấtO(1)Phần tử ở giữa chính là giá trị đích ngay lần so sánh đầu tiên.
Trung bìnhO(log n)Mỗi lần so sánh chia đôi vùng tìm kiếm còn lại.
Xấu nhấtO(log n)Vùng tìm kiếm thu hẹp còn một phần tử trước khi tìm thấy hoặc không thấy.
Không gianO(1)Phiên bản vòng lặp chỉ lưu các chỉ số lo, hi và mid.

Từng bước thực hiện

BướcĐiều gì xảy ra
1Đặt lo là chỉ số đầu tiên và hi là chỉ số cuối cùng của mảng đã sắp xếp.
2Tính chỉ số ở giữa: mid = (lo + hi) // 2.
3Nếu a[mid] bằng giá trị đích, trả về mid (đã tìm thấy).
4Nếu a[mid] **nhỏ hơn** giá trị đích, giá trị đích chỉ có thể nằm ở nửa phải: đặt lo = mid + 1.
5Nếu a[mid] **lớn hơn** giá trị đích, tìm ở nửa trái: đặt hi = mid - 1.
6Lặp lại từ bước 2 khi lo <= hi; nếu vùng tìm kiếm rỗng, giá trị đích không có trong mảng.

Ví dụ minh họa

Tìm 5 trong [1, 2, 3, 5, 7, 8, 9]:

LượtVùng tìm kiếm (lo..hi)mida[mid]Thao tác
1[1, 2, 3, 5, 7, 8, 9] (0..6)35a[3] = 5: tìm thấy giá trị đích tại chỉ số 3.

Từng bước khi không tìm thấy

Tìm 4 trong cùng mảng đó cho thấy vùng tìm kiếm rỗng dần như thế nào:

LượtVùng tìm kiếm (lo..hi)mida[mid]Thao tác
10..6355 > 4: tìm ở nửa trái, hi = 2.
20..2122 < 4: tìm ở nửa phải, lo = 2.
32..2233 < 4, nên lo thành 3 và vùng tìm kiếm rỗng: không tìm thấy.

Khi nào nên dùng tìm kiếm nhị phân

Nên dùng khiTránh dùng khi
Dữ liệu đã được sắp xếp (hoặc bạn tìm kiếm trên đó nhiều lần)Dữ liệu chưa sắp xếp và chỉ tìm một lần (sắp xếp trước tốn O(n log n))
Cấu trúc dữ liệu hỗ trợ truy cập ngẫu nhiên nhanh (mảng)Bạn chỉ có thể truy cập tuần tự (danh sách liên kết)
Tập dữ liệu lớn (O(log n) thể hiện rõ sức mạnh ở quy mô lớn)Tập dữ liệu rất nhỏ (duyệt tuần tự cũng nhanh như vậy mà đơn giản hơn)

Một cách cài đặt Binary Search gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C, Pseudocode. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Binary Search bằng Python

Python
1def binary_search(a, target):2    lo, hi = 0, len(a) - 13    while lo <= hi:4        mid = (lo + hi) // 25        if a[mid] == target:6            return mid7        if a[mid] < target:8            lo = mid + 1  # search the right half9        else:10            hi = mid - 1  # search the left half11    return -112
13
14nums = [1, 2, 3, 5, 7, 8, 9]  # must be sorted15print("Index of 5:", binary_search(nums, 5))16print("Index of 4:", binary_search(nums, 4))
Chạy code này trong Playground Python

Câu hỏi thường gặp về tìm kiếm nhị phân

Độ phức tạp thời gian của tìm kiếm nhị phân là bao nhiêu?
O(log n) trong trường hợp trung bình và xấu nhất, vì mỗi lần so sánh chia đôi vùng tìm kiếm còn lại, và O(1) trong trường hợp tốt nhất khi phần tử ở giữa đầu tiên chính là giá trị đích. Phiên bản vòng lặp dùng thêm O(1) bộ nhớ.
Vì sao tìm kiếm nhị phân cần mảng đã sắp xếp?
Bước chia đôi dựa vào thứ tự: so sánh giá trị đích với phần tử ở giữa chỉ cho bạn biết nên loại nửa nào khi mọi phần tử bên trái đều nhỏ hơn và mọi phần tử bên phải đều lớn hơn. Với dữ liệu chưa sắp xếp, suy luận đó không còn đúng, nên hãy dùng tìm kiếm tuyến tính hoặc sắp xếp trước.
Tìm kiếm nhị phân và tìm kiếm tuyến tính khác nhau như thế nào?
Tìm kiếm tuyến tính duyệt từng phần tử một (O(n)) và chạy được trên mọi mảng; tìm kiếm nhị phân chia đôi vùng tìm kiếm của mảng đã sắp xếp (O(log n)) nhưng cần dữ liệu đầu vào đã sắp xếp. Với vài phần tử thì khác biệt không đáng kể; ở quy mô lớn, tìm kiếm nhị phân thắng áp đảo.
Tìm kiếm nhị phân cần bao nhiêu lần so sánh?
Tối đa khoảng log2(n) + 1: 10 lần so sánh đủ cho 1.000 phần tử, 20 lần đủ cho 1.000.000 phần tử. Mức tăng theo logarit đó khiến nó trở thành cách tra cứu mặc định trên dữ liệu đã sắp xếp.
Lỗi tràn số kinh điển trong tìm kiếm nhị phân là gì?
Tính phần tử giữa bằng (lo + hi) / 2 có thể gây tràn số với kiểu số nguyên có kích thước cố định khi lo + hi vượt quá giá trị lớn nhất của kiểu. Cách an toàn là mid = lo + (hi - lo) / 2. Trong Python điều này không quan trọng (số nguyên có độ chính xác tùy ý), nhưng trong Java/C/C++ đây là một bug có thật và rất nổi tiếng.
Tìm kiếm nhị phân có giống cây tìm kiếm nhị phân không?
Hai khái niệm có chung ý tưởng chia đôi nhưng khác nhau về cấu trúc: tìm kiếm nhị phân là thuật toán chạy trên mảng đã sắp xếp, còn cây tìm kiếm nhị phân là cấu trúc dữ liệu liên kết giữ các khóa theo thứ tự, nên khi tra cứu bạn đi sang trái hoặc phải tại mỗi nút.
Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu