Tôi phải đồng ý rằng thật kỳ lạ khi lần đầu tiên bạn nhìn thấy một thuật toán O (log n) ... lôgarit đó đến từ đâu vậy? Tuy nhiên, hóa ra có một số cách khác nhau để bạn có thể có được một thuật ngữ nhật ký để hiển thị trong ký hiệu big-O. Ở đây có một ít:
Chia lặp lại cho một hằng số
Lấy một số n bất kỳ; nói, 16. Bạn có thể chia n cho hai lần trước khi nhận được một số nhỏ hơn hoặc bằng một? Đối với 16, chúng tôi có điều đó
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Lưu ý rằng điều này kết thúc với bốn bước để hoàn thành. Điều thú vị là chúng ta cũng có log 2 16 = 4. Hmmm ... 128 thì sao?
128 / 2 = 64
64 / 2 = 32
32 / 2 = 16
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Điều này mất bảy bước và log 2 128 = 7. Đây có phải là sự trùng hợp không? Không! Có một lý do chính đáng cho điều này. Giả sử rằng chúng ta chia một số n cho 2 i lần. Khi đó ta được số n / 2 i . Nếu chúng ta muốn tìm giá trị của i trong đó giá trị này nhiều nhất là 1, chúng ta nhận được
n / 2 i ≤ 1
n ≤ 2 tôi
log 2 n ≤ i
Nói cách khác, nếu chúng ta chọn một số nguyên i sao cho i ≥ log 2 n, thì sau khi chia n thành một nửa i nhân, chúng ta sẽ có giá trị lớn nhất là 1. Giá trị i nhỏ nhất mà điều này được đảm bảo gần đúng là log 2 n, vì vậy nếu chúng ta có một thuật toán chia hết cho 2 cho đến khi số đủ nhỏ, thì chúng ta có thể nói rằng nó kết thúc ở bước O (log n).
Một chi tiết quan trọng là không quan trọng bạn đang chia n cho hằng số nào (miễn là nó lớn hơn một); Nếu bạn chia cho hằng số k, sẽ cần log k n bước để đạt được 1. Vì vậy, bất kỳ thuật toán nào chia nhiều lần kích thước đầu vào cho một số phân số sẽ cần lần lặp O (log n) để kết thúc. Những lần lặp lại đó có thể mất rất nhiều thời gian và do đó thời gian chạy thực không cần phải là O (log n), nhưng số bước sẽ là logarit.
Vì vậy, điều này xuất hiện ở đâu? Một ví dụ cổ điển là tìm kiếm nhị phân , một thuật toán nhanh để tìm kiếm một giá trị trong mảng đã sắp xếp. Thuật toán hoạt động như sau:
- Nếu mảng trống, hãy trả về rằng phần tử không có trong mảng.
- Nếu không thì:
- Nhìn vào phần tử giữa của mảng.
- Nếu nó bằng với yếu tố chúng ta đang tìm kiếm, hãy trả lại thành công.
- Nếu nó lớn hơn phần tử chúng tôi đang tìm kiếm:
- Vứt bỏ nửa sau của mảng.
- Nói lại
- Nếu nó nhỏ hơn phần tử chúng tôi đang tìm kiếm:
- Bỏ nửa đầu của mảng.
- Nói lại
Ví dụ: để tìm kiếm 5 trong mảng
1 3 5 7 9 11 13
Đầu tiên chúng ta sẽ xem xét yếu tố giữa:
1 3 5 7 9 11 13
^
Vì 7> 5 và vì mảng đã được sắp xếp, chúng ta biết thực tế là số 5 không thể nằm ở nửa sau của mảng, vì vậy chúng ta có thể loại bỏ nó. Cái lá này
1 3 5
Vì vậy, bây giờ chúng ta xem xét phần tử giữa ở đây:
1 3 5
^
Vì 3 <5, chúng ta biết rằng 5 không thể xuất hiện trong nửa đầu của mảng, vì vậy chúng ta có thể ném nửa đầu mảng để lại
5
Một lần nữa chúng ta nhìn vào phần giữa của mảng này:
5
^
Vì đây chính xác là số chúng tôi đang tìm kiếm, chúng tôi có thể báo cáo rằng 5 thực sự nằm trong mảng.
Vì vậy, làm thế nào hiệu quả là điều này? Chà, trên mỗi lần lặp, chúng ta đang loại bỏ ít nhất một nửa số phần tử mảng còn lại. Thuật toán dừng ngay khi mảng trống hoặc chúng ta tìm thấy giá trị mà chúng ta muốn. Trong trường hợp xấu nhất, phần tử không có ở đó, vì vậy chúng tôi tiếp tục giảm một nửa kích thước của mảng cho đến khi hết phần tử. Điều này mất bao lâu? Chà, vì chúng ta cứ lặp đi lặp lại việc cắt mảng làm đôi, nên chúng ta sẽ thực hiện tối đa O (log n) lần lặp, vì chúng ta không thể cắt mảng thành một nửa nhiều hơn O (log n) lần trước khi chạy ngoài các phần tử của mảng.
Các thuật toán tuân theo kỹ thuật chung là chia để trị (cắt vấn đề thành nhiều phần, giải quyết các phần đó, sau đó đặt vấn đề lại với nhau) có xu hướng có các số hạng logarit trong chúng vì lý do tương tự - bạn không thể tiếp tục cắt một số đối tượng trong gấp rưỡi O (log n) lần. Bạn có thể muốn xem xét sắp xếp hợp nhất như một ví dụ tuyệt vời về điều này.
Xử lý các giá trị một chữ số tại một thời điểm
Có bao nhiêu chữ số trong cơ số 10 số n? Chà, nếu có k chữ số trong số, thì chúng ta sẽ có chữ số lớn nhất là bội số của 10 k . Số lớn nhất có k chữ số là 999 ... 9, k lần, và giá trị này bằng 10 k + 1 - 1. Do đó, nếu chúng ta biết rằng n có k chữ số trong đó, thì chúng ta biết rằng giá trị của n là nhiều nhất là 10 k + 1 - 1. Nếu chúng ta muốn tìm k theo n, chúng ta nhận được
n ≤ 10 k + 1 - 1
n + 1 ≤ 10 k + 1
log 10 (n + 1) ≤ k + 1
(log 10 (n + 1)) - 1 ≤ k
Từ đó ta nhận được rằng k xấp xỉ logarit cơ số 10 của n. Nói cách khác, số chữ số trong n là O (log n).
Ví dụ, chúng ta hãy nghĩ về sự phức tạp của việc cộng hai số lớn quá lớn để vừa với một từ máy. Giả sử rằng chúng ta có các số đó được biểu diễn trong cơ số 10, và chúng ta sẽ gọi các số là m và n. Một cách để cộng chúng là thông qua phương pháp cấp trường - viết các số ra từng chữ số một, sau đó làm từ phải sang trái. Ví dụ: để thêm 1337 và 2065, chúng tôi sẽ bắt đầu bằng cách viết các số dưới dạng
1 3 3 7
+ 2 0 6 5
==============
Chúng tôi thêm chữ số cuối cùng và mang số 1:
1
1 3 3 7
+ 2 0 6 5
==============
2
Sau đó, chúng tôi thêm chữ số thứ hai đến cuối cùng ("áp chót") và mang theo 1:
1 1
1 3 3 7
+ 2 0 6 5
==============
0 2
Tiếp theo, chúng tôi thêm chữ số từ thứ ba đến cuối cùng ("đối đầu"):
1 1
1 3 3 7
+ 2 0 6 5
==============
4 0 2
Cuối cùng, chúng tôi thêm chữ số từ thứ tư đến cuối cùng ("tiền áp chót" ... Tôi yêu tiếng Anh):
1 1
1 3 3 7
+ 2 0 6 5
==============
3 4 0 2
Bây giờ, chúng ta đã làm được bao nhiêu việc? Chúng tôi thực hiện tổng số O (1) công việc cho mỗi chữ số (nghĩa là một lượng công việc không đổi) và có tổng số O (max {log n, log m}) cần được xử lý. Điều này cho tổng độ phức tạp O (max {log n, log m}), bởi vì chúng ta cần truy cập từng chữ số trong hai số.
Nhiều thuật toán nhận được số hạng O (log n) trong chúng từ việc làm việc một chữ số tại một thời điểm trong một cơ số nào đó. Một ví dụ cổ điển là sắp xếp cơ số , sắp xếp các số nguyên từng chữ số một. Có nhiều cách sắp xếp theo cơ số, nhưng chúng thường chạy trong thời gian O (n log U), trong đó U là số nguyên lớn nhất có thể được sắp xếp. Lý do cho điều này là mỗi lần vượt qua kiểu sắp xếp mất O (n) thời gian và có tổng số lần lặp O (log U) cần thiết để xử lý từng chữ số O (log U) của số lớn nhất được sắp xếp. Nhiều thuật toán nâng cao, chẳng hạn như thuật toán đường đi ngắn nhất của Gabow hoặc phiên bản mở rộng của thuật toán luồng tối đa Ford-Fulkerson , có thuật ngữ nhật ký về độ phức tạp của chúng vì chúng hoạt động một chữ số tại một thời điểm.
Đối với câu hỏi thứ hai của bạn về cách bạn giải quyết vấn đề đó, bạn có thể muốn xem câu hỏi liên quan này khám phá một ứng dụng nâng cao hơn. Với cấu trúc chung của các vấn đề được mô tả ở đây, bây giờ bạn có thể hiểu rõ hơn về cách suy nghĩ về các vấn đề khi bạn biết có một thuật ngữ nhật ký trong kết quả, vì vậy tôi khuyên bạn không nên xem câu trả lời cho đến khi bạn đưa ra. Một vài suy nghĩ.
Hi vọng điêu nay co ich!
O(log n)
có thể thấy như: Nếu bạn tăng gấp đôi kích thước bài toánn
, thuật toán của bạn chỉ cần thêm một số bước không đổi.