顾名思义,贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。当然,希望贪心算法得到 的最终结果也是整体最优的。虽然贪心算法不能对所有问题都得到整体最优解,但对许多 问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即 使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
从问题的某一个初始解出发,向给定的目标递推。推进的每一步不是依据某一固定的 递推式,而是做一个当时看似最佳的贪心选择,不断地将问题实例归纳为更小的相似的子 问题,并期望通过所做的局部最优选择产生出一个全局最优解。
用贪心算法求解的问题中看到这类问题一般具有 2 个重要的性质:贪心选择性质和最 优子结构性质。
1.贪心选择性质──可通过做局部最优(贪心)选择来达到全局最优解
贪心策略通常是自顶向下做的。第一步为一个贪心选择,将原问题变成一个相似 的、但规模更小的问题,而后的每一步都是当前看似最佳的选择。这种选择可能依赖于已 作出的所有选择,但不依赖有待于做的选择或子问题的解。从求解的全过程来看,每一次 贪心选择都将当前问题归纳为更小的相似子问题,而每一个选择都仅做一次,无重复回溯过程,因此贪心法有较高的时间效率。
2.最优子结构──问题的最优解包含了子问题的最优解
贪心选择和最优子结构: 整体的最优解可通过一系列局部最优解达到.每次的选择可以 依赖以前作出的选择,但不能依赖于后面的选择, 最优子结构:问题的整体最优解中包含着它 的子问题的最优解。
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题 的最优子结构性质是该问题可用动态规划算法或贪心算法求解的关键特征。
3.与动态规划的区别
动态规划算法通常以自底向上的方式解各子问题。
而贪心算法则通常以自顶向下的 方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规
模更小的子问题。
题目:
- HDU 2037 今年暑假不 AC(活动安排问题)
- POJ 1700 Crossing River(过河问题)
- POJ 3253 Fence Repair(哈夫曼树)
- 田忌赛马问题
- PAT刷题
PATB1020月饼
#include<iostream>
#include<algorithm>
#include<string>
#include<cstdio>
using namespace std;
const int maxn=1001;
struct mooncake
{
double cap;
double sumprice;
double price;
}mcake[maxn];
bool cmp(mooncake a,mooncake b)
{
return a.price>b.price;
}
int main()
{
int n,d;
double ans=0;
cin>>n>>d;
for(int i=0;i<n;i++)
{
cin>>mcake[i].cap;
}
for(int i=0;i<n;i++)
{
cin>>mcake[i].sumprice;
mcake[i].price=mcake[i].sumprice/mcake[i].cap;
}
sort(mcake,mcake+n,cmp);
for(int i=0;i<n;i++)
{
if(mcake[i].cap<=d)
{
d-=mcake[i].cap;
ans+=mcake[i].sumprice;
}
else
{
ans+=mcake[i].price*d;
break;
}
}
printf("%.2lf\n",ans);
return 0;
}
思路难点:
- 存储方式结构体存储而非数组存储,当然数组存储也是可以做出来但是会比较麻烦。
- 开始的排序输入输出都没有问题,到了核心代码区域不一定会想到。
- 单价最高的枚举方式,如果需求量比库存量要多的话,那么将该种月饼全部卖出(已经排好序了,所以说第一个是单价最高的月饼),那么需求量就发生变化,减去选择的库存量,获得收益则加上该种月饼全部卖出的总售价大小。
- 如果说需求量没有库存量多的话,那么就选择单价最高的月饼,这样就获得的收益最大。
- 策略正确性证明:正确,局部最优且全局最优。
PATB1023组个最小数
//先输出第一个不为0的数字
//然后再逐一输出
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int main()
{
int arr[11];
for(int i=0;i<10;i++)
{
cin>>arr[i];
}
for(int i=1;i<10;i++)
{
if(arr[i]>0)
{
printf("%d",i);
arr[i]--;
break;//注意break语句的重要性找到第一个大于零的就直接退出循环
}
}
for(int i=0;i<10;i++)
{
for(int j=0;j<arr[i];j++)
{
printf("%d",i);
}
/*if(arr[i]>0)//错误原因是缺失了循环不为0的只输出了一次,所以说要进行修改
{
printf("%d",i);
arr[i]--;
}*/
}
return 0;
}
PATA1038 Recover the Smallest Number
思路:首先我们可能会想到字典序排序,是字典序小的在前面,字典序大的在后面吗?32 312很明显32字典序比312大,那么是32312吗?31232呢?很明显后面的31232更符合要求,那么不妨设置一个前提条件s1+s2<s2+s1,意思就是对数字串s1与s2,如果s1+s2<s2+s1的话,那么把s1放在s2的前面。可以自己验证这一策略是否满足最优解。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
string str[10010];
bool cmp(string a,string b)
{
return a+b<b+a;
}
int main()
{
int n;
cin>>n;
string ans;
for(int i=0;i<n;i++)
{
cin>>str[i];
}
sort(str,str+n,cmp);
for(int i=0;i<n;i++)
{
ans+=str[i];
}
while(ans.size()!=0&&ans[0]=='0')
{
ans.erase(ans.begin());//去除前导0
}
if(ans.size()==0)
{
cout<<0<<endl;
}
else
{
cout<<ans<<endl;
}
return 0;
}
我觉得思维点很重要,首先自己贪心没想到,其次拼接以及交换也没想到。感觉思路还是不行。
PATA1067 Sort with Swap(0, i) (25 分)
说实话,我有思路,但是我不确定能不能写出来,首先自己模拟样例的9是如何得来的。其实不难发现一个规律,首先交换的是0和0所在位置本来应该在的数字,如果交换完后发现0已经回到0应在的位置,但是序列依然不是有序的例如:
0 5 2 3 6 4 9 7 8 1这下该怎么办其实这时不论你是交换0--9,还是0--6都是可以的,(意思就是随意选择一个还没有回到本位的数字)最终的结果都是9。因为问题回归的本质还是0和0所在的位置本来应该在的数字的交换。但是我不知道这个思路如何转换为代码。
看了大佬的解析,我的思路是没有问题的,代码转换上面太差,自己真的好菜,要么没有思路,要么就是有思路不会写,要么就是看了解析后才会写,wow,wtnl ! 定义left为除0以外不在本位上的个数。下面给出伪代码:
left含义就是除0以外不在本位上的个数。
for(int i=0;i<n;i++)
{
cin>>num;
arr[num]=i;
if(i==num&&num!=0)
{
left--;
}
}
int k=1;k存放除0以外当前不在本位上的最小的数
while(left>0)
{
//0在原数位上,此时找到一个不在本位上的数,令其与0交换
if(arr[0]==0)
{
while(k<n)
{
if(arr[k]!=k)
{
swap(arr[k],arr[0]);
cnt++;//计数器
break;
}
k++;
}
}
else//0不在原数位上,0和这个数位上本应有的数进行交换
{
swap(arr[0],arr[arr[0]])//多思考交换的时候如何交换
cnt++;
left--;
}
}
cout<<cnt<<endl;
return 0;
}
这不是代码只是代码和思路的混合体。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100010;
int arr[maxn];
int main()
{
int n,cnt=0,num;
cin>>n;
int sum=n-1;
for(int i=0;i<n;i++)
{
cin>>num;
arr[num]=i;
if(num==i&&i!=0)//除0以外有本位上的数,就不用去移动
{
sum--;
}
}
int k=1;
while(sum>0)
{
//0在本位上,0和随便一个不在本位上的数进行交换
if(arr[0]==0)
{
while(k<n)
{
if(arr[k]!=k)
{
swap(arr[k],arr[0]);
cnt++;
break;
}
k++;
}
}
else
{
//0不在本位上,0和应该在本位上的数进行交换
swap(arr[0],arr[arr[0]]);
cnt++;
sum--;
}
}
cout<<cnt<<endl;
return 0;
}