Có một hệ thống đằng sau sự kỳ diệu của phân tích thuật toán?


159

Có rất nhiều thắc mắc về cách phân tích thời gian chạy của thuật toán (xem, ví dụ, ). Nhiều người giống nhau, ví dụ, những người yêu cầu phân tích chi phí của các vòng lặp lồng nhau hoặc thuật toán phân chia & chinh phục, nhưng hầu hết các câu trả lời dường như được thiết kế riêng.

Mặt khác, các câu trả lời cho một câu hỏi chung khác giải thích bức tranh lớn hơn (đặc biệt liên quan đến phân tích tiệm cận) với một số ví dụ, nhưng không làm thế nào để làm bẩn tay bạn.

Có một phương pháp chung, có cấu trúc để phân tích chi phí của các thuật toán không? Chi phí có thể là thời gian chạy (độ phức tạp thời gian) hoặc một số thước đo chi phí khác, chẳng hạn như số lượng so sánh được thực hiện, độ phức tạp không gian hoặc một thứ khác.

Điều này được cho là trở thành một câu hỏi tham khảo có thể được sử dụng để chỉ người mới bắt đầu; do đó phạm vi rộng hơn bình thường của nó. Xin lưu ý đưa ra các câu trả lời chung chung, được trình bày một cách chính thức được minh họa bằng ít nhất một ví dụ nhưng dù sao cũng bao gồm nhiều tình huống. Cảm ơn!


3
Cảm ơn tác giả của StackEdit vì đã thuận tiện để viết các bài đăng dài như vậy, và các độc giả beta của tôi FrankW , Juho , GillesSebastian đã giúp tôi giải quyết một số sai sót trước đó.
Raphael

1
Này @Raphael, đây là thứ tuyệt vời. Tôi nghĩ rằng tôi sẽ đề nghị kết hợp nó dưới dạng PDF để lưu hành? Loại điều này có thể trở thành một tài liệu tham khảo thực sự hữu ích.
hadsed

1
@hadsed: Cảm ơn, tôi rất vui vì nó hữu ích cho bạn! Hiện tại, tôi thích rằng một liên kết đến bài đăng này được lưu hành xung quanh. Tuy nhiên, nội dung người dùng SE được "cấp phép theo cc by-sa 3.0 với yêu cầu ghi công" (xem phần chân trang) để bất kỳ ai cũng có thể tạo tệp PDF từ đó, miễn là ghi công được đưa ra.
Raphael

2
Tôi không đặc biệt có thẩm quyền về vấn đề này, nhưng có bình thường không có tham chiếu đến định lý Master trong bất kỳ anwer nào không?
babou

1
@babou Tôi không biết "bình thường" nghĩa là gì ở đây. Theo quan điểm của tôi, định lý Master không có kinh doanh ở đây: đây là về phân tích các thuật toán, định lý Master là một công cụ rất cụ thể để giải quyết (một số) các đợt tái phát (và rất đại khái ở đó). Vì toán học đã được đề cập ở nơi khác (ví dụ ở đây ), tôi đã chọn chỉ bao gồm phần từ thuật toán đến toán học ở đây. Tôi cung cấp tài liệu tham khảo cho các bài viết liên quan đến làm việc toán học trong câu trả lời của tôi.
Raphael

Câu trả lời:


134

Dịch mã sang toán học

Đưa ra một ngữ nghĩa hoạt động chính thức (nhiều hơn hoặc ít hơn), bạn có thể dịch mã (giả giả thuật toán) theo nghĩa đen thành một biểu thức toán học mang lại cho bạn kết quả, miễn là bạn có thể điều khiển biểu thức thành một dạng hữu ích. Điều này hoạt động tốt cho các biện pháp chi phí phụ gia như số lượng so sánh, hoán đổi, báo cáo, truy cập bộ nhớ, chu kỳ một số nhu cầu máy trừu tượng, v.v.

Ví dụ: So sánh trong Bubbledort

