Có trường hợp nào bạn muốn O(log n)
độ phức tạp O(1)
thời gian hơn độ phức tạp thời gian không? Hay O(n)
đến O(log n)
?
bạn có bất kì ví dụ nào không?
Có trường hợp nào bạn muốn O(log n)
độ phức tạp O(1)
thời gian hơn độ phức tạp thời gian không? Hay O(n)
đến O(log n)
?
bạn có bất kì ví dụ nào không?
Câu trả lời:
Có thể có nhiều lý do để thích một thuật toán có độ phức tạp thời gian O lớn hơn so với thuật toán thấp hơn:
10^5
tốt hơn từ quan điểm của big-O so với 1/10^5 * log(n)
( O(1)
so với O(log(n)
), nhưng đối với hợp lý nhất, thuật n
toán đầu tiên sẽ hoạt động tốt hơn. Ví dụ, độ phức tạp tốt nhất cho phép nhân ma trận là O(n^2.373)
nhưng hằng số cao đến mức không có thư viện tính toán nào (theo hiểu biết của tôi) sử dụng nó.O(n*log(n))
hay O(n^2)
thuật toán.O(log log N)
độ phức tạp về thời gian để tìm một mục, nhưng cũng có một cây nhị phân tìm thấy cùng một thứ O(log n)
. Ngay cả đối với số lượng lớn của n = 10^20
sự khác biệt là không đáng kể.O(n^2)
và yêu cầu O(n^2)
bộ nhớ. Có thể tốt hơn theo O(n^3)
thời gian và O(1)
không gian khi n không thực sự lớn. Vấn đề là bạn có thể chờ đợi trong một thời gian dài, nhưng rất nghi ngờ bạn có thể tìm thấy một RAM đủ lớn để sử dụng nó với thuật toán của bạnO(n^2)
, tệ hơn quicksort hoặc sáp nhập, nhưng là một thuật toán trực tuyến, nó có thể sắp xếp một danh sách các giá trị khi chúng nhận được (như đầu vào của người dùng) trong đó hầu hết các thuật toán khác chỉ có thể hoạt động hiệu quả trên một danh sách đầy đủ các giá trị.Luôn có hằng số ẩn, có thể thấp hơn trên thuật toán O (log n ). Vì vậy, nó có thể hoạt động nhanh hơn trong thực tế cho dữ liệu thực tế.
Cũng có những lo ngại về không gian (ví dụ như chạy trên máy nướng bánh mì).
Ngoài ra còn có mối quan tâm về thời gian của nhà phát triển - O (log n ) có thể dễ dàng thực hiện và xác minh hơn 1000 ×.
lg n
là như vậy, vì thế, rất gần để k
cho lớn n
mà hầu hết các hoạt động sẽ không bao giờ nhận thấy sự khác biệt.
Tôi ngạc nhiên không ai đề cập đến các ứng dụng giới hạn bộ nhớ.
Có thể có một thuật toán có ít thao tác dấu phẩy động hơn do độ phức tạp của nó (tức là O (1) < O (log n )) hoặc do hằng số ở phía trước độ phức tạp nhỏ hơn (tức là 2 n 2 <6 n 2 ) . Bất kể, bạn vẫn có thể thích thuật toán có nhiều FLOP hơn nếu thuật toán FLOP thấp hơn có nhiều bộ nhớ hơn.
Ý tôi là "giới hạn bộ nhớ" là bạn thường truy cập dữ liệu liên tục hết bộ nhớ cache. Để tìm nạp dữ liệu này, bạn phải kéo bộ nhớ từ không gian bộ nhớ thực của bạn vào bộ đệm trước khi bạn có thể thực hiện thao tác trên đó. Bước tìm nạp này thường khá chậm - chậm hơn nhiều so với hoạt động của chính bạn.
Do đó, nếu thuật toán của bạn yêu cầu nhiều thao tác hơn (nhưng các thao tác này được thực hiện trên dữ liệu đã có trong bộ đệm [và do đó không cần tìm nạp]), thuật toán vẫn sẽ thực hiện thuật toán của bạn với ít thao tác hơn (phải được thực hiện khi hết -cache data [và do đó yêu cầu tìm nạp]) về mặt thời gian thực tế.
O(logn)
hơn O(1)
. Bạn có thể dễ dàng tưởng tượng ra một tình huống trong đó đối với tất cả tính khả thi của bạn n
, ứng dụng ít bộ nhớ sẽ chạy trong thời gian nhanh hơn, thậm chí ở độ phức tạp cao hơn.
Trong bối cảnh mà bảo mật dữ liệu là mối quan tâm, một thuật toán phức tạp hơn có thể thích hợp hơn với thuật toán ít phức tạp hơn nếu thuật toán phức tạp hơn có khả năng chống lại các cuộc tấn công thời gian tốt hơn .
(n mod 5) + 1
nó, nó vẫn còn O(1)
, nhưng tiết lộ thông tin về n
. Vì vậy, một thuật toán phức tạp hơn với thời gian chạy mượt mà hơn có thể được ưa thích hơn, ngay cả khi nó có thể không có triệu chứng (và thậm chí có thể trong thực tế) chậm hơn.
Alistra đóng đinh nó nhưng không cung cấp bất kỳ ví dụ nào nên tôi sẽ làm.
Bạn có một danh sách 10.000 mã UPC cho những gì cửa hàng của bạn bán. UPC 10 chữ số, số nguyên cho giá (giá bằng đồng xu) và 30 ký tự mô tả cho hóa đơn.
Phương pháp O (log N): Bạn có một danh sách được sắp xếp. 44 byte nếu ASCII, 84 nếu Unicode. Cách khác, coi UPC là int64 và bạn nhận được 42 & 72 byte. 10.000 bản ghi - trong trường hợp cao nhất bạn đang xem xét một chút dưới một megabyte dung lượng lưu trữ.
Cách tiếp cận O (1): Không lưu trữ UPC, thay vào đó bạn sử dụng nó làm mục nhập vào mảng. Trong trường hợp thấp nhất, bạn đang xem xét gần một phần ba terabyte dung lượng lưu trữ.
Cách tiếp cận bạn sử dụng phụ thuộc vào phần cứng của bạn. Trên hầu hết mọi cấu hình hiện đại hợp lý, bạn sẽ sử dụng phương pháp log N. Tôi có thể hình dung cách tiếp cận thứ hai là câu trả lời đúng nếu vì lý do nào đó bạn đang chạy trong môi trường mà RAM rất ngắn nhưng bạn có nhiều bộ nhớ lớn. Một phần ba terabyte trên đĩa không phải là vấn đề lớn, việc lấy dữ liệu của bạn trong một đầu dò của đĩa là điều đáng giá. Cách tiếp cận nhị phân đơn giản mất 13 trung bình. (Tuy nhiên, lưu ý rằng bằng cách phân cụm các phím của bạn, bạn có thể chuyển xuống 3 lần đọc được bảo đảm và trong thực tế, bạn sẽ lưu trữ bộ đệm đầu tiên.)
malloc(search_space_size)
và đăng ký vào những gì nó trả lại dễ dàng như nó có được.
Hãy xem xét một cây đỏ-đen. Nó có quyền truy cập, tìm kiếm, chèn và xóa O(log n)
. So sánh với một mảng, có quyền truy cập O(1)
và phần còn lại của các hoạt động O(n)
.
Vì vậy, đưa ra một ứng dụng nơi chúng tôi chèn, xóa hoặc tìm kiếm thường xuyên hơn chúng tôi truy cập và lựa chọn giữa chỉ hai cấu trúc này, chúng tôi sẽ thích cây đỏ đen. Trong trường hợp này, bạn có thể nói rằng chúng tôi thích O(log n)
thời gian truy cập cồng kềnh hơn của cây đỏ đen .
Tại sao? Bởi vì quyền truy cập không phải là mối quan tâm hàng đầu của chúng tôi. Chúng tôi đang đánh đổi: hiệu suất của ứng dụng của chúng tôi bị ảnh hưởng nặng nề hơn bởi các yếu tố khác ngoài yếu tố này. Chúng tôi cho phép thuật toán cụ thể này chịu hiệu suất vì chúng tôi kiếm được lợi nhuận lớn bằng cách tối ưu hóa các thuật toán khác.
Vì vậy, câu trả lời cho câu hỏi của bạn chỉ đơn giản là thế này: khi tốc độ tăng trưởng của thuật toán không phải là điều chúng tôi muốn tối ưu hóa , khi chúng ta muốn tối ưu hóa thứ khác . Tất cả các câu trả lời khác là trường hợp đặc biệt của điều này. Đôi khi chúng tôi tối ưu hóa thời gian chạy của các hoạt động khác. Đôi khi chúng tôi tối ưu hóa cho bộ nhớ. Đôi khi chúng tôi tối ưu hóa để bảo mật. Đôi khi chúng tôi tối ưu hóa khả năng bảo trì. Đôi khi chúng tôi tối ưu hóa cho thời gian phát triển. Ngay cả hằng số ghi đè đủ thấp để quan trọng hóa là tối ưu hóa thời gian chạy khi bạn biết tốc độ tăng trưởng của thuật toán không phải là tác động lớn nhất đến thời gian chạy. (Nếu tập dữ liệu của bạn nằm ngoài phạm vi này, bạn sẽ tối ưu hóa cho tốc độ tăng trưởng của thuật toán vì cuối cùng nó sẽ thống trị hằng số.) Mọi thứ đều có chi phí, và trong nhiều trường hợp,
O(log n)
"cây đỏ đen" là gì? Chèn 5
vào vị trí 2 của mảng [1, 2, 1, 4]
sẽ dẫn đến [1, 2, 5, 1 4]
(phần tử 4
sẽ nhận được chỉ mục được cập nhật từ 3 đến 4). Làm thế nào bạn có được hành vi này trong O(log n)
"cây đỏ đen" mà bạn tham chiếu là "danh sách được sắp xếp"?
Đúng.
Trong một trường hợp thực tế, chúng tôi đã thực hiện một số thử nghiệm khi thực hiện tra cứu bảng với cả các chuỗi chuỗi ngắn và dài.
Chúng tôi đã sử dụng một std::map
, một std::unordered_map
hàm băm lấy mẫu nhiều nhất gấp 10 lần chiều dài của chuỗi (các khóa của chúng tôi có xu hướng giống như hướng dẫn, vì vậy điều này là tốt) và một hàm băm lấy mẫu mọi ký tự (về lý thuyết giảm va chạm), một vectơ chưa được sắp xếp trong đó chúng ta thực hiện ==
so sánh và (nếu tôi nhớ chính xác) một vectơ chưa được sắp xếp trong đó chúng ta cũng lưu trữ một hàm băm, trước tiên so sánh hàm băm, sau đó so sánh các ký tự.
Các thuật toán này có phạm vi từ O(1)
(unordered_map) đến O(n)
(tìm kiếm tuyến tính).
Đối với N có kích thước khiêm tốn, khá thường xuyên O (n) đánh bại O (1). Chúng tôi nghi ngờ điều này là do các bộ chứa dựa trên nút yêu cầu máy tính của chúng tôi nhảy xung quanh trong bộ nhớ nhiều hơn, trong khi các bộ chứa dựa trên tuyến tính thì không.
O(lg n)
tồn tại giữa hai. Tôi không nhớ nó đã làm như thế nào.
Sự khác biệt về hiệu năng không phải là lớn và trên dữ liệu lớn hơn, bộ dữ liệu dựa trên hàm băm hoạt động tốt hơn nhiều. Vì vậy, chúng tôi bị mắc kẹt với bản đồ không có thứ tự dựa trên hàm băm.
Trong thực tế, cho kích thước hợp lý n, O(lg n)
là O(1)
. Nếu máy tính của bạn chỉ có đủ chỗ cho 4 tỷ mục trong bảng của bạn, thì O(lg n)
bị giới hạn ở trên 32
. (lg (2 ^ 32) = 32) (trong khoa học máy tính, lg là viết tắt của nhật ký dựa trên 2).
Trong thực tế, thuật toán lg (n) chậm hơn thuật toán O (1) không phải do yếu tố tăng trưởng logarit, mà bởi vì phần lg (n) thường có nghĩa là có một mức độ phức tạp nhất định đối với thuật toán và độ phức tạp đó thêm vào hệ số hằng lớn hơn bất kỳ "tăng trưởng" nào từ thuật ngữ lg (n).
Tuy nhiên, các thuật toán O (1) phức tạp (như ánh xạ băm) có thể dễ dàng có hệ số hằng tương tự hoặc lớn hơn.
Khả năng thực hiện một thuật toán song song.
Tôi không biết nếu có là một ví dụ cho các lớp O(log n)
và O(1)
, nhưng đối với một số vấn đề, bạn chọn một thuật toán với một lớp phức tạp cao hơn khi các thuật toán là dễ dàng hơn để thực hiện song song.
Một số thuật toán không thể song song nhưng có độ phức tạp thấp. Xem xét một thuật toán khác đạt được kết quả tương tự và có thể được song song dễ dàng, nhưng có lớp phức tạp cao hơn. Khi được thực thi trên một máy, thuật toán thứ hai chậm hơn, nhưng khi được thực thi trên nhiều máy, thời gian thực hiện thực tế sẽ ngày càng thấp hơn trong khi thuật toán thứ nhất không thể tăng tốc.
Giả sử bạn đang thực hiện một danh sách đen trên một hệ thống nhúng, trong đó các số từ 0 đến 1.000.000 có thể được đưa vào danh sách đen. Điều đó cho bạn hai tùy chọn có thể:
Truy cập vào bitet sẽ có quyền truy cập liên tục được đảm bảo. Về mặt phức tạp thời gian, nó là tối ưu. Cả hai từ một lý thuyết và từ một quan điểm thực tế (đó là O (1) với chi phí không đổi cực kỳ thấp).
Tuy nhiên, bạn có thể muốn giải pháp thứ hai. Đặc biệt nếu bạn mong đợi số lượng danh sách đen sẽ rất nhỏ, vì nó sẽ hiệu quả hơn về bộ nhớ.
Và ngay cả khi bạn không phát triển cho một hệ thống nhúng mà bộ nhớ khan hiếm, tôi chỉ có thể tăng giới hạn tùy ý từ 1.000.000 lên 1.000.000.000.000 và đưa ra lập luận tương tự. Sau đó, bitet sẽ cần khoảng 125G bộ nhớ. Có độ phức tạp trong trường hợp xấu nhất được bảo đảm là O (1) có thể không thuyết phục được sếp của bạn cung cấp cho bạn một máy chủ mạnh mẽ như vậy.
Ở đây, tôi rất thích tìm kiếm nhị phân (O (log n)) hoặc cây nhị phân (O (log n)) trên bit O (1). Và có lẽ, một bảng băm với độ phức tạp trong trường hợp xấu nhất là O (n) sẽ đánh bại tất cả chúng trong thực tế.
Câu trả lời của tôi ở đây Lựa chọn trọng số ngẫu nhiên nhanh trên tất cả các hàng của ma trận ngẫu nhiên là một ví dụ trong đó thuật toán có độ phức tạp O (m) nhanh hơn một thuật toán có độ phức tạp O (log (m)), khi m
không quá lớn.
Mọi người đã trả lời chính xác câu hỏi của bạn, vì vậy tôi sẽ giải quyết một câu hỏi hơi khác mà mọi người thực sự có thể nghĩ đến khi đến đây.
Rất nhiều thuật toán và cấu trúc dữ liệu "O (1) thời gian" thực sự chỉ mất thời gian O (1) dự kiến , có nghĩa là thời gian chạy trung bình của chúng là O (1), có thể chỉ theo một số giả định nhất định.
Ví dụ phổ biến: hashtables, mở rộng "danh sách mảng" (còn gọi là mảng / vectơ có kích thước động).
Trong các trường hợp như vậy, bạn có thể thích sử dụng các cấu trúc dữ liệu hoặc thuật toán có thời gian được đảm bảo hoàn toàn bị ràng buộc theo logarit, mặc dù trung bình chúng có thể hoạt động kém hơn.
Do đó, một ví dụ có thể là một cây tìm kiếm nhị phân cân bằng, có thời gian chạy trung bình kém hơn nhưng tốt hơn trong trường hợp xấu nhất.
Một câu hỏi tổng quát hơn là nếu có những tình huống mà người ta sẽ thích một O(f(n))
thuật toán để một O(g(n))
thuật toán mặc dù g(n) << f(n)
như n
có xu hướng đến vô cùng. Như những người khác đã đề cập, câu trả lời rõ ràng là "có" trong trường hợp f(n) = log(n)
và g(n) = 1
. Nó đôi khi có ngay cả trong trường hợp f(n)
là đa thức nhưng g(n)
là cấp số nhân. Một ví dụ nổi tiếng và quan trọng là Thuật toán Simplex để giải các bài toán lập trình tuyến tính. Trong những năm 1970 nó đã được hiển thị O(2^n)
. Vì vậy, hành vi trường hợp xấu hơn của nó là không thể. Nhưng - trường hợp trung bình hành vi là cực kỳ tốt, ngay cả đối với các vấn đề thực tế với hàng chục ngàn biến và ràng buộc. Trong những năm 1980, các thuật toán thời gian đa thức (như vậythuật toán điểm bên trong của Karmarkar ) cho lập trình tuyến tính đã được phát hiện, nhưng 30 năm sau, thuật toán đơn giản dường như vẫn là thuật toán được lựa chọn (ngoại trừ một số vấn đề rất lớn). Điều này là vì lý do rõ ràng rằng hành vi trường hợp trung bình thường quan trọng hơn hành vi trường hợp xấu hơn, nhưng cũng vì một lý do tinh tế hơn rằng thuật toán đơn giản theo một nghĩa nào đó có nhiều thông tin hơn (ví dụ thông tin nhạy cảm dễ trích xuất hơn).
Để đặt 2 xu của tôi vào:
Đôi khi một thuật toán phức tạp tồi tệ hơn được chọn thay cho thuật toán tốt hơn, khi thuật toán chạy trên một môi trường phần cứng nhất định. Giả sử thuật toán O (1) của chúng tôi không truy cập tuần tự mọi phần tử của một mảng có kích thước cố định rất lớn để giải quyết vấn đề của chúng tôi. Sau đó đặt mảng đó vào ổ cứng cơ học, hoặc băng từ.
Trong trường hợp đó, thuật toán O (logn) (giả sử nó truy cập đĩa liên tục), trở nên thuận lợi hơn.
Có một trường hợp sử dụng tốt để sử dụng thuật toán O (log (n)) thay vì thuật toán O (1) mà nhiều câu trả lời khác đã bỏ qua: tính bất biến. Bản đồ băm có O (1) đặt và nhận, giả sử phân phối tốt các giá trị băm, nhưng chúng yêu cầu trạng thái có thể thay đổi. Bản đồ cây bất biến có O (log (n)) đặt và nhận, tốc độ này không có triệu chứng. Tuy nhiên, tính bất biến có thể đủ giá trị để bù đắp cho hiệu suất kém hơn và trong trường hợp cần giữ lại nhiều phiên bản của bản đồ, tính bất biến cho phép bạn tránh phải sao chép bản đồ, đó là O (n), và do đó có thể cải thiện hiệu suất.
Đơn giản: Bởi vì hệ số - chi phí liên quan đến thiết lập, lưu trữ và thời gian thực hiện của bước đó - có thể lớn hơn nhiều, với một vấn đề O lớn nhỏ hơn so với vấn đề lớn hơn. Big-O chỉ là thước đo khả năng mở rộng của thuật toán .
Hãy xem xét ví dụ sau từ Từ điển của Hacker, đề xuất một thuật toán sắp xếp dựa trên Giải thích cơ học lượng tử nhiều thế giới :
- Cho phép mảng ngẫu nhiên sử dụng quy trình lượng tử,
- Nếu mảng không được sắp xếp, phá hủy vũ trụ.
- Tất cả các vũ trụ còn lại hiện đang được sắp xếp [bao gồm cả vũ trụ bạn đang ở].
(Nguồn: http://catb.org/~esr/jargon/html/B/bogo-sort.html )
Lưu ý rằng big-O của thuật toán này là O(n)
, vượt qua mọi thuật toán sắp xếp đã biết cho đến nay trên các mục chung. Hệ số của bước tuyến tính cũng rất thấp (vì nó chỉ là so sánh, không phải là hoán đổi, được thực hiện tuyến tính). Trên thực tế, một thuật toán tương tự có thể được sử dụng để giải quyết bất kỳ vấn đề nào trong cả NP và co-NP trong thời gian đa thức, vì mỗi giải pháp có thể (hoặc bằng chứng có thể là không có giải pháp) có thể được tạo bằng quy trình lượng tử, sau đó được xác minh trong thời gian đa thức.
Tuy nhiên, trong hầu hết các trường hợp, có lẽ chúng tôi không muốn mạo hiểm rằng Nhiều thế giới có thể không chính xác, chưa kể rằng hành động thực hiện bước 2 vẫn "để lại như một bài tập cho người đọc".
Tại bất kỳ điểm nào khi n bị giới hạn và hệ số nhân không đổi của thuật toán O (1) cao hơn giới hạn trên log (n). Ví dụ: lưu trữ giá trị trong hàm băm là O (1), nhưng có thể yêu cầu tính toán đắt tiền của hàm băm. Nếu các mục dữ liệu có thể được so sánh một cách tầm thường (đối với một số thứ tự) và ràng buộc trên n sao cho log n nhỏ hơn đáng kể so với tính toán băm trên bất kỳ một mục nào, thì việc lưu trữ trong cây nhị phân cân bằng có thể nhanh hơn lưu trữ trong một hàm băm.
Trong tình huống thời gian thực khi bạn cần một giới hạn trên chắc chắn, bạn sẽ chọn ví dụ như một heapsort trái ngược với Quicksort, bởi vì hành vi trung bình của heapsort cũng là hành vi tồi tệ nhất của nó.
Thêm vào các câu trả lời đã tốt. Một ví dụ thực tế sẽ là các chỉ mục Hash so với các chỉ mục cây B trong cơ sở dữ liệu postgres.
Các chỉ mục băm tạo thành một chỉ mục bảng băm để truy cập dữ liệu trên đĩa trong khi btree như tên cho thấy sử dụng cấu trúc dữ liệu Btree.
Trong thời gian Big-O, đây là O (1) so với O (logN).
Các chỉ mục băm hiện không được khuyến khích trong các postgres vì trong tình huống thực tế đặc biệt là trong các hệ thống cơ sở dữ liệu, việc băm mà không va chạm là rất khó (có thể dẫn đến độ phức tạp trường hợp xấu nhất O (N)) và vì điều này, thậm chí còn khó hơn để thực hiện chúng sụp đổ an toàn (được gọi là ghi trước khi đăng nhập - WAL trong postgres).
Sự đánh đổi này được thực hiện trong tình huống này vì O (logN) đủ tốt cho các chỉ mục và thực hiện O (1) là khá khó khăn và sự khác biệt về thời gian sẽ không thực sự quan trọng.
hoặc là
Đây thường là trường hợp cho các ứng dụng bảo mật mà chúng tôi muốn thiết kế các vấn đề có thuật toán chậm nhằm mục đích ngăn chặn ai đó nhận được câu trả lời cho một vấn đề quá nhanh.
Dưới đây là một vài ví dụ ngoài đỉnh đầu của tôi.
O(2^n)
thời gian hy vọng n
độ dài của khóa (đây là lực lượng vũ phu).Ở những nơi khác trong CS, Quick Sort là O(n^2)
trong trường hợp xấu nhất nhưng trong trường hợp chung là O(n*log(n))
. Vì lý do này, phân tích "Big O" đôi khi không phải là điều duy nhất bạn quan tâm khi phân tích hiệu quả thuật toán.
O(log n)
thuật toán hơn thuậtO(1)
toán nếu hiểu cái trước, nhưng không phải cái sau ...