代码改变世界

贪心算法简介

  youxin  阅读(1347)  评论(0编辑  收藏  举报

在实际生活中,经常有这样一类问题:它有n个输入,它的解由这n个输入中的一个子集组成,但这个子集必须满足事先给定的某些条件,有时,把这些条件称为约束条件,把满足约束条件的解称为可行解。满足约束条件的解可能不止一个,因此可行解也不是唯一的。为了衡量可行解的优劣,事先给出一定的标准,这些标准通常以函数的形式给出,把这些函数称为目标函数。使目标函数取极值(极大或极小)的可行解,称为最优解。如下面的货币兑付问题:

有2^n个不同的向量,把所有这些向量称为问题的解空间

 

贪婪法设计思想:

贪婪法通常用来解决具有最大值和最小值的优化问题,它犹如登山一样,一步一步地向前推进,从某一个初始状态出发,根据当前局部的而不是全局的最优决策,以满足约束方程为条件,以使得目标函数的值增加最快或最慢为准则,选择一个能够最快地达到要求的输入元素,以便尽快地构成问题的可行解。

设计方法描述如下:

复制代码
greedy(A,n)
{
    solution=null;
    for(i=1;i<n;i++)
    {
        x=select(A);
        if(feasible(solution,x))
            solution=union(solution,x);
    }
    return solution;
}
复制代码

开始时,使初始的解向量为空,然后,使用select按照某种决策标准,从A中选择一个输入X,用feasible判断:解向量加入是否可行,如果可行,把x合并到解向量中,并把它从A中删除;否则,丢弃x,重新从A中选择另一个输入,重复上述步骤,直到找到一个满足问题的解。

或者采用下面的形式:

复制代码
Greedy(C)  //C是问题的输入集合即候选集合
{
    S={ };  //初始解集合为空集
    while (not solution(S))  //集合S没有构成问题的一个解
    {
       x=select(C);    //在候选集合C中做贪心选择
       if feasible(S, x)  //判断集合S中加入x后的解是否可行
          S=S+{x};
          C=C-{x};
    }
    return S;
}
复制代码

 

用贪心法求解问题应该考虑如下几个方面:
(1)候选集合C:为了构造问题的解决方案,有一个候选集合C作为问题的可能解,即问题的最终解均取自于候选集合C。例如,在付款问题中,各种面值的货币构成候选集合。
(2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成一个满足问题的完整解。例如,在付款问题中,已付出的货币构成解集合。
 
(3)解决函数solution:检查解集合S是否构成问题的完整解。例如,在付款问题中,解决函数是已付出的货币金额恰好等于应付款。
   (4)选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。例如,在付款问题中,贪心策略就是在候选集合中选择面值最大的货币。
  (5)可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。例如,在付款问题中,可行函数是每一步选择的货币和已付出的货币相加不超过应付款。       

贪婪选择形状:指所求问题的全局最优解,可以通过一系列局部最优的选择来达到。每进行一次选择,就得到一个局部的解,并把所求解的问题简化为一个规模更小的类似子问题

 

 动态规划法通常以自底向上的方式求解各个子问题,而贪心法则通常以自顶向下的方式做出一系列的贪心选择。

 

最优子结构,是指一个问题的最优解包含其子问题的最优解,

 

 动态规划法通常以自底向上的方式求解各个子问题,而贪心法则通常以自顶向下的方式做出一系列的贪心选择。
 

贪心算法是我们在《算法设计技巧与分析》这门课中所学习到的几种重要的算法之一,顾名思义,贪心算法总是作出在当前看来最好的选择。也就是该算法并不从整体最优考虑,它所作出的选择只是在某种意义上的从局部的最优选择,寻找到解决问题的次优解的方法。虽然我们希望贪心算法得到的最终结果也是整体最优的,但是在某些情况下,该算法得到的只是问题的最优解的近似。

3.  算法思想:

贪心法的基本思路:

——从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。

该算法存在问题:

1. 不能保证求得的最后解是最佳的;

2. 不能用来求最大或最小解问题;

3. 只能求满足某些约束条件的可行解的范围。(这个很重要)

实现该算法的过程:

 

Begin 从问题的某一初始解出发;

while 能朝给定总目标前进一步 do

     求出可行解的一个解元素;

由所有解元素组合成问题的一个可行解


4.  关于贪心算法在背包问题中的应用的探讨

(1) 问题描述

0-1背包问题:给定n种物品和一个背包。物品i的重量是Wi,其价值为Vi,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 在选择装入背包的物品时,对每种物品i只有2种选择,即装入背包(1)或不装入背包(0)。不能将物品i装入背包多次,也不能只装入部分的物品i。

背包问题:与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。

背包问题可以定义如下:给出n个大小为s1,s2,…,sn,值为v1,v2,…,vn的项目,并设背包容量为C,要找到非负实数x1,x2,…,xn, 使和

 

在约束                      下最大。

(2) 动态规划解决方案:是解决0/1背包问题的最优解

(i) 若i=0或j=0,  V[i,j] = 0

        (ii)  若j<si, V[i,j] = V[i-1,j]   (仅用最优的方法,选取前i-1项物品装入体积为j 的背包,因为第i项体积大于j,装不下这一项,所以背包里面的i-1项就达到最大值)

(iii) 若i>0和j>=si, Max{V[i-1,j],V[i-1,j-si]+vi} (第一种情况是包中的i-1项已经达到最大值,第二种情况是i-1项占j-si的体积再加上第i项的总的价值,取这两种情况的最大值。)

//sj和vj分别为第j项物品的体积和价值,C是总体积限制。

//V[i,j]表示从前i项{u1,u2,…,un}中取出来的装入体积为j的背包的物品的最大//价值。[13]

3)贪心算法解决背包问题有几种策略

(i) 一种贪婪准则为:从剩余的物品中,选出可以装入背包的价值最大的物品,利用这种规则,价值最大的物品首先被装入(假设有足够容量),然后是下一个价值最大的物品,如此继续下去。这种策略不能保证得到最优解。例如,考虑n=2, w=[100,10,10], p =[20,15,15], c = 105。当利用价值贪婪准则时,获得的解为x= [ 1 , 0 , 0 ],这种方案的总价值为2 0。而最优解为[ 0 , 1 , 1 ],其总价值为3 0。

(ii) 另一种方案是重量贪婪准则是:从剩下的物品中选择可装入背包的重量最小的物品。虽然这种规则对于前面的例子能产生最优解,但在一般情况下则不一定能得到最优解。考虑n= 2 ,w=[10,20], p=[5,100], c= 2 5。当利用重量贪婪策略时,获得的解为x =[1,0], 比最优解[ 0 , 1 ]要差。

(iii) 还有一种贪婪准则,就是我们教材上提到的,认为,每一项计算yi=vi/si,即该项值和大小的比,再按比值的降序来排序,从第一项开始装背包,然后是第二项,依次类推,尽可能的多放,直到装满背包。

有的参考资料也称为价值密度pi/wi贪婪算法。这种策略也不能保证得到最优解。利用此策略试解n= 3 ,w=[20,15,15], p=[40,25,25], c=30 时的最优解。虽然按pi /wi 非递(增)减的次序装入物品不能保证得到最优解,但它是一个直觉上近似的解。而且这是解决普通背包问题的最优解,因为在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1≤i≤n。(也就是物品可以任意分割,那么贪心法得到的是最优解,如果不能任意分割,贪心法得到的不一定是最优解,是一个可行解)。

如图1,大体上说明了动态规划解决的0/1背包问题和贪心算法解决的问题之间的区别,

 图1

4)贪心算法解决背包问题的算法实现

