Làm thế nào để mô tả các thuật toán, chứng minh và phân tích chúng?


20

Trước khi đọc Nghệ thuật lập trình máy tính (TAOCP) , tôi chưa xem xét sâu những câu hỏi này. Tôi sẽ sử dụng mã giả để mô tả các thuật toán, hiểu chúng và ước tính thời gian chạy chỉ về các đơn hàng tăng trưởng. Các TAOCP triệt để thay đổi tâm trí của tôi.

TAOCP sử dụng tiếng Anh trộn lẫn với các bước và goto để mô tả thuật toán và sử dụng biểu đồ dòng chảy để hình dung thuật toán dễ dàng hơn. Có vẻ như ở cấp độ thấp, nhưng tôi thấy rằng có một số lợi thế, đặc biệt là với biểu đồ dòng chảy, điều mà tôi đã bỏ qua rất nhiều. Chúng ta có thể gắn nhãn cho mỗi mũi tên với một xác nhận về tình trạng hiện tại tại thời điểm tính toán đi qua mũi tên đó và đưa ra bằng chứng quy nạp cho thuật toán. Tác giả nói:

Đó là sự ganh đua của tác giả mà chúng tôi thực sự hiểu tại sao thuật toán chỉ có giá trị khi chúng tôi đạt đến điểm mà tâm trí của chúng tôi đã ngầm điền vào tất cả các xác nhận, như đã được thực hiện trong Hình.4.

Tôi chưa có kinh nghiệm như vậy. Một lợi thế khác là, chúng ta có thể đếm số lần mỗi bước được thực hiện. Thật dễ dàng để kiểm tra với luật đầu tiên của Kirchhoff. Tôi chưa phân tích chính xác thời gian chạy, vì vậy một số có thể đã bị bỏ qua khi tôi ước tính thời gian chạy.±1

Phân tích các đơn đặt hàng tăng trưởng đôi khi là vô ích. Ví dụ: chúng ta không thể phân biệt quicksort với heapsort vì tất cả chúng đều là , trong đó là số lượng biến ngẫu nhiên dự kiến , vì vậy chúng ta nên phân tích hằng số, giả sử, và , do đó chúng ta có thể so sánh và tốt hơn. Ngoài ra, đôi khi chúng ta nên so sánh các đại lượng khác, chẳng hạn như phương sai. Chỉ một phân tích sơ bộ về các đơn đặt hàng tăng trưởng thời gian chạy là không đủ. Là TAOCPE X X E ( T 1 ( n ) ) = A 1 n lg n + B 1 n + O ( log n ) E ( T 2 ( n ) ) = A 2 lg n + B 2 n + O (E(T(n))=Θ(nlogn)EXXE(T1(n))=A1nlgn+B1n+O(logn)T 1 T 2E(T2(n))=A2lgn+B2n+O(logn)T1T2 chuyển các thuật toán sang ngôn ngữ lắp ráp và tính toán thời gian chạy, nó quá khó đối với tôi, vì vậy tôi muốn biết một số kỹ thuật để phân tích thời gian chạy gần hơn một chút, cũng hữu ích, đối với các ngôn ngữ cấp cao hơn như C, C ++ hoặc mã giả.

Và tôi muốn biết phong cách mô tả nào chủ yếu được sử dụng trong các công trình nghiên cứu và cách xử lý những vấn đề này.


6
Bạn nên rất cẩn thận khi so sánh thời gian thực hiện các thuật toán này một cách chặt chẽ. Máy tính thực sự có bộ nhớ cache, thanh ghi và đường ống, có thể thay đổi thời gian chạy mạnh mẽ. Nếu bạn muốn tìm ra thuật toán nào thực sự nhanh hơn, bạn phải thực sự chạy nó trên máy tính.
Svick

1
Trên thực tế, phân tích lắp ráp như sử dụng Knuth là cách dễ dàng hơn việc phân tích mã thực ngoài đời vì không có gì là ẩn và dòng điều khiển rất dễ dàng. Bạn đang yêu cầu thực hành; Tôi nghĩ rằng nhận xét của Dave áp dụng. Các học viên có nhiều khả năng thiết kế các thuật toán của họ bằng các phép đo thời gian chạy hơn là phân tích nghiêm ngặt. Nhưng sau đó, tôi không phải là học viên nên hãy lấy những gì tôi nói bằng một hạt muối.
Raphael

1
@Raphael Trong thực tế của tôi có nghĩa là trong thực tế các công trình nghiên cứu , không phải lập trình .
Yai0Phah

@Frank, những gì bạn có nghĩa là bởi sai ? Kiểm tra hiệu suất của tôi cho tôi phương sai thời gian.
edA-qa mort-ora-y

@Raphael, điểm đầu tiên của bạn không còn thực sự đúng nữa. Các chip hiện đại sắp xếp lại lắp ráp của bạn, lưu trữ / tải không theo thứ tự, và chạy và tải dự đoán. Đối với đồng thời và các vấn đề trước đó, thực sự cần phải phân tích kỹ lưỡng, nhưng tôi không thực hiện nó ở dạng chính thức.
edA-qa mort-ora-y

Câu trả lời:


18

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ử mergesortthự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+1leftrightLwhileresultresultleftright. 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>1whilereversenwhilenreverse2ndanh 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)=0T(n)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)=0T(n)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=f1=0fn=fn2+fn2+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

