完全背包问题(贪心算法)

概述

给定N个物品和一个容量为C的背包,物品i的重量是Wi,其价值为Vi,背包问题是如何选择入背包的物品,使得装入背包的物品的总价值最大,但不能超过总容量。在背包问题中可以将物品的一部分装入背包(物品可以拆分的装入背包),但不能重复装入。

解题思路

用贪心法求解背包问题的关键是如何选定贪心策略,使得按照一定的顺序选择每个物品,并尽可能的装入背包,直到背包装满。
至少有三种看似合适的贪心策略。

选择价值最大的物品,放入背包。

因为这可以尽可能快的增加背包的总价值,但是,虽然每一步选择获得了背包价值的极大增长,但背包容量却可能消耗的太快,使得装入背包的物品个数减少,从而不能保证目标函数达到最大。

选择重量最轻的物品,放入背包。

因为这可以装入尽可能多的物品,从而增加背包的总价值。但是,虽然每一步选择使背包的容量消耗的慢了,但背包的价值却没能保证迅速的增长,从而不能保证目标函数达到最大。

单位重量价值最大的物品,放入背包。

最大价值和最大重量两种贪心策略或者只考虑背包价值的增长,或者只考虑背包容量的消耗,而为了求得背包问题的最优解,需要在背包价值增长和背包容量消耗二者之间寻找平衡。正确的贪心策略是选择单位重量价值最大的物品。
例如:有三个物品,其重量分别为{20,30,10},价值分别为{60,120,50},背包的容量为50,应用三种贪心策略装入背包的物品和获得的价值如下图所示:

程序代码

程序思想:首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。

伪代码

1
2
3
4
5
6
7
8
9
10
11
Knapsack(v,w,W,x,n)
x <- 0
c <- W
for i <- 1 to n
do if w[i] ≤ c
then x[i] <- 1
c <- c - w[i]
if i ≤ n
then x[i] <- c/w[i]

return x

对N个物品按其单位重量价值从大到小进行排序,排序的时间复杂度为O(nlog2n),整个算法最耗时的也是这个排序。

Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package demo1;



/**
* 按照单位价值最高来计算
* 贪心算法解决 背包问题 非0/1背包
* @author Administrator
*
*/
public class Knapsack {

private float m; // 背包容量
private float[] v; // 三个物品的价值
private float[] w; // 三个物品的质量
private float[] x; // 物品在背包中占的比例
private int n; // 物品个数
double[] p_w_v; // 每个物品的单位重量价值

public static void main(String[] args) throws Exception {
Knapsack ksp = new Knapsack();
/*ksp.sort(ksp.n, ksp.v, ksp.w);
System.out.println("w:");
for (int i = 0; i < ksp.w.length; i++) {
System.out.print(ksp.w[i] + " ");
}
System.out.println();
System.out.println("v:");
for (int i = 0; i < ksp.v.length; i++) {
System.out.print(ksp.v[i] + " ");
}*/
ksp.knapsack();
}

public Knapsack() {
this.m = 50.0f; // 背包容量为50
this.v = new float[] { 60.0f, 120.0f, 50.0f }; // 三个物品的价值分别为60,120,100
this.w = new float[] { 10.0f, 30.0f, 20.0f }; // 三个物品的质量分别是10,30,20
this.x = new float[3]; // 往背包装东西的比例
this.n = 3; // 三个物品
this.p_w_v = new double[n]; // 每个物品的单位重量价值
}

// 对物品的单位重量价值进行排序
public void sort(int n, float[] v, float[] w) throws Exception {
//计算物品重量平均价值
for (int i = 0; i < n; i++) {
p_w_v[i] = v[i] / w[i];
}
System.out.println("单位重量价值:");
print(p_w_v);
System.out.println("");
insertSort(p_w_v, w, v);
print(p_w_v);
}

// 打印输出数组
public void print(double a[]) {
int len = a.length;
for (int i = 0; i < len; i++) {
System.out.print(a[i] + " ");
}
}

// 打印输出数组
public void print(String str) {
System.out.println(str);
}

/**
* 从大到小排序:根据单位重量的价值,把质量和价值从大到小排序
*
* @param a 每个物品的单位重量价值
* @param b 三个物品的质量分别是10,30,20
* @param c 三个物品的价值分别为60,120,100
*/
public void insertSort(double[] a, float[] b, float[] c) {// 排序静态函数,实现排序功能
int len = a.length;// 获得数组长度
double temp;// 临时变量,用于交换值
float w_temp;
float v_temp;
// 从大到小排序
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (a[j + 1] > a[j]) { // 如果后一下值比前一个值大,则交换两个值的大小,

temp = a[j + 1];
w_temp = w[j + 1];
v_temp = v[j + 1];

a[j + 1] = a[j];
w[j + 1] = w[j];
v[j + 1] = v[j];

w[j] = w_temp;
v[j] = v_temp;
a[j] = temp;
}
}
}
}

// 贪心算法核心思想
public void knapsack() throws Exception {
sort(n, v, w);
int i;
for (i = 0; i < n; i++) {
x[i] = 0;
}
//重量
float c = m;
//遍历物品数目
for (i = 0; i < n; i++) {
//大于剩余重量,退出循环
if (w[i] > c) {
break;
}
//i物品,100%放入背包里
x[i] = 1;
//剩余的重量
c -= w[i];
}
//计算超重的那个物品,占比例
if (i < n) {
x[i] = c / w[i];
}
System.out.println();
print("物品一可以装入的比例: " + x[0]);
print("物品二可以装入的比例: " + x[1]);
print("物品三可以装入的比例: " + x[2]);
print("可以装入的最大价值为: " + (x[0] * v[0] + x[1] * v[1] + x[2] * v[2]));
print("可以装入的最大重量为" + (x[0] * w[0] + x[1] * w[1] + x[2] * w[2]));
}
}

C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//4d2 贪心算法 背包问题
#include "stdafx.h"
#include <iostream>
using namespace std;

const int N = 3;

void Knapsack(int n,float M,float v[],float w[],float x[]);

int main()
{
float M = 50;//背包所能容纳的重量
//这里给定的物品按单位价值减序排序
float w[] = {0,10,20,30};//下标从1开始
float v[] = {0,60,100,120};

float x[N+1];

cout<<"背包所能容纳的重量为:"<<M<<endl;
cout<<"待装物品的重量和价值分别为:"<<endl;
for(int i=1; i<=N; i++)
{
cout<<"["<<i<<"]:("<<w[i]<<","<<v[i]<<")"<<endl;
}

Knapsack(N,M,v,w,x);

cout<<"选择装下的物品比例如下:"<<endl;
for(int i=1; i<=N; i++)
{
cout<<"["<<i<<"]:"<<x[i]<<endl;
}

return 0;
}

void Knapsack(int n,float M,float v[],float w[],float x[])
{
//Sort(n,v,w);//这里假定w[],v[]已按要求排好序
int i;
for (i=1;i<=n;i++)
{
x[i]=0;//初始化数组x[]
}

float c=M;
for (i=1;i<=n;i++)//物品整件被装下,x[i]=1
{
if (w[i]>c)
{
break;
}
x[i]=1;
c-=w[i];
}

//物品i只有部分被装下
if (i<=n)
{
x[i]=c/w[i];
}
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×