6.0引入
0-1背包问题:有一个背包,最多能承载100斤的重量,现在有5个物品,其重量和价值如下表所示。如何装物品,使得在不超重的情况下背包中的物品价值最大?(每件物品只有一个,且物品不能分割)
我们可以选用不同的贪心策略:
1.每次挑选价值最大的物品装入
2.每次挑选重量最小的物品装入
3.每次选取单位重量价值最大的物品装入
通过对于每种策略的演算,我们可以发现,贪心法有时候得不到最优解。
例如我们选取第一个和第二个策略时,是无法得到最优解的。
6.1概述
贪心法的基本思路:从问题的某一个初始解出发,根据某种贪心策略,向给定的目标推进,每一步都做一个当时看似最佳的贪心选择,不断地将问题变为更小的相似子问题,并期望通过所做的局部最优选择产生出一个全局最优解。
也就是说贪心法不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。
适合贪心法求解的问题必须具备以下两要素:
1.贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。
2.最优子结构性质:原问题能转化为一组同类的子问题,原问题做贪心选择之后,子问题处理方法同原问题类似,并且原问题的最优解包含着它子问题的最优解。
6.2贪心法示例
6.2.1汽车加油问题
明确贪心策略:走到自己能走到并且离自己最远的那个加油站,在那个加油站加满油后,同从起点出发一样,再按照同样的方法贪心选择下一个加油站。
int main() {
int i,num = 0,s=0; //num加油次数;s加满油后已行驶的公里数
int a[N+2]= {0,1,2,3,4,5,1,6,6};
int b[N+1]= {0}; //加油站加油最优解b1~bN
for(i = 1; i <=N+1 ; i++)
if(a[i] > K) {
printf("no solution\n");
return 0;
}
for(i = 1,s = 0; i < N+1; i++) {
s += a[i];
if(s + a[i+1] > K) {
num++;
b[i]=1;
s = 0;
}
}
return 0;
}
6.2.2活动安排问题
明确贪心策略:
1.优先选择具有最早开始时间的活动
2.优先选择持续时间最短的活动
3. 优先选择具有最早结束时间的活动
我们选择第三个贪心策略,因为这样每一次的选择,可以使得余下的时间最大化,可以安排更多的活动。
struct Action { //活动的类型声明
int b; //活动起始时间
int e; //活动结束时间
bool operator<(const Action &s) const { //重载<关系函数
return e<=s.e; //用于按活动结束时间递增排序
}
};
int n=11;
Action
A[]= {{0},{1,4},{3,5},{0,6},{5,7},{3,8},{5,9},{6,10},{8,11},
{8,12},{2,13},{12,15}
}; //下标0不用
//求解结果表示
bool ans[MAX]; //标记选择的活动
int Count=0; //选取的兼容活动个数
void solve() { //求解最大兼容活动子集
memset(ans,0,sizeof(flag)); //初始化为false
sort(A+1,A+n+1); //A[1..n]按活动结束时间递增排序
int preEnd=0; //前一个兼容活动的结束时间
for (int i=1; i<=n; i++) //扫描所有活动
if (A[i].b>=preEnd) { //找到一个兼容活动
ans[i]=true; //选择A[i]活动
preEnd=A[i].e; //更新preend值
}
}
6.2.3蓄栏保留问题
这个题与活动安排问题类似,也是找出最大兼容活动子集,找到的最大兼容活动子集个数就是最少蓄栏个数。
struct Cow { //奶牛的类型声明
int no; //牛编号
int b; //起始时间
int e; //结束时间
bool operator<(const Cow &s) const { //重载<关系函数
if (e==s.e) //结束时间相同按开始时间递增排序
return b<=s.b;
else //否则按结束时间递增排序
return e<=s.e;
}
};
int n=5;
Cow A[]= {{0},{1,1,10},{2,2,4},{3,3,6},{4,5,8},{5,4,7}};
//下标0不用
//求解结果表示
int ans[MAX]; //ans[i]表示第A[i].no头牛的蓄栏编号
void solve() { //求解最大兼容活动子集个数
sort(A+1,A+n+1); //A[1..n]按指定方式排序
memset(ans,0,sizeof(ans)); //初始化为0
int num=1; //蓄栏编号
for (int i=1; i<=n; i++) { //i、j均为排序后的下标
if (ans[i]==0) { //第i头牛还没有安排蓄栏
ans[i]=num; //第i头牛安排蓄栏num
int preEnd=A[i].e; //前一个兼容活动的结束时间
for (int j=i+1; j<=n; j++) { //查找一个最大兼容活动子集
if (A[j].b>preEnd && ans[j]==0) {
ans[j]=num; //将兼容活动子集中活动安排在num蓄栏中
preEnd=A[j].e; //更新结束时间
}
}
num++; //查找下一个最大兼容活动子集,num增1
}
}
}
6.2.4股票买卖问题
1.若只能买卖一次,且无手续费
int main() {
int prices[]= {0,3,9,2,5,4,7,1};
int n=sizeof(prices)/sizeof(int)-1;
int buy=1,sell=1,k=1,profit=0;
for(int i = 2; i <= n; i++) {
if(prices[i]-prices[k]>profit) { //今天是否卖出
profit = prices[i]-prices[k];
buy=k;
sell=i;
}
if(prices[i] < prices[k]) k=i; //今天的价格是否更低
}
printf("buy=%d,sell=%d,profit=%d\n",buy,sell,profit);
return 0;
}
2.若可以多次买入或卖出,无手续费
#include<stdio.h>
#define MAX 50
int prices[]={0,23,18,22,35,16,8,4,30,35};
int buy[MAX]={0};
int sell[MAX]={0};
int n=sizeof(prices)/sizeof(int)-1;
int profit=0;
void Yuce(){
int buy=0,sell=0,profit=0;
int i;
for(i=1;i<=n;i++){
if(i==1){
if(prices[1]<prices[2])
buy=i;
}
else if(i==n){
if(prices[n]>prices[n-1]&&buy)
sell=n;
}
else{
if(prices[i]>prices[i+1]){
if(buy)sell=i;
}
else if(!buy)buy=i;
}
if(buy&&sell){
printf("buy at %d day,sell at %d day,profit is %d\n",buy,sell,prices[sell]-prices[buy]);
profit+=prices[sell]-prices[buy];
buy=sell=0;
}
}
printf("total prices is %d",profit);
}
int main(){
Yuce();
}
6.2.5跳跃游戏
#include<stdio.h>
#define MAX 50
int num[]={0,3,5,2,7,6,9,6,2,4,9,2,3,4,8,7,3,15,6,7,4};
int n=sizeof(num)/sizeof(int) -1;
int arr[MAX]={0};
void find(){
int longest=1,flag=0,sum=0;
int i,j;
for(i=1;i<=n;i++)
arr[i]=i+num[i];
for(i=1;i<=n;i++){
if(longest>=n){
printf("from %d to %d\n",i,n);
sum++;
flag=1;
break;
}
if(i>longest)break;
int max=i;
for(j=i;j<=arr[i];j++){
if(arr[j]>arr[max])
max=j;
}
if(arr[max]>longest){
longest=arr[max];
sum++;
printf("from %d to %d\n",i,max);
i=max;
i--;
continue;
}
}
if(flag)printf("total jump %d times\n",sum);
else printf("cannot arrive\n");
}
int main(){
find();
}