{f2m=2fm+e2mf2m+1=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=1n1(nk)Δfk=fnnf1 .

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)=k1Δfkks=112sk1Δekks=: (s)

mà cùng với công thức của Perron dẫn chúng ta đến

fn=nf1+n2πi3i3+i(s)ns(12s)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)

fnnlog2(n)+nA(log2(n))+1

Trong đó là hàm tuần hoàn có các giá trị trong .A[1,0.9]


  1. Biến đổi Mellin và tiệm cận: sự tái phát sáp nhập của Flajolet và Golin (1992)
  2. 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=n1
    en=nn2n2+1n2n2+1

Câu hỏi của tôi chạy thời gian phân tích này là, làm thế nào để xác định và chính xác , gần với thực tiễn (ví dụ: sẵn sàng để so sánh sắp xếp hợp nhất và qsort). β T ( n ) = T ( n / 2 ) + T ( n / 2 ) + α n + βαβT(n)=T(n/2)+T(n/2)+αn+β
Yai0Phah

@Frank: Câu trả lời ngắn gọn là Bạn không thể ; các hằng số phụ thuộc vào các chi tiết triển khai, bao gồm cả kiến ​​trúc máy, ngôn ngữ và trình biên dịch, không phù hợp với thuật toán cơ bản.
JeffE

@JeffE Tôi phải khẳng định rằng, và chỉ nên đủ chính xác để thực hiện một số so sánh. Tóm lại, một mô hình toán học có thể thực hiện rất nhiều công việc , không có ngôn ngữ máy, để xác định các hằng số. betaαβ
Yai0Phah

@JeffE chẳng hạn, MIX / MMIX trong taocp là, nhưng quá khó để dịch thuật toán sang ngôn ngữ máy như vậy.
Yai0Phah

@FrankScience: Để gần thực hành, bạn sẽ phải tính tất cả các thao tác (giống như Knuth vậy). Sau đó, bạn có thể khởi tạo kết quả của mình bằng chi phí vận hành dành riêng cho máy để có thời gian chạy thực (bỏ qua các hiệu ứng theo thứ tự các thao tác có thể có, bộ đệm, đường ống, ...). Thông thường, mọi người chỉ đếm một số thao tác và trong trường hợp đó, việc sửa và không cho bạn biết nhiều. betaαβ
Raphael

3

"Một môn học lập trình" của Dijkstra là tất cả về phân tích và chứng minh các thuật toán và thiết kế cho tính khả thi. Trong lời nói đầu của cuốn sách đó, Dijkstra giải thích cách một ngôn ngữ nhỏ được xây dựng rất đơn giản được thiết kế đúng để được phân tích đủ để giải thích chính thức nhiều thuật toán:

Khi bắt đầu một cuốn sách như thế này, người ta ngay lập tức phải đối mặt với câu hỏi: "Tôi sẽ sử dụng ngôn ngữ lập trình nào?", Và đây không phảimột câu hỏi chỉ để trình bày! Một khía cạnh quan trọng nhất, nhưng cũng khó nắm bắt nhất của bất kỳ công cụ nào là ảnh hưởng của nó đối với thói quen của những người tự rèn luyện cách sử dụng nó. Nếu công cụ là ngôn ngữ lập trình, thì ảnh hưởng này - dù chúng ta có thích hay không - ảnh hưởng đến thói quen suy nghĩ của chúng ta. Sau khi phân tích tầm ảnh hưởng đó theo sự hiểu biết tốt nhất của tôi, tôi đã đi đến kết luận rằng không có ngôn ngữ lập trình hiện có nào, cũng không phải là một tập hợp con của chúng, phù hợp với mục đích của tôi; mặt khác, tôi biết bản thân mình chưa sẵn sàng cho việc thiết kế một ngôn ngữ lập trình mới mà tôi đã thề sẽ không làm như vậy trong năm năm tiếp theo, và tôi có một cảm giác khác biệt nhất là thời kỳ đó chưa trôi qua! (Trước đó, trong số nhiều thứ khác, chuyên khảo này đã được viết.

Sau đó, anh giải thích rằng mình đã có được ngôn ngữ nhỏ như thế nào.

Tôi nợ người đọc một lời giải thích tại sao tôi giữ ngôn ngữ nhỏ đến mức nó thậm chí không chứa các thủ tục và đệ quy. ... Vấn đề là tôi cảm thấy không cần đến họ để có được thông điệp của mình, viz. làm thế nào một sự phân tách các mối quan tâm được lựa chọn cẩn thận là điều cần thiết cho việc thiết kế trong tất cả các chương trình chất lượng cao về mọi mặt; các công cụ khiêm tốn của ngôn ngữ mini đã cho chúng ta quá nhiều vĩ độ cho các thiết kế không cần thiết, nhưng rất thỏa đáng.

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.