贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心法的关键是设计最优度量标准或者叫贪心准则。一般能用贪心法解决的问题都有如下特性:
- 存在最优度量标准,指所求问题的整体最优解可以通过一系列局部最优解的选择,即最优度量标准来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
- 有最优子结构特性。当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
普通背包
【问题】 给定s个物品和一个容量为p的背包,物品i的重量为wi,其价值为vi,背包问题是如何选择装入背包的物品,使得装入背包中物品的总价值最大。注意和0/1背包的区别,在背包问题中,可以将某种物品的一部分装入背包中,但不可以重复装入。
【想法】最优度量标准可以选择什么呢?
1. 收益优先,收益越大的越提前放。
2. 重量优先,重量越大的越提前放。
3. 单位重量收益最大的物品先放入背包。
此处不必赘述,第三个选择必然是最优解,看代码实现:
import java.util.Arrays;
import java.util.Comparator;
/**
* 贪心法求解
* @author xuexiaolei
* @version 2017年12月07日
*/
public class GreedyAlgorithm {
/**
* 普通背包问题,可以将物品的一部分放入背包,比如黄金喽
* 简单贪心:将物品的单位价值降序排列
* @param s 物品个数,与物品重量,价值一一对应
* @param w 物品重量
* @param v 物品价值
* @param p 背包大小
* @return 返回最大价值
*/
public static double normalPackage(int s, double[] w, double[] v, double p){
double[][] vperw = new double[s][2];//[0]单位重量下的价值[1]编号
for (int i = 0; i < s; i++) {
vperw[i][0] = v[i] / w[i];
vperw[i][1] = i;
}
//按照收益比排序
Arrays.sort(vperw, new Comparator<double[]>() {
@Override public int compare(double[] o1, double[] o2) {
return o2[0]-o1[0]>0?1:-1;
}
});
// //看排序是否正确
// for (int i = 0; i < s; i++) {
// System.out.print(vperw[i][0] + " ");
// }
// System.out.println();
double tmpW = 0;//临时重量
double result = 0;//价值
for (int i = 0; i < s; i++) {
int b = (int) vperw[i][1];//当前物品的id
double thisW = w[b];
if (thisW < p-tmpW){//如果可以放下完整的物品
result += v[b];
tmpW += w[b];
System.out.println("物品 " + b +" 放入比例 1 获得收益 " + v[b]);
}else {
double canW = p-tmpW;//可以放的重量
result += vperw[i][0] * canW;
System.out.println("物品 " + b +" 放入比例 "+ canW/thisW +" 获得收益 " + vperw[i][0] * canW);
break;
}
}
return result;
}
public static void main(String[] args) {
double[] w = new double[]{18,15,10};
double[] v = new double[]{25,24,15};
double result = normalPackage(w.length, w, v, 20);
System.out.println("获取最大收益为:" + result);
}
}
运行结果:
物品 1 放入比例 1 获得收益 24.0
物品 2 放入比例 0.5 获得收益 7.5
获取最大收益为:31.5