zoukankan      html  css  js  c++  java
  • 基础算法:贪心

    贪心算法的定义与性质:

    • 贪心算法是通过做出一系列 选择 来求出问题的最优解。在每一个决策点,它作出在 当前看来最好 的选择。

      • (也就是说贪心算法并 不从整体最优上加以考虑 ,它所作出的选择只是在某种意义上的 局部最优选择 。大概也是贪心算法名字的由来)

      • (贪心算法进行决策时, 可能依赖于以往所做出的选择,但决不依赖于将来所做的选择,或者子问题的解 。)

    • 贪心算法必须满足 最优子结构 。与动态规划不同,贪心的最优子结构,必须满足 选择后只留下一个子问题

      • (从最优子结构的角度说:相当于特殊的动态规划。动态规划的选择后往往会产生多个子问题。)

      • (正是由于这种差异,动态规划算法通常以自底向上的方式解各子问题,而 贪心算法则通过以自项向下的方式 进行,以 迭代 的方式做出相继的贪心选择,每做一次贪心选择就将所求问题简化为规模更小的子问题 。)

    • 贪心的正确性:必须满足 贪心选择剩余兼容活动集 的最优解,组合在一起后,就会得到原问题的最优解。

    PS:可以设计一个递归算法实现贪心策略,再将递归算法转化为迭代算法。因为迭代的效率更高。
    PS:若在进行贪心选择中,不得不考虑枚举众多选择来找到最优,通常意味着可以改进贪心选择,使其更高效。通常的改进办法是:使用 合适的数据结构预处理

    贪心算法设计步骤:

    • 将问题转化为:对一次选择后,只剩下一个子问题需要求解。(构造最优子结构)

    • 证明做出贪心选择后,原问题总是存在最优解,即其正确性。

    例题:

    删数问题:

    策略:从前往后依次删除“山峰”。

    证明
    (因为证明过程太长,其中LaTex公式渲染较多,所以新开了一个随笔证明。)

    代码如下:

    #include<bits/stdc++.h>
    #define MAXN 10000
    using namespace std;
    int main()
    {
    	string s;	int len,k;
    	cin>>s>>k;	len=s.size();
    	for(int t=1;t<=k;++t)
    	{
    		for(int i=0;i<len;++i)
    		{
    			if(s[i]>s[i+1])
    			{
    				for(int j=i;j<len;++j)
    					s[j]=s[j+1];
    				--len;
    				break;
    			}
    		}
    	}
    	int i;	for(i=0; s[i]=='0' ;++i);
    	if(i==len) cout<<"0";
    	else
    	{
    		for(;i<len;++i)
    			cout<<s[i];
    	}
    	return 0;
    }
    

    做题感悟:

    • 思考贪心策略时,可以先从最简单的入手,利用从 特殊到一般 的思想。
    • 对比较抽象的最优问题来说,利用反证法是一个不错的选择。即证明,没有方案比现在更优。

    雇佣计划:

    题目描述:

    一位管理员项目的经理想要确定每个月需要的工人,他当然知道每月所需要的最少工人数。当他雇佣或解雇一个工人时,会有一此额外的支出。一旦一个工人被雇佣,即使他不工作,他也将得到工资。这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资。现在他在考虑一个问题:为了把项目的费用控制在最低,他将每月雇佣或解雇多少个工人。

    输入格式:

    共三行。
    第一行为月数N(不超过12)。
    第二行含雇佣一个工人的费用,一个工人的工资和解雇一个工人的费用(<101)。
    第三行含N个数,分别表示每月最少需要的工人数(<1001)每个数据之间用空格相隔。

    输出格式:

    仅一行,表示项目的最小总费用。

    贪心策略:

    分类讨论:
    1.若工人缺少,雇佣。
    2.若工人恰好,就不变,照常发工资。(这一条可以与1合并)
    3.若工人冗余,有两种选择。要么解雇。要么供着。

    其实这题的贪心证明很简单。就是比较哪一种方案的花费更少。并没有一个数学上的证明。

    也告诉我们。有时贪心策略并不是一定是一个数学公式。

    也可以是一个一个的枚举。

    代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    int ned[100]; 
    int main()
    {
        int n,a,b,c,more;
    	int choice1,choice2,k;
        scanf("%d%d%d%d",&n,&a,&b,&c);
        for(int i=1;i<=n;++i) scanf("%d",&ned[i]);
        int ans=(a+b)*ned[1];
        for (int i=2;i<=n;++i)
        {
            ans+=b*ned[i],more=ned[i]-ned[i-1];
            if(more>=0) ans+=a*more;
            else
            {
            	more=abs(more);
                for (int j=1;j<=more;++j)
                {
                    choice1=0;
                    for (k=i+1;k<=n;++k)
                        if (ned[k]>ned[i]) {choice1=a;break;}	choice1+=c;//dismiss
                    choice2=b*(k-i);//employ
                    if (choice1>=choice2) ++ned[i],ans+=b;
                    else {ans+=c*(more-j+1);break;}
                }
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    

    做题感悟:

    • 枚举有时也是一种好方法哦。

    三国游戏:

    题目链接

    贪心策略:人的一方必赢。选择时挑选最大的次大值。

    证明:定义运算 ((a,b))(a)(b) 匹配的武力值。

    设人取走的一名武将为 (m),那么与 (m) 匹配的武将中,必然存在两个人 (n_1,n_2),分别使得匹配后的武力值为最大值 (max=(m,n_1)),和次大值 (secmax=(m,n_2))

    (m)满足的条件是,让(secmax)为最大的次大值。

    根据题意,计算机的原则,是破坏人所选武将的最大值。所以,当人取走(m)后,计算机会取走(n_1),那么出于贪心规则,人就会取走 (n_2)

    而如果计算机想要获得胜利,所取的武将,必须要大于(secmax),即必须取到(max),而要与(n_1)组成(max),就只有已经被人取走的(m),计算机无法取走。而剩下的值,一定都小于(secmax)

    所以,人必胜,武将的最大武力值为(secmax)

    代码如下:

    #include <bits/stdc++.h>
    int w[505][505];
    int main()
    { 
        int n,m1,m2,m=0;	std::scanf("%d",&n);
        for(int i=1;i<n;++i)
        {
        	for(int j=i+1;j<=n;++j)
        	{
        		std::scanf("%d",&w[i][j]);
        		w[j][i]=w[i][j];
        	}
        }
        for(int i=1;i<=n;++i)
    	{
    		m1=m2=0;
    		for (int j=1;j<=n;++j)
    		{
    			if (w[i][j]>m1)	m2=m1,m1=w[i][j];	
    			else m2=std::max(m2,w[i][j]);
    			m=std::max(m,m2);
    		}
    	} 
    	std::printf("1
    %d",m);
    	return 0;
    }
    
  • 相关阅读:
    04-Go语言之运算符
    02-Go语言之变量和常量
    idea 无法加载识别本地类
    阿里云OSS实践篇
    jemeter 压测入门篇(附带工具)
    SpringBoot 中的那些“开关”
    java8 新特性之4大函数式接口
    java8 新特性之optional
    VSCode vue开发前配置
    前端架构演进及主流UI
  • 原文地址:https://www.cnblogs.com/cyl-oi-miracle/p/13492891.html
Copyright © 2011-2022 走看看