Big-O cho vòng lặp lồng nhau


8

Tôi đang đọc bài đăng này trên Big-O
Nó nói rằng đoạn mã sau là O (n ^ 2):

bool ContainsDuplicates(String[] strings)
{
    for(int i = 0; i < strings.Length; i++)
    {
        for(int j = 0; j < strings.Length; j++)
        {
            if(i == j) // Don't compare with self
            {
                continue;
            }

            if(strings[i] == strings[j])
            {
                return true;
            }
        }
    }
    return false;
}

Nhưng tôi không thể hiểu tại sao.
Vòng lặp bên trong làm một cái gì đó không đổi.
Vậy nó là tổng của 1 .... N của hằng số. tức là số không đổi O (1).
Vòng lặp bên ngoài là tổng của O (1).
Vì vậy, tôi sẽ tưởng tượng nó là n * O (1).

Tôi nghĩ rằng tôi đang hiểu nhầm một cái gì đó ở đây.
Tôi không nghĩ rằng tất cả các vòng lặp lồng nhau có nghĩa là O (n ^ 2), phải không?


3
Nơi nào bạn có ý tưởng rằng vòng lặp bên trong là "làm một cái gì đó không đổi"? Đó là một vòng lặp. Đó là manh mối đầu tiên của bạn rằng nó cần O (N).
Paul Tomblin

2
Điều này có thể được giảm xuống O (N ^ 2/2) bằng cách bắt đầu vòng lặp bên trong i+1thay vì 0. Như đã viết, trong trường hợp xấu nhất (không có bản sao) 1/2 các so sánh là dư thừa.
Peter Rowell

1
O (N ^ 2/2) = O (N ^ 2) (Trong cả hai trường hợp, khi N đi đến vô cùng, nhân đôi N có nghĩa là tăng gấp bốn lần thời gian chạy. Đó là tất cả O (N ^ 2).)
David Schwartz

5
Đáng chú ý là trong một ngôn ngữ như C có so sánh chuỗi O(N), mã này thực sự sẽ là O(N^2 * M)M là độ dài của chuỗi.
Isak Savo

2
So sánh (của chuỗi có độ dài không đổi) là O (1). N so sánh là O (N). So sánh N ^ 2 là O (N ^ 2).
SF.

Câu trả lời:


18

Lỗi của bạn là với vòng lặp bên trong. Nó làm một cái gì đó không đổi n lần, vì vậy nó là O ( n ). Vòng lặp bên ngoài thực hiện vòng lặp bên trong n lần, vì vậy nó là O ( n × n ) hoặc O ( n 2  ).

Nói chung, nếu số lần lặp mà một vòng lặp thực hiện phụ thuộc vào kích thước của đầu vào, thì đó là O ( n ). Và nếu k vòng lặp như vậy được lồng nhau, đó là O ( n k  ).


1
có lẽ một ký hiệu dễ hiểu hơn sẽ nói rằng thuật toán chạy trong O (i * j). Ở đây cả hai vòng lặp lặp lại theo chiều dài Chuỗi f, do đó i = j = n trong đó n là chuỗi.length
Newtopian

1
@Newtopian: Bạn thường cho rằng các chuỗi có độ dài không đổi; Đó thực sự là một xấp xỉ khá tốt trong thực tế.
Donal Fellows

@Donal thực sự, chúng ta có thể, với kiến ​​thức tập hợp dữ liệu phù hợp đưa ra các giả định như vậy, nhưng như mọi khi có ngoại lệ, phân tích DNA xuất hiện trong đó độ dài chuỗi có thể dài vài ký tự cho một codon thành nhiều byte cho toàn bộ nhiễm sắc thể. Điều đó nói trong trường hợp này, ví dụ cụ thể, độ dài chuỗi không có kết quả vì chúng ta tự lặp lại các chuỗi chứ không phải các ký tự. Đó là, trừ khi toán tử đẳng thức đã bị quá tải đến một số logic thú vị tùy thuộc vào độ dài của toán hạng. Tuy nhiên, tôi nghi ngờ OP có ý định đào sâu điều này trong phân tích của mình.
Newtopian

@Newtopian: Công bằng mà nói, ContainsDuplicateschức năng / phương pháp từ câu hỏi không có khả năng được sử dụng với nhiễm sắc thể đầy đủ.
Donal Fellows

@Donal: có lẽ không phải là không :-)
Newtopian

4

Nếu độ dài của chuỗi là n, kiểm tra nếu i == jsẽ thực hiện n ^ 2 lần. Thứ tự của một thuật toán là thứ tự của bất kỳ phần nào của nó có thứ tự cao nhất. Vì 'i' được so sánh với 'j' n ^ 2 lần, nên thứ tự của thuật toán có thể có thể nhỏ hơn O(n^2).

Đối với 'n' rất lớn, nhân đôi 'n' sẽ tăng gấp bốn lần thời gian chạy.


Nhưng không phải là bài kiểm tra i==jO (1) sao?
dùng10326

