贪心算法
1. 概念
求解最优化问题的算法通常需要经过一系列的步骤,在每个步骤都面临多种选择。对于许多最优化问题,使用动态规划来求最优解就有点杀鸡用牛刀了,所以,一种更简单、高效的算法就可以用上了。这种算法就是贪心算法。
贪心算法,在对问题求解的时候,总是做出在当前看来最好的选择。也就是说,它不从总体最优上加以考虑,它所作出的仅仅是在某种意义上的局部最优解。
贪心算法并不保证得到最优解,但是对于很多问题,它确实可以求到最优解。
贪心算法没有固定的算法框架,算法设计的关键就是贪心策略的选择。
框架为:
从问题的某一初始解出发;
while(能朝给定目标前进一步)
{
利用可行的策略,求解可行解的一个解元素;
}
有所有解元素组合成问题的一个可行解
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。实际上,贪心算法适用的情况很少。
2. 贪心算法的基本思路:
- 建立数学模型来描述问题。
- 把求解的问题分成若干个子问题。
- 对每一子问题求解,得到子问题的局部最优解。
- 把子问题的解局部最优解合成原来解问题的一个解。
3. 例子
1.找零钱
题目:
一个小孩买了价值少于100元的零食,并将100元的钱交给售货员。售货员希望用数目最少张的钱找给小孩。假设提供了数目不限的面值为50元、20元、10元、5元、2元及1元的纸钞。售货员分步骤组成要找的零钱数,每次加入一张纸钞。选择纸钞时所采用的贪婪准则如下:每一次选择应使零钱数尽量增大。为保证解法的可行性(即:所给的零钱等于要找的零钱数),所选择的钱不应使零钱总数超过最终所需的数目。
分析:
要找回零钱的数量最小,肯定是从最大面值的开始想,一个50元的当然数量小于5个10元的。
所以,我们应该从最大面值开始,按照递减的顺序考虑各种硬币。先用大面值的硬币,当金额不足这个大面值,就考虑较小面值的下一个。
代码:
#include<iostream>
using namespace std;
int money[6] = {50,20,10,5,2,1};
int main()
{
int n; //买了n元的东西
while(cin>>n)
{
int k = 0;
int s[10]; //记录每种钱用了几张
int r = 100 - n; //需要找回r元
//先尽量用大面值的,然后递减面值
for(int i = 0 ;i < 6; i++)
{
s[i] = r / money[i];
r = r % money[i];
}
for(int i = 0; i < 6; i++)
{
cout<<"用了"<<money[i]<<"元的钱"<<s[i]<<"张"<<endl;
}
}
}
2. 活动
问题:
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si < fi。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
分析:
将活动按照结束时间进行从小到大排序。然后用i代表第i个活动,s[i]代表第i个活动开始时间,f[i]代表第i个活动的结束时间。按照从小到大排序,挑选出结束时间尽量早的活动,并且满足后一个活动的起始时间晚于前一个活动的结束时间,全部找出这些活动就是最大的相容活动子集合。事实上系统一次检查活动i是否与当前已选择的所有活动相容。若相容活动i加入已选择活动的集合中,否则,不选择活动i,而继续下一活动与集合A中活动的相容性。若活动i与之相容,则i成为最近加入集合A的活动,并取代活动j的位置。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
//n是总共活动,s[]是开始时间,f[]是结束时间
//a[]代表是否可以假如序列
//按结束时间的非减序:.f1≤f2≤…≤fn排列
void greedyselector(int n, int s[], int f[], bool a[])
{
a[0] = true;
int j = 0;
for(int i = 1; i < n; i++)
{
if(s[i] >= f[j])
{
a[i] = true;
j = i;
}
else
a[i]= false;
}
}
int main()
{
int s[13] = {1,3,0,5,3,5,6,8,8,2,12,10};
int f[13] = {4,5,6,7,8,9,10,11,12,13,14,15};
bool a[13] = {0};
greedyselector(12,s,f,a);
int sum = 0;
cout<<"end time :"<<endl;
for(int i = 0; i < 12 ;i++)
{
if(a[i])
{
sum++;
cout<<f[i]<<" ";
}
}
cout<<endl;
cout<<"sum is: "<<sum<<endl;
}
3. 其他
- 哈夫曼编码
- 0-1背包问题
- 磁盘文件的存储
- 生产调度问题
- 信息查询