Ouch. This algorithm is wrong; there is no proof because it's wrong and therefore it's impossible to prove that it's correct.;) Tôi để nó ở đây vì tôi quá gắn bó để xóa nó hoàn toàn, và đó là một minh chứng tốt về lý do tại sao bạn nên chính thức chứng minh thuật toán thay vì nói "điều này có vẻ đúng! Không có cách nào có thể làm việc này không thành công! "
Tôi đang đưa ra giải pháp này mà không cần bằng chứng, hiện tại. Tôi có một bản phác thảo bằng chứng nhưng tôi gặp khó khăn trong việc chứng minh cấu trúc con tối ưu cho vấn đề này. Dù sao ...
Vấn đề
Cho một mảng vuông số, chọn càng nhiều số "không chồng chéo" càng tốt để tổng các số được chọn sẽ được tối đa hóa. "Không trùng lặp" có nghĩa là không có hai số có thể từ cùng một hàng hoặc cùng một cột.
Algorithm
- Hãy
A
là một mảng vuông n by n
số.
- Hãy để
Aij
biểu thị phần tử A
trong hàng thứvà cột j
.
- Hãy
S(i1:i2, j1:j2)
biểu thị tổng tối ưu các số không chồng chéo cho một mảng con vuông A
chứa giao điểm của hàng i1
-i2
và cột j1
-j2
.
Sau đó, số tiền tối ưu của số không chồng chéo được ký hiệu S(1:n , 1:n)
và được đưa ra như sau:
S(1:n , 1:n) = max { [ S( 2:n , 2:n ) + A11 ]
[ S( 2:n , 1:n-1) + A1n ]
[ S(1:n-1 , 2:n ) + An1 ]
[ S(1:n-1 , 1:n-1) + Ann ] }
(recursively)
Note that S(i:i, j:j) is simply Aij.
Đó là, số tiền tối ưu cho một mảng vuông kích thước n
thể được xác định bằng cách tính toán riêng số tiền tối ưu cho mỗi một trong bốn mảng phụ có kích thước n-1
, và sau đó tối đa hóa tổng của mảng phụ và phần tử "bị loại bỏ".
S for |# # # #|
|# # # #|
|# # # #|
|# # # #|
Is the best of the sums S for:
|# | | #| |# # # | | # # #|
| # # #| |# # # | |# # # | | # # #|
| # # #| |# # # | |# # # | | # # #|
| # # #| |# # # | | #| |# |
Thực hiện
Thuật toán đệ quy trên cho thấy một giải pháp đệ quy:
def S(A,i1,i2,j1,j2):
if (i1 == i2) and (j1==j2):
return A[i1][j1]
else:
return max (S(A, i1+1, i2, j1+1, j2) + A[i1][j1] ],
S(A, i1+1, i2, j1, j2-1) + A[i1][j2] ],
S(A, i1, i2-1, j1+1, j2) + A[i2][j1] ],
S(A, i1, i2-1, j1, j2-1) + A[i2][j2] ],)
Lưu ý rằng điều này sẽ làm cho các cuộc gọi đến O(4^n)
S()
!! Điều này là tốt hơn nhiều so với giai thừa O(n!)
thời gian phức tạp của giải pháp "sức mạnh vũ phu", nhưng hiệu suất vẫn khủng khiếp.
Điều quan trọng cần lưu ý ở đây là nhiều cuộc gọi được lặp lại với cùng các tham số. Ví dụ, trong việc giải quyết một mảng 3 * 3, mỗi mảng 2 * 2 được giải quyết nhiều lần.
Điều này cho thấy hai giải pháp khả thi cho sự tăng tốc:
- Tận dụng đệ quy chức năng
S()
kết quả bộ nhớ cache để nó chỉ cần S(A,i1,i2,j1,j2)
một lần cho mỗi i1,i2,j1,j2
. Điều này có nghĩa là S()
chỉ cần tính toán kết quả O(n^3)
- tất cả các yêu cầu khác sẽ được lọc từ bộ nhớ cache. (Điều này được gọi là memoising.)
- Thay vì bắt đầu từ phía trên, với
n*n
mảng lớn, và làm việc xuống qua bài toán liên tiếp nhỏ hơn, hãy bắt đầu ở dưới cùng với các bài toán nhỏ nhất có thể và xây dựng lên với trường hợp n*n
. Điều này được gọi là lập trình động. Đây cũng là O(n^3)
, nhưng nó nhanh hơn nhiều O(n^3)
vì bạn không phải nhấn vào bộ nhớ cache mọi lúc.
Các giải pháp lập trình năng động tiến hành một chút như:
- Tìm giải pháp tối ưu cho tất cả 1x1 tiểu mảng. (Không đáng kể.)
- Tìm các giải pháp tối ưu cho tất cả các mảng phụ 2x2.
- Tìm các giải pháp tối ưu cho tất cả các mảng phụ 3x3.
- ...
- Tìm giải pháp tối ưu cho tất cả các mảng phụ n-1 * n-1.
- Tìm các giải pháp tối ưu cho mảng phụ n * n hoàn chỉnh.
Ghi chú về giải pháp này:
chưa
- Không có bằng chứng. Tôi đang giải quyết nó.
- Bạn sẽ lưu ý thuật toán trên chỉ cung cấp cho bạn
S()
, tối ưu tổng số. Nó không cho bạn biết con số nào thực sự tạo nên số tiền đó. Bạn có thể thêm vào phương pháp của riêng bạn để backtracing đường dẫn của bạn đến giải pháp.
- Các thuật toán trên không đảm bảo tài sản đó gắn như
2,2
vs 1,3
sẽ bị phá vỡ trong lợi của việc có tất cả các số cá nhân được càng lớn càng tốt (để 2,2
thắng.) Tôi tin rằng bạn có thể định nghĩa max()
để phá vỡ mối quan hệ có lợi cho số lượng lớn nhất có thể, và điều đó sẽ làm những gì bạn muốn, nhưng tôi không thể chứng minh điều đó.
ghi chú chung:
Dynamic programming là một kỹ thuật mạnh mẽ cho việc đưa ra các thuật toán nhanh cho bất kỳ vấn đề trưng bày hai thuộc tính:
- Hạ tầng cơ sở tối ưu: Một vấn đề có thể được chia nhỏ thành hơi nhỏ các bộ phận, mỗi bộ phận có thể được sử dụng như một phần của giải pháp cho vấn đề ban đầu.
- Các vấn đề phụ chồng chéo có nghĩa là có ít vấn đề con thực tế cần giải quyết và các giải pháp cho các vấn đề con được sử dụng lại nhiều lần.
Nếu vấn đề có nền móng tối ưu, và vấn đề phá vỡ vào hơi vấn đề nhỏ hơn - nói một vấn đề về kích thước n
chia ra làm bài toán kích thước n-1
- thì vấn đề có thể được giải quyết bằng cách lập trình năng động .
Nếu bạn có thể chia nhỏ vấn đề thành nhiều những phần nhỏ hơn - nói chặt một vấn đề về kích thước n
vào nửa, mỗi kích thước n/2
- đó là chia để trị, không lập trình năng động. Giải pháp chia và chinh phục thường là rất nhanh - ví dụ: tìm kiếm nhị phân sẽ tìm thấy phần tử trong một mảng được sắp xếp theo thời gian O(log n)
.
Nghe có vẻ như nó sẽ tuân theo giải pháp lập trình động trong thời gian 'O (n^3)'. –
Tôi có một thuật toán lập trình động cho điều này sẽ chạy trong thời gian 'O (n^3)'. Tôi phải viết nó lên và chính thức chứng minh nó là chính xác. Nó sẽ có hiệu suất tốt hơn đáng kể so với một giải pháp đệ quy ngây thơ ... miễn là tôi có thể chứng minh nó hoạt động. ;) –