5
Bản thân bài kiểm tra là O (1). Vòng lặp bên trong thực hiện phép thử 'n' lần, vì vậy đó là O (n * 1). Vòng lặp bên ngoài thực hiện vòng lặp bên trong 'n' lần, vì vậy đó là O (n * n * 1). Vì vậy, mã tổng thể là O (n ^ 2). Nếu bạn thực hiện bước O (1) n ^ 2 lần, thì đó là O (n ^ 2).
David Schwartz

1
Nit nhỏ: thực sự với bất kỳ giá trị nào của N nhân đôi, nó sẽ tăng gấp bốn lần thời gian chạy - chỉ là nó không quan trọng lắm đối với N. nhỏ
Peter Rowell

@Peter: Điều đó không đúng. Ví dụ: đi từ n = 2 đến n = 4 có thể không tăng gấp đôi thời gian chạy vì máy 32 bit có thể sử dụng cùng số lượng bộ nhớ tìm nạp để đọc 2 byte là 4. Tương tự, đối với n nhỏ, chi phí đầu vào của chức năng có thể là đáng kể và đó là hằng số. Lớn hơn n có thể có dự đoán chi nhánh tốt hơn trung bình. Và như thế.
David Schwartz

5
Trong ngữ cảnh này, "bery Large 'n'" có nghĩa là 'n' có xu hướng vô cùng, nhưng bỏ qua mọi sự thiếu hiệu quả có thể xảy ra do số lượng lớn (chẳng hạn như không khớp với số nguyên 32 bit hoặc 64 bit). Điểm của ký hiệu big-O là phân tích hiệu suất của thuật toán thay đổi như thế nào khi tăng, miễn là nó nằm trong khả năng của máy. (Điều này có thể là một phân minh trong bối cảnh này, nhưng nói chung, hiểu được điều gì lớn-O ký hiệu bao gồm và bỏ qua là rất quan trọng để hiểu được ý nghĩa của nó.)
David Schwartz

2

Bạn đang hiểu sai ý nghĩa của một hoạt động liên tục.

Một hoạt động liên tục là một hoạt động luôn luôn thực hiện trong khoảng thời gian cố định độc lập với đầu vào mà nó nhận được.

i == j là một hoạt động liên tục vì nó thực hiện câu lệnh này trong khoảng thời gian cố định. Hãy nói rằng nó mất 1 đơn vị thời gian.

Nhưng hoạt động liên tục này được thực hiện (không có giá trị nào của i) * (không có giá trị nào của j) lần. Hãy nói rằng tôi và j được chạy 10 lần mỗi cái. Sau đó, bằng cách tính toán, phải mất 100 đơn vị thời gian để hoàn thành i == j khi trong một vòng lặp lồng nhau. Vì vậy, nó sẽ thay đổi khi giá trị của i và j khác nhau.

Chúng tôi có thể chắc chắn rằng i == j sẽ được thực hiện trong 1 đơn vị thời gian nhưng chúng tôi không thể biết bao nhiêu lần i == j sẽ được thực hiện.

Nếu nó được thực hiện n lần thì sẽ mất n đơn vị thời gian. Vòng lặp bên ngoài thực hiện vòng lặp bên trong n lần. Vì vậy, về bản chất, i == j hoạt động được thực hiện n ^ 2 lần.

Tất cả các vòng lặp lồng nhau có nghĩa là O (n ^ (không có vòng lặp lồng nhau)). Ở đây O có nghĩa là giới hạn trên có nghĩa là mã sẽ thực thi nhỏ hơn hoặc bằng giá trị của O (). Đây là sự đảm bảo rằng nó sẽ không mất nhiều thời gian hơn nhưng nó có thể mất ít thời gian hơn không lớn hơn.


0

Các vòng lặp lồng nhau chạy O ( i 1 * i 2 * ... * i n ), trong đó n là số vòng lặp lồng nhau và i x là số lần lặp trong vòng x . (Hoặc, nói cách khác, đó là sản phẩm của số lần lặp trong mỗi vòng lặp.)

Cặp vòng lặp của bạn lặp lại trên cùng một mảng hai lần, do trùng hợp ngẫu nhiên là O (n * n) hoặc O (n 2 ).

Những gì xảy ra bên trong vòng lặp trong cùng được coi là không đổi bởi vì nó sẽ xảy ra mọi lúc. Ký hiệu Big-O không thực sự là về việc đo hiệu năng của mã tuyến tính, nó tập trung vào việc so sánh tương đối về cách các thuật toán lặp hoạt động khi đưa ra n mục đầu vào để xử lý. Một số thao tác thực sự không thực hiện bất kỳ thao tác lặp nào (chẳng hạn như đẩy hoặc bật trên ngăn xếp) được gọi là O (1) vì chúng xử lý một yếu tố của dữ liệu.


1
không đúng nếu i_k không phải là hằng số vấn đề. Ngoài ra quicksort được thực hiện mà không cần đệ quy là một vòng lặp lồng nhau nhưng nó vẫn là O (n * log n)
ratchet freak

Quicksort với lựa chọn trục ngẫu nhiên là không đặc trưng, ​​có nghĩa là con số O (n log n) là trung bình. Nó vẫn phù hợp với những gì tôi đã nói: i1 = ni2 = log n .
Blrfl
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.