贪心算法及其经典例题

引言:

贪心算法是经典算法之一,也叫贪婪算法或启发式算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。例如求最小生成树的Prim算法和Kruskal算法都是漂亮的贪心算法。

贪心算法适用的问题:

背包问题、带有期限的作业排序,最小生成树、单源点最短路径

适用贪心算法的场景:

简单地说,问题能够分解成子问题来解决,子问题的最优解能够递推到最终问题的最优解。这种子问题最优解成为最优子结构。

贪心算法和动态规划算法的不同

贪心算法与动态规划算法的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

贪心算法的正确性:

要想证明贪心算法的正确性,那么就要证明出贪心解就等于最优解。大多数很容易证明出贪心解等于最优解。证明贪心解等于最优解的方法有:数学归纳法、反证法等等。

问题描述:

有一类问题,它有n个输入,而它的解就是这n个解的某个子集,这些子集必须满足某些先给定的条件。贪心方法是指在对问题求解时,总是做出在当前看来是最好的选择。通过选择局部最优已到达或接近全局最优。

贪心算法的相关题目:

题目1:合并果子

题目链接
题目描述:
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。

输入输出示例:
在这里插入图片描述

题解:

这道题很明显就可以使用贪心算法求解。每次选择两个最小的果子堆进行合并,从而达到消耗的体力最小。如果选择较大的果子堆进行合并,则合并后的果子堆更大,对于以后的合并产生不利会逐渐增加。所以肯定不会是最优解。

代码:

#include
#include
#include
using namespace std;
int main()
{
	int n;
	cin>>n;
	vector<int> ve(n);
	int i;
	for(i=0;i<n;i++)
	cin>>ve[i];
    int sum=0;
    sort(ve.begin(),ve.end()); 
    for(i=0;i<n-1;i++)
    {
        ve[i+1]+=ve[i];//找到前面两个最小的
        sum+=ve[i+1];
        int j=i+2,temp=ve[i+1];
        while(j<n&&temp>ve[j])//把合并后的值插入,使序列递增
		{
			ve[j-1]=ve[j];
			j++;
		}
        ve[j-1]=temp;
    }
	cout<<sum<<endl;
	return 0;
}

题目2:部分背包问题

题目链接
题目描述:

在这里插入图片描述
输入输出:
贪心算法及其经典例题_第1张图片

题解:

因为金币可以任意分割,那么要想拿走价值最大的金币,就要去单位重量价值最大的金币。所以我们只需对单位重量价值最大的金币进行排序,直到取出的金币装满整个背包。

代码:

#include
#include
#include
#include
using namespace std;
struct coin
{
	int wealth;
	int weight;
	double avg;
};
bool cmp(coin a,coin b)
{
	return a.avg>b.avg;
}
int main()
{
	vector<coin> ve;
	int a,b,i,n,sum;
	cin>>n>>sum;
	for(i=0;i<n;i++)
	{
		cin>>b>>a;
		coin s;
		s.wealth=a;
		s.weight=b;
		s.avg=a*1.0/b;
		ve.push_back(s);
	}
	sort(ve.begin(),ve.end(),cmp);
	double money=0;
	for(i=0;i<ve.size();i++)
	{
		if(sum>=ve[i].weight)
		{
			money+=ve[i].wealth;
			sum-=ve[i].weight;
		}
		else
		{
			money+=ve[i].avg*sum;
			break;
		}
	}
	cout<<setiosflags(ios::fixed)<<setprecision(2)<<money<<endl;
	return 0;
}

题目3:买卖股票的最佳时期

题目链接
题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入输出:
贪心算法及其经典例题_第2张图片

题解:

要想取得最大的效益,那么如果明天的股票更贵,那么今天就买,如果明天的股票便宜,那么今天就卖,由此可以得到最大效益。

代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0)
        {
            return 0;
        }
        int i,j;
        int sum=0;
        
        for(i=0;i<prices.size()-1;i++)
        {
            if(prices[i+1]>prices[i])
            {
                j=i+1;
                while(j+1<prices.size()&&prices[j+1]>prices[j])
                {
                    j++;
                }
                sum+=prices[j]-prices[i];
                i=j;
            }
        }
        return sum;
    }
};

你可能感兴趣的:(算法)