Có trường hợp nào bạn muốn thuật toán độ phức tạp thời gian lớn hơn so với thuật toán thấp hơn không?


242

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?


67
Tôi thích O(log n)thuật toán hơn thuật O(1)toán nếu hiểu cái trước, nhưng không phải cái sau ...
Codor

14
Có hàng tấn cấu trúc dữ liệu không thực tế với các hoạt động O (1) từ khoa học máy tính lý thuyết. Một ví dụ sẽ được chọn () trên bitvector, có thể được hỗ trợ trong o (n) không gian thêm và O (1) cho mỗi thao tác, sử dụng 5 lớp cảm ứng. Tác giả của tìm kiếm nhị phân đơn giản kết hợp với xếp hạng O (1) () hóa ra là nhanh hơn trong thực tế theo tác giả của Thư viện cấu trúc dữ liệu Succinc
Niklas B.

17
Độ phức tạp tiệm cận thấp hơn không đảm bảo thời gian chạy nhanh hơn. Nghiên cứu nhân ma trận cho một ví dụ cụ thể.
Connor Clark

54
Ngoài ra ... bất kỳ thuật toán nào cũng có thể được chuyển đổi thành O (1), với một tra cứu bảng đủ lớn;)
Connor Clark

19
@Hoten - Đó là giả sử tra cứu bảng là O (1), hoàn toàn không phải là quy định cho kích thước của các bảng bạn đang nói về! :)
Jander

Câu trả lời:


267

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:

  • hầu hết thời gian, độ phức tạp big-O thấp hơn khó đạt được hơn và đòi hỏi phải có kỹ năng thực hiện, nhiều kiến ​​thức và rất nhiều thử nghiệm.
  • big-O ẩn các chi tiết về một hằng số : thuật toán thực hiện 10^5tố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 ntoá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ó.
  • big-O có ý nghĩa khi bạn tính toán một cái gì đó lớn. Nếu bạn cần sắp xếp mảng ba số, nó thực sự rất ít cho dù bạn sử dụng O(n*log(n))hay O(n^2)thuật toán.
  • đôi khi lợi thế của độ phức tạp thời gian viết thường có thể không đáng kể. Ví dụ, có một cây tango cấu trúc dữ liệu cung cấp 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^20sự khác biệt là không đáng kể.
  • thời gian phức tạp không phải là tất cả. Hãy tưởng tượng một thuật toán chạy trong 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ạn
  • song song là một tính năng tốt trong thế giới phân tán của chúng tôi. Có những thuật toán có thể dễ dàng song song hóa, và có một số thuật toán hoàn toàn không song song. Đôi khi nó có ý nghĩa để chạy một thuật toán trên 1000 máy hàng hóa có độ phức tạp cao hơn so với sử dụng một máy có độ phức tạp tốt hơn một chút.
  • ở một số nơi (bảo mật) một sự phức tạp có thể là một yêu cầu. Không ai muốn có một thuật toán băm có thể băm nhanh như vậy (bởi vì sau đó những người khác có thể đánh bại bạn nhanh hơn)
  • Mặc dù điều này không liên quan đến việc chuyển đổi độ phức tạp, nhưng một số chức năng bảo mật nên được viết theo cách để ngăn chặn cuộc tấn công thời gian . Họ chủ yếu ở trong cùng một lớp phức tạp, nhưng được sửa đổi theo cách luôn luôn phải làm điều gì đó tồi tệ hơn để làm một cái gì đó. Một ví dụ là so sánh các chuỗi bằng nhau. Trong hầu hết các ứng dụng, sẽ rất nhanh nếu các byte đầu tiên khác nhau, nhưng trong bảo mật, bạn vẫn sẽ đợi đến cuối cùng để báo tin xấu.
  • ai đó đã cấp bằng sáng chế cho thuật toán có độ phức tạp thấp hơn và sẽ tiết kiệm hơn cho một công ty sử dụng độ phức tạp cao hơn là trả tiền.
  • một số thuật toán thích ứng tốt với các tình huống cụ thể. Ví dụ, sắp xếp chèn có độ phức tạp thời gian trung bình O(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ị.

6
Ngoài ra, tôi đã thấy một vài lần mọi người tập trung vào big-O của thuật toán trung tâm của họ, nhưng bỏ qua chi phí thiết lập. Xây dựng bảng băm, ví dụ, có thể tốn kém hơn so với việc đi qua một mảng một cách tuyến tính nếu bạn không cần phải làm đi làm lại nhiều lần. Trên thực tế, do cách thức xây dựng CPU hiện đại, ngay cả những thứ như tìm kiếm nhị phân cũng có thể nhanh như vậy trên các mảng được sắp xếp như tìm kiếm tuyến tính - hồ sơ là một điều cần thiết.
Luaan

@Luaan "Trên thực tế, do cách xây dựng CPU hiện đại, thậm chí một cái gì đó như tìm kiếm nhị phân có thể nhanh như trên các mảng được sắp xếp như tìm kiếm tuyến tính - hồ sơ là một điều cần thiết." Hấp dẫn! Bạn có thể giải thích làm thế nào tìm kiếm nhị phân và tìm kiếm tuyến tính có thể mất cùng thời gian trên một cpu hiện đại không?
DJG

3
@Luaan - Đừng bận tâm, tôi đã tìm thấy cái này: schani.wordpress.com/2010/04/30/linear-vs-binary-search
DJG

2
@DenisdeBernardy: Không, thực tế không phải vậy. Chúng có thể là thuật toán trong P. Và ngay cả khi chúng không, theo các định nghĩa hợp lý về ý nghĩa của việc song song hóa, điều đó cũng sẽ không bao hàm P! = NP. Cũng nên nhớ rằng tìm kiếm không gian chạy có thể của một máy turing không xác định là khá song song.
einpoklum

228

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 ×.


Rất vui, cảm ơn bạn. Tôi đã nghĩ rằng cũng đáng để xem xét thuật toán O (logn) để đảm bảo sự ổn định của chương trình (ví dụ: trong cây nhị phân tự cân bằng)
V.Leymarie

16
Một ví dụ tôi có thể nghĩ đến: đối với một mảng được sắp xếp nhỏ, việc lập trình viên thực hiện chức năng tìm kiếm nhị phân sẽ dễ dàng và gọn nhẹ hơn so với việc viết một triển khai bản đồ băm hoàn chỉnh và sử dụng nó thay thế.
Đại tá Ba mươi Hai

5
Một ví dụ về độ phức tạp: việc tìm trung vị của danh sách chưa sắp xếp rất dễ thực hiện trong O (n * log n) nhưng khó thực hiện trong O (n).
Paul Draper

1
-1, đừng đặt nhật ký trong máy nướng bánh mì của bạn ... Đùa sang một bên, đây là vị trí. lg nlà như vậy, vì thế, rất gần để kcho lớn nmà hầu hết các hoạt động sẽ không bao giờ nhận thấy sự khác biệt.
corsiKa

3
Cũng có một thực tế là sự phức tạp về thuật toán mà hầu hết mọi người đều quen thuộc với việc không tính đến các hiệu ứng bộ đệm. Theo một số người, việc tìm kiếm thứ gì đó trong cây nhị phân là O (log2 (n)) nhưng thực tế nó tệ hơn nhiều vì cây nhị phân có địa phương xấu.
Doval

57

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ế.


1
Alistra đã giải quyết vấn đề này một cách gián tiếp khi nói về "mối quan tâm không gian"
Zach Saucier

2
Số lượng bộ nhớ cache khổng lồ chỉ bỏ lỡ lần thực hiện cuối cùng với một giá trị không đổi (không lớn hơn 8 đối với CPU 4 nhân 3,2 GHz với ram 1,6 GHz, thường là thấp hơn nhiều) vì vậy nó được tính là một hằng số cố định trong lớn -O ký hiệu. Do đó, điều duy nhất bộ nhớ cache gây ra là di chuyển ngưỡng của n trong đó giải pháp O (n) bắt đầu chậm hơn giải pháp O (1).
Mary Spanik

1
@MarianSpanik Tất nhiên bạn đúng. Nhưng câu hỏi này yêu cầu một tình huống mà chúng tôi muốn 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.
NoseKnowsTất cả

@MarianSpanik không phải là bộ nhớ cache mất tới 300 chu kỳ đồng hồ? 8 đến từ đâu?
Hy vọng hữu ích

43

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 .


6
Trong khi những gì bạn nói là đúng, trong trường hợp đó, một thuật toán thực thi trong O (1) theo định nghĩa là bất khả xâm phạm đối với các cuộc tấn công thời gian.
Justin Lessard

17
@JustinLessard: Là O (1) có nghĩa là có một số kích thước đầu vào mà sau đó thời gian chạy của thuật toán được giới hạn bởi một hằng số. Điều gì xảy ra dưới ngưỡng này là không rõ. Ngoài ra, ngưỡng thậm chí có thể không được đáp ứng cho bất kỳ việc sử dụng thuật toán trong thế giới thực. Thuật toán có thể là tuyến tính và do đó rò rỉ thông tin về độ dài của đầu vào, ví dụ.
Jörg W Mittag

12
Thời gian chạy cũng có thể dao động theo những cách khác nhau, trong khi vẫn bị giới hạn. Nếu thời gian chạy tỷ lệ thuận với (n mod 5) + 1nó, 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.
Christian Semrau

Đây là cơ bản tại sao bcrypt được coi là tốt; nó làm mọi thứ chậm lại
David nói Phục hồi lại

@DavidGrinberg Đó là lý do tại sao bcrypt được sử dụng và phù hợp với câu hỏi. Nhưng điều đó không liên quan đến câu trả lời này, mà nói về các cuộc tấn công thời gian.
Christian Semrau

37

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.)