Hãy xem xét thuật toán này sắp xếp một mảng nhất định A:

 bubblesort(A) do                   1
  n = A.length;                     2
  for ( i = 0 to n-2 ) do           3
    for ( j = 0 to n-i-2 ) do       4
      if ( A[j] > A[j+1] ) then     5
        tmp    = A[j];              6
        A[j]   = A[j+1];            7
        A[j+1] = tmp;               8
      end                           9
    end                             10
  end                               11
end                                 12

Giả sử chúng ta muốn thực hiện phân tích thuật toán sắp xếp thông thường, đó là đếm số lượng so sánh phần tử (dòng 5). Chúng tôi lưu ý ngay rằng số lượng này không phụ thuộc vào nội dung của mảng A, chỉ phụ thuộc vào độ dài của nó . Vì vậy, chúng ta có thể dịch các từ (lồng) hoàn toàn theo nghĩa đen thành các khoản tiền (lồng nhau); biến vòng lặp trở thành biến tổng và phạm vi mang. Chúng tôi nhận được:nfor

Ccmp(n)=i=0n2j=0ni21==n(n1)2=(n2) ,

trong đó là chi phí cho mỗi lần thực hiện của dòng 5 (chúng tôi tính).1

Ví dụ: Hoán đổi trong Bubbledort

Tôi sẽ biểu thị bằng chương trình con bao gồm các dòng đến và bởi chi phí để thực hiện chương trình con này (một lần).Pi,jijCi,j

Bây giờ hãy nói rằng chúng tôi muốn đếm các giao dịch hoán đổi , đó là tần suất được thực thi. Đây là một "khối cơ bản", là một chương trình con luôn được thực hiện nguyên tử và có một số chi phí không đổi (ở đây, ). Hợp đồng các khối như vậy là một đơn giản hóa hữu ích mà chúng ta thường áp dụng mà không cần suy nghĩ hoặc nói về nó.P6,81

Với một bản dịch tương tự như trên, chúng ta đến với công thức sau:

Cswaps(A)=i=0n2j=0ni2C5,9(A(i,j)) .

A(i,j) biểu thị trạng thái của mảng trước lần lặp của .(i,j)P5,9

Lưu ý rằng tôi sử dụng thay vì làm tham số; chúng ta sẽ sớm thấy tại sao. Tôi không thêm và làm tham số của vì chi phí không phụ thuộc vào chúng ở đây (trong mô hình chi phí thống nhất , nghĩa là); nói chung, họ chỉ có thể.AnijC5,9

Rõ ràng, chi phí của phụ thuộc vào nội dung của (các giá trị và cụ thể) vì vậy chúng tôi phải tính đến điều đó. Bây giờ chúng tôi phải đối mặt với một thách thức: làm thế nào để chúng tôi "tháo gỡ" ? Chà, chúng ta có thể làm cho sự phụ thuộc vào nội dung của rõ ràng:P5,9AA[j]A[j+1]C5,9A

