参考书籍:算法设计与分析——C++语言描述(第二版)
//贪心法框架
SolutionType Greedy(SType a[], int n)
{
//初始时,解向量不包含任何分量
SolutionType solution=EmptySet;
//多步决策,每次选择解向量的一个分量
for(int i = 0; i//遵循最优量度标准选择一个分量
SType x= Select(a);
//判定加入分量x后的部分解是否可行
if(Feasible(solution, x))
//形成新的部分解
solution=Union(solution, x);
}
return solution;//返回生成的最优解
}
对于一个贪心算法,必须进一步证明该算法的每一步上所做出的选择,都必然最终导致问题的一个整体最优解。
背包问题:已知一个载重为M的背包和n件物品,第i件物品的重量为 wi ,如果将第i件物品全部放进背包,将有收益 pi 这里, wi>0,pi>0,0≤i<n 。所谓背包问题,是指求一种最佳装载方案,使得收益最大。
用贪心法求解 0/1背包问题只能求得近似解。
一般背包问题:
基本步骤:
//背包问题的贪心算法
template<class T>
class Knapsack
{
public:
//创建一个一维数组w和p,并赋初值
Knapsack(int mSize, float cap, float *wei, T *prof);
//数组x为背包问题的最优解
void GreedyKnapsack(float* x);
...
private:
float m, *w;//m为背包载重量,w指示存储n个物品重量的数组
T* p;//p指示存储n个物品收益的数组;
int n;//n为物品数量
};
template<class T>
vois Knapsack::GreedyKnapsack(float* x)
{
//前置条件:w[i]已按p[i]/w[i]的非增次序排列;
float u = m;//u为背包剩余载重量,初始时为m
for(int i = 0;i0;//对解向量初始化
for(i = 0;i//按最优量度标准选择解的分量
if(w[i]>u)
break;
x[i]=1.0;
u=u-w[i];
}
if(i
定理:如果 p0/w0≥p1/w1≥⋯≥pn−1/wn−1 ,则以上程序求得的背包问题的解是最优解。
C语言实验:设有载重 M=20 的背包,3件物品的重量为 (w0,w1,w2)=(18,15,10) ,物品装入的收益为 (p0,p1,p2)=(25,24,15) 。
#include
#include
void GreedyKnapsack(float p[], float w[], float x[], int n, float m);
void SortW(float* p, float* w, int n);
int main()
{
float p[3] = { 25,24,15 }, w[3] = { 18,15,10 };
float x[3] = { 0,0,0 };
float M = 20;
//对w[i]进行排序,按p[i]/w[i]的非增次序排列
SortW(p, w, 3);
printf("after sorting:\n");
for (int i = 0; i < 3; i++) {
printf("p[%d] = %f ", i, p[i]);
}
printf("\n");
for (int i = 0; i < 3; i++) {
printf("w[%d] = %f ", i, w[i]);
}
printf("\n");
GreedyKnapsack(p, w, x, 3, M);
printf("The solution is:\n");
for (int i = 0; i < 3; i++) {
printf("x[%d] = %f ", i, x[i]);
}
printf("\n");
system("pause");
return 0;
}
void SortW(float* p, float* w, int n)
{
int i = 0, j = n;
float tmp = 0;
for (i = 0; i < n; i++) {
for (j = 0; j < n - i-1; j++) {
if (p[j] / w[j] < p[j + 1] / w[j + 1]) {
tmp = p[j];
p[j] = p[j + 1];
p[j + 1] = tmp;
tmp = w[j];
w[j] = w[j + 1];
w[j + 1] = tmp;
}
}
}
}
void GreedyKnapsack(float p[], float w[], float x[], int n, float m)
{
//前置条件:w[i]已按p[i]/w[i]的非增次序排列;
float u = m;//u为背包剩余载重量,初始时为m
int i = 0;
for (i = 0; i0;//对解向量初始化
for (i = 0; i//按最优量度标准选择解的分量
if (w[i]>u)
break;
x[i] = 1.0;
u = u - w[i];
}
if (i
实验结果:
after sorting:
p[0] = 24.000000 p[1] = 15.000000 p[2] = 25.000000
w[0] = 15.000000 w[1] = 10.000000 w[2] = 18.000000
The solution is:
x[0] = 1.000000 x[1] = 0.500000 x[2] = 0.000000
请按任意键继续. . .
一般来说,适用于贪心法求解的问题大都具有下面两个特征:最优量度标准和最优子结构。
最优量度标准(optimization criterion)或贪心准则(greedy criterion),也称贪心选择性质(greedy choice property)。是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
所谓贪心法的最优量度标准,是指可以根据该量度标准,实行多步决策进行求解,虽然在该量度意义下所做的这些选择都是局部最优的,但最终得到的解却是全局最优的。选择最优量度标准是使用贪心法求解问题的核心问题。值得注意的是,贪心算法每步做出的选择可以依赖以前做出的选择,但决不依赖将来的选择,也不依赖于子问题的解。虽然贪心算法的每一步选择也将问题简化为一个规模更小的子问题,但由于贪心算法每一步选择不依赖子问题的解,每步选择只按最优量度标准进行,因此,对于一个贪心算法,必须证明所采用的量度标准能够导致一个整体最优解。
贪心法的当前选择可能会依赖于已经做出的选择,但不依赖于尚未做出的选择和子问题,因此它的特征是自顶向下,一步一步地做出贪心决策。
所谓最优子结构特征是 关于问题最优解的特征。当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性(optimal substructure)。一个可用贪心法求解的问题往往呈现最优子结构特性。
一般而言,如果一个最优化问题的解结构具有元组形式,并具有最优子结构特性,我们可以尝试选择量度标准。如果经过证明(一般是归纳法),确认该量度标准能够导致最优解,便可容易地按贪心法的算法框架设计出求解该问题的具体的贪心算法。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
并非所有的具有最优子结构特性的最优化问题,都能够找到最优量度标准,此时,可以考虑采用动态规划法来求解。
一个问题能够用贪心策略的条件是该问题的解是向量结构的,具有最优子结构特性,还要求能够通过分析问题获取最优量度标准。但是,按照该量度标准一次生成解的分量所形成的解是否确实是最优解仍需证明。
一个问题能够使用贪心策略的条件如下: