Từ cách tính nhân ma trận cho thấy: nhân một ma trận kích thước m×n với một ma
trận n×p, số phép nhân phải thực hiện là m.n.p. Mặt khác phép nhân các ma trận có
tính kết hợp, tức là:
(A.B).C = A.(B.C)
Do đó khi tính tích nhiều ma trận, ta có thể thực hiện theo các trình tự khác nhau. Việc lựa chọn trình tự tính sẽ quyết định số phép nhân cần thực hiện.
Ví dụ ta có 4 ma trận: A(13,5), B(5,89), C(89,3), D(3,34) thì ta có một số cách tính A.B.C.D theo các trình tự khác nhau như sau:
1. Tính theo trình tự (((A.B).C).D) thì số phép nhân là 10592. 2. Tính theo trình tự ((A.B).(C.D)) thì số phép nhân là 54201. 3. Tính theo trình tự ((A.(B.C)).D thì số phép nhân là 2856. 4. Tính theo trình tự A.((B.C).D) thì số phép nhân là 4055. 5. Tính theo trình tự A.(B.(C.D)) thì số phép nhân là 26418.
Phép nhân (nhất là các phép nhân số thực) thường có thời gian thực hiện lâu hơn phép cộng, trừ. Do đó người ta đặt ra bài toán:
Cho n ma trận A1,A2…An, ma trận Ai có kích thước là di-1×di. Hãy xác định trình tự
nhân ma trận A1.A2…An sao cho số phép nhân cần thực hiện là ít nhất.
Nếu dùng phương pháp vét cạn để giải bài toán này thì chúng ta sẽ xét mọi cách đặt các dấu ngoặc khác nhau để tìm số phép nhân của từng cách đặt, từ đó tìm được cách
đặt tối ưu. Gọi T(n) là số cách đặt dấu ngoặc với n ma trận thì ta dễ dàng chứng minh được công thức tính T(n) như sau:
∑−= − = − = N1 1 i ) i n ( T ). i ( T ) n ( T
Dãy T(n) được gọi là dãy Catalan, bảng sau cho một số phần tử của dãy: n 1 2 3 4 5 10 15 T(n) 1 1 2 5 1 4 486 2 2674440
Dễ thấy là T(n) tăng rất nhanh theo n. Người ta đã chứng minh được T(n) cỡ 4n/n. Kết
luận là giải bằng phương pháp vét cạn thì chỉ giải được với những giá trị n rất bé. Đáng ngạc nhiên là ta có thể giải bài toán nhân ma trận bằng phương pháp quy hoạch động với độ phức tạp tính toán chỉ là O(n3).
Gọi F(i,j) là số phép nhân để tính tích Ai.Ai+1....Aj thì ta thấy: Nếu i=j thì chỉ có một ma trận do đó F(i,i)=0.
Nếu j=i+1 thì ta chỉ phải nhân Ai và Ai+1. Ai có kích thước di-1×di, Ai+1 có kích thước di×di+1, do đó F(i,i+1)=di-1.di.di+1.
Nếu j>i+1 thì ta thấy có thể tính Ai.Ai+1....Aj cách chọn một vị trí k nào đó để đặt ngoặc theo trình tự:
Ai.Ai+1....Aj = (Ai..Ak).(Ak+1..Aj)
Ma trận kết quả của phép nhân (Ai..Ak) có kích thước di-1×dk, ma trận kết quả của
phép nhân (Ak+1..Aj) có kích thước dk×dj. Với cách đặt đó ta sẽ mất F(i,k) phép nhân
để có kết quả trong dấu ngoặc thứ nhất, mất thêm F(k+1,j) phép nhân để có kết quả
trong dấu ngoặc thứ hai, và cuối cùng mất di-1.dk.dj để nhân 2 ma trận kết quả đó. Từ
đó tổng số phép nhân của cách đặt đó là:
F(i,k)+F(k+1,j)+di-1.dk.dj
Có nhiều cách chọn k : k=i+1,i+2,…j-1. Ta chọn cách cho phép số phép nhân ít nhất: F(i,j) = min(F(i,k)+F(k+1,j)+di-1.dk.dj) với mọi k=i+1,i+2,...j-1.
Tóm lại ta có công thức quy hoạch động như sau: 1. F(i,i)=0
2. F(i,i+1)=di-1.di.di+1
Đã có công thức quy hoạch động, ta sẽ chọn cấu trúc dữ liệu là một bảng 2 chiều F để lưu F(i,j). Tuy nhiên có một chú ý khi cài đặt là để tính được F(i,j), ta phải tính F(i,k) và F(k+1,j) trước. Như vậy trình tự tính không phải là tính các giá trị F(i,j) với i,j từ nhỏ đến lớn. Ta có nhận xét là trong công thức trên j-i lớn hơn k-i và j-k. Do đó nếu ta tính các phần tử F(x,y) với y-x từ nhỏ đến lớn thì khi tính F(i,j), ta đã có F(i,k) và F(k+1,j).
procedure Optimize; begin
for i := 1 to n do F[i,i] := 0;
for i := 1 to n-1 do F[i,i+1] := d[i-1]*d[i]*d[i+1]; for m := 2 to n-1 do begin
for i := 1 to n-m do begin j := i + m; F[i,j] := ∞; for k := i+1 to j-1 do
F[i,j] := min(F[i,j], F[i,k]+F[k+1,j]+d[i-1]*d[k]*d[j]); end;
end; end;
Kết quả của bài toán là F[1,n]. Để lần vết ta có thể dựa trên bảng phương án hoặc đặt riêng một bảng truy vết: T[i,j] sẽ là chỉ số k mà F[i,j] đạt min. Khi đó ta biết rằng đã
đặt cặp dấu ngoặc (Ai…Ak) và (Ak+1…Aj). Ta lại dựa vào T[i,k] và T[k+1,j] để cách
đặt dấu ngoặc ở các ma trận bên trong.