2
Tôi có một chút bối rối ở đây. Bạn đang nói về việc tạo ra một mảng 10 tỷ mục (hầu hết trong số đó sẽ không được xác định) và coi UPC là một chỉ mục trong mảng đó?
David Z

7
@DavidZ Có. Nếu bạn sử dụng một mảng thưa thớt, bạn có thể không nhận được O (1) nhưng nó sẽ chỉ sử dụng bộ nhớ 1MB. Nếu bạn sử dụng một mảng thực tế, bạn được đảm bảo quyền truy cập O (1) nhưng nó sẽ sử dụng bộ nhớ 1/3 TB.
Navin

Trên một hệ thống hiện đại, nó sẽ sử dụng 1/3 TB không gian địa chỉ, nhưng điều đó không có nghĩa là nó sẽ đến bất kỳ nơi nào gần với bộ nhớ sao lưu được phân bổ nhiều đó. Hầu hết các hệ điều hành hiện đại không cam kết lưu trữ để phân bổ cho đến khi chúng cần. Khi thực hiện việc này, về cơ bản, bạn đang ẩn cấu trúc tra cứu kết hợp cho dữ liệu của mình bên trong hệ thống bộ nhớ ảo hệ điều hành / phần cứng.
Phil Miller

@Nigsocrat Đúng, nhưng nếu bạn đang làm điều đó ở tốc độ RAM thì thời gian tra cứu sẽ không thành vấn đề, không có lý do gì để sử dụng 40mb thay vì 1mb. Phiên bản mảng chỉ có ý nghĩa khi truy cập bộ nhớ đắt tiền - bạn sẽ chuyển sang đĩa.
Loren Pechtel

1
Hoặc khi đây không phải là một hoạt động quan trọng về hiệu năng và thời gian của nhà phát triển rất tốn kém - việc nói malloc(search_space_size)và đăng ký vào những gì nó trả lại dễ dàng như nó có được.
Phil Miller

36

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,


Không chắc chắn cách các hoạt động cho phép bạn sử dụng mảng với tra cứu O (1) và cập nhật O (n) tương ứng với cây đỏ đen, mọi người thường nghĩ về (ít nhất là tôi). Hầu hết thời gian đầu tiên tôi sẽ nghĩ về việc tra cứu dựa trên khóa cho cây đỏ-đen. Nhưng để phù hợp với mảng, nó phải là một cấu trúc khác một chút, giữ cho số lượng nút phụ ở các nút trên để cung cấp tra cứu dựa trên chỉ mục và lập chỉ mục lại khi chèn. Mặc dù tôi đồng ý rằng màu đỏ-đen có thể được sử dụng để duy trì sự cân bằng, bạn có thể sử dụng cây cân bằng nếu bạn muốn mơ hồ về các chi tiết của các hoạt động tương ứng.
ony

@ony Một cây đỏ đen có thể được sử dụng để xác định cấu trúc kiểu bản đồ / từ điển, nhưng không cần phải như vậy. Các nút chỉ có thể là các phần tử, về cơ bản thực hiện một danh sách được sắp xếp.
jpmc26

