贪心算法(又称贪婪算法):在问题求解时,总是做出在当前看来是最好的选择。即:求局部最优解,是不是全局最优解,还要进一步判断。
- 很多问题不是最朴素的贪心,往往需要我们做一些变化,才可以用贪心。(一般需要排序)
- 贪心效率高于动态规划;
- 若要用贪心算法求全局最优解,必须保证他是局部最优解;
贪心选择
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。
最优子结构
当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
基本思想
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
过程
- 建立数学模型来描述问题;
- 把求解的问题分成若干个子问题;
- 对每一个子问题求解,得到子问题的局部最优解;
- 把子问题的解局部最优解合成原来问题的一个解。
典例:田忌赛马,今年暑假不AC…
e.g. : 今年暑假不AC
Problem Description
“今年暑假不AC?”
“是的。”
“那你干什么呢?”
“看世界杯呀,笨蛋!”
“@#$%^&*%…”
确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视了。
作为球迷,一定想看尽量多的完整的比赛,当然,作为新时代的好青年,你一定还会看一些其它的节目,比如新闻联播(永远不要忘记关心国家大事)、非常6+7、超级女生,以及王小丫的《开心辞典》等等,假设你已经知道了所有你喜欢看的电视节目的转播时间表,你会合理安排吗?(目标是能看尽量多的完整节目)
Input
输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Ti_s,Ti_e (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。
Output
对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。
Sample Input
12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0
Sample Output
5
思路:1、因为每一组节目都有自己的开始结束时间,所以用结构体来保存开始结束时间;
2、因为节目长短不同,为了尽可能多看,结束的时间距离上个结束的时间越短,就说明整个前边的节目时长也就越 短,就说明可看的节目也就可能更多;用sort排序;
贪心排序:
| \ \\\\\ …! | 一 | 二 | 三 | 四 | 五 |…
| -start time-| 1 | 0 | 3 | 3 | 2 |…
|-end time -| 3 | 4 | 7 | 8 | 9 |…结束时间从小到大排序
找,后面开始时间 >= 前一个结束时间的 ,记作可看的项,比如先看一,再看三… 以此实现可看数量最多!
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
struct time{
//存电视开始结束时间
int start;
int end;
};
int cmp(time s,time e)
{
/*对节目的结束时间从小到大排序,如果结束时间相同,
则 按开始时间从大到小排序
*/
if(s.end==e.end)
return s.start>e.start;
return s.end<e.end;
}
int main()
{
int n;
struct time a[105];
while(~scanf("%d",&n)&&n){
for(int i=0;i<n;i++){
scanf("%d%d",&a[i].start,&a[i].end);
}
sort(a,a+n,cmp);//对时间排序
int cnt;
int t,i;
//如果开始时间比上一个结束时间迟,则多看+1
for(i=1,t=a[0].end,cnt=1;i<n;i++){
if(a[i].start>=t){
t=a[i].end;
cnt++;
}
}
printf("%d\n",cnt);
}
return 0;
}
图
概念:图是将各种模型抽象成一些顶点,这些顶点之间可以通过图的边实现连接,即vretex和edge,如下图所示圆圈中的顶点(或节点,结点)和黑色的边:
度数:点可连接边的数目,
出度:指向别的点
入度:指向这个点
可图性判断
两个概念:
1、度序列:若把图G所有顶点的度数排成一个序列S,则称S为图G的度序列。
2、序列是可图的:一个非负整数组成的有限序列如果是某个无向图的度序列,则称该序列可图。
Havel-Hakimi定理:
由非负整数组成的非增序列S:d1, d2 ,…, dn
(n≥2,d1≥1)是可图的,当且仅当序列S1:d2-1,
d3-1,…,dd1+1-1,dd1+2,…,dn是可图的。
其中,序列S1中有n-1个非负整数,S序列中d1
后的前d1个度数(即d2~dd1+1)减1后构成S1中的前
d个数。
贪心一般需伴随排序:
e.g. : 给出一些数的度,判断他们是否可以构成图
& 3 1 4 5 4 1 2
//从大到小排序后:
5 4 4 3 2 1 1
//(度最大的那个分解!最大的不要了,序列减1,)
//如果上面的可图,减完后的依旧可图
//如果减完后变成000.. 则说明可图
*3 3 2 1 0* 1
*2 1 0 0* 1
*0 0 0* 0
//后面的1 可以放到前面
//说明可图
& 4 4 3 2 1 0
3 2 1 0 0
1 0 -1 -1
//不可图
& 3 2 2 1 1 1
1 1 0 1 1
0 0 1 1
0 0
//可图