Mã bài : ITPRG3_12.7 Giới thiệu
Quy hoạch động (dynamic programming) là một phương pháp có ý tưởng giống như phương pháp chia để trị. Nó cho phép chúng ta phân rã một bài toán lớn thành những bài tốn nhỏ hơn và tìm nghiệm những bài tốn nhỏ này trước, sau đó tìm ngược lại nghiệm của bài tốn lớn. Điểm khác nhau cơ bản giữa hai phương pháp này phương pháp chia để trị phân rã và giải quyết bài toán từ trên xuống (top-down) cịn quy hoạch động giải quyết bài tốn theo kiểu từ dưới lên (down-top).
Mục tiêu thực hiện
Học xong bài này học viên sẽ có khả năng:
Nắm bắt được ý tưởng của phương pháp quy hoạch động.
Sử dụng phương pháp quy hoạch động để giải quyết các bài toán sắp xếp các đồ vật vào ba lơ, tìm dãy con chung của hai dãy số, tìm đường đi ngắn nhất…
Áp dụng phương pháp quy hoạch động để giải quyết một số bài toán trong thực tế.
Nêu ra lợi thế của phương pháp quy hoạch động trong việc xây dựng một số thuật toán.
So sánh cách tiếp cận của phương pháp quy hoạch động so với các phương pháp khác.
.21. Phương pháp tổng quát
Trong nhiều trường hợp, để giải một bài toán đã cho ta đưa về giải một số bài toán con rồi kết hợp nghiệm của bài toán con ta nhận được nghiệm của bài toán cần giải. Để giải các bài toán con này ta lại đưa về việc giải các bài tốn con nhỏ hơn. Q trình trên sẽ tiếp tục cho tới khi nhận được các bài tốn con có thể giải được dễ dàng. Đó là kỹ thuật chia để trị mà ta đã xét. Chia để trị là kỹ thuật từ trên xuống (top-down), nó giải các bài tốn con nhận được trong q trình chia nhỏ một cách độc lập. Tuy nhiên, trong q trình phân chia như thế, thơng thường ta gặp rất nhiều lần cùng một bài tốn con. Do đó, nếu giải quyết bài tốn bằng kỹ thuật top-down chúng ta phải tính lại nhiều lần cùng một bài tốn. Thuật toán nhận được sẽ kém hiệu quả.
Số tổ hợp chập k của n, ký hiệu là hoặc là số các cách chọn k phần tử khác nhau từ một tập n phần tử. Các số tổ hợp còn được gọi là hệ số nhị thức. Các hệ số nhị thức có rất nhiều ứng dụng trong tốn học và thường được sử dụng để phân tích và đánh giá các thuật tốn. Công thức sau đây cho phép ta tính được thơng qua và
=1 nếu i =0 hoặc i=j
= + nếu 0<i<j
Chúng ta có thể tính các hệ số nhị thức một cách trực tiếp bởi hàm đệ quy sau : function Coef(k, n :integer) :integer ;
begin
if(k=0)or(k=n) then coef:=0
else coef:= coef(k,n-1) + coef(k-1,n-1); end;
Khi đó, để tính coef(k, n), rất nhièu giá trị coef(i, j) với i<k, j<n được tính lặp lại nhiều lần. Chẳng hạn, để tính ceof(3, 5) ta phải tính lặp lại hai lần coef(2, 3), 3 lần coef(1, 2) ... Thời gian sẽ rất lớn, thực tế là không chấp nhận được khi các giá trị đầu vào lớn.
Một cách tiếp cận khác để tính là ta tính các với i<k, j<n xuất phát từ trường hợp
đơn giản nhất =1 với jn. Trong quá trình tính ta sẽ sử dụng một bảng để lưu các giá trị đã tính. Bằng cách đó ta sẽ tránh được việc tính lại nhiều lần cùng một giá trị nào đó. Cụ thể ở đây ta sử sụng mảng C[0..k, 0...n], trong đó C[i, j] (0 i k, 0 j n) lưu và tính C[i, j] lần lượt theo hàng. Thực tế, vì i j, ta chỉ cần tính các giá trị của tam giác nằm trên đường chéo chính. Vì vậy = + , mỗi phần tử C[i, j] ở hàng i cột j, là tổng của hai phần tử ở bên trái nó, phần tử C[i, j–1] và phần tử nằm trên phần tử này là C[i –1, j –1].
Sau đây là bảng lưu các giá trị , 0 i k=3 và 0 j n =5
j = 0 1 2 3 4 5
Phân tích thiết kế thuật tốn
1 1 2 3 4 5
2 1 3 6 10
3 1 4 10
Chúng ta dễ dàng viết được thủ tục tính , với 0 i k và 0 j n và i j, bằng phương pháp này: begin for j:= 0 to n do C[0,j]:=1 for i:= 1 to k do begin C[i,j]:= 1; for j:= i +1 to n do
C[i,j]:= C[i,j –1] +C[i –1,j –1]; end;
end;
Quy hoạch động là lỹ thuật từ dưới lên (bottom–up). Chúng ta xuất phát từ những trường hợp riêng đơn giản nhất của bài toán, thường là thấy ngay nghiệm của chúng. Sau đó kết hợp nghiệm của chúng thì ta được nghiệm của bài tốn con có cỡ lớn hơn. Rồi lại kết hợp nghiệm của các bài toán con này để nhận nghiệm của bài toán lớn hơn nữa và cứ thế tiếp tục cho đến khi nhận được nghiệm của bài toán đã cho. Tư tưởng cơ bản của bài toán quy hoạch động là quá trình “đi từ dưới lên”, ta sử dụng một bảng để lưu giữ lời giải của cả bài toán con đã giải. Khi giải một bài toán con cần đến bài toán con cỡ nhỏ hơn, ta chỉ cần tìm ở trong bảng mà khơng cần giải lại . Chính vì thế mà thuật tốn được thiết kế bằng quy hoạch động sẽ rất có hiệu quả.
Để giải một bài toán quy hoạch động, chúng ta cần tiến hành những cơng việc sau : Tìm nghiệm của các bài tốn con (các trường hợp riêng) đơn giản nhất.
Tìm ra các cơng thức (hoặc quy tắc) xây dựng nghiệm của bài toán con thơng qua nghiệm của các bài tốn con cỡ nhỏ hơn.
Tạo ra một bảng để lưu giữ các nghiệm của bài tốn con. Sau đó tính nghiệm của các bài tốn con theo các cơng thức đã tìm ra và lưu vào bảng.
Từ bảng đã làm đầy tìm cách xây dựng nghiệm của bài tốn. Sau đây chúng ta sẽ đưa ra một số ví dụ minh họa.
.22. Bài toán sắp xếp các đồ vật vào ba lơ
Một chiếc ba lơ có thể chứa được một khối lượng w. Có n loại đồ vật được đánh số 1, 2, ..., n. Mỗi đồ vật loại i có khối lượng ai và có giá trị ci (i=1, 2, ..., n). Cần sắp xếp các đồ vật vào ba lơ để ba lơ có giá trị lớn nhất có thể được. Giả sử rằng mỗi loại có đủ nhiều để xếp.
Phát biểu bài toán : Cho trước w, ai, ci (i=1, 2, ..., n) là các số nguyên dương. Cần tìm x=(x1, x2, ..., xn), trong đó xi là số ngun không âm sao cho:
w (1)
và đạt max (2)
Xét trường hợp riêng đơn giản nhất : chỉ có một loại đồ vật (n=1). Trường hợp này ta tìm được ngay lời giải : xếp đồ vật vào ba lô cho tới khi nào khơng xếp được nữa thì thơi, tức là x1 = w div a1.
Gọi f(k, v) là giá rtị lớn nhất của ba lô chứa được khối lượng v và chỉ xếp k loại đồ vật 1, 2, ..., k ; với i k n, 1 v w. Ta tìm được cơng thức để tính f(k, v).
Với k = 1, 1 v w, ta có : x1 = v div a1 và f(1, v) = x1c1.
Giả sử đã tính được f(s, u), với 1 s < và 1 u v, ta cần tính f(k, v) với 1 v w. Gọi yk = v div ak, ta có:
f(k, v) = max {f (k-1, u) + xkck} (3)
Trong đó, max được lấy với tất cả xk = 0, 1, ..., yk và u = v - xkak Giá trị lớn nhất của ba lô sẽ là f(n, w).
Chúng ta sẽ sử dụng mảng A[1...n, 1...w] để lưu các kết quả trung gian. A[ k,v] (1 k n, 1 v w) là bản ghi gồm hai trường, một trường chứa f(f, v) và một trường chứa xk, trong đó xk là giá trị mà trong (3) biểu thức f(k-1, u) + xkck đạt max.
Từ công thức (3), suy ra rằng các giá trị của bảng A có thể tính dược dễ dàng lần lượt theo dịng 1, 2, ..., n.
Vấn đề đặt ra bây giờ là từ bảng A đã làm đầy, làm thế nào để xác định được x =(x1, x2, ..., xn) và f(n, w).
Phân tích thiết kế thuật tốn
Ơ A[n, w] chứa f(n, w) và xn. Tính v = w – xnan. Tìm đến ơ A[n –1, v] ta biết được xn-1. Tiếp tục q trình đó, ta tìm được xn-2, ... , x2, x1.
.23. Bài tốn tìm đường đi ngắn nhất từ một đỉnh
Cho đồ thị G với tập đỉnh V và tập các cạnh E (đồ thị có hướng hoặc vơ hướng). Mỗi cạnh của đồ thị được gán một nhãn, đó là một giá trị khơng âm, nhãn này cịn được gọi là giá của cạnh. Cho trước một đỉnh xác định v, gọi là đỉnh nguồn. Bài tốn đặt ra là tìm đường đi ngắn nhất từ đỉnh v đến các đỉnh còn lại của G. Tức là tìm đường đi từ v đến các đỉnh còn lại với tổng các giá của các cạnh trên đường đi là nhỏ nhất. Nếu như đồ thị có hướng thì đường đi này là đường đi có hướng.
Ta có thể giải bài tốn bằng cách xác định một tập hợp S chứa các đỉnh mà khoảng cách ngắn nhất từ nó đến đỉnh nguồn v đã biết. Khởi đầu S = {V}. Sau đó tại mỗi bước ta sẽ thêm vào S các đỉnh mà khoảng cách từ nó đến v là ngắn nhất. Với giả thiết rằng mỗi cung có một giá trị khơng âm thì ta ln ln tìm được một đường đi ngắn nhất như vậy mà chỉ đi qua các đỉnh đã tồn tại trong S.
Ðể dễ dàng chi tiết hóa thuật tốn , giả sử G có n đỉnh và nhãn trên mỗi cung được lưu trong mảng C, tức là C[i, j] bằng giá (có thể xem là độ dài) của cung (i, j). Nếu i và j khơng có cung nối thì ta cho C[i, j] =Ġ. Ta sẽ dùng một mảng D có n phần tử để lưu độ dài của đường đi ngắn nhất từ v đến mỗi đỉnh của đồ thị. Khởi đầu thì giá trị này chính là độ dài cạnh (v, i), tức D[i] = C[v, i]. Tại mỗi bước của thuật tốn thì D[i] sẽ lưu độ dài đường đi ngắn nhất từ đỉnh v đến đỉnh i, đường đi này chỉ đi qua các đỉnh đã có trong S.
Ðể cài đặt thuật toán dễ dàng, ta giả sử các đỉnh của đồ thị được đánh số từ 1 đến n và đỉnh nguồn là đỉnh 1.
Dưới đây là thuật toán Dijkstra để giải bài toán trên : Procedure Dijkstra ;
Begin
S := [1] ; { S chỉ chứa đỉnh nguồn } For i:=2 to n do
D[i] := C[1, i] ; { Khởi đầu các giá trị cho D } For i:=1 to n - 1 do
Begin
Lấy đỉnh w trong V - S sao cho D[w] là nhỏ nhất ; Thêm w vào S ;
For mỗi đỉnh u thuộc V - S do D[u] := Min (D[u], D[w] + C[w, u]) ;
End; End;
Nếu muốn lưu trữ lại các đỉnh trên đường đi ngắn nhất để có thể xây dựng lại đường đi này từ đỉnh nguồn đến các đỉnh khác, ta dùng một mảng P. Mảng này sẽ lưu P[u] = w với đỉnh u là đỉnh trước của đỉnh w trên đường đi ngắn nhất. Lúc khởi đầu ta cho P[u] = 1, với mọi u<>1.
Thuật toán Dijkstra ở trên sẽ được viết lại như sau : Procedure Dijkstra ;
Begin
S := [1] ; { S chỉ chứa đỉnh nguồn } For i:=2 to n do
Begin
D[i] := C[1, i] ; { Khởi đầu các giá trị cho D }
P[i] := 1 ; { Khởi đầu các giá trị cho P } End ;
For i:=1 to n - 1 do Begin
Lấy đỉnh w trong V - S sao cho D[w] là nhỏ nhất ; Thêm w vào S ;
For mỗi đỉnh u thuộc V - S do If (D[w] + C[w, u] < D [u]) then Begin D[u] := D[w] + C[w, u] ; P[u] := w ; End ; End; End;
Phân tích thiết kế thuật toán
.24. Dãy con chung của hai dãy số
Cho hai dãy số nguyên a = (a1, ..., am) và b = (b1, ..., bn). Chúng ta cần tìm dãy số nguyên c = (c1, ..., ck) sao cho c là dãy con của a và b và c có độ dài lớn nhất có thể được.
Ví dụ : Nếu a = (3, 5, 1, 3, 5, 5, 3) và b = (1, 5, 3, 5, 3, 1) thì dãy con chung dài nhất là c = (5 , 3, 5, 3), hoặc c = (1, 3, 5, 3), hoặc c = (1, 5, 5, 3).
Trường hợp đơn giản, khi một trong hai dãy a và b rỗng (m =0 hoặc n=0), ta thấy dãy con dài nhất là dãy rỗng.
Ta xét các đoạn đầu của hai dãy a và b có độ dài i và j tương ứng (a1, a2,...,ai) và b(b1, b2,..,bj) với iv m, 0jn. Gọi L(i, j) là độ dài lớn nhất của dãy con chung của hai dãy (a1, a2, ..., ai) và (b1, b2, ..., bj). Như vậy, L(m, n) sẽ là độ dài lớn nhất của dãy con chung của a và b.
Bây giờ ta tìm cách tính L(i, j) thơng qua L(s, t) với s i, t j. Dễ dàng thấy sự đúng đắn của cơng thức sau:
Nếu i=0 hoặc j=0 thì L(i, j) =0
Nếu i>0 và j>0 và ai bj thì L(i, j)= max {L(i,j-1), L(i-1,j)}
Nếu i>0 và j>0 và ai =bj thì L(i, j) = 1 + L(i-1, j-1)
Chúng ta sẽ lưu các giá trị L(i, j) vào mảng L[0..m, 0..n]. Từ công thức (2) và (3) ta thấy rằng, nếu biết L[i, j-1], L[i-1, j] và L[i-1, j-1] ta tính ngay được L[i, j], do đó ta có thể tính được các phần tử của mảng L[0..m, 0..n] từ góc trên bên trái lần lượt theo các đường chéo song song.
0 1 2 n
0 1 2
m
Bây giờ từ mảng L đã được làm đầy, ta xây dựng dãy con chung dài nhất là k = L[m, n]. Ta xác định các thành phần của c = (c1, ..., ck-1,ck) lần lượt từ bên phải. Trong bảng L ta đi từ ô L[m, n]. Giả sử ta đang ở ô L[i, j] và ta đang cần xác định cl (1 l k). Nếu ai = bi thì ta lấy cl = ai, giảm l đi một và đi lên ơ L[i-1, j-1]. Cịn nếu ai bj thì hoặc L[i, j] = L[i, j-1] hoặc L[i, j] = L[i-1, j]. Trong
trường hợp L[i, j] = L[i, j-1], ta đi tới ơ L[i, j-1]. Cịn nếu L[i, j] = L[i-1, j] thì ta đi lên ơ L[i-1, j]. Tiếp tục quá trình trên ta xác định được tất cả các thành phần của dãy con dài nhất c.
Bài toán này và bài tốn chiếc ba lơ thuộc lớp tối ưu. Quy hoạch động là phương pháp hay được sử dụng để giải các bài toán tối ưu. Đương nhiên khơng phải bài tốn tối ưu nào cũng có thể giải bằng quy hoạch động mà chỉ có thể sử dụng kỹ thuật quy hoạch động cho các bài tốn thõa mãn ngun lý tối ưu. Có thể phát biểu nguyên lý tối ưu một cách đơn giản như sau: nghiệm tối ưu của một bài toán con bất kỳ (của bài toán) là một sự kết hợp các nghiệm tối ưu của các bài tốn con của nó.
.25. Bài toán người bán hàng
Chúng ta trở lại bài toán người bán hàng đã xét ở các phần trước. Ta đã đưa ra một thuật toán được thiết kế dựa trên kỹ thuật tham lam cho bài tóan này. Thuật tốn này chỉ cho nghiệm gần đúng. Ta cũng đã đưa ra một thuật toán theo phương pháp nhánh và cận cho bài toán này. Trong mục này ta đã áp dụng phương pháp quy hoạch động để giải bài toán này.
Giả sử G = (V, E) là đồ thị định hướng với V ={1, 2, ..., n} và dộ dài các cung là C[i, j] > 0 nếu (i, j) E và C[i, j] = nếu (i, j) E. Khơng mất tính tổng qt, ta giả sử đường đi của người bán hàng là đường đi xuất phát từ đỉnh 1 qua tất cả các đỉnh con lại đúng một lần rồi lại trở về đỉnh 1.
Phân tích thiết kế thuật tốn
Bất kỳ đường đi nào của người bán hàng có thể phân thành cung (1,k) với k V – {1} và đường đi từ đỉnh k đến đỉnh 1 qua mỗi đỉnh thuộc V- {1, k} đúng một lần. Dễ dàng thấy rằng, nếu đường đi của người bán hàng là ngắn nhất thì đường đi từ k tới 1 qua các đỉnh thuộc V – {1, k} phải ngắn nhất. Do đó nguyên lý tối ưu được thỏa mãn. Gọi d(i, S) là độ dài đường đi ngắn nhất từ đỉnh i tới