Đây có phải là một quy tắc phù hợp với xu hướng để xác định các ký hiệu của Big Big của một thuật toán không?


29

Tôi đã tìm hiểu thêm về Ký hiệu Big O và cách tính toán dựa trên cách viết thuật toán. Tôi đã bắt gặp một bộ "quy tắc" thú vị để tính toán một thuật toán Ký hiệu Big O và tôi muốn xem liệu tôi có đang đi đúng hướng hay không.

Ký hiệu Big O: N

function(n) {
    For(var a = 0; i <= n; i++) { // It's N because it's just a single loop
        // Do stuff
    }
}

Ký hiệu Big O: N 2

function(n, b) {
    For(var a = 0; a <= n; a++) {
        For(var c = 0; i <= b; c++) { // It's N squared because it's two nested loops
            // Do stuff
        }
    }
}

Ký hiệu Big O: 2N

function(n, b) {
    For(var a = 0; a <= n; a++) {
        // Do stuff
    }
    For(var c = 0; i <= b; c++) { // It's 2N the loops are outside each other
        // Do stuff
    }
}

Ký hiệu Big O: NLogN

function(n) {
    n.sort(); // The NLogN comes from the sort?
    For(var a = 0; i <= n; i++) {
        // Do stuff
    }
}

Các ví dụ của tôi và các ký hiệu tiếp theo có đúng không? Có những ký hiệu bổ sung nào tôi nên biết?


3
Gọi đó là quy tắc ngón tay cái thay vì công thức và có lẽ bạn đang đi đúng hướng. Tất nhiên, nó hoàn toàn phụ thuộc vào chính xác những gì "làm công cụ" làm. Nhật ký (N) thường xuất phát từ các thuật toán thực hiện một số loại phân vùng nhị phân / giống như cây. Đây là một bài viết blog tuyệt vời về chủ đề này.
Daniel B

15
Không có một thứ như 2Ntrong ký hiệu big-O.
vartec

15
@ JörgWMittag vì O (2n) = O (n) theo định nghĩa của Big O
ratchet freak

3
@ JörgWMittag: đây thực sự không phải là nơi để trolling.
vartec

3
@vartec - Tôi không tin JörgWMittag cố tình troll. Trong nghiên cứu gần đây của tôi, tôi đã nhận thấy rất nhiều nhầm lẫn giữa ký hiệu Big-O nghiêm ngặt và "ngôn ngữ chung" trộn lẫn Big-O, Theta và các dẫn xuất khác. Tôi không nói rằng cách sử dụng phổ biến là chính xác; chỉ là nó xảy ra rất nhiều

Câu trả lời:


26

Chính thức, ký hiệu big-O mô tả mức độ phức tạp.

Để tính ký hiệu big-O:

  1. xác định công thức cho độ phức tạp thuật toán. Giả sử, ví dụ, hai vòng lặp với một vòng khác được lồng bên trong, sau đó ba vòng khác không được lồng vào nhau:2N² + 3N
  2. xóa mọi thứ trừ thuật ngữ cao nhất: 2N²
  3. loại bỏ tất cả các hằng số:

Nói cách khác, hai vòng lặp với một vòng khác được lồng bên trong, sau đó ba vòng khác không được lồng vào nhau là O (N²)

Điều này tất nhiên giả định rằng những gì bạn có trong các vòng lặp của bạn là những hướng dẫn đơn giản. Nếu bạn có ví dụ sort()bên trong vòng lặp, bạn sẽ phải nhân độ phức tạp của vòng lặp với độ phức tạp của việc sort()triển khai ngôn ngữ / thư viện cơ bản của bạn đang sử dụng.


Nói một cách nghiêm túc "loại bỏ tất cả các hằng số" sẽ biến 2N³thành N. "loại bỏ tất cả các hằng số cộng và nhân" sẽ gần với sự thật hơn.
Joachim Sauer

@JoachimSauer: N² = N * N, không có hằng số ở đó.
vartec

@vartec: theo lập luận tương tự 2N = N+N.
Joachim Sauer

