贪心算法
什么是贪心算法?
贪心算法,又名贪婪法,是寻找最优解问题的常用方法,这种方法模式一般将求解过程分成若干个步骤,但每个步骤都应用贪心原则,选取当前状态下最好/最优的选择(局部最有利的选择),并以此希望最后堆叠出的结果也是最好/最优的解。{看着这个名字,贪心,贪婪这两字的内在含义最为关键。这就好像一个贪婪的人,他事事都想要眼前看到最好的那个,看不到长远的东西,也不为最终的结果和将来着想,贪图眼前局部的利益最大化,有点走一步看一步的感觉。
由于贪心没有固定的模版,只是一种做题的思想,下面我们根据一些题目来了解贪心算法如何局部找最优。
P3817 小A的糖果
题目描述
小 A 有 n 个糖果盒,第 i 个盒中有 ai 颗糖果。
小 A 每次可以从其中一盒糖果中吃掉一颗,他想知道,要让任意两个相邻的盒子中糖的个数之和都不大于 x,至少得吃掉几颗糖。
输入格式
输入的第一行是两个用空格隔开的整数,代表糖果盒的个数 n 和给定的参数 x。
第二行有 n 个用空格隔开的整数,第 i 个整数代表第 i 盒糖的糖果个数 ai。
输出格式
输出一行一个整数,代表最少要吃掉的糖果的数量。
输入输出样例
输入 #1复制
3 3 2 2 2
输出 #1复制
1
输入 #2复制
6 1 1 6 1 2 0 4
输出 #2复制
11
输入 #3复制
5 9 3 1 4 1 5
输出 #3复制
0
说明/提示
样例输入输出 1 解释
吃掉第 2 盒中的一个糖果即可。
样例输入输出 2 解释
第 2 盒糖吃掉 66 颗,第 4 盒吃掉 22 颗,第 6 盒吃掉 33 颗。
数据规模与约定
- 对于 30%30% 的数据,保证 n≤20,ai,x≤100。
- 对于 70%70% 的数据,保证 3n≤103,ai,x≤105。
- 对于 100%100% 的数据,保证 2≤n≤105,0≤ai,x≤109。
首先,这道题我们分析一下该如何找到一个最好的值并修改它,他要求两个相邻的盒子相加<=x,那我们首先以样例一举例子:
2+2>3,我们把第一个2减去一个1,变成1 2,这样就满足条件。
继续 还是2 2 ,继续将第一个2减1,变成1 2
最后数组变成了 1 1 2,最少吃2个糖果,显然这不是最优解,样例答案是1
我们再后头想一下,如果把2 2,后面这个2减1的话,就同时满足的左右两边,这个方法好像可行,这样将第二个数减去成了局部最优解,我们用程序来实现一下。
#include <stdio.h>
#include <stdlib.h>
long long a[100010];
int main()
{
int n,x;
long long sum=0;
scanf("%d%d",&n,&x);// 0 2 2 2 数据
for(int i=1;i<=n;i++)// 0 1 2 3 下标
{ scanf("%d",a+i);//这里可以用边读边写来
if(a[i]+a[i-1]>x) //直接从第二个数开始,这里下标从1开始,方便相减
{
int k=a[i]; //记录当前的糖果数量
a[i]=x-a[i-1];// 用a[i]+a[i-1]<=x推出来
sum+=k-a[i]; //用之前的数据减去满足条件后的糖果,用sum记录吃掉多少
}
}
printf("%lld",sum);
return 0;
}
P2240 【深基12.例1】部分背包问题
题目描述
阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 (N≤100) 堆金币,第 i 堆金币的总重量和总价值分别是 (1≤mi,vi≤100)。阿里巴巴有一个承重量为 T(T≤1000) 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?
输入格式
第一行两个整数 N,T。
接下来 N 行,每行两个整数 mi,vi。
输出格式
一个实数表示答案,输出两位小数
输入输出样例
输入 #1复制
4 50 10 60 20 100 30 120 15 45
输出 #1复制
240.00
这道题只需要算出每堆金币的占比,然后对占比进行排序,用for循环进行枚举,如果加上当前的容量大于T后使用berak跳出,用(T-当前背包的容量),得出占比后再乘以价值。
#include <stdio.h>
#include <stdlib.h>
float a[110][2];
float b[110][2];
int n,T;
int main()
{ scanf("%d%d",&n,&T);
for(int i=0;i<n;i++)
{
scanf("%f%f",&a[i][0],&a[i][1]);
b[i][0]=a[i][0]/a[i][1];//将占比算出来
b[i][1]=a[i][1];
}
for(int i=0;i<n-1;i++)//冒泡排序,数据范围是<=100,所以不会爆,其实是不会用sort对二维数组排序......
{
for(int j=0;j<n-i-1;j++)
{
if(b[j][0]>b[j+1][0])
{
float t=b[j][0];
b[j][0]=b[j+1][0];
b[j+1][0]=t;
t=b[j][1];
b[j][1]=b[j+1][1];
b[j+1][1]=t;
}
}
}
float k=0;//记录容量
float sum=0;//记录价值
int q;
for(q=0;q<n;q++)//用for枚举,什么时候超出背包容量
{
k+=b[q][0]*b[q][1];//b[q][0]*b[q][1]这个相乘后是最开始的每堆金币的容量,开始算占比,算绕回去了
sum+=b[q][1];//对价值统计
if(k>T)
break;
}
if(q<n)
{
k-=b[q][0]*b[q][1];//因为加了容量后超出范围了,所以减去
sum-=b[q][1];//同样
int p=T-k;//这里用来记录背包还剩多少容量,用q记录的金堆给他塞满
k+=p;
sum+=p/(b[q][0]*b[q][1])*b[q][1];//这里算价值,(b[q][0]*b[q][1])是在b[q][1]占了多少比值
}
printf("%.2f",sum);//输出
return 0;
}
上面讲完了部分背包,我们来看看完整的背包问题,01背包----------头大,只能用动态规划来写,贪心写不来 。
这里再总结一下贪心的优缺点
总结:贪心算法的优缺点
优点:简单,高效,省去了为了找最优解可能需要穷举操作,通常作为其它算法的辅助算法来使用;
缺点:不从总体上考虑其它可能情况,每次选取局部最优解,不再进行回溯处理,所以很少情况下得到最优解。
动态规划-01背包
问题描述
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
现有四个物品,小偷背包总容量为8,怎么可以偷得价值最多的物品。
物品编号: 1 2 3 4
物品重量: 2 3 4 5
物品价值: 3 4 5 8
这里很明显偷2 4 价值最高为12,那么我们如何来分析这个题目呢?
首先,我们有8个容量的空间,我们可以先找到物品编号为4的,这里有两个选择偷还是不偷,
这里我们来看这张图,当我们到第四编号时,有偷和不偷两种选择,我们先来看看偷得情况,首先偷得话先检查背包容量是否足够,8肯定够了,所以我们往下走,这时到编号3了,这时又有两种选择,偷还是不偷,明显这里只能不偷,背包容量不够了,到编号2了,这时还是偷还是不偷,选择偷,背包容量空了,选择不偷到编号1去了,还是偷还是不偷,偷得话前面没物品了只能结束,偷完后,没物品了,所以结束了,这一遍模拟下来,动态规划其实跟递推差不多。------这里就进行思路讲解吧,代码就不贴了,晚安。