Câu hỏi lớn về một thuật toán có tốc độ tăng trưởng (n ^ 2 + n) / 2


16

Tôi đang hỏi câu hỏi này vì tôi bối rối về một khía cạnh liên quan đến ký hiệu O lớn.

Tôi đang sử dụng cuốn sách, Cấu trúc dữ liệu và Trừu tượng với Java của Frank Carrano. Trong chương về "Hiệu quả của thuật toán", ông trình bày thuật toán sau:

int sum = 0, i = 1, j = 1
for (i = 1 to n) {
    for (j = 1 to i)
        sum = sum + 1
}

Ông ban đầu mô tả thuật toán này có tốc độ tăng trưởng là (n 2  + n) / 2 . Mà nhìn vào có vẻ trực quan.

Tuy nhiên, sau đó được tuyên bố rằng (n 2  + n) / 2 hoạt động như n 2 khi n lớn. Trong cùng một đoạn ông nói (n 2  + n) / 2 cũng cư xử giống như n 2 / 2 . Ông sử dụng điều này để phân loại thuật toán trên là O (n 2 ) .

Tôi nhận được rằng (n 2  + n) / 2 cũng tương tự như n 2 / 2 vì tỷ lệ khôn ngoan, n làm cho sự khác biệt nhỏ. Điều tôi không nhận được là tại sao (n 2  + n) / 2n 2 giống nhau, khi n lớn.

Ví dụ: nếu n = 1.000.000 :

(n^2 + n) / 2 =  500000500000 (5.000005e+11)
(n^2) / 2     =  500000000000 (5e+11)
(n^2)         = 1000000000000 (1e+12)

Cái cuối cùng không giống nhau chút nào. Trên thực tế, khá rõ ràng, nó gấp đôi so với giữa. Vậy làm thế nào Frank Carrano có thể nói họ giống nhau? Ngoài ra, thuật toán được phân loại là O (n 2 ) như thế nào . Nhìn vào vòng lặp bên trong đó tôi sẽ nói đó là n 2 + n / 2


Nếu bạn quan tâm tôi đã đưa ra câu trả lời cho ba vòng lặp lồng nhau với sơ đồ cây thực thi Kiểm tra một câu đố liên quan đến các vòng lặp lồng nhau
Grijesh Chauhan



1
về cơ bản, ý tưởng là khi nphát triển, cả hai chức năng 'n ^ 2` và chức năng của bạn, hoạt động tương tự nhau, có một sự khác biệt không đổi trong tốc độ tăng trưởng của chúng. Nếu bạn có một biểu thức phức tạp, chức năng phát triển nhanh hơn chiếm ưu thế.
AK_

1
@MichaelT: Tôi không nghĩ đây là một bản sao của câu hỏi đó, vì câu hỏi kia chỉ là vấn đề đếm sai. Đây là một câu hỏi tinh tế hơn về lý do tại sao các thuật ngữ nhỏ hơn (cụ thể, bội số không đổi và đa thức bậc thấp hơn) bị bỏ qua. Người hỏi ở đây dường như đã hiểu vấn đề được nêu ra trong câu hỏi khác, và một câu trả lời đủ cho câu hỏi đó sẽ không trả lời câu hỏi này.
sdenham

Câu trả lời:


38

Khi tính toán độ phức tạp Big-O của một thuật toán, điều được hiển thị là yếu tố đóng góp lớn nhất cho việc tăng thời gian thực hiện nếu số lượng phần tử mà bạn chạy thuật toán tăng lên.

Nếu bạn có một thuật toán có độ phức tạp (n^2 + n)/2và bạn nhân đôi số phần tử, thì hằng số 2không ảnh hưởng đến việc tăng thời gian thực hiện, thuật ngữ này ngây ra sự nhân đôi trong thời gian thực hiện và thuật ngữ n^2gây ra sự gia tăng gấp bốn lần khi thực hiện thời gian.
n^2thuật ngữ này có đóng góp lớn nhất, độ phức tạp của Big-O là O(n^2).


2
Tôi thích điều đó, nó trở nên rõ ràng hơn một chút.
Andrew S

7
Đây là rất lượn sóng tay. Có thể đúng hoặc có thể sai. Nếu bạn có thể mất một lượng nhỏ toán học, hãy xem một trong những câu trả lời dưới đây.
usr

2
Lý do này quá mơ hồ: nó có nghĩa là chúng ta có thể kết luận rằng O(n * log n) = O(n), điều đó không đúng.
cfh

