一、算法的描述
从问题的某一个初始解出发逐步逼近给定的目标,每一步都作一个不可回溯的决策,尽可能地求得最好的解。当达到某算法中的某一步不需要再继续前进时,算法停止。
二、算法的设计思想
首先贪婪算法的原理是通过局部最优来达到全局最优,采用的是逐步构造最优解的方法。在每个阶段,都作出一个看上去最优的(在一定的标准下),决策一旦作出,就不可再更改。用贪婪算法只能解决通过局部最优的策略能达到全局最优的问题。因此一定要注意判断问题是否适合采用贪婪算法策略,找到的解是否一定是问题的最优解。
三、算法的适用场景
贪婪算法对问题只需考虑当前局部信息就要做出决策,也就是说使用贪婪算法的前提是“局部最优策略能导致产生全局最优解”。该算法的适用范围较小,
若应用不当,不能保证求得问题的最佳解。一般情况下通过一些实际的数据例子(当然要有一定的普遍性),就能从直观上就能判断一个问题是否可以用贪婪算法,如数列极差问题。更准确的方法是通过数学方法证明问题对贪婪策略的选用性。
四、算法框架
从问题的某一初始解出发;
while能朝给定总目标前进一步do;
利用可行的决策,求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解。
五、典型例题:
1.埃及分数
题目:
设计一个算法,把一个真分数表示为埃及分数之和的形式。所谓埃及分数,是指分子为1的分数。如:7/8=1/2+1/3+1/24。
实例:
逐步选择分数所包含的最大埃及分数,这些埃及分数之和就是问题的一个解。
如:
7/8>1/2,
7/8-1/2>1/3,
7/8-1/2-1/3=1/24。
思路分析:
1)找最小的n(最大的埃及分数1/n),使分数f>1/n;
2)输出1/n;
3)计算f=f-1/n;
4)若此时的f是埃及分数,输出f,算法结束,否则返回1)。
5)记真分数F=A/B;对B/A进行整除运算,商为D,余数为0<K<A,它们导出关系如下:
B=AD+K,B/A=D+K/A<D+1,A/B>1/(D+1),记C=D+1。
6)这样分数F所包含的“最大”埃及分数就是1/C。
进一步计算:A/B-1/C=(AC-B)/BC
7)也就是说继续要解决的是有关分子为A=AC-B,分母为B=BC的问题。
实例:
7/8=1/2+1/3+1/24的解题步骤:
同样用变量A表示分子,变量B表示分母;
C=8/7+1=2 //说明7/8>1/2,
打印1/2
A=72-8=6,B=BC=16 //计算7/8-1/2=(72-8)/(72)=6/16=A/B
C=16/6+1=3 //说明16/6>1/3,
打印1/3
A=63-16=2,B=BC=163=48
//计算6/16-1/3=(63-16)/(163)=2/48=A/B
A>1但B/A为整数24,打印1/24 结束。
main( )
{
int a,b,c;
print(“input element”);
input(a);
print(“input denominator”);
input(b);
if(a>=b)
print(“input error”);
else if (a=1 or b mod a=0)
print( a, "/",b, "=" 1, "/",b/a);
else
while(a<>1)
{
c = b /a + 1;
a = a * c – b; b = b * c;
print( "1/",c);
if( a > 1)
print("+");
if (b mod a =0)
{
print ("1/"; b / a);
a=1;
}
}
}
2.数列极差问题
题目:
在黑板上写N个正整数排成一个数列,进行如下操作:每一次擦去其中的两个数a和b,然后在数列中加入一个数a×b+1,如此下去直至黑板上剩下一个数,在所有按这种操作方式最后得到的数中,最大的记作max,最小的记作min,则该数列的极差定义为M=max-min。
实例:
对三个具体数据3,5,7讨论,可能有以下三种结果:
(35+1)7+1=113,
(37+1)5+1=111,
(57+1)3+1=109
(先运算小数据得到的是最大值,先运算大数据得到的是最小值。)
思路分析:
(ab+1)c+1=aaa+(2k1+k2)aa+(k1(k1+k2)+1)a+k1+k2+1
(ac+1)b+1=aaa+(2k1+k2)aa+(k1(k1+k2)+1)a+k1+1
(bc+1)a+1=aaa+(2k1+k2)a*a+(k1(k1+k2)+1)*a+1
显然此问题适合用贪婪策略,不过在求最大值时,要先选择较小的数操作。求最小值时,要先选择较大的数操作。这是一道两次运用贪心策略解决的问题。
1)不断从现有的数据中,选取最大和最小的两个数,计算后的结果继续参与运算,直到剩余一个数算法结束。
2)选取最大和最小的两个数较高效的算法是用二分法完成,这里仅用简单的逐个比较方法来求解。注意到由于找到的两个数将不再参与其后的运算,其中一个用它们的计算结果代替,另一个用当前的最后一个数据覆盖即可。所以不但要选取最大和最小,还必须记录它们的位置,以便将其覆盖。
3)求max、min过程必须独立,即求max和min都必须从原始数据开始,否则不能找到真正的max和min。
int s1,s2;
main( )
{ int j,n,a[100],b[100],max,min;
print(“How mang data?”);
input(n);
print(“input these data”);
for (j=1;j<=n;j=j+1)
{
input(a[j]);
b[j]=a[j];}
min= calculatemin(a,n);
max= calculatemax(b,n);
print(“max-min=”, max-min);
}
}
calculatemin(int a[],int n)
{
int j;
while (n>2)
{
max2(a,n);
a[s1]= a[s1]* a[s2]+1;
a[s2]=a[n];
n=n-1;
}
return(a[1]* a[2]+1);
}
max2(int a[],int n)
{
int j;
if(a[1]>=a[2])
{
s1=1;
s2=2;
}//大->s1
else
{
s1=2;
s2=1;
}
for (j=3;j<=n;j++)
{
if (a[j]>a[s1])
{
s2=s1;
s1=j;
}
else if (a[j]>a[s2])
s2=j;
}
}
calculatemax(int a[],int n)
{
int j;
while (n>2)
{
min2(a,n);
a[s1]= a[s1]* a[s2]+1;
a[s2]=a[n];
n=n-1;
}
return(a[1]* a[2]+1);
}
min2(int a[ ],int n)
{
int j;
if(a[1]<=a[2])
{
s1=1;
s2=2;
}
else
{
s1=2;
s2=1;
}
for (j=3;j<=n;j++)
if (a[j]<a[s1])
{
s2=s1;
s1=j;
}
else if (a[j]<a[s2])
s2=j;
}