贪心算法是一种求最优解的方法,不是从整体上思考问题,而是选择局部最优解,由局部进而得到整个问题的最优解。
利用贪心策略解题,需要注意两个问题:
(1)该题是否适合于用贪心策略求解;
(2)如何选择贪心策略,以得到问题的最优/较优解。
所以如何去选择贪心策略是很重要的。
例题
1.最优装载问题
有一批集装箱要装上一艘载重量为c的轮船,其中集装箱i的重量为wi。最优装载问题要求确定在装载体积不受限制的情况下,将尽可能多的集装箱装上轮船。
贪心策略:优先装载最轻的
算法实现
struct load {
int index; //集装箱编号
int w; //集装箱重量
}box[1001];
bool cmp (load a, load b)
{
return a.w<b.w ;//升序排列
}
while (cin>>c>>n)
{
memset(box, 0, sizeof(box));
memset(x, 0, sizeof(x));//清空
for (int i=1; i<=n; i++)
{
cin>>box[i].w;
box[i].index = i;
}
//按集装箱的重量升序排序
sort(box, box+n+1, cmp);
if (box[1].w>c)
{
printf("No answer!\n");
continue;
}
int i;
for (i=1; i<=n && box[i].w<=c; i++)
{
x[box[i].index] = 1;
c -= box[i].w;
}
cout<<i-1<<endl;//输出装载的集装箱数量
for (i=1; i<=n; i++)
if (x[i]) cout<<i;
cout<<endl;//输出装载的集装箱编号
}
2.背包问题
给定一个载重量为M的背包,考虑n个物品,其中第i个物品的重量 ,价值wi (1≤i≤n),要求把物品装满背包,且使背包内的物品价值最大。(物品可以分割)
贪心策略:选择性价比最高的
算法实现
struct bag{
int w; //物品的重量
int v; //物品的价值
double c; //性价比
}a[1001]; //存放物品的数组
bool cmp(bag a, bag b)
{
return a.c >= b.c;降序
}
//形参n是物品的数量,c是背包的容量,数组a是按物品的性价比降序排序
double knapsack(int n, bag a[], double c)
{
double cleft = c; //背包的剩余容量
int i = 0;
double b = 0; //获得的价值
//当背包还能完全装入物品i
while(i<n && a[i].w<cleft)
{
cleft -= a[i].w;
b += a[i].v;
i++;
}
//装满背包的剩余空间
if (i<n) b += 1.0*a[i].v*cleft/a[i].w;
return b;
}
3.货币找零
假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?
贪心策略:每一步尽可能用面值大的纸币
算法实现
int Count[7] = {3, 0, 2, 1, 2, 2, 3};
int value[7] = {1, 2, 5, 10, 20, 50, 100};
int ans=0;
void solve(int money)
{
for(int i = N-1; i >= 0; i--)
{
int c = min(money / value[i], Count[i]);
money = money - c * value[i];
if(c != 0)
ans+=c;
}
if(money > 0)
{
cout<<"不能找零"<<endl;
}
else
cout<<ans<<endl;
}
4.区间调度问题
有n项工作,每项工作分别在Si开始,Ti结束。例如S={1,2,4,6,8},T={3,5,7,8,10}。对每项工作,你都可以选择与否,若选择参加,则必须至始至终参加全程参与,且参与工作的时间段不能有重叠,你最多能选择几项工作。
贪心策略:每次都选取结束时间最早的
算法实现
int S[5]={1,2,4,6,8};
int T[5]={3,5,7,9,10};
pair<int, int> itv[n];//对工作排序的pair数组
int solve()
{
//为了让结束时间早的工作排在前面,把T存入first,把S存入second
for(int i = 0; i < n; i ++)
{
itv[i].first = S[i];
itv[i].second = T[i];
}
sort(itv, itv + n);
int count = 0;//选取的结果
int t = 0; //最后所选工作的结束时间
for(int i = 0; i < n; i ++)
{
if(t < itv[i].first)
{
count ++;
t = itv[i].second;
}
}
return count;
}
5.字典序最小问题
给定长度为N的字符串S,要构造一个长度为N字符串T。T是一个空串,反复执行下列任意操作:
从S的头部删除一个字符,加到T的尾部;
从S的尾部删除一个字符,加到T的尾部;
目标是要构造字典序尽可能小的字符串T。
贪心策略:不断取S得开头和末尾中较小的一个字符放到T的末尾
算法实现
int main()
{
int n=6;
char S[7]="ACDBCB";
int a=0,b=n-1;
while(a<=b)
{
bool left=false;//把从左起和从右起的字符串比较
for(int i=0;a+i<=b;i++)
{
if(S[a+i]<S[b-i])
{
left=true;
break;
}
else if(S[a+i]>S[b-i])
{
left=false;
break;
}
}
//左右两边谁大输出谁
if(left) putchar(S[a++]);
else putchar(S[b--]);
}
}
6.分发饼干
你想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。注意:你可以假设胃口值为正。一个小朋友最多只能拥有一块饼干。
贪心策略:大饼干分给胃口大的孩子
算法实现:
int fn(vector<int>& g, vector<int>& s)//胃口是g,饼干是s
{
if(g.empty()|| s.empty()) return 0;
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int res=0, index=s.size()-1;
for(int i=g.size()-1;i>=0;){
if(index>=0)
{
if(g[i]<=s[index])
{
res++;
index--;
}
i--;
}
else
break;
}
return res;
}
7.小船过河问题
只有一艘船,能乘2人,船的运行速度为2人中较慢一人的速度,过去后还需一个人把船划回来,问把n个人运到对岸,最少需要多久。
贪心策略:
先将所有人过河所需的时间按照升序排序,我们考虑把单独过河所需要时间最多的两个旅行者送到对岸去,有两种方式:
最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来,所需时间为:t[0]+2t[1]+t[n-1];
最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来,所需时间为:2t[0]+t[n-2]+t[n-1]。
算法实现;
int main()
{
int a[1000],t,n,sum;
cin>>t;
while(t--)
{
cin>>n;
sum=0;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
while(n>3)
{
sum=min(sum+a[1]+a[0]+a[n-1]+a[1],sum+a[n-1]+a[0]+a[n-2]+a[0]);
n-=2;
}
if(n==3)
sum+=a[0]+a[1]+a[2];
else if(n==2)
sum+=a[1];
else
sum+=a[0];
cout<<sum<<endl;
}
}