Nó có thể không phải là câu trả lời chính xác nhất hoặc đúng nhất về mặt ngữ nghĩa, nhưng điều quan trọng ở đây là nó khiến tôi bắt đầu hiểu được điểm trung tâm và tôi nghĩ đó là mục tiêu của tác giả. Nó cố tình mơ hồ vì các chi tiết thường có thể làm sao lãng các nguyên tắc cốt lõi. Điều quan trọng là nhìn thấy gỗ cho cây.
Andrew S

Bart đã thực sự nói về các điều khoản, không phải các yếu tố. Hiểu được điều đó, chúng ta không thể kết luận điều đó O(n * log n) = O(n). Tôi nghĩ rằng điều này đưa ra một lời giải thích tốt về lý do đằng sau định nghĩa.
Đánh dấu Foskey

10

Định nghĩa là

f(n) = O(g(n))

nếu tồn tại một số hằng số C> 0 sao cho tất cả n lớn hơn một số n_0, chúng ta có

|f(n)| <= C * |g(n)|

Điều này rõ ràng đúng với f (n) = n ^ 2 và g (n) = 1/2 n ^ 2, trong đó hằng số C phải là 2. Cũng dễ dàng nhận thấy rằng nó đúng với f (n) = n ^ 2 và g (n) = 1/2 (n ^ 2 + n).


4
"Nếu tồn tại một số hằng số C> 0 sao cho, tất cả n," sẽ là "Nếu có một số hằng số C, n_0 sao cho tất cả n> n_0,"
Taemyr

@Taemyr: Miễn là hàm gkhông khác, điều đó thực sự không cần thiết vì bạn luôn có thể tăng hằng số C để làm cho câu lệnh đúng với nhiều giá trị n_0 đầu tiên.
cfh

Không, chúng tôi miễn là chúng tôi đang xem xét các chức năng thì không có số lượng hữu hạn của các giá trị n_0 tiềm năng.
Taemyr

@Taemyr: n_0 là một số hữu hạn. Chọn C = max {f (i) / g (i): i = 1, ..., n_0}, và sau đó câu lệnh sẽ luôn giữ cho các giá trị n_0 đầu tiên, vì bạn có thể dễ dàng kiểm tra.
cfh

Trong CS, điều này ít được quan tâm vì n thường là kích thước đầu vào và do đó kín đáo. Trong trường hợp đó, người ta có thể chọn C sao cho n_0 = 1 hoạt động. Nhưng định nghĩa chính thức là bất kỳ n nào lớn hơn một số ngưỡng, loại bỏ toàn bộ quá trình nitpicking trong việc áp dụng định nghĩa.
Taemyr

6

Khi nói về độ phức tạp, bạn chỉ quan tâm đến thay đổi yếu tố thời gian dựa trên số lượng yếu tố ( n).

Như vậy, bạn có thể loại bỏ bất kỳ yếu tố không đổi (như 2ở đây).

Điều này để lại cho bạn với O(n^2 + n).

Bây giờ, đối với một nsản phẩm lớn hợp lý , nghĩa là n * n, sẽ lớn hơn đáng kể so với chỉ n, đó là lý do bạn cũng được phép bỏ qua phần đó, điều này khiến bạn thực sự có một sự phức tạp cuối cùng O(n^2).

Đó là sự thật, đối với những con số nhỏ sẽ có một sự khác biệt đáng kể, nhưng điều này càng trở nên nhỏ hơn khi bạn ntrở nên lớn hơn .


Làm thế nào lớn n phải được cho sự khác biệt để trở thành cận biên? Ngoài ra, tại sao / 2 bị loại bỏ, sự tồn tại của nó giảm một nửa giá trị?
Andrew S

6
@AndrewS Bởi vì Big O Notation nói về sự tăng trưởng. Chia cho 2 là không liên quan bên ngoài bối cảnh điểm chuẩn và dấu thời gian vì cuối cùng nó không thay đổi tốc độ tăng trưởng. Thành phần lớn nhất, tuy nhiên, và đó là tất cả những gì bạn giữ.
Neil

2
@Niel, rực rỡ quá rõ ràng. Tôi ước những cuốn sách sẽ đặt nó như thế. Đôi khi tôi nghĩ rằng các tác giả biết quá nhiều rằng họ quên rằng những người phàm trần không sở hữu kiến ​​thức chức năng của họ và do đó không đưa ra những điểm quan trọng rõ ràng, mà thay vào đó chôn vùi nó trong một số mô tả toán học chính thức hoặc bỏ qua tất cả cùng tin rằng nó được ngụ ý.
Andrew S

Tôi ước tôi có thể nâng cao câu trả lời này hơn một lần! @Neil, bạn nên viết những cuốn sách Big O.
Tersizardos

3

