概念&&介绍

贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。所以说只有证明局部最优解在全局最优解序列中,才能通过贪心算法得到问题的全局最优解。也就是说选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

如图0.0所示,我们从​点出发到达点​,如果我们想要在经过的路径中累计数值最大,我们可以采取在每个分支处选择数值最大的点的贪心策略。但显然我们会选择​的路径,但实际上正解是路径​,这是由于我们的选择是具有后效性的,当前选择的点会影响到后面可选择的点,所以会导致我们不能得到全局最优解。

图0.0

如图0.1所示,我们选择三次,每次从两点中选择一个点并累加它的数值,如果我们想要使累计的数值最大,我们可以选择三次点​,并且我们最后得出的解是正解。这是由于我们的选择不具有后效性,当前选择的点并不会影响到后面可选择的点,所以我们可以得到全局最优解。

图0.1

算法流程

  1. 把求解的问题分成若干个子问题。
  2. 对每一子问题求解,得到子问题的局部最优解。
  3. 把子问题的解局部最优解合成原来解问题的一个解。

适用问题

局部最优策略能导致产生全局最优解。或者将贪心算法进行修改以求出全局最优解

深入理解&&例题

如果前面的两个小例子不能使你透彻地理解贪心算法,那么我们可以来看看下面的几个小例题,通过它们来深入理解贪心算法以及贪心算法使用的场景。

删数问题

读入一个高精度整数​,去掉其中​个数字后剩下的数字按照原来的次序组成一个新的正整数。寻求一种方案,使得最后组成的数最小。

分析题目,我们可以设计一种贪心策略:进行​轮删数,每轮删除最大的一个数,如果数值相同则删除靠前的一个。

我们可以对这个贪心策略进行证明:每轮删除的数字并不会影响后面可删除的数字,因此这种贪心策略不具有后效性,且​次删数后剩余所有数字组成的数即为问题的可行解,因此这种贪心策略可行。

include<bits/stdc++.h>

using namespace std;
int s,len;
char n[241];
int main(){
cin>>n;
cin>>s;
len=strlen(n);
while(s--){
int p,q;
p=0,q=n[0];
for(int i=1;i<len;i++){
if(n[i]>q){
q=n[i];
p=i;
}
}//找到目标数
for(int i=p+1;i<len;i++){
n[i-1]=n[i];
}
len--;//删数
}
for(int i=0;i<len;i++){
cout<<n[i];
}
return 0;
}

均分纸牌

如图3.0所示,有​堆纸牌,编号分别为​。每堆上有若干张,但纸牌总数必为​的倍数。可以在任一堆上取若于张纸牌,然后移动。移牌规则:编号为​堆上取的纸牌,只能移到编号为​的堆上;编号为​的堆上取的纸牌,只能移到编号为​的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

分析题目,我们可以得知:要想使移动次数最少,就要把移动的浪费降到最低。例如在相邻的两堆间移动​次就是一种浪费,因为这​次移动都可以合并成​次。

图3.0

因此,我们可以采用移动一次使得一堆牌数达到平均值的贪心策略:先把每堆的牌数减去平均数,然后由左而右的顺序移动纸牌。若第​堆纸牌的张数​不为​,则将值移动到下一堆。如图3.1所示,所有堆的纸牌平均数为​,我们可以先将每堆减掉平均数。

图3.1

然后遍历每堆纸牌,如图3.2所示,纸牌数不为​则向下一堆移动,每次移动将移动次数​,最后得到图3.3。

图3.2

这种贪心策略类似于“把不足平均值的责任推给下一堆,直至多于平均值的纸牌堆来弥补”。通过求解两个纸牌堆之间的最优子解,把所有子解合并得到问题的一个可行解。

include<bits/stdc++.h>

using namespace std;
int n,a[101],sum,ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
sum/=n;
for(int i=1;i<=n;i++){
a[i]-=sum;
}
for(int i=1;i<=n;i++){
if(a[i]!=0){
a[i+1]+=a[i];
ans++;
}
}
printf("%dn",ans);
return 0;
}

糖果传递

有​个小朋友坐成一圈,每人有​个糖果。每人只能给左右两人传递糖果。每人每次传递一个糖果代价为​。求使所有人获得均等糖果的最小代价。

和均分纸牌类似,现在假设编号为​的人初始有​个糖果。对于​号来说,他给了​号​个糖果,还剩​个;但是因为​号给了他​个糖果,所以最后还剩​个糖果。根据题设,该金币数等于​。换句话说,我们得到了一个方程:​。

同理,对于第​个人,有​。最终,我们可以得到​个方程,一共​个变量,是不是可以直接解方程组了呢?很可惜,还不行。因为从前​个方程可以推导出最后一个方程。所以,实际上只有​个方程是有用的。

尽管无法直接解出答案,我们还是可以尝试着用​表示出其他的​,则本题就变成了单变量的极值问题。

对于第​个人,​(令​,下面类似) 对于第​个人,​(​) 对于第​个人,​ ......

对于第​个人,​。这是一个多余的等式,并不能给我们更多的信息。我们希望所有的​的绝对值之和尽量小,即​要最小。注意到​的集合意思是数轴上点​到​的距离,所以问题变成了:给定数轴上的​个点,找出一个到它们的距离之和尽量小的点。

结论:给定数轴上的​个点,在数轴上的所有点中,中位数离所有顶点的距离之和最小。凡是能转化为这个模型的题目都可以用中位数求解。

include<bits/stdc++.h>

define maxn 1000001

using namespace std;
int n,a[maxn];
long long c[maxn],sum,ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]),sum+=a[i];
}
sum/=n;
for(int i=1;i<=n;i++){
c[i]+=sum-a[i]+c[i-1];
}
sort(c+1,c+n+1);
int mid=c[n/2];
for(int i=1;i<=n;i++){
ans+=abs(mid-c[i]);
}
printf("%lld",ans);
return 0;
}

进阶习题

参考资料

  1. 贪心算法 百度百科
  2. 贪心策略取得最优解的条件_常用算法之贪心算法 CSDN @weixin_39799825
  3. 贪心算法的最优解条件 CSDN @逆羽飘扬
  4. 贪心算法-例题讲解 博客园 @In'f

原文地址 https://oiermikec.fun/index.php/2021/01/09/38.html


MikeC
1 声望0 粉丝

誰もがエリートです