2
@JoachimSauer, "nói đúng" của bạn là hoàn toàn không thông thường. Xem en.wikipedia.org/wiki/Constant_(mathatures) . Khi nói về đa thức, "hằng số" luôn chỉ các hệ số, không liên quan đến số mũ.
Ben Lee

1
@vartec, xem bình luận của tôi ở trên. Việc bạn sử dụng "hằng số" ở đây là hoàn toàn chính xác và thông thường.
Ben Lee

6

Nếu bạn muốn phân tích các thuật toán này, bạn cần xác định // do do, vì điều đó thực sự có thể thay đổi kết quả. Giả sử các công cụ đòi hỏi một số lượng hoạt động O (1) không đổi.

Dưới đây là một số ví dụ với ký hiệu mới này:

Ví dụ đầu tiên của bạn, truyền tải tuyến tính: điều này là chính xác!

TRÊN):

for (int i = 0; i < myArray.length; i++) {
    myArray[i] += 1;
}

Tại sao nó là tuyến tính (O (n))? Khi chúng ta thêm các phần tử bổ sung vào đầu vào (mảng), số lượng hoạt động xảy ra sẽ tăng tỷ lệ thuận với số lượng phần tử chúng ta thêm vào.

Vì vậy, nếu phải mất một thao tác để tăng một số nguyên ở đâu đó trong bộ nhớ, chúng ta có thể mô hình hóa công việc mà vòng lặp thực hiện với f (x) = 5x = 5 thao tác bổ sung. Đối với 20 yếu tố bổ sung, chúng tôi thực hiện 20 hoạt động bổ sung. Một vượt qua duy nhất của một mảng có xu hướng tuyến tính. Các thuật toán như sắp xếp xô, có thể khai thác cấu trúc dữ liệu để thực hiện sắp xếp trong một lần truyền của một mảng.

Ví dụ thứ hai của bạn cũng sẽ đúng và trông như thế này:

Ô (N ^ 2):

for (int i = 0; i < myArray.length; i++) {
    for (int j = 0; j < myArray.length; j++) {
        myArray[i][j] += 1;
    }
}