Không phải là "(n² + n) / 2 hoạt động như n² khi n lớn", đó là (n² + n) / 2 phát triển nhưkhi n tăng .

Ví dụ: khi n tăng từ 1.000 đến 1.000.000

(n² + n) / 2  increases from  500500 to  500000500000
(n²) / 2      increases from  500000 to  500000000000
(n²)          increases from 1000000 to 1000000000000

Tương tự, khi n tăng từ 1.000.000 lên 1.000.000.000

(n² + n) / 2  increases from  500000500000 to  500000000500000000
(n²) / 2      increases from  500000000000 to  500000000000000000
(n²)          increases from 1000000000000 to 1000000000000000000

Chúng phát triển tương tự, đó là những gì Big O Notation nói về.

Nếu bạn vẽ đồ thị (n² + n) / 2 và n² / 2 trên Wolfram Alpha , chúng giống nhau đến mức chúng khó phân biệt bằng n = 100. Nếu bạn vẽ cả ba trên Wolfram Alpha , bạn sẽ thấy hai dòng cách nhau bởi hệ số không đổi là 2.


Điều này là tốt, nó làm cho nó rất rõ ràng với tôi. Cảm ơn vì đã trả lời.
Andrew S

2

Có vẻ như bạn cần phải làm việc với ký hiệu O lớn hơn một chút. Ký hiệu này tiện lợi đến mức nào, nó rất dễ gây hiểu lầm vì sử dụng dấu bằng, không được sử dụng ở đây để biểu thị sự bình đẳng của các hàm.

Như bạn đã biết, ký hiệu này biểu thị sự so sánh tiệm cận của các hàm và viết f = O (g) có nghĩa là f (n) phát triển nhanh nhất là g (n) khi n đi đến vô cùng. Một cách đơn giản để dịch điều này là nói rằng hàm f / g bị chặn. Nhưng tất nhiên, chúng ta phải quan tâm đến những nơi g bằng 0 và chúng ta kết thúc với định nghĩa mạnh mẽ hơn mà bạn có thể đọc gần như ở mọi nơi .

Các ký hiệu này hóa ra rất thuận tiện cho việc tính toán - đây là lý do tại sao nó rất phổ biến - nhưng nó nên được xử lý cẩn thận vì dấu bằng chúng ta thấy ở đó không biểu thị sự bình đẳng của các hàm . Điều này khá giống như nói rằng 2 = 5 mod 3 không ngụ ý rằng 2 = 5 và nếu bạn quan tâm đến đại số, bạn thực sự có thể hiểu ký hiệu O lớn như một modulo bình đẳng gì đó.

Bây giờ, để quay lại câu hỏi cụ thể của bạn, việc tính toán một vài giá trị số và so sánh chúng là hoàn toàn vô ích: tuy nhiên, một triệu lớn là không có hành vi tiệm cận. Sẽ hữu ích hơn khi vẽ tỷ lệ của các hàm f (n) = n (n-1) / 2g (n) = n² - nhưng trong trường hợp đặc biệt này, chúng ta có thể dễ dàng thấy rằng f (n) / g (n) nhỏ hơn 1/2 nếu n> 0 ngụ ý rằng f = O (g) .

Để cải thiện sự hiểu biết của bạn về ký hiệu, bạn nên

  • Làm việc với một định nghĩa rõ ràng, không phải là một ấn tượng mờ dựa trên những thứ tương tự nhau - như bạn vừa trải nghiệm nó, một ấn tượng mờ như vậy không hoạt động tốt.

  • Dành thời gian để làm việc ra các ví dụ chi tiết. Nếu bạn tập luyện ít nhất là năm ví dụ trong vòng một tuần, nó sẽ đủ để cải thiện sự tự tin của bạn. Đây là một nỗ lực chắc chắn có giá trị.


Lưu ý bên đại số Nếu A là đại số của tất cả các hàm Ν →C, tập hợp con của các hàm bị chặn, đã cho một hàm f tập hợp các hàm thuộc O (f) là một hàm C của A và quy tắc tính toán trên lớn Ký hiệu O chỉ mô tả cách A hoạt động trên các mô hình con này. Do đó, đẳng thức mà chúng ta thấy là một đẳng thức của C -submodules của A , đây chỉ là một dạng mô đun khác.


1
Bài viết Wikipedia đó rất khó để theo dõi sau những mảnh nhỏ đầu tiên. Nó được viết cho các nhà toán học thành đạt bởi nhà toán học tài ba và không phải là loại văn bản giới thiệu mà tôi mong đợi từ một bài báo bách khoa. Cảm ơn cho cái nhìn sâu sắc của bạn mặc dù tất cả đều tốt.
Andrew S

