贪心算法入门

贪心的官方定义

  • 贪心算法的定义
    贪心算法,又称贪婪算法,就是在解决问题的每一步,都在一定的标准下做一个最优决策,做出最优决策所依据的标准称为贪心准则。

贪心算法每一步必须满足以下条件:   
1)可行的:即它必须满足问题的约束。
2)局部最优:他是当前步骤中所有可行选择中最佳的局部选择。
3)不可更改:即选择一旦做出,在算法的后面步骤就不可改变了。

注意:贪心算法不考虑整体的最优,只是做出了当前来说最好的选择(无后效性)(贪心思想)

  • 贪心算法的性质
    1)最优子结构:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
    2)贪心选择性:所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
  • 贪心算法解题的基本步骤
    1 )建立数学模型来描述问题。
    2 )问题分成几个子问题。
    3 )每一个子问题都找到一个最优解。
    4 )基于子问题的最优解来求得整个问题的最优解。
    5 )证明是否存在辛普森悖论(即贪心解是否等于最优解)。
    ps:我们要注意:每个子问题最优,不代表整体最优,这便是辛普森悖论。因此贪心的最大难点在于证明贪心算法取得的结果是否最优,这也是贪心问题上限很高的原因。
    先来个小题来理解一下吧!

删数问题

Description
键盘输入一个高精度的正整数n(≤100位),去掉其中任意s个数字后剩下的数字按照原来的左右次序组成一个新的正整数。编程对给定的n与s,寻找一种方案,使得剩下的数字组成的新数最小。

Input
输入两个数字,分别为原始数n,要去掉的数字数s (s < n)。

Output
输出去掉s个数后最小的数

Sample
Input
178543 4
Output
13
解题思路:当我们随意找几个数,进行删除比较后,我们容易发现贪心准则就是每一次删除当前数组的上升子序列的末尾数字。如123456,删去6肯定比删去6之前的任意一个数字得到的结果要小。
下面我们要判断贪心解是否等于最优解。
贪心算法入门_第1张图片
综上,我们得出贪心解等于最优解。代码如下:

#include 
#include  

using namespace std;
char s[101];

int main()
{
	int k;
	cin>>s>>k;
	int n=strlen(s);
	int i=0;
	while(k--){
	    while(i<n && s[i]<=s[i+1]) i++;  //寻找第一个上升子序列的末尾值 
	    for(int j=i+1;j<n;j++) s[j-1]=s[j];//删掉一个元素就让所有的数字向前移动一位 
	    n--;    //每删除一个就让长度减一 
	    i=0;
}
    while(i<n && s[i]=='0') i++;   //消除前置0 
    if(i==n) printf("0\n");       //如果前置0的长度达到了n就为0 
    for(;i<n;i++)printf("%c",s[i]);
    return 0;

装船问题(背包问题)

Description
王小二毕业后从事船运规划工作,吉祥号货轮的最大载重量为M吨,有10种货物可以装船。第i种货物有wi吨,总价值是pi。王小二的任务是从10种货物中挑选若干吨上船,在满足货物总重量小于等于M的前提下,运走的货物的价重比最大。

Input
输入数据的第一行有一个正整数M(0 < M < 10000),表示所有货物最大载重量。在接下来的10行中,每行有若干个数(中间用空格分开),第i行表示的是第i种货物的货物的总价值pi ,总重量wi。(pi是wi的整数倍,0 < pi , wi < 1000)

Output
输出一个整数,表示可以得到的最大价值。

Sample
Input
100
10 10
20 10
30 10
40 10
50 10
60 10
70 10
80 10
90 10
100 10
Output
550
来源 :SDUTOJ

解题思路:每次取质价比最大的,先由质价比的大小进行排序。

#include 

using namespace std;

struct {
	int p,w,m;
}a[10001],k;     //注意多申请一个结构体变量做交换的中间变量 

void q_sort(int l,int r){
	int i=l;
	int j=r;
	k=a[i];
	if(i>=j) return ;
	else{
		while(i<j){
			while(i<j&&a[j].m<k.m) j--;
			a[i]=a[j];
			while(i<j&&a[i].m>=k.m) i++;
			a[j]=a[i];
		}
		a[i]=k;
		q_sort(l,i-1);
		q_sort(i+1,r);
	}
}

int main(){
	int M;
	cin>>M;
	for(int i=1;i<=10;i++){
		cin>>a[i].p>>a[i].w;
		a[i].m=a[i].p/a[i].w;
	}
	q_sort(1,10);
    int res=0;
	for(int i=1;i<=10;i++)
	{
		if(M-a[i].w>=0){
			res+=a[i].p;
			M-=a[i].w;
		}
		else
		{
			res+=M*a[i].m;
			break;
		}
	}
	cout<<res<<endl;
	return 0;
	
}

贪心与动态规划的区别

贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
  贪心法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码……对于其他问题,贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。在不同情况,选择最优的解,可能会导致辛普森悖论(Simpson’s Paradox),不一定出现最优的解。(摘自某学长博客)

我对贪心算法的理解

学到贪心时,我最直观的感受是:贪心解题时,没有固定的模板,所以要想拿下贪心的题目,就要通过多多刷题来寻找“题感”。贪心的道理其实理解起来蛮简单的,就是每个子问题最优就行了,还有我觉的贪心解题第一个难点通过建立数学模型来找到贪心策略,第二个难点就是证明贪心解的最优性(简单的问题可以不用证明)。

结语

明天就大年三十了,在这里提前祝大家新年快乐,希望来年大家可以学到更多的知识,遇见一个更美好的自己,奔涌吧,后浪!

你可能感兴趣的:(SDUT,贪心算法)