Có rất nhiều cách tiếp cận khả thi. Cái nào phù hợp nhất phụ thuộc vào
- những gì bạn đang cố gắng thể hiện,
- bao nhiêu chi tiết bạn muốn hoặc cần.
Nếu thuật toán là một thuật toán được biết đến rộng rãi mà bạn sử dụng như một chương trình con, bạn thường duy trì ở mức cao hơn. Nếu thuật toán là đối tượng chính đang được điều tra, có lẽ bạn muốn chi tiết hơn. Điều tương tự cũng có thể được nói cho các phân tích: nếu bạn cần một thời gian chạy trên cao bị ràng buộc, bạn tiến hành khác với khi bạn muốn đếm chính xác các câu lệnh.
Tôi sẽ cung cấp cho bạn ba ví dụ cho thuật toán Mergesort nổi tiếng, hy vọng sẽ minh họa điều này.
Cấp độ cao
Thuật toán Mergesort lấy một danh sách, chia nó thành hai phần (khoảng) dài bằng nhau, đệ quy trên các danh sách một phần đó và hợp nhất các kết quả (đã sắp xếp) để kết quả cuối cùng được sắp xếp. Trên danh sách đơn hoặc trống, nó trả về đầu vào.
Thuật toán này rõ ràng là một thuật toán sắp xếp chính xác. Tách danh sách và hợp nhất từng danh sách có thể được thực hiện trong thời gian , điều này mang lại cho chúng tôi sự tái phát trong trường hợp xấu nhất thời gian chạy . Theo định lý Master, giá trị này ước tính thành .T ( n ) = 2 T ( nΘ(n)T(n)∈Θ(nlogn)T(n)=2T(n2)+Θ(n)T(n)∈Θ(nlogn)
Mức trung bình
Thuật toán Mergesort được đưa ra bởi mã giả sau:
procedure mergesort(l : List) {
if ( l.length < 2 ) {
return l
}
left = mergesort(l.take(l.length / 2)
right = mergesort(l.drop(l.length / 2)
result = []
while ( left.length > 0 || right.length > 0 ) {
if ( right.length == 0 || (left.length > 0 && left.head <= right.head) ) {
result = left.head :: result
left = left.tail
}
else {
result = right.head :: result
right = right.tail
}
}
return result.reverse
}
Chúng tôi chứng minh tính đúng đắn bằng cảm ứng. Đối với các danh sách có độ dài bằng 0 hoặc một, thuật toán là chính xác. Theo giả thuyết cảm ứng, giả sử mergesort
thực hiện chính xác các danh sách độ dài tối đa đối với một số tùy ý, nhưng cố định . Bây giờ hãy để là một danh sách có độ dài . Theo giả thuyết cảm ứng, và giữ các phiên bản được sắp xếp (không tăng dần) của lần đầu tiên. nửa sau của sau các cuộc gọi đệ quy. Do đó, vòng lặp chọn trong mỗi lần lặp phần tử nhỏ nhất chưa được nghiên cứu và nối nó vào ; do đó, một danh sách không được sắp xếp ngày càng chứa tất cả các yếu tố từ vànn>1Ln+1left
right
Lwhile
result
result
left
right
. Ngược lại là một phiên bản sắp xếp không theo thứ tự , là kết quả trả về - và mong muốn -.L
Đối với thời gian chạy, chúng ta hãy đếm các so sánh phần tử và liệt kê các hoạt động (chiếm ưu thế trong thời gian chạy không theo triệu chứng). Danh sách chiều dài ít hơn hai nguyên nhân không. Đối với các danh sách có độ dài , chúng tôi có các thao tác gây ra bằng cách chuẩn bị đầu vào cho các cuộc gọi đệ quy, các cuộc gọi từ chính các cuộc gọi đệ quy cộng với vòng lặp và một . Cả hai tham số đệ quy có thể được tính toán với tối đa hoạt động danh sách mỗi. Các vòng lặp được thực hiện chính xác lần và mỗi lần lặp nguyên nhân nhiều nhất là một so sánh yếu tố và chính xác hai hoạt động danh sách. Trận chung kết có thể được thực hiện để sử dụngn>1while
reverse
nwhile
nreverse
2ndanh sách hoạt động - mọi yếu tố được loại bỏ khỏi đầu vào và đưa vào danh sách đầu ra. Do đó, số lượng hoạt động đáp ứng tái phát sau đây:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Vì rõ ràng là không giảm, nên đủ để xem xét cho sự tăng trưởng tiệm cận. Trong trường hợp này , việc tái phát đơn giản hóa thànhTn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
Theo định lý Master, chúng ta có kéo dài đến thời gian chạy của .T∈Θ(nlogn)mergesort
Mức cực thấp
Hãy xem xét việc triển khai (tổng quát) này của Mergesort trong Isabelle / HOL :
types dataset = "nat * string"
fun leq :: "dataset \<Rightarrow> dataset \<Rightarrow> bool" where
"leq (kx::nat, dx) (ky, dy) = (kx \<le> ky)"
fun merge :: "dataset list \<Rightarrow> dataset list \<Rightarrow> dataset list" where
"merge [] b = b" |
"merge a [] = a" |
"merge (a # as) (b # bs) = (if leq a b then a # merge as (b # bs) else b # merge (a # as) bs)"
function (sequential) msort :: "dataset list \<Rightarrow> dataset list" where
"msort [] = []" |
"msort [x] = [x]" |
"msort l = (let mid = length l div 2 in merge (msort (take mid l)) (msort (drop mid l)))"
by pat_completeness auto
termination
apply (relation "measure length")
by simp+
Điều này đã bao gồm bằng chứng về sự xác định rõ ràng và chấm dứt. Tìm một bằng chứng (gần như) hoàn toàn chính xác ở đây .
Đối với "thời gian chạy", đó là số lần so sánh, một lần lặp lại tương tự như lần lặp lại trong phần trước có thể được thiết lập. Thay vì sử dụng định lý Master và quên các hằng số, bạn cũng có thể phân tích nó để có được một xấp xỉ gần như bằng với số lượng thực. Bạn có thể tìm thấy phân tích đầy đủ trong [1]; đây là một phác thảo sơ bộ (nó không nhất thiết phải phù hợp với mã của Isabelle / HOL):
Như trên, sự lặp lại cho số lượng so sánh là
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
trong đó là số lượng so sánh cần thiết để hợp nhất các kết quả một phần². Để loại bỏ các tầng và trần nhà, chúng tôi thực hiện phân biệt trường hợp về việc là chẵn:enn
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
Sử dụng sự khác biệt về phía trước / phía sau của và chúng ta có được điều đóe nfnen
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1 .
Tổng phù hợp với phía bên phải của công thức của Perron . Chúng tôi xác định chuỗi tạo Dirichlet của làΔ∇fk
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
mà cùng với công thức của Perron dẫn chúng ta đến
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds .
Đánh giá tùy thuộc vào trường hợp nào được phân tích. Ngoài ra, chúng ta có thể - sau một số mánh khóe - áp dụng định lý dư lượng để có được⊟(s)
fn∼n⋅log2(n)+n⋅A(log2(n))+1
Trong đó là hàm tuần hoàn có các giá trị trong .A[−1,−0.9]
- Biến đổi Mellin và tiệm cận: sự tái phát sáp nhập của Flajolet và Golin (1992)
- Trường hợp tốt nhất:
Trường hợp xấu nhất:
Trường hợp trung bình:en=⌊n2⌋
en=n−1
en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1