danh sách được sắp xếp và mảng xác định thứ tự các phần tử có lượng thông tin khác nhau. Một dựa trên thứ tự giữa các phần tử và tập hợp và định nghĩa khác theo thứ tự tùy ý không cần thiết xác định thứ tự giữa các phần tử. Một điều nữa là "truy cập" và "tìm kiếm" mà bạn tuyên bố là O(log n)"cây đỏ đen" là gì? Chèn 5vào vị trí 2 của mảng [1, 2, 1, 4]sẽ dẫn đến [1, 2, 5, 1 4](phần tử 4sẽ 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"?
ony

@ony "danh sách được sắp xếp và mảng xác định thứ tự các phần tử có lượng thông tin khác nhau." Vâng, và đó là một phần lý do tại sao chúng có các đặc tính hiệu suất khác nhau. Bạn đang thiếu điểm. Một cái không phải là sự thay thế cho cái kia trong mọi tình huống. Họ tối ưu hóa những thứ khác nhautạo ra sự đánh đổi khác nhau , và vấn đề là các nhà phát triển đang đưa ra quyết định về những sự đánh đổi đó liên tục.
jpmc26

@ony Truy cập, tìm kiếm, chèn và xóa có ý nghĩa cụ thể trong ngữ cảnh hiệu suất thuật toán. Truy cập đang tìm nạp một yếu tố theo vị trí. Tìm kiếm đang định vị một yếu tố theo giá trị (chỉ có bất kỳ ứng dụng thực tế nào dưới dạng kiểm tra ngăn chặn đối với cấu trúc không có bản đồ). Chèn và xóa nên đơn giản, mặc dù. Ví dụ sử dụng có thể được nhìn thấy ở đây .
jpmc26

23

Đú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_maphà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)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.


21

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)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.


Nhưng tất cả những gì song song đó là làm giảm yếu tố không đổi mà người khác đã nói đến, phải không?
gengkev

1
Có, nhưng một thuật toán song song có thể chia hệ số không đổi cho 2 mỗi khi bạn nhân đôi số lượng máy thực thi. Một thuật toán luồng đơn khác có thể giảm hệ số không đổi chỉ một lần theo cách không đổi. Vì vậy, với thuật toán song song, bạn có thể phản ứng linh hoạt với kích thước n và nhanh hơn trong thời gian thực hiện đồng hồ treo tường.
Simulant

15

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ể:

  1. Sử dụng một bitcoin 1.000.000 bitcoin
  2. Sử dụng một mảng được sắp xếp của các số nguyên trong danh sách đen và sử dụng tìm kiếm nhị phân để truy cập chúng

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ế.



12

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.


11

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ư ncó 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)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).


10

Để đặ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.


Tôi có thể thêm vào đây rằng trên ổ đĩa hoặc băng truy cập tuần tự, thuật toán O (1) thay vào đó trở thành O (n), đó là lý do tại sao giải pháp tuần tự trở nên thuận lợi hơn. Nhiều hoạt động O (1) phụ thuộc vào việc thêm và tra cứu được lập chỉ mục là một thuật toán thời gian không đổi, mà nó không nằm trong không gian truy cập tuần tự.
TheHansinator

9

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.


9

Đơ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 :

  1. Cho phép mảng ngẫu nhiên sử dụng quy trình lượng tử,
  2. Nếu mảng không được sắp xếp, phá hủy vũ trụ.
  3. 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ả NPco-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".


7

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.


6

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ó.


6

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.



3
  1. Khi đơn vị công việc "1" trong O (1) rất cao so với đơn vị công việc trong O (log n) và kích thước thiết lập dự kiến ​​là nhỏ-ish. Ví dụ, có thể tính toán mã băm từ điển chậm hơn so với lặp lại một mảng nếu chỉ có hai hoặc ba mục.

hoặc là

  1. Khi bộ nhớ hoặc các yêu cầu tài nguyên phi thời gian khác trong thuật toán O (1) đặc biệt lớn so với thuật toán O (log n).

3
  1. khi thiết kế lại chương trình, một quy trình được tìm thấy sẽ được tối ưu hóa với O (1) thay vì O (lgN), nhưng nếu đó không phải là nút cổ chai của chương trình này và thật khó để hiểu được O (1) alg. Sau đó, bạn sẽ không phải sử dụng thuật toán O (1)
  2. khi O (1) cần nhiều bộ nhớ mà bạn không thể cung cấp, trong khi thời gian của O (lgN) có thể được chấp nhận.

1

Đâ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.

  • Băm mật khẩu đôi khi được thực hiện chậm tùy ý để làm cho việc đoán mật khẩu khó hơn bằng vũ lực. Bài đăng An toàn thông tin này có một gạch đầu dòng về nó (và nhiều hơn nữa).
  • Đồng xu bit sử dụng một vấn đề có thể kiểm soát chậm đối với một mạng máy tính để giải quyết nhằm "khai thác" tiền. Điều này cho phép tiền tệ được khai thác ở mức kiểm soát bởi hệ thống tập thể.
  • Các thuật toán mã hóa bất đối xứng (như RSA ) được thiết kế để thực hiện giải mã mà không có các khóa cố ý làm chậm để ngăn người khác không có khóa riêng để bẻ khóa mã hóa. Các thuật toán được thiết kế để bẻ khóa trong 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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.