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=0n−2∑j=0n−i−21=⋯=n(n−1)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,ji
j
Ci,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=0n−2∑j=0n−i−2C5,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))+{10,A(i,j)[j]>A(i,j)[j+1],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.
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=0n−2∑j=0n−i−21=n(n−1)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
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=0n−2∑j=0n−i−20=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.
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!∑A∑i=0n−2∑j=0n−i−2C5,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ác là một nửa chi phí trong trường hợp xấu nhất.
- 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-1
vò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.
- " " là ký hiệu toán học cho "giá trị mong đợi", ở đây chỉ là mức trung bình.E
- 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èn và Sắ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 E
củ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àc∘E1E2
- đá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 P
như 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 P
củ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)CR(ψ/A),A evaluates to true under ψ,else
Nói chung, việc đánh giá A
rấ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 P
củ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 , ..., .ψiQ
xi
x
x1
xi-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
xi
có 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 P
của mẫu while A do Q end
, gán chi phí
CP(ψ) =CA(ψ)+{0CQ(ψ/A)+CP(ψ/A;Q),A evaluates to false under ψ, 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<+{0c+=+c/+C1,4({i:=i0+1;x:=⌊x0/2⌋}),x0≤0, 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!c…i
x
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>c>+c+=+c/+C1,4(⌊x/2⌋),x≤0, 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 P
có dạng M(x)
cho một số tham số trong x
đó M
mộ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, x
có 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})C4({n:=n0}),n0≤1, else=c≤+{creturncreturn+c∗+ccall+Cfac({n:=n0−1}),n0≤1, else
Lưu ý rằng chúng tôi bỏ qua trạng thái toàn cầu, vì fac
rõ 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à ! 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.
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.
- 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í.
- 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ẻ phân tích thuật toán xung quanh việc sử dụng các kỹ thuật tương tự như thế này.