Bạn đánh giá quá cao cấp độ trong văn bản của Wikipedia! :) Nó không phải là viết tốt, chắc chắn. Graham, Knuth và Patashnik đã viết một cuốn sách đáng yêu về Bê tông Toán học cho học sinh trong CS. Bạn cũng có thể dùng thử Nghệ thuật lập trình máy tính, hay một cuốn sách lý thuyết số được viết vào những năm 50 (Hardy & Wright, Rose) vì chúng thường nhắm vào cấp học sinh trung học. Bạn không cần phải đọc toàn bộ cuốn sách, nếu bạn chọn một cuốn, chỉ là phần về tiệm cận! Nhưng trước khi bạn cần quyết định bạn cần hiểu bao nhiêu. :)
Michael Le Barbier Grünewald

1

Tôi nghĩ rằng bạn hiểu sai ý nghĩa của chữ O lớn.

Khi bạn thấy O (N ^ 2) về cơ bản có nghĩa là: khi vấn đề lớn gấp 10 lần, thời gian để giải quyết nó sẽ là: 10 ^ 2 = 100 lần lớn hơn.

Hãy đấm 1000 và 10000 trong phương trình của bạn: 1000: (1000 ^ 2 + 1000) / 2 = 500500 10000: (10000 ^ 2 + 10000) / 2 = 50005000

50005000/500500 = 99,91

Vì vậy, trong khi N có kích thước lớn gấp 10 lần, thì các giải pháp lại lớn gấp 100 lần. Do đó, nó hoạt động: O (N ^ 2)


1

nếu n là một 1,000,000thì

(n^2 + n) / 2  =  500000500000  (5.00001E+11)
(n^2) / 2      =  500000000000  (5E+11)
(n^2)          = 1000000000000  (1E+12)

1000000000000.00 là gì?

Mặc dù độ phức tạp cho chúng ta cách dự đoán chi phí trong thế giới thực (giây hoặc byte tùy thuộc vào việc chúng ta đang nói về độ phức tạp thời gian hay độ phức tạp không gian), nhưng nó không cho chúng ta một số giây hoặc bất kỳ đơn vị cụ thể nào khác.

Nó cho chúng ta một mức độ tỷ lệ.

Nếu một thuật toán phải thực hiện một vài lần n², thì nó sẽ mất n² × c cho một số giá trị của c đó là mỗi lần lặp lại mất bao lâu.

Nếu một thuật toán phải thực hiện một cái gì đó ² 2 lần, thì nó sẽ mất n² × c cho một số giá trị của c dài gấp đôi mỗi lần lặp.

Dù bằng cách nào, thời gian thực hiện vẫn tỷ lệ thuận với n².

Bây giờ, những yếu tố không đổi này không phải là thứ chúng ta có thể bỏ qua; thực sự bạn có thể có trường hợp thuật toán có độ phức tạp O (n²) tốt hơn so với thuật toán có độ phức tạp O (n), bởi vì nếu chúng ta đang làm việc trên một số lượng nhỏ các mặt hàng thì tác động của các yếu tố liên quan sẽ lớn hơn và có thể lấn át các mối quan tâm khác . (Thật vậy, thậm chí O (n!) Giống như O (1) cho các giá trị đủ thấp của n).

Nhưng chúng không phải là những gì phức tạp cho chúng ta biết.

Trong thực tế, có một vài cách khác nhau để chúng tôi có thể cải thiện hiệu suất của thuật toán:

  1. Cải thiện hiệu quả của mỗi lần lặp: O (n²) vẫn chạy trong n² × c giây, nhưng c nhỏ hơn.
  2. Giảm số lượng các trường hợp đã thấy: O (n²) vẫn chạy trong n² × c giây, nhưng n nhỏ hơn.
  3. Thay thế thuật toán bằng một thuật toán có cùng kết quả, nhưng độ phức tạp thấp hơn: Ví dụ: nếu chúng ta có thể đánh giá lại một cái gì đó O (n²) thành một cái gì đó O (n log n) và do đó thay đổi từ n² × c₀ giây thành (n log n) × c₁ giây .

Hoặc để xem xét nó theo một cách khác, chúng tôi đã có f(n)×cvài giây và bạn có thể cải thiện hiệu suất bằng cách giảm c, giảm nhoặc giảm những gì ftrả về cho trước n.

Việc đầu tiên chúng ta có thể thực hiện bằng một số vi lệnh trong vòng lặp hoặc sử dụng phần cứng tốt hơn. Nó sẽ luôn luôn cho một sự cải tiến.

