贪心选择的概念
贪心算法总是做出在当前看来是最好的选择(局部最优解),通过选择局部的最优解最终可以得到整体的最优解。也就是将一个大问题分解成小的问题后,只需考虑小问题的最优解就可以了,最终组成大问题的最优解。这就需要确定每一步所做的贪心选择能够使问题整体最优解。
解题的基本思路
贪心算法基本的解题思路:
⒈ 建立数学模型来描述问题。
⒉ 把求解的问题分成若干个子问题。
⒊ 对每一子问题求解,得到子问题的局部最优解。
⒋ 把子问题的解局部最优解合成原来解问题的一个解。
对贪心算法的理解
尽最大的可能将满足题意的物品(或其他)选中,并尽可能的多选。
贪心算法的基本流程:
Greedy (C) { //C是问题的输入集合即候选集合
S = { }; //初始解集合为空集
while (not solution(S)) { //集合S没有构成问题的一个解
x = select(C); //在候选集合C中做贪心选择
if feasible(S, x) //判断集合S中加入x后的解是否可行
S = S + {x};
C = C - {x};
}
return S;
}
例题
部分背包问题
虽然这里和刚才说的贪心的做题基本有一些小小的区别,但是大体的思路是相同的。
问题的输入集合即候选集合:表示每个单位金子的价格c[105]和数量a[105]
初始解集合为空集:sum 和total;
在候选集合C中做贪心选择:for循环中的if;
判断集合S中加入x后的解是否可行:for循环中的条件 (条件繁琐时,可以写成函数)
#include<stdio.h>
int a[105], b[105];
float c[105];
int main(void){
int i, n, t;
float sum, total;
scanf("%d %d", &n, &t);
for (i = 0; i < n; i++) {
scanf("%d %d", &a[i], &b[i]);
c[i] = b[i] / (a[i] * 1.0);
}
sort(0, n - 1); //排序函数,没有放在这里
sum = 0;
total = 0;
for (i = 0; i < n; i++) {
if (sum + a[i] <= t) {
sum += a[i];
total += a[i] * c[i];
} else {
total += (t - sum) * c[i];
break;
}
}
printf("%.2f", total);
return 0;
}
纸币找零问题
题目: 假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。
现在要用这些钱来支付K元,至少要用多少张纸币?
用贪心算法的思想,很显然,每一步尽可能多的用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。
#include<stdio.h>
int min(int n, int m) {
if (m > n) {
return n;
} else {
return m;
}
}
int main(void) {
int k, t = 0, i, c[7], v;
int a[6] = {2, 5, 2, 1, 1, 1}; //存储对应面额钱币的张数
int b[6] = {1, 5, 10, 20, 50, 100}; //对应的钱币面额
scanf("%d", &k);
for(i = 5; i >= 0; i--) {
v = min(k / b[i], a[i]); //取某一面额 需要的张数和有的张数的较小值
k -= v * b[i];
t += v; //t记录使用的纸币的张数
}
if (k > 0) { //所有的纸币搭配无法达到对应的金额
t = -1;
}
if(t == -1) {
printf("no\n");
} else {
printf("%d\n", t);
}
return 0;
}
贪心算法与动态规划的区别
1.还是以纸币找零问题来说,如果改变纸币的面值为1, 3 ,4 ,想要得到总共为6元的钱。
按照贪心算法来计算就应该是 一张4元,两张1元。显然这不是最优解,所以说,贪心算法不一定能得到最优解,这也就是贪心算法的局限性。
但是如果用动态规划的话,就可以得到最优的解。
2.贪心算法是自顶向下的,而动态规划是自底向上的。
3.贪心算法是动态规划的一种特例,贪心算法可以解决的题目用动态规划也可以解决,但动态规划的部分题目,贪心算法解决不了。
4.同一道题目,贪心算法和动态规划都可以解决,贪心算法的时间复杂度和空间复杂度比动态规划高。
坚果保龄球
这道题也是属于一道贪心算法的题目,目的是用最少的坚果消灭所有僵尸。
#include<stdio.h>
int c[200005];
int main(void) {
int n, p[200005], a[200005];
int i, j, sum = 0, t = 0, q;
scanf("%d", &n);
for (i = 0; i < n; i++) {
scanf("%d %d", &p[i], &a[i]);
c[i] = p[i] * 1000000 + a[i]; //将两个数据构成一个新的数据,数据对这个新的数据排序就可以实现对每行的僵尸进入时间排序。
}
sort(0, n - 1); //排序
for (i = 0; i < n; i++) {
p[i] = c[i] / 1000000; //重新将数据分离出来
a[i] = c[i] % 1000000;
}
q = a[0];
sum = 1;
for (i = 1; i < n; i++) {
if (q + 60 <= a[i] && p[i - 1] == p[i]) {
sum++; //同一行某一时刻达到了这行的僵尸数最大。
q = a[i];//另下一个僵尸进入的时间为起始时间
}
if (p[i - 1] != p[i]) { //遍历到第二行,无论上一行什么情况,下一行都得重新开始一个坚果。
sum++;
q = a[i];
}
}
printf("%d\n", sum);
return 0;
}