代码如下:

            #include <iostream.h>

            struct goodinfo

            {

             float p; //物品效益

             float w; //物品重量

             float X; //物品该放的数量

             int flag; //物品编号

            };//物品信息结构体

            void Insertionsort(goodinfo goods[],int n)

            {//插入排序,按pi/wi价值收益进行排序,一般教材上按冒泡排序

             int j,i;

             for(j=2;j<=n;j++)

             {

                 goods[0]=goods[j];

                 i=j-1;             

                 while (goods[0].p>goods[i].p)

                 {

                   goods[i+1]=goods[i];

                   i--;

                 }

                 goods[i+1]=goods[0];

             }

            }//按物品效益,重量比值做升序排列

            void bag(goodinfo goods[],float M,int n)

            {

            

             float cu;

             int i,j;

             for(i=1;i<=n;i++)

                goods[i].X=0;

             cu=M;  //背包剩余容量

             for(i=1;i<n;i++)

             {

               if(goods[i].w>cu)//当该物品重量大与剩余容量跳出

               break;

               goods[i].X=1;

               cu=cu-goods[i].w;//确定背包新的剩余容量

             }

             if(i<=n)

               goods[i].X=cu/goods[i].w;//该物品所要放的量

            /*按物品编号做降序排列*/

             for(j=2;j<=n;j++)

             {

                 goods[0]=goods[j];

                 i=j-1;             

                 while (goods[0].flag<goods[i].flag)

                  {

               goods[i+1]=goods[i];

               i--;

              }

              goods[i+1]=goods[0];

             }

            ///////////////////////////////////////////

             cout<<"最优解为:"<<endl;

             for(i=1;i<=n;i++)

             {

              cout<<""<<i<<"件物品要放:";

              cout<<goods[i].X<<endl;

             }

            }

            void main()

            {

             cout<<"|--------运用贪心法解背包问题---------|"<<endl;

             int j,n;    float M;

             goodinfo *goods;//定义一个指针

             while(j)

             {

             cout<<"请输入物品的总数量:";

             cin>>n;

             goods=new struct goodinfo [n+1];//

             cout<<"请输入背包的最大容量:";

             cin>>M;

             cout<<endl;

             int i;

             for(i=1;i<=n;i++)

              { goods[i].flag=i;

               cout<<"请输入第"<<i<<"件物品的重量:";

               cin>>goods[i].w;

               cout<<"请输入第"<<i<<"件物品的效益:";

               cin>>goods[i].p;

               goods[i].p=goods[i].p/goods[i].w;//得出物品的效益,重量比

               cout<<endl;

              

              }            

             Insertionsort(goods,n);

             bag(goods,M,n);

             cout<<"press <1> to run agian"<<endl;

             cout<<"press <0> to exit"<<endl;

             cin>>j;

             }

            }