Điều thứ hai chúng ta có thể làm bằng cách có thể xác định một trường hợp mà chúng ta có thể rút ngắn thuật toán trước khi mọi thứ được kiểm tra hoặc lọc ra một số dữ liệu sẽ không có ý nghĩa. Nó sẽ không cải thiện nếu chi phí thực hiện điều này lớn hơn mức tăng, nhưng nhìn chung nó sẽ là một cải tiến lớn hơn so với trường hợp đầu tiên, đặc biệt là với một n lớn.

Thứ ba chúng ta có thể làm bằng cách sử dụng một thuật toán hoàn toàn khác. Một ví dụ cổ điển sẽ thay thế một loại bong bóng bằng quicksort. Với số lượng phần tử thấp, chúng tôi có thể làm mọi thứ trở nên tồi tệ hơn (nếu c₁ lớn hơn c₀), nhưng nó thường cho phép đạt được mức tăng lớn nhất, đặc biệt là với n rất lớn.

Trong sử dụng thực tế, các biện pháp phức tạp cho phép chúng tôi suy luận về sự khác biệt giữa các thuật toán một cách chính xác bởi vì chúng bỏ qua vấn đề làm thế nào giảm n hoặc c sẽ giúp, tập trung vào kiểm tra f()


"O (n!) Giống như O (1) với giá trị n đủ thấp" là sai. Phải có một cách tốt hơn để giải thích rằng "khi nđược giữ đủ thấp, Big-O không thành vấn đề".
Ben Voigt

@BenVoigt Tôi chưa bắt gặp một người có cùng tác động tu từ như lần đầu tiên tôi đọc nó; ban đầu nó không phải của tôi, tôi đã đánh cắp nó từ Eric Lippert, người có thể đã bắt nguồn nó hoặc có thể lấy nó từ người khác. Tất nhiên, nó tham chiếu các câu chuyện cười như "π bằng 3 cho các giá trị nhỏ của π và giá trị lớn của 3" vẫn cũ hơn.
Jon Hanna

0

Yếu tố không đổi

Điểm của ký hiệu O lớn là bạn có thể chọn một yếu tố hằng số lớn tùy ý để O (hàm (n)) luôn lớn hơn hàm C * (n). Nếu thuật toán A chậm hơn một tỷ lần so với thuật toán B, thì chúng có cùng độ phức tạp O, miễn là sự khác biệt đó không tăng lên khi n phát triển lớn tùy ý.

Chúng ta hãy giả sử một hệ số không đổi 1000000 để minh họa khái niệm - nó lớn hơn một triệu lần so với cần thiết, nhưng điều đó minh họa điểm mà chúng được coi là không liên quan.

(n ^ 2 + n) / 2 "nằm gọn trong" O (n ^ 2) vì với mọi n, dù lớn đến đâu, (n ^ 2 + n) / 2 <1000000 * n ^ 2.

(n ^ 2 + n) / 2 "không vừa" một bộ nhỏ hơn, ví dụ O (n) vì đối với một số giá trị (n ^ 2 + n) / 2> 1000000 * n.

Các yếu tố không đổi có thể lớn tùy ý - một thuật toán có thời gian chạy n năm có độ phức tạp O (n) "tốt hơn" so với thuật toán có thời gian chạy là n * log (n) micro giây.


0

Big-O là tất cả về thuật toán "phức tạp" như thế nào. Nếu bạn có hai thuật toán và một mất n^2*kvài giây để chạy và thuật toán kia mất n^2*jvài giây để chạy, thì bạn có thể tranh luận về cái nào tốt hơn và bạn có thể thực hiện một số tối ưu hóa thú vị để cố gắng ảnh hưởng khoặc j, nhưng cả hai các thuật toán này rất chậm so với thuật toán cần n*mchạy. Không quan trọng bạn tạo các hằng số nhỏ như thế nào , khoặc jvới đầu vào đủ lớn, n*mthuật toán sẽ luôn giành chiến thắng, ngay cả khi mkhá lớn.

Vì vậy, chúng tôi gọi hai thuật toán đầu tiên O(n^2) , và chúng tôi gọi thứ hai O(n). Nó phân chia thế giới thành các lớp thuật toán độc đáo . Đây là những gì big-O là tất cả về. Nó giống như phân chia các phương tiện thành xe hơi và xe tải và xe buýt, v.v ... Có rất nhiều biến thể giữa các xe và bạn có thể dành cả ngày để tranh luận về việc liệu một chiếc Prius có tốt hơn một chiếc Chevy Volt hay không, nhưng vào cuối ngày nếu bạn cần đặt 12 người thành một, thì đây là một cuộc tranh luận khá vô nghĩa. :)

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.