Trong trường hợp này, với mỗi phần tử bổ sung trong mảng đầu tiên, i, chúng ta phải xử lý TẤT CẢ j. Thêm 1 vào i thực sự thêm (độ dài của j) vào j. Như vậy, bạn đã đúng! Mẫu này là O (n ^ 2) hoặc trong ví dụ của chúng tôi thực sự là O (i * j) (hoặc n ^ 2 nếu i == j, thường là trường hợp với các hoạt động ma trận hoặc cấu trúc dữ liệu vuông.

Ví dụ thứ ba của bạn là nơi mọi thứ thay đổi tùy thuộc vào đồ dùng; Nếu mã như được viết và làm công cụ là một hằng số, thì thực tế nó chỉ là O (n) vì chúng ta có 2 lượt của một mảng có kích thước n và 2n giảm xuống n. Các vòng lặp nằm ngoài nhau không phải là yếu tố chính có thể tạo ra mã 2 ^ n; đây là một ví dụ về hàm có 2 ^ n:

var fibonacci = function (n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    else {
        return (fibonacci(n-2) + fibonacci(n-1));
    }
}

Hàm này là 2 ^ n, bởi vì mỗi lệnh gọi đến hàm tạo ra HAI cuộc gọi bổ sung cho hàm (Fibonacci). Mỗi lần chúng ta gọi hàm, số lượng công việc chúng ta phải làm tăng gấp đôi! Điều này phát triển siêu nhanh, như cắt đầu một con hydra và có hai cái mới mọc lên mỗi lần!

Đối với ví dụ cuối cùng của bạn, nếu bạn đang sử dụng một loại nlgn như sắp xếp hợp nhất, bạn đúng rằng mã này sẽ là O (nlgn). Tuy nhiên, bạn có thể khai thác cấu trúc dữ liệu để phát triển các loại nhanh hơn trong các tình huống cụ thể (chẳng hạn như trong phạm vi giá trị giới hạn đã biết, như từ 1-100.) Tuy nhiên, bạn đã nghĩ đúng rằng mã thứ tự cao nhất chiếm ưu thế; vì vậy nếu một loại O (nlgn) bên cạnh bất kỳ thao tác nào mất ít thời gian hơn O (nlgn), thì độ phức tạp tổng thời gian sẽ là O (nlgn).

Trong JavaScript (ít nhất là trong Firefox), sắp xếp mặc định trong Array.prototype.sort () thực sự là MergeSort, vì vậy bạn đang xem O (nlgn) cho kịch bản cuối cùng của mình.


Là ví dụ Fibonacci của bạn thực sự là Fibonacci? Tôi biết điều này không tranh cãi với quan điểm mà bạn đang cố gắng thực hiện, nhưng cái tên có thể gây hiểu lầm cho người khác và do đó gây mất tập trung nếu nó không thực sự là Fibonacci.
Paul Nikonowicz

1

Ví dụ thứ hai của bạn (vòng lặp ngoài từ 0 đến n , vòng lặp bên trong từ 0 đến b ) sẽ là O ( nb ), không phải O ( n 2 ). Quy tắc là bạn đang tính toán thứ gì đó n lần và đối với mỗi người bạn đang tính toán thứ khác b lần, do đó sự tăng trưởng của chức năng này chỉ phụ thuộc vào sự tăng trưởng của n * b .

Ví dụ thứ ba của bạn chỉ là O ( n ) - bạn có thể xóa tất cả các hằng số khi chúng không phát triển với n và tăng trưởng là những gì ký hiệu Big-O hướng tới.

Đối với ví dụ cuối cùng của bạn, vâng, ký hiệu Big-O của bạn chắc chắn sẽ đến từ phương thức sắp xếp, nếu nó dựa trên so sánh (như thường lệ), ở dạng hiệu quả nhất, O ( n * logn ) .


0

Hãy nhớ rằng đây là một đại diện gần đúng của thời gian chạy. "Quy tắc ngón tay cái" là gần đúng vì nó không chính xác nhưng mang lại xấp xỉ bậc một tốt cho mục đích đánh giá.

Thời gian chạy thực tế sẽ phụ thuộc vào dung lượng heap, tốc độ của bộ xử lý, tập lệnh, sử dụng tiền tố hoặc toán tử gia tăng sau sửa lỗi, v.v., yadda. Phân tích thời gian chạy phù hợp sẽ cho phép xác định chấp nhận nhưng có kiến ​​thức về những điều cơ bản cho phép bạn lập trình ngay từ đầu.

Tôi đồng ý rằng bạn đang đi đúng hướng để hiểu cách Big-O được hợp lý hóa từ sách giáo khoa thành một ứng dụng thực tế. Đó có thể là rào cản khó vượt qua.

Tốc độ tăng trưởng tiệm cận trở nên quan trọng trên các tập dữ liệu lớn và các chương trình lớn, vì vậy đối với các ví dụ điển hình bạn chứng minh rằng nó không quan trọng đối với cú pháp và logic hợp lý.


-1

Big oh, theo định nghĩa có nghĩa là: đối với hàm f (t) tồn tại hàm c * g (t) trong đó c là hằng số tùy ý sao cho f (t) <= c * g (t) cho t> n trong đó n là một hằng số tùy ý, sau đó f (t) tồn tại trong O (g (t)). Đây là một ký hiệu toán học được sử dụng trong khoa học máy tính để phân tích các thuật toán. Nếu bạn bối rối, tôi sẽ khuyên bạn nên xem xét các mối quan hệ đóng cửa, bằng cách đó bạn có thể thấy trong một cái nhìn chi tiết hơn về cách các thuật toán này có được các giá trị lớn này.

Một số hậu quả của định nghĩa này: O (n) thực sự phù hợp với O (2n).

Ngoài ra có nhiều loại thuật toán sắp xếp khác nhau. Giá trị Big-Oh tối thiểu cho một loại so sánh là O (nlogn) tuy nhiên có rất nhiều loại với big-oh tệ hơn. Ví dụ sắp xếp lựa chọn có O (n ^ 2). Một số loại không so sánh có thể có giá trị lớn hơn tốt hơn. Một loại xô, ví dụ có O (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.