Cho một tập hợp các phần tử, mỗi phần tử có trọng lượng và giá trị khác nhau, xác định các phần tử có thể đưa vào một tập hợp khác sao cho trọng lượng bé hơn hoặc bằng một giới hạn cho t
Trang 1SỬ DỤNG PHƯƠNG PHÁP QUY HOẠCH ĐỘNG
GIẢI BÀI TOÁN BA LÔ 0-1
Bài toán xếp ba lô (Knapsack problem) là một bài toán tối ưu hóa tổ hợp (combinatorial optimization) Cho một tập hợp các phần tử, mỗi phần tử có trọng lượng và giá trị khác nhau, xác định các phần tử có thể đưa vào một tập hợp khác sao cho trọng lượng bé hơn hoặc bằng một giới hạn cho trước, và tổng giá trị các phần tử là lớn nhất Tên của bài toán bắt nguồn từ việc phải xếp các
đồ vật ba lô sao cho chứa được nhiều đồ có giá trị nhất
Một cách phát biểu khác về bài toán:
Một tên trộm sau khi đột nhập vào nhà thì thấy có N loại đồ vật có kích thước và giá trị khác nhau Vật gì hắn ta cũng muốn mang đi, nhưng lại chỉ mang một chiếc túi có dung lượng M (có thể chứa được một số đồ vật sao cho tổng kích thước chỉ nhỏ hơn hay bằng M) Vấn đề đặt ra cho tên trộm là hắn phải chọn lựa một danh sách các đồ vật sẽ mang đi sao cho tổng giá trị lấy cắp là lớn nhất
Sơ lược về Quy hoạch động (Dynamic programming)
Ý tưởng để phát triển một thuật toán quy hoạch động cho bài toán xếp
ba lô:
Bước 1: Cấu trúc: Tìm ra đặc điểm cấu trúc của phương án tối ưu.
Phân rã vấn đề lớn thành nhiều vấn đề nhỏ hơn Tìm ra sự liên kết giữa cấu trúc của phương án tối ưu và giải pháp của từng vấn đề nhỏ
Trang 2Bước 2: Nguyên tắc tối ưu: Định nghĩa giá trị của một phương án tối ưu
bằng phương pháp đệ quy Thể hiện giải pháp của vấn đề gốc bằng các giải pháp cho từng vấn đề nhỏ hơn
Bước 3: Tính từ dưới lên (bottom-up computation): Tính toán giá trị của
một giải pháp tối ưu bằng cách tính toán từ dưới lên bằng cách sử dụng cấu trúc bảng
Bước 4: Xây dựng một giải pháp tối ưu: Tổng hợp giải pháp tối ưu bằng
các thông tin đã được tính toán từ những vấn đề nhỏ hơn
Bước 3 và 4 thường được gộp chung lại
Bài toán xếp ba lô dạng 0-1:
Mỗi đồ vật chỉ có số lượng là 1, do vậy tên trộm chỉ được chọn lấy (1) hoặc không lấy (0) đồ vật đó
Bài toán xếp ba lô 0-1 có thể được phát biểu dưới dạng toán học như sau: Giả sử có n phần tử từ x1 đến xn, và xi có giá trị là vi và trọng lượng và wi Trọng lượng tối đa của chiếc ba lô là W Và ngầm định rằng giá trị và trọng lượng của đồ vật luôn không âm Để đơn giản hóa chúng ta cũng ngầm định rằng giá trị của các món đồ được liệt kê theo danh sách tăng dần về trọng lượng
1 Phân rã vấn đề lớn thành nhiều vấn đề nhỏ:
Chúng ta xây dựng một mảng V[0 n, 0 w] Trong đó i <= 1 ≤ n và 0 ≤ w
và ≤ W Giá trị cuối cùng V[i,w] sẽ lưu trữ giá trị lớn nhất (giá trị tổng hợp) của bất kỳ tập hợp con của các món đồ vật {1, 2, ,i} có trọng lượng tối đa là W Nếu chúng ta có thể tính toán tất cả các giá trị trong mảng này thì phần tử V[n, W] sẽ chứa giá trị lớn nhất của các món đồ mà có thể bỏ vừa vào ba lô
2 Định nghĩa các giá trị của giải pháp tối ưu một cách đệ qui bằng các giải pháp cho các vấn đề con.
Thiết lập ban đầu:
V[0, w] = 0 với 0 ≤ w ≤ W
V[i, w] = - vô cùng cho w ≤ 0, không hợp lệ
Bước tính toán đệ qui:
V[i,w] = Max(V[i−1,w],vi+V[i−1,w−wi])
Trang 3(Giá trị của ô V[i, w] sẽ bằng Max của giá trị của ô ngay trên đầu nó và giá trị của vi + giá trị còn có thể chứa được trong ba lô)
3 Tính từ-dưới-lên (bottom-up computing)
Đáy: V[0, w] = 0 với tất cả 0 ≤ w ≤ W
Các dòng khác được tính từng dòng một:
V[i,w] = Max(V[i−1,w], vi+V[i−1,w−wi])
2
Tuy nhiên bảng này không mô tả tập hợp con nào sẽ mang lại kết quả tối
ưu nhất Do đó chúng ta phải sử dụng một mảng Keep[i, w] để xác định tập hợp con T nào của các phần tử sẽ mang lại giá trị lớn nhất
Nếu Keep[n, W] = 1 thì phần tử n thuộc T Sau đó chúng ta có thể lặp lại quá trình này với Keep[n−1,W−wn] (Tức là phần tử thứ i=n−1 và có wi=W−wn) Nếu Keep[n, W] = 0 thì phần tử thứ n không thuộc T Và chúng ta lặp lại quá trình xét này cho Keep[n-1, W]
Đánh giá độ phức tạp của thuật toán
Thuật toán giải bài toán cái túi có thời gian đánh giá là O(nL) Thời gian này phụ thuộc tuyến tính vào dữ liệu vào L.Trên thực tế,thuật toán này làm việc hiệu quả khi L không là quá lớn
Xem xét một ví dụ cụ thể
Trang 4Giả sử chúng ta có 3 món đồ (và một món đồ đặt biệt có v=0 và w=0) lần lược có trọng lượng và giá trị như sau:
Item1 có v1=5 và w1=3
Item2 có v2=3 và w2=2
Item3 có v3=4 và w3=1
Và một ba lô có W=5
Đầu tiên chúng ta cần kẻ 2 bảng (nói cách khác là tạo 2 array) như sau:
0 1 2 3
0 1 2 3
Chúng ta sẽ áp dụng qui hoạch động để điền giá trị vào bảng này, mục đích
là biết được giá trị của ô cuối cùng V[3,5], khi đó cả ba món đồ đều được chọn
để bỏ vào ba lô có dung lượng là 5 Một khi bảng V được điền đầy đủ thì ta biết được phải bỏ món đồ nào vào ba lô là hợp lý Nhưng nó không cho ta biết sự kết hợp các món đồ nào để đặt vào ba lô để có giá trị lớn nhất, đó là mục đích của bảng Keep
Chi tiết:
Dựa theo bảng V, khi dung lượng của ba lô là 1 và 2 thì ba lô không thể chứa được Item 1 cho nên ta điền 0 vào ô 1 và 2 Tuy nhiên khi dung lượng của
ba lô tăng lên 3 thì Item 1 có thể nằm vừa trong ba lô Do đó chúng ta điền 5 (giá trị của món đồ vào bảng V) và 1 vào bảng Keep (cho biết món đồ này có thể giữ trong ba lô, nếu nó còn trống 3 đơn vị)
Trang 50 0 0 0 0 0 0
2 3
2 3
Tiếp theo, khi dung lượng của ba lô là 4, thì chúng ta hiển nhiên có thể đặt Item 1 vào ba lô Ta so sánh giá trị hiện tại có thể đặt vào với giá trị ở ngay trên
nó V[0, 4] = 0, ta thấy 5 > 0 Cho nên ta loại bỏ giá trị trước đó Và nó còn trống
1 đơn vị dung lượng, cho nên ta xem tiếp sử dụng Item trước đó (Item 0) có thể
bỏ vừa vào ba lô được nữa hay không? Giá trị V[0, 1] = 0 là câu trả lời Và chúng ta có thể bỏ vào ba lô hai Item 0 và Item 1 với tổng giá trị là 5 + 0 Tất nhiên ta cũng điền 1 vào Keep[1, 4]
Tiếp tục với ô V[1, 5] ta có được bảng như sau:
2 3
2 3
Đối với dòng thứ 3, ta cũng làm tương tự như dòng thứ 2 Tuy nhiên ô cuối cùng của dòng thứ 3 V[2, 5] hơi phức tạp một chút vì nó có thể chứa được cả Item 1 và Item 2
V[2, 1] = 0 Vì không có Item nào vừa trong ba lô có dung lượng bằng 1 V[2, 2] = 3 Vì chỉ có Item 2 (v=3 và w=2) vừa trong ba lô
Trang 6V[2, 3] = 5 Vì phần tử hiện hành (Item 2) bỏ vừa vào ba lô, nhưng có giá trị nhỏ hơn nếu bỏ phần tử trên nó V[1, 3] =5, nên chọn phần tử trên nó bỏ vào ba lô V[2, 4] = 5 Lý do như trên
V[2, 5] = 8 Tương tự trên, ta chọn phần tử trước nó có giá trị cao hơn phần
tử hiện hành, nhưng vẫn còn trống 2 đơn vị, do đó ta vẫn có thể bỏ vào thêm V[2,2] = 2 Tổng cộng có V[2, 5] = V[1, 5] + V[2,2] = 8
Keep[2, 1] = 0 Vì không chọn bỏ Item 2 nào vào ba lô
Keep[2, 2] = 1 Vì chọn bỏ Item 2 vào ba lô
Keep[2, 3] = 0 Vì phần tử hiện hành là Item 2 không được chọn, chọn phần
tử trên nó V[1, 3] có giá trị cao hơn bỏ vào ba lô
Keep[2, 4] = 0 Vì tương tự như trên
Keep[2, 5] = 1 Vì có chọn bỏ Item 2 vào ba lô, sau đó vẫn còn trống 3 đơn
vị ta bỏ thêm V[1, 3] = 5 vào Do đó ta giữ phương án này
3
3
Đối với dòng thứ 4: Ta sử dụng cả 3 Item có giá trị
Item1 có v1 = 5 và w1 = 3 và Item2 có v2 = 3 và w2 = 2 và Item3 có v3 =
4 và w3 = 1
V[3, 1] = 4 Vì nó chỉ bỏ vừa Item 3 (v=4, w=1)
V[3, 2] = 4 Vì nó chỉ bỏ vừa Item 3
V[3, 3] = 7 Vì nó bỏ vừa Item 3(v=4, w=1), còn trống 2 đơn vị, có thể bỏ thêm Item 2 (v=3, w=2) Và lớn hơn ô ở trên V[2, 3] = 5
Trang 7V[3, 4] = 9 Vì nó bỏ vừa Item 3(v=4, w=1), còn trống 3 đơn vị, có thể bỏ thêm Item 1 (v=5, w=3) Và lớn hơn ô ở trên V[2, 4] = 5
V[3, 5] = 9 Tương tự như trên
Keep[3, 1] = 1 Vì chọn bỏ Item 3 vào ba lô
Keep[3, 2] = 1 Vì chọn bỏ Item 3 vào ba lô
Keep[3, 3] = 1 Vì chọn bỏ Item 3 vào ba lô
Keep[3, 4] = 1 Vì chọn bỏ Item 3 vào ba lô
Keep[3, 5] = 1 Vì chọn bỏ Item 3 vào ba lô
Dựa vào giá trị cuối cùng trong bảng V (V[4,3] = 9) Ta biết được ba lô có thể chứa được tổng giá trị là 9 mà không vượt quá dung lượng của ba lô
Dựa vào mô tả ở trên ta có thể tính toán được tập hợp cấu thành phương
án tối ưu cho bài toán này
Ta có n=3 và W=5
Xét Keep[3,5] = 1 do đó ta ta đưa Item 3 vào trong tập hợp T
Tiếp theo xét: Keep[3−1,W−w3] = Keep[2,4]
Keep[2, 4] = 0 do đó ta không đưa Item 2 vào tập hợp T Sau đó xét tiếp Keep[n−1,W−w3]=Keep[1,4] = 1 Do đó ta đưa Item 1 vào tập hợp T
Cuối cùng ta thấy kết quả cuối cùng của bài toán xếp ba lô như sau:
Giá trị lớn nhất lấy được Vmax = 9 và T= {1,3}
Trang 8CODE CHƯƠNG TRÌNH
#include <stdio.h>
#include <conio.h>
int main(int argc, char* argv[])
{
// khai bao bien
int w; // Khoi luong cua balo
int n; // So luong do vat cho vao balo
int KL[500]; // mang luu khoi luong cua cac do vat; int GT[500]; // mang luu gia tri cua cac do vat;
int HamKetQua[500][500];
int i;// chỉ số chay theo số do vat : từ 0 >n
int j;// chỉ số chạy theo trọng lượng của balo : từ 0 > w
Trang 9/* - Phần nhập dữ liệu đầu vào -*/ // Nhập khối lượng của balo Giả thiết là khối lượng balo chỉ nằm trong 3 tới 500
// số 500 có thể thay đổi tùy theo ta định nghĩa
do
{
printf("Nhap khoi luong ma balo co the duoc:= ");
scanf("%d",&w);
}
while (w<3|| w>500);
// Nhập số lượng đồ vật
do
{
printf("So do vat dung de cho vao balo:= ");
scanf("%d",&n);
}
while (n<3|| n>500);
for (i=0;i<n;i++)
{
// Phần này dùng để nhập mảng khối lượng các đồ vật // Khối lượng mỗi đồ vật phải >=1 và nhỏ hơn bằng khối lượng balo, nếu thỏa mãn nhập lại
do {
printf("KT Dovat[%d]:=", i+1 );
scanf("%d", &KL[i]);
} while (KL[i]<1|| KL[i]>w);
Trang 10// Phần này dùng để nhập mảng giá trị các túi // Giá trị mỗi đồ vật phải >=1, nếu nhỏ hơn 1 sẽ nhập lại do
{
printf("GT Dovat[%d]:=", i+1 );
scanf("%d", >[i]);
} while (GT[i]<1);
}
/* - Phần thuật toán xử lý chương trình -*/ // khởi tạo, khi balo không chứa đồ vật nào thì tất cả giá trị ban đầu bằng 0 for (j=0;j<=w; j++)
{
HamKetQua[0][j] =0;
}
// phần chương trình chính
for(i=1;i<=n;i++)
{
for (j=0;j<=w;j++) {
HamKetQua[i][j] = HamKetQua[i-1][j];
if (KL[i-1] <=j && HamKetQua[i][j] < HamKetQua[i-1][j-KL[i-1]] + GT[i-1])
{
HamKetQua[i][j] = HamKetQua[i-1][j-KL[i-1]] + GT[i-1];
}
Trang 11}
/* - Phần hiển thị kế quả ra màn hình -*/ // Hiển thị bảng phương án
for(i=0; i<=n+1; i++)
{
// hien thị dòng tiều đề bảng phương án
if (i==0)
{
printf(" GT ");
printf(" KL ");
printf("i|v");
for(j=0; j<=w; j++) {
printf("%4d",j);
} } // hiển thị dòng balo không chứa gói hàng nào
else if (i==1)
{
printf(" ");
printf(" ");
printf("%4d",i-1);
for(j=0; j<=w; j++) {
printf("%4d",HamKetQua[i-1][j]);
} }
else // hiển thị nội dung bảng phương án
{
Trang 12printf("%4d",KL[i-2]);
printf("%4d",i-1);
for(j=0; j<=w; j++) {
printf("%4d",HamKetQua[i-1][j]); }
}
printf("\n");
}
// Hiển thị các đồ vật đã được lựa chọn vào Ba lô j= w;
printf("Cac Do vat duoc chon la:=");
for (i= n;i>0;i )
{
if (HamKetQua[i][j] != HamKetQua[i-1][j]) {
j= j-KL[i-1];
printf("%4d",i);
}
}
getch();
}