Tải bản đầy đủ (.doc) (12 trang)

Phân tích và đánh giá thuật toán sử dụng phương pháp quy hoạch động giải bài toán ba lô 0 1

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (160.14 KB, 12 trang )

SỬ 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ỏ.

1


Bướ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])
2


(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])
V[i,w]
0
1
2
...
...
n

0
0

1
0

2
0

3
0

...
0

...
0


W
0
bottom

up

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ể

3


Giả 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:

V[i,w] w=0
0
1
2
3

1

2

3

4

5

Keep
0
1
2
3

1

2

3

4


5

0

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:
Đối với dòng thứ 2: Item1 có v1=5 và w1=3
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ị).
V[i,w] w=0

1

2
4

3

4

5



0
1
2
3

0
0

0
0

0
0

0
5

0

0

Keep
0
1
2
3

0
0

0

1
0
0

2
0
0

3
0
1

4
0

5
0

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:
V[i,w] w=0
0

0
1
0
2
3

1
0
0

2
0
0

3
0
5

4
0
5

5
0
5

Keep
0
1
2

3
4
5
0
0
0
0
0
0
0
1
0
0
0
1
1
1
2
3
Đối với dòng thứ 3: Item1 có v1=5 và w1=3 và Item2 có v2=3 và w2=2
Đố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ô
5


V[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.
V[i,w] w=0
0
0
1
0
2
0
3

1
0
0
0

2
0
0
3


3
0
5
5

4
0
5
5

5
0
5
8

Keep
0
1
2
3

1
0
0
0

2
0
0
1


3
0
1
0

4
0
1
0

5
0
1
1

0
0
0
0

Đố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

6



V[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ô.
V[i,w] w=0
0
0
1
0
2
0
3
0

1
0
0
0
4

2
0

0
3
4

3
0
5
5
7

4
0
5
5
9

5
0
5
8
9

Keep
0
1
2
3

1
0

0
0
1

2
0
0
1
1

3
0
1
0
1

4
0
1
0
1

5
0
1
1
1

0
0

0
0
0

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}

7


CODE 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


8


/*---------------------- 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{
// 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);

9


// 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][jKL[i-1]] + GT[i-1])
{
HamKetQua[i][j] = HamKetQua[i-1][j-KL[i-1]] +

GT[i-1];
}
10


}
}
/*---------------------- 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
{
11


printf("%4d",GT[i-2]);
printf("%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();
}

12



×