C5,9(A(i,j))=C5(A(i,j))+{1,A(i,j)[j]>A(i,j)[j+1]0,else .

Đối với bất kỳ mảng đầu vào cụ thể nào, các chi phí này được xác định rõ, nhưng chúng tôi muốn có một tuyên bố chung hơn; chúng ta cần phải đưa ra các giả định mạnh mẽ hơn. Hãy để chúng tôi điều tra ba trường hợp điển hình.

  1. Trường hợp xấu nhất

    Chỉ cần nhìn vào tổng và lưu ý rằng , chúng ta có thể tìm thấy một giới hạn trên tầm thường cho chi phí:C5,9(A(i,j)){0,1}

    Cswaps(A)i=0n2j=0ni21=n(n1)2=(n2) .

    Nhưng điều này có thể xảy ra không , tức là có cho giới hạn trên này đạt được không? Khi nó bật ra, có: nếu chúng ta nhập một mảng được sắp xếp ngược của các phần tử riêng biệt theo cặp, mỗi lần lặp phải thực hiện hoán đổi¹. Do đó, chúng tôi đã lấy được số lần hoán đổi chính xác trong trường hợp xấu nhất của Bubbledort.A

  2. Trường hợp tốt nhất

    Ngược lại, có một giới hạn dưới tầm thường:

    Cswaps(A)i=0n2j=0ni20=0 .

    Điều này cũng có thể xảy ra: trên một mảng đã được sắp xếp, Bubbledort không thực hiện một trao đổi duy nhất.

  3. Trường hợp trung bình

    Trường hợp xấu nhất và tốt nhất mở ra một khoảng cách. Nhưng số lượng hoán đổi điển hình là gì? Để trả lời câu hỏi này, chúng ta cần xác định "điển hình" nghĩa là gì. Về lý thuyết, chúng tôi không có lý do để thích một đầu vào hơn đầu vào khác và vì vậy chúng tôi thường giả định phân phối đồng đều trên tất cả các đầu vào có thể, đó là mọi đầu vào đều có khả năng như nhau. Chúng tôi giới hạn bản thân trong các mảng với các phần tử khác biệt theo cặp và do đó giả sử mô hình hoán vị ngẫu nhiên .

    Sau đó, chúng tôi có thể viết lại chi phí của mình như thế này²:

    E[Cswaps]=1n!Ai=0n2j=0ni2C5,9(A(i,j))

    Bây giờ chúng ta phải vượt ra ngoài các thao tác đơn giản. Bằng cách xem xét thuật toán, chúng tôi lưu ý rằng mọi trao đổi sẽ loại bỏ chính xác một phép đảo ngược trong (chúng tôi chỉ trao đổi neighbours³). Đó là, số lượng các giao dịch hoán đổi thực hiện trên là chính xác số lượng các đảo của . Vì vậy, chúng ta có thể thay thế hai khoản tiền bên trong và nhận đượcAAinv(A)A

    E[Cswaps]=1n!Ainv(A) .

    May mắn cho chúng tôi, số lần đảo ngược trung bình đã được xác định là

    E[Cswaps]=12(n2)

    đó là kết quả cuối cùng của chúng tôi. Lưu ý rằng đây chính xácmột nửa chi phí trong trường hợp xấu nhất.


  1. Lưu ý rằng thuật toán đã được xây dựng cẩn thận sao cho lần lặp "cuối cùng" với i = n-1vòng lặp bên ngoài không bao giờ thực hiện bất cứ điều gì không được thực hiện.
  2. " " là ký hiệu toán học cho "giá trị mong đợi", ở đây chỉ là mức trung bình.E
  3. Chúng ta học theo cách mà không có thuật toán nào chỉ hoán đổi các phần tử lân cận có thể nhanh hơn bất thường so với Bubbledort (thậm chí trung bình) - số lượng đảo ngược là giới hạn thấp hơn cho tất cả các thuật toán như vậy. Điều này áp dụng cho ví dụ Sắp xếp chènSắp xếp lựa chọn .

Phương pháp chung

Chúng ta đã thấy trong ví dụ rằng chúng ta phải dịch cấu trúc điều khiển sang toán học; Tôi sẽ trình bày một tập hợp điển hình của các quy tắc dịch thuật. Chúng tôi cũng đã thấy rằng chi phí của bất kỳ chương trình con cụ thể nào có thể phụ thuộc vào trạng thái hiện tại , nghĩa là (khoảng) các giá trị hiện tại của các biến. Vì thuật toán (thường) sửa đổi trạng thái, nên phương thức chung hơi cồng kềnh để ghi chú. Nếu bạn bắt đầu cảm thấy bối rối, tôi khuyên bạn nên quay lại ví dụ hoặc trang điểm cho riêng mình.

Chúng tôi biểu thị với trạng thái hiện tại (hãy tưởng tượng nó như một tập các phép gán biến). Khi chúng tôi thực hiện một chương trình bắt đầu ở trạng thái , chúng tôi sẽ kết thúc ở trạng thái (được cung cấp kết thúc).ψPψψ/PP

  • Báo cáo cá nhân

    Chỉ đưa ra một tuyên bố S;, bạn chỉ định nó có giá . Điều này thường sẽ là một chức năng không đổi.CS(ψ)

  • Biểu thức

    Nếu bạn có một biểu thức Ecủa biểu mẫu E1 ∘ E2(giả sử, một biểu thức số học có thể là phép cộng hoặc phép nhân, bạn sẽ cộng chi phí theo cách đệ quy:

    CE(ψ)=c+CE1(ψ)+CE2(ψ) .

    Lưu ý rằng

    • chi phí hoạt động có thể không đổi mà phụ thuộc vào các giá trị của và vàcE1E2
    • đánh giá các biểu thức có thể thay đổi trạng thái trong nhiều ngôn ngữ,

    vì vậy bạn có thể phải linh hoạt với quy tắc này.

  • Sự nối tiếp

    Đưa ra một chương trình Pnhư một chuỗi các chương trình Q;R, bạn thêm chi phí vào

    CP(ψ)=CQ(ψ)+CR(ψ/Q) .

  • Điều kiện

    Đưa ra một chương trình Pcủa biểu mẫu if A then Q else R end, chi phí phụ thuộc vào tiểu bang:

    CP(ψ)=CA(ψ)+{CQ(ψ/A),A evaluates to true under ψCR(ψ/A),else

    Nói chung, việc đánh giá Arất có thể thay đổi trạng thái, do đó cập nhật chi phí của các chi nhánh riêng lẻ.

  • Vòng lặp

    Đưa ra một chương trình Pcủa mẫu for x = [x1, ..., xk] do Q end, gán chi phí

    CP(ψ)=cinit_for+i=1kcstep_for+CQ(ψi{x:=xi})

    trong đó là trạng thái trước khi xử lý giá trị , tức là sau khi lặp với việc được đặt thành , ..., .ψiQxixx1xi-1

    Lưu ý các hằng số phụ để bảo trì vòng lặp; biến vòng lặp phải được tạo ( ) và gán các giá trị của nó ( ). Điều này có liên quan vìcinit_forcstep_for

    • việc tính toán tiếp theo xicó thể tốn kém và
    • một for-loop với phần thân trống (ví dụ: sau khi đơn giản hóa trong cài đặt trường hợp tốt nhất với chi phí cụ thể) sẽ không có chi phí bằng 0 nếu nó thực hiện các lần lặp.
  • Vòng lặp trong khi

    Đưa ra một chương trình Pcủa mẫu while A do Q end, gán chi phí

    CP(ψ) =CA(ψ)+{0,A evaluates to false under ψCQ(ψ/A)+CP(ψ/A;Q), else

    Bằng cách kiểm tra thuật toán, sự lặp lại này thường có thể được biểu diễn độc đáo dưới dạng một tổng tương tự như tổng số cho các vòng lặp.

    Ví dụ: Xem xét thuật toán ngắn này:

    while x > 0 do    1
      i += 1          2
      x = x/2         3
    end               4
    

    Bằng cách áp dụng quy tắc, chúng tôi nhận được

    C1,4({i:=i0;x:=x0}) =c<+{0,x00c+=+c/+C1,4({i:=i0+1;x:=x0/2}), else

    với một số chi phí không đổi cho các báo cáo riêng lẻ. Chúng tôi giả định rằng những điều này không phụ thuộc vào trạng thái (các giá trị của và ); điều này có thể đúng hoặc không đúng trong "thực tế": nghĩ về tràn!cix

    Bây giờ chúng ta phải giải quyết sự tái phát này cho . Chúng tôi lưu ý rằng không phải số lần lặp không phải là chi phí của thân vòng lặp phụ thuộc vào giá trị của , vì vậy chúng tôi có thể loại bỏ nó. Chúng tôi còn lại với sự tái phát này:C1,4i

    C1,4(x)={c>,x0c>+c+=+c/+C1,4(x/2), else

    Điều này giải quyết với phương tiện cơ bản để

    C1,4(ψ)=log2ψ(x)(c>+c+=+c/)+c> ,

    giới thiệu lại toàn bộ trạng thái một cách tượng trưng; if , thì .ψ={,x:=5,}ψ(x)=5

  • Cuộc gọi thủ tục

    Đưa ra một chương trình Pcó dạng M(x)cho một số tham số trong xđó Mmột thủ tục với tham số (được đặt tên) p, gán chi phí

    CP(ψ)=ccall+CM(ψglob{p:=x}) .

    Lưu ý lại hằng số (thực tế có thể phụ thuộc vào !). Các cuộc gọi thủ tục rất tốn kém do cách chúng được thực hiện trên các máy thực và đôi khi thậm chí còn chi phối thời gian chạy (ví dụ: đánh giá số lần tái phát số Fibonacci một cách ngây thơ).ccallψ

    Tôi đề cập đến một số vấn đề ngữ nghĩa mà bạn có thể có với nhà nước ở đây. Bạn sẽ muốn phân biệt trạng thái toàn cầu và địa phương như vậy để thực hiện các cuộc gọi. Hãy chỉ giả sử chúng ta vượt qua tiểu bang duy nhất toàn cầu ở đây và Mđược một nhà nước địa phương mới, khởi tạo bằng cách thiết lập giá trị của pđể x. Hơn nữa, xcó thể là một biểu thức mà chúng ta (thường) cho rằng được đánh giá trước khi vượt qua nó.

    Ví dụ: Xem xét thủ tục

    fac(n) do                  
      if ( n <= 1 ) do         1
        return 1               2
      else                     3
        return n * fac(n-1)    4
      end                      5
    end                        
    

    Theo quy tắc, chúng tôi nhận được:

    Cfac({n:=n0})=C1,5({n:=n0})=c+{C2({n:=n0}),n01C4({n:=n0}), else=c+{creturn,n01creturn+c+ccall+Cfac({n:=n01}), else

    Lưu ý rằng chúng tôi bỏ qua trạng thái toàn cầu, vì facrõ ràng không truy cập bất kỳ. Sự tái phát đặc biệt này rất dễ giải quyết

    Cfac(ψ)=ψ(n)(c+creturn)+(ψ(n)1)(c+ccall)

Chúng tôi đã đề cập đến các tính năng ngôn ngữ mà bạn sẽ gặp trong mã giả điển hình. Coi chừng chi phí ẩn khi phân tích mã giả cấp cao; nếu nghi ngờ, mở ra. Các ký hiệu có vẻ rườm rà và chắc chắn là một vấn đề của hương vị; các khái niệm được liệt kê không thể bỏ qua, mặc dù. Tuy nhiên, với một số kinh nghiệm, bạn sẽ có thể thấy ngay phần nào của trạng thái có liên quan đến thước đo chi phí, ví dụ "kích thước vấn đề" hoặc "số đỉnh". Phần còn lại có thể được bỏ - điều này đơn giản hóa mọi thứ đáng kể!

Nếu bạn nghĩ rằng điều này quá phức tạp, hãy lưu ý: đó ! Lấy chi phí chính xác của các thuật toán trong bất kỳ mô hình nào gần với máy thật để kích hoạt dự đoán thời gian chạy (thậm chí là tương đối) là một nỗ lực khó khăn. Và điều đó thậm chí không xem xét bộ nhớ đệm và các hiệu ứng khó chịu khác trên máy thật.

Do đó, phân tích thuật toán thường được đơn giản hóa đến mức có thể dễ dàng toán học. Chẳng hạn, nếu bạn không cần chi phí chính xác, bạn có thể đánh giá quá cao hoặc đánh giá thấp tại bất kỳ thời điểm nào (đối với giới hạn trên. Giới hạn dưới): giảm tập hợp các hằng số, loại bỏ các điều kiện, đơn giản hóa các khoản tiền, v.v.

Một lưu ý về chi phí tiệm cận

Những gì bạn thường sẽ tìm thấy trong văn học và trên các trang web là "phân tích Big-Oh". Thuật ngữ thích hợp là phân tích tiệm cận , có nghĩa là thay vì lấy chi phí chính xác như chúng ta đã làm trong các ví dụ, bạn chỉ đưa ra chi phí lên đến một yếu tố không đổi và trong giới hạn (nói đại khái là "cho lớn ").n

Điều này là (thường) công bằng vì các báo cáo trừu tượng có một số chi phí (thường không xác định) trong thực tế, tùy thuộc vào máy móc, hệ điều hành và các yếu tố khác, và thời gian chạy ngắn có thể bị chi phối bởi hệ điều hành thiết lập quy trình ở vị trí đầu tiên và không có gì. Vì vậy, bạn nhận được một số nhiễu loạn, anyway.

Đây là cách phân tích tiệm cận liên quan đến phương pháp này.

  1. Xác định các hoạt động chi phối (gây ra chi phí), đó là các hoạt động xảy ra thường xuyên nhất (tối đa các yếu tố không đổi). Trong ví dụ Bubbledort, một lựa chọn có thể là so sánh trong dòng 5.

    Ngoài ra, ràng buộc tất cả các hằng số cho các hoạt động cơ bản bằng sự tôn trọng tối đa (từ phía trên) của chúng. tối thiểu của họ (từ bên dưới) và thực hiện phân tích thông thường.

  2. Thực hiện phân tích bằng cách sử dụng số lượng thực hiện của hoạt động này như chi phí.
  3. Khi đơn giản hóa, cho phép ước tính. Cẩn thận chỉ cho phép ước tính từ phía trên nếu mục tiêu của bạn là giới hạn trên ( ). từ bên dưới nếu bạn muốn giới hạn dưới ( ).OΩ

Hãy chắc chắn rằng bạn hiểu ý nghĩa của các biểu tượng Landau . Hãy nhớ rằng giới hạn như vậy tồn tại cho cả ba trường hợp ; sử dụng không có nghĩa là phân tích trường hợp xấu nhất.O

đọc thêm

Có nhiều thách thức và thủ thuật hơn trong phân tích thuật toán. Dưới đây là một số khuyến nghị đọc.

Có nhiều câu hỏi được gắn thẻ xung quanh việc sử dụng các kỹ thuật tương tự như thế này.


1
có thể một số tài liệu tham khảo và ví dụ cho định lý chính (và phần mở rộng của nó ) để phân tích tiệm cận
Nikos M.

@NikosM Nó nằm ngoài phạm vi ở đây (xem thêm các bình luận về câu hỏi trên). Lưu ý rằng tôi liên kết đến bài viết tham khảo của chúng tôi về việc giải quyết các đợt tái phát trong đó trình bày định lý Master et al.
Raphael

@Nikos M: 0,02 đô la của tôi: trong khi định lý chính hoạt động cho một số lần tái phát, nó sẽ không cho nhiều người khác; có các phương pháp tiêu chuẩn để giải quyết tái phát. Và có những thuật toán mà chúng ta thậm chí sẽ không tái phát trong thời gian chạy; một số kỹ thuật đếm tiên tiến có thể cần thiết. Đối với một người có nền tảng toán học tốt, tôi đề xuất cuốn sách xuất sắc của Sedgewick và Flajolet, "Phân tích thuật toán", có các chương như "quan hệ lặp lại", "tạo hàm" và "xấp xỉ tiệm cận". Các cấu trúc dữ liệu hiển thị như các ví dụ không thường xuyên và trọng tâm là các phương thức!
Jay

@Raphael Tôi không thể tìm thấy bất kỳ đề cập nào trên web cho phương pháp "Dịch mã này sang Toán học" dựa trên ngữ nghĩa hoạt động. Bạn có thể cung cấp bất kỳ tài liệu tham khảo cho cuốn sách, giấy hoặc bài viết liên quan đến điều này chính thức hơn?. Hoặc trong trường hợp này được phát triển bởi bạn, bạn có cái gì sâu hơn không?
Wyvern666

1
@ Wyvern666 Thật không may, không. Tôi tự làm nó, theo như bất cứ ai có thể yêu cầu để tạo ra một cái gì đó như thế này. Có lẽ tôi sẽ tự mình viết một tác phẩm có thể đọc được. Điều đó nói rằng, toàn bộ công việc xoay quanh tổ hợp phân tích (Flajolet, Sedgewick, và nhiều người khác) là nền tảng của điều này. Họ không bận tâm với ngữ nghĩa chính thức của "mã" hầu hết thời gian, nhưng họ cung cấp toán học để đối phó với chi phí phụ gia của "thuật toán" nói chung. Tôi thành thật nghĩ rằng các khái niệm được trình bày ở đây không sâu sắc - mặc dù toán học bạn có thể tham gia.
Raphael

29

Số lượng thi hành

Có một phương pháp khác, được Donald E. Knuth vô địch trong sê-ri Nghệ thuật lập trình máy tính . Ngược lại với việc dịch toàn bộ thuật toán thành một công thức , nó hoạt động độc lập với ngữ nghĩa của mã ở phía "đặt mọi thứ lại với nhau" và chỉ cho phép đi đến cấp độ thấp hơn khi cần thiết, bắt đầu từ chế độ xem "mắt đại bàng". Mỗi tuyên bố có thể được phân tích độc lập với phần còn lại, dẫn đến tính toán rõ ràng hơn. Tuy nhiên, kỹ thuật cho vay chính nó với mã khá chi tiết, không phải mã giả cấp cao hơn nhiều.

Phương pháp

Về nguyên tắc khá đơn giản:

  1. Chỉ định mỗi câu lệnh một tên / số.
  2. Chỉ định mỗi câu lệnh một số chi phí .SiCi
  3. Xác định cho mọi câu lệnh số lần thực hiện của nó .Siei
  4. Tính tổng chi phí

    C=ieiCi .

Bạn có thể chèn ước tính và / hoặc số lượng tượng trưng tại bất kỳ điểm nào, làm suy yếu sự tôn trọng. khái quát kết quả cho phù hợp.

Hãy lưu ý rằng bước 3 có thể phức tạp tùy ý. Thông thường bạn phải làm việc với các ước tính (tiệm cận), chẳng hạn như " " để có kết quả.e77O(nlogn)

Ví dụ: Tìm kiếm theo chiều sâu

Hãy xem xét thuật toán chuyển đổi đồ thị sau:

dfs(G, s) do
  // assert G.nodes contains s
  visited = new Array[G.nodes.size]     1
  dfs_h(G, s, visited)                  2
end 

dfs_h(G, s, visited) do
  foo(s)                                3
  visited[s] = true                     4

  v = G.neighbours(s)                   5
  while ( v != nil ) do                 6
    if ( !visited[v] ) then             7
      dfs_h(G, v, visited)              8
    end
    v = v.next                          9
  end
end

Chúng tôi giả định rằng biểu đồ (không bị chặn) được đưa ra bởi danh sách kề trong các nút . Gọi là số cạnh.{0,,n1}m

Chỉ cần nhìn vào thuật toán, chúng ta thấy rằng một số câu lệnh được thực thi thường xuyên như những câu lệnh khác. Chúng tôi giới thiệu một số trình giữ chỗ , và cho số lần thực hiện :ABCei

i123456789eiAABBBB+CCB1C

Cụ thể, vì mỗi cuộc gọi đệ quy trong dòng 8 gây ra một cuộc gọi trong dòng 3 (và một cuộc gọi được gây ra bởi cuộc gọi ban đầu từ ). Hơn nữa, vì điều kiện phải được kiểm tra một lần mỗi lần lặp nhưng sau đó một lần nữa để rời khỏi nó.e8=e31foodfse6=e5+e7while

Rõ ràng là . Bây giờ, trong một bằng chứng chính xác, chúng tôi sẽ chỉ ra rằng được thực hiện chính xác một lần cho mỗi nút; đó là, . Nhưng sau đó, chúng tôi lặp lại trên mỗi danh sách kề, chính xác một lần và mỗi cạnh bao hàm tổng cộng hai mục (một cho mỗi nút sự cố); chúng ta có tổng số lần lặp . Sử dụng cái này, chúng tôi rút ra bảng sau:A=1fooB=nC=2m

i123456789ei11nnn2m+n2mn12m

Điều này dẫn chúng ta đến tổng chi phí chính xác

C(n,m)=(C1+C2C8)+ n(C3+C4+C5+C6+C8)+ 2m(C6+C7+C9).

Bằng cách khởi tạo các giá trị phù hợp cho chúng ta có thể rút ra được nhiều chi phí cụ thể hơn. Chẳng hạn, nếu chúng ta muốn đếm số lượt truy cập bộ nhớ (mỗi từ), chúng ta sẽ sử dụngCi

i123456789Cin00110101

và lấy

Cmem(n,m)=3n+4m .

đọc thêm

Xem ở dưới cùng của câu trả lời khác của tôi .


8

Phân tích thuật toán, như chứng minh định lý, phần lớn là một nghệ thuật (ví dụ: có những chương trình đơn giản (như bài toán Collatz ) mà chúng ta không biết cách phân tích). Chúng ta có thể chuyển đổi một vấn đề phức tạp về thuật toán sang một vấn đề toán học, như đã được Raphael trả lời một cách toàn diện , nhưng sau đó để thể hiện ràng buộc về chi phí của một thuật toán theo các hàm đã biết, chúng ta còn lại:

  1. Sử dụng các kỹ thuật mà chúng tôi biết từ các phân tích hiện có, chẳng hạn như tìm giới hạn dựa trên các lần lặp mà chúng tôi hiểu hoặc tổng hợp / tích phân chúng tôi có thể tính toán.
  2. Thay đổi thuật toán thành một cái gì đó mà chúng ta biết cách phân tích.
  3. Hãy đến với một cách tiếp cận hoàn toàn mới.

1
Tôi đoán tôi không thấy cách này bổ sung bất cứ điều gì hữu ích và mới, hơn và hơn các câu trả lời khác. Các kỹ thuật đã được mô tả trong các câu trả lời khác. Điều này đối với tôi giống như một nhận xét hơn là một câu trả lời cho câu hỏi.
DW

1
Tôi dám khẳng định rằng những câu trả lời khác chứng minh rằng đó không phải là một nghệ thuật. Bạn có thể không làm được (tức là toán học), và một số sáng tạo (như cách áp dụng toán học đã biết) có thể là cần thiết ngay cả khi bạn, nhưng điều đó đúng với bất kỳ nhiệm vụ nào . Tôi cho rằng chúng ta không khao khát tạo ra toán học mới ở đây. (Trên thực tế, câu hỏi này tương ứng với câu trả lời của nó nhằm mục đích làm sáng tỏ toàn bộ quá trình.)
Raphael

4
@Raphael Ari đang nói về việc đưa ra một chức năng dễ nhận biết là ràng buộc, thay vì số lượng các lệnh được thực hiện bởi chương trình (đó là địa chỉ câu trả lời của bạn). Trường hợp chung một nghệ thuật - không có thuật toán nào có thể đưa ra một ràng buộc không cần thiết cho tất cả các thuật toán. Tuy nhiên, trường hợp phổ biến là một tập hợp các kỹ thuật đã biết (như định lý chính).
Gilles

@Gilles Nếu mọi thứ không có thuật toán tồn tại là một nghệ thuật, thợ thủ công (đặc biệt là lập trình viên) sẽ bị trả giá tệ hơn.
Raphael

1
@AriTrachlenberg đưa ra một điểm quan trọng mặc dù, có vô số cách để đánh giá độ phức tạp của thời gian thuật toán. Các định nghĩa ký hiệu Big O tự gợi ý hoặc nêu trực tiếp bản chất lý thuyết của chúng tùy thuộc vào tác giả. "Kịch bản tồi tệ nhất" rõ ràng để ngỏ khả năng phỏng đoán và hoặc những sự kiện mới trong số bất kỳ ai đưa ra cho N người trong phòng thảo luận. Không đề cập đến bản chất của các ước tính tiệm cận là một cái gì đó ... cũng không chính xác.
Brian Ogden
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.