目录
贪心算法
在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。
贪心算法不是从整体上考虑问题,而是在每一步选择中都采取在当前状态下最优的选择,即希望通过问题的局部最优解求出整个问题的最优解。
这种策略是一种很简洁的方法,对许多问题它能产生整体最优解,但不能保证总是有效,因为它不是对所有问题都能得到整体最优解。
如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。
利用贪心策略解题,需要解决两个问题:
- (1)该题是否适合于用贪心策略求解;
- (2)如何选择贪心标准,以得到问题的最优/较优解。
例题
活动安排问题
1.描述
设有n个活动的集合E={1,2,…,n},每个活动都要使用同一资源,如演讲会场等,而在同一时间段内只有一个活动能使用这一资源。
每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si<fi。如果选择了活动i,则在[si ,fi )内占用资源。
若区间[si ,fi )与区间[sj,fj )不相交,则称活动i与活动j是相容的。
活动安排问题就是在所给的活动集合中选出最大的相容活动子集合。
此时追求的最优是在最短的时间内安排最多的活动
2.思路
按照起止时间排序:有可能遇到初始时间很早,但结束时间很晚的活动。不合适
按照终止时间排序:优先选择较早终止且不与前面活动相交的活动
3.代码
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
struct action{
int s;//起始时间
int f;//结束时间
int index;//活动的变化
};
bool cmp(const action &a,const action &b){
if(a.f<=b.f)
return true;
return false;
}
void GreedySelector(int n,action a[],bool b[]){
b[1]=true;//选中排序后的第一个活动
int preEnd=1;//记录最近一次加入集合b的活动
for(int i=2;i<=n;i++)
if(a[i].s>=a[preEnd].f){
b[i]=true;
preEnd=i;
}
}
int main()
{
action a[1001];
int n;
scanf("%d",&n);
bool b[n+1];
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i].s,&a[i].f,&a[i].index);
b[i]=false;
}
sort(a,a+n+1,cmp);
GreedySelector(n,a,b);
for(int i=1;i<=n;i++)
if(b[i])
printf("%d\n",a[i].index);
return 0;
}
背包问题
1.描述
给定一个载重量为M的背包,考虑n个物品,其中第i个物品的重量 ,价值wi (1≤i≤n),要求把物品装满背包,且使背包内的物品价值最大。
- 如果物品不可以分割,称为0—1背包问题(动态规划),可以参考我另一篇博客
- 如果物品可以分割,则称为背包问题(贪心算法)。
2.思路
有3种方法来选取物品:
- (1)当作0—1背包问题,用动态规划算法,获得最优值220;
- (2)当作0—1背包问题,用贪心算法,按性价比从高到底顺序选取物品,获得最优值160。由于物品不可分割,剩下的空间白白浪费,不是最优。
- (3)当作背包问题,用贪心算法,按性价比从高到底的顺序选取物品,获得最优值240。由于物品可以分割,剩下的空间装入物品3的一部分,而获得了更好的性能。
3.代码
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
struct bag{
int w;//物品的重量
int v;//物品的价值
double c;//性价比
double x;//此物品装入背包的量
int index;
};
bool cmp(bag a,bag b){
if(a.c>=b.c)
return true;
return false;
//return a.c>=b.c;
}
double pack(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;
a[a[i].index].x=1.0;
i++;
}
//把物品分割后装满背包
if(i<n){
a[a[i].index].x=1.0*cleft/a[i].w;
b+=a[a[i].index].x*a[i].v;
}
return b;
}
int main()
{
bag a[1001];
int n,c;
scanf("%d%d",&n,&c);
for(int i=0;i<n;i++){
scanf("%d%d%lf%d",&a[i].w,&a[i].v,&a[i].c,&a[i].index);
a[i].x=0;
}
sort(a,a+n,cmp);
printf("%f\n",pack(n,a,c));
for(int i=0;i<n;i++)
if(a[i].x!=0)
printf("%d:%f\n",a[i].index,a[i].x);
return 0;
}
删数问题
1.描述
给定n位正整数a,去掉其中任意k≤n个数字后,剩下的数字按原次序排列组成一个新的正整数。对于给定的n位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案。
输入:第1行是1个正整数a,第2行是正整数k。
输出:对于给定的正整数a,编程计算删去k个数字后得到的最小数。
2.思路
n位数a,n可能特别大,因此可以用字符串存放,a表示为x1x2… xn,要删去k位数,使得剩下的数字组成的整数最小。
将该问题记为T,最优解A=xi1 xi2…xim (i1<i2<..<im,m=n-k),在删去k个数后剩下的数字按原次序排成的新数,其最优值记为N。
采用最近下降点优先的贪心策略:即x1<x2<... <xi-1<xi,如果xi+1<xi(下降点),则删去xi,即得到一个新的数且这个数为n-1位中最小的数N,可表示为x1x2…xi-1 xi+1...xn。
显然删去1位数后,原问题T变成了对n- 1位数删去k- 1个数的新问题T'
新问题和原问题性质相同,只是问题规模由n减小为n-1,删去的数字个数由k减少为k-1。
基于此种删除策略,对新问题T’,选择最近下降点的数继续进行删除,直至删去k个数为止。
3.代码
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
string a;
int k,i;
cin>>a>>k;
if(k>=a.size())
a.erase();
else
while(k>0){
i=0;
while(a[i]<=a[i+1])
i++;
if(i<a.size()-1){
//cout<<"a:"<<a<<endl;
a.erase(i,1);//取出a[i]
k--;
//针对12345这种情况,删5-4-3……
}else if(i == a.size()-1){
a.erase(i,1);
k--;
}
//cout<<"k:"<<k<<endl;
}
while(a.size()>1&&a[0]=='0')
a.erase(0,1);
cout<<a<<endl;
return 0;
}
多处最优服务次序
1.描述
设有n个顾客同时等待一项服务,顾客i需要的服务时间为ti,1≤i≤n,共有s处可以提供此项服务。应如何安排n个顾客的服务次序才能使平均等待时间达到最小?平均等待时间是n个顾客等待服务时间的总和除以n。
给定的n个顾客需要的服务时间和s的值,编程计算最优服务次序。
输入:第一行有2个正整数n和s,表示有n个顾客且有s处可以提供顾客需要的服务。接下来的一行中,有n个正整数,表示n个顾客需要的服务时间。
输出:最小平均等待时间,输出保留3位小数。
2.思路
假设原问题为T,并已经知道某个最优服务序列,即最优解为A={t1,t2....tn,其中ti为第i个用户需要的服务时间,则每个用户等待时间T为:
T1=t1;
T2=t1 + t2;
Tn=t1 + t2+...+tn
那么总的等待时间,即最优值N为:
N=nt1 + (n - 1)t2+…+2tn-1+tn
平均等待时间是n个顾客等待时间的总和除以n,故本题实际上就是求使顾客等待时间的总和最小的服务次序。
贪心策略:对服务时间最短的顾客先服务。
首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T变成了需对n-1个顾客服务的新问题T’。 新问题和原问题相同,只是问题规模由n减小为n-1。
3.代码
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
//顾客等待的队列为client,提供服务的窗口s个
double greedy(int client[],int s,int n){
//服务窗口的顾客等待时间
int service[s+1];
//服务窗口顾客等待时间的总和
int sum[s+1];
//按顾客的服务时间升序排序
sort(client,client+n);
//初始化
memset(service,0,sizeof(service));
memset(sum,0,sizeof(sum));
//贪心算法
int i=0;//顾客的指针
int j=0;//窗口的指针
while(i<n){
service[j]+=client[i];
sum[j]+=service[j];
i++;j++;
if(j==s)
j=0;
}
double t=0;
for(i=0;i<s;i++)
t+=sum[i];
t/=n;
return t;
}
int main()
{
int client[1000];
int s,n;
scanf("%d%d",&n,&s);//顾客数,窗口数
for(int i=0;i<n;i++)
scanf("%d",&client[i]);
printf("%f",greedy(client,s,n));
return 0;
}