Khi nào tôi có thể sử dụng lập trình động để giảm độ phức tạp thời gian của thuật toán đệ quy?


13

Lập trình động có thể giảm thời gian cần thiết để thực hiện thuật toán đệ quy. Tôi biết rằng lập trình động có thể giúp giảm độ phức tạp thời gian của các thuật toán. Các điều kiện chung sao cho nếu được thỏa mãn bởi một thuật toán đệ quy sẽ ngụ ý rằng việc sử dụng lập trình động sẽ làm giảm độ phức tạp thời gian của thuật toán? Khi nào tôi nên sử dụng lập trình động?


Câu trả lời:


9

Lập trình động rất hữu ích là thuật toán đệ quy của bạn thấy mình đạt được cùng một tình huống (tham số đầu vào) nhiều lần. Có một sự chuyển đổi chung từ các thuật toán đệ quy sang lập trình động được gọi là ghi nhớ , trong đó có một bảng lưu trữ tất cả các kết quả từng được tính theo quy trình đệ quy của bạn. Khi thủ tục đệ quy được gọi trên một tập hợp các đầu vào đã được sử dụng, các kết quả chỉ được tìm nạp từ bảng. Điều này làm giảm Fibros đệ quy thành Fibros lặp.

Lập trình động có thể thậm chí thông minh hơn, áp dụng tối ưu hóa cụ thể hơn. Ví dụ, đôi khi không cần lưu trữ toàn bộ bảng trong bộ nhớ tại bất kỳ thời điểm nào.


Bộ đếm sau đó sẽ là bất cứ khi nào độ phức tạp không gian của việc ghi nhớ lớn hơn dữ liệu đầu vào (có lẽ chỉ> O (N)), rất có thể lập trình động sẽ không có ích. Đó là, khi bạn không thường xuyên gặp phải tình huống tương tự.
edA-qa mort-ora-y

1
Memoisation! = Lập trình động!
Raphael

1
Tôi không nghĩ chúng ta đang nói vậy, nhưng câu hỏi cho thấy giảm độ phức tạp thời gian. Tự lập trình động chỉ đơn giản là phân vùng vấn đề. Lập trình động + ghi nhớ là một cách chung để cải thiện độ phức tạp thời gian nếu có thể .
edA-qa mort-ora-y

@ edA-qamort-ora-y: Phải. Tôi nghĩ điều quan trọng là phải chỉ ra điều đó một cách rõ ràng, vì rõ ràng OP nhầm lẫn / trộn lẫn các khái niệm.
Raphael

8

Nếu bạn chỉ tìm cách tăng tốc thuật toán đệ quy của mình, thì việc ghi nhớ có thể là đủ. Đây là kỹ thuật lưu trữ kết quả của các cuộc gọi chức năng để các cuộc gọi trong tương lai có cùng tham số có thể sử dụng lại kết quả. Điều này được áp dụng nếu (và chỉ khi) chức năng của bạn

  • không có tác dụng phụ và
  • không chỉ phụ thuộc vào các tham số của nó (tức là không phụ thuộc vào một số trạng thái).

Nó sẽ giúp bạn tiết kiệm thời gian nếu (và chỉ khi) hàm được gọi với cùng một tham số lặp đi lặp lại. Các ví dụ phổ biến bao gồm định nghĩa đệ quy của các số Fibonacci, nghĩa là

f(0)= =0f(1)= =1f(n+2)= =f(n+1)+f(n), n0

ff(n)f(n+1)

Lưu ý rằng, ngược lại, sự phân biệt bên cạnh vô dụng đối với các thuật toán như sắp xếp hợp nhất: thường là một vài danh sách một phần (nếu có) giống hệt nhau và kiểm tra đẳng thức rất tốn kém (sắp xếp chỉ tốn kém hơn một chút!).

Trong triển khai thực tế, cách bạn lưu trữ kết quả là rất quan trọng đối với hiệu suất. Sử dụng bảng băm có thể là sự lựa chọn rõ ràng, nhưng có thể phá vỡ địa phương. Nếu tham số của bạn là số nguyên không âm, mảng là một lựa chọn tự nhiên nhưng có thể gây ra chi phí bộ nhớ lớn nếu bạn chỉ sử dụng một số mục. Do đó, sự sành sỏi là sự đánh đổi giữa hiệu quả và chi phí; cho dù nó trả hết tùy thuộc vào kịch bản cụ thể của bạn.


Lập trình động là một con thú hoàn toàn khác. Nó được áp dụng cho các vấn đề với tài sản mà

  • nó có thể được phân vùng thành các bài toán con (có thể theo nhiều cách),
  • những vấn đề con có thể được giải quyết độc lập,
  • Các giải pháp (tối ưu) của các bài toán con này có thể được kết hợp với các giải pháp (tối ưu) của bài toán ban đầu và
  • các bài toán con có cùng thuộc tính (hoặc là tầm thường).

Điều này thường (ngầm) ngụ ý khi mọi người gọi Nguyên tắc Tối ưu của Bellman .

Bây giờ, điều này chỉ mô tả một lớp các vấn đề có thể được thể hiện bằng một loại đệ quy nhất định. Đánh giá những cái đó là (thường) hiệu quả bởi vì sự phân biệt có thể được áp dụng cho hiệu quả lớn (xem ở trên); thông thường, các bài toán con nhỏ hơn xảy ra như là một phần của nhiều vấn đề lớn hơn. Các ví dụ phổ biến bao gồm khoảng cách chỉnh sửathuật toán Bellman-Ford .


Bạn có nói rằng có những trường hợp lập trình động sẽ dẫn đến sự phức tạp thời gian tốt hơn, nhưng việc ghi nhớ sẽ không giúp ích gì (hoặc ít nhất là không nhiều)? bạn có bất kì ví dụ nào không? Hay bạn chỉ nói rằng lập trình động chỉ hữu ích cho một tập hợp các vấn đề trong đó việc ghi nhớ là gì?
Svick

@svick: Lập trình động không tăng tốc bất cứ điều gì mỗi se, chỉ khi đệ quy DP được đánh giá với sự phân biệt (thường là (!) Trường hợp). Một lần nữa: DP là một cách để mô hình hóa các vấn đề theo phương thức đệ quy, phân biệt đối xử là một kỹ thuật để tăng tốc các thuật toán đệ quy phù hợp (bất kể DP). Nó không có ý nghĩa để so sánh trực tiếp cả hai. Tất nhiên, bạn cố gắng mô hình hóa một vấn đề như DP vì bạn mong muốn áp dụng sự phân biệt và do đó giải quyết nó nhanh hơn các cách tiếp cận ngây thơ (r) có thể. Nhưng quan điểm DP không phải lúc nào cũng dẫn đến thuật toán hiệu quả nhất.
Raphael

Nếu bạn có sẵn nhiều bộ xử lý, lập trình động sẽ cải thiện đáng kể hiệu năng trong thế giới thực khi bạn có thể song song hóa các bộ phận. Nó không thực sự thay đổi sự phức tạp thời gian mặc dù.
edA-qa mort-ora-y

@ edA-qamort-ora-y: Điều đó đúng với bất kỳ đệ quy. Tuy nhiên, điều này không rõ ràng rằng điều này tạo ra khả năng tăng tốc tốt, bởi vì việc phân chia kém hiệu quả hơn các ranh giới của bộ xử lý.
Raphael

Sửa chữa: đánh giá DP tái phát một cách ngây thơ vẫn có thể nhanh hơn (rất nhiều) so với lực lượng vũ phu; xem ở đây .
Raphael
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.