[即教材例7.6,假如有容量为9(C=9)的背包,要装入4(n=4)种体积为2,3,4和5的物品,它们的价值分别是3,4,5和7。对这个问题课本是用动态规划的方式给予实现通过列表格可知,

 

0

1

2

3

4

5

6

7

8

9

0

0

0

0

0

0

0

0

0

0

0

1

0

0

3

3

3

3

3

3

3

3

2

0

0

3

4

4

7

7

7

7

7

3

0

0

3

4

4

7

8

9

9

12

4

0

0

3

4

5

7

8

10

11

12

有两种方案可以达到最优解,装物品价值为5和7或者是装入物品价值为3,4和5。这样我们获得的价值是12。

现在我们来讨论用上述贪心算法把这个问题看成普通背包问题进行讨论,以这个实例运行程序可以有这样的结果:(WindowsXP系统,Microsoft Visual C++ 2005)

 

 这样得到的结果是价值为3体积是2的放1,价值是3体积是4的放0.666667,价值是5体积是7的放1,最后得到的结果是占的体积是9.000001,(期望值是C=9),而总的价值是12.66668,(动态规划我们得出的结论是12),所以我们可以看到用贪心算法解决普通背包问题时得到的解是最优解。

5.其它方面对背包问题的探讨

(1) UIST 2002的第15次年会上,描述了第二届UIST界面接口设计比赛,队伍将会设计并应用一种界面接口来解决非常有挑战性的真实世界中的问题。装包问题经常在工业中提到。虽然在各种装载问题的算法的发展上做出了很多的努力,但是许多重要的装载问题仍然是由人工解决而不是计算机。考虑一个二维多边形装载如图:它由许多多边形组成,这是做成裤子的布块。调动这些废旧的布块是服装厂的一个重要的工作。同样的问题在许多工业中遇到,也许非常惊讶的是,即使计算机算法在计算机几何学中有很大的发展,但是这些问题的解决更好的是通过手工专家来做。[4]

 

1。一个服装工厂的多边形装载问题。[5]

(2) 背包问题解决的是把一系列物品装进背包内,要使总重量,体积等不超过某个最大值。一种简单的算法(第一种算法[9])把按先拿到的物品放到第一个包中。1973年,J.Ullman证明了这种算法能够达到最优解的70%(Hoffman 1998,p.171[6])。一种改进的算法第一次把物品按照体积的从大到小的顺序放进包中。1973年,D. Johnson表明这种算法有22%的可能永远也不可能达到最优解,并且进一步指出没有有效的背包算法能够保证做到比22%还好(Hoffman 1998,p.172[6])[10][11][12]

存在着这样一种物品的安排,在应用这种算法时,如果我们移去一种物品,则需要另外的一个包,这要比把这种物品包含其中的数量要多(Hoffman 1998,pp.172-173[6])。第一个这样的例子由Sylvia Halasz构建,并在Graham上发表(1976,pp.223 && 225,Fig.5.46)

(3)

 

(4) 背包问题是著名的NP完备类困难问题,对这个问题的求解前人已经研究出了不少的经典的方法,对该问题虽然能得到很不错的结果。但是这些传统的优化方法还存在一些缺点。遗传算法克服了传统优化方法的缺点,借助了大自然的演化过程,是多线索而非单线索的全局优化方法,采用的是种群和随机搜索机制。将遗传算法应用于背包问题,并通过实例证明了该算法在解决该问题也是比较有效的。

与传统的优化算法相比,遗传算法作为一种新的全局优化搜索算法,遗传算法以其简单通用、鲁棒性强、适于并行处理以及高效、实用等显著特点,在各个领域得到了广泛应用,取得了良好效果,并逐渐成为重要的智能算法之一。背包问题是一个典型的NP完全难题,对该问题求解方法的研究无论是在理论上,还是在实践中都具有一定的意义,如管理中的资旅分配、投资决策、装载问题等均可建模为背包问题。[15

 转自:http://qing614ye.blog.163.com/blog/static/104837138201131593656822/

贪心法什么时候得到最优解跟多参考:http://baike.baidu.com/view/298415.htm?fromId=1628576

编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
历史上的今天:
2012-08-16 php开发简单网站模板
点击右上角即可分享
微信分享提示