【ACM学习周记】02 - 贪心算法

这周学的主要是贪心算法的知识。相对于第一周来说,难度增加了不少。关于贪心算法的基础知识,详见我上一篇博客。

贪心算法

工欲善其事,必先利其器。贪心算法亦是如此。作为解决问题的一种工具,在合适的条件下能达到事半功倍的效果。关于贪心算法的选择条件在此不再累赘。接下来,通过引入小题,逐步构建贪心体系。

一、贪心结构

整体结构

下面这两个mermaid类型TB图表可能在部分手机上字体显示过小,在电脑上是正常的

判断适合贪心
回溯
引入问题
整体问题
寻找贪心方法
得到部分最优解
得到全局最优解

编程结构

建立数据结构体
排序 自定义cmp
载入选择算法
得出结果

二、典型题型载入

1.简单贪心

最优装载量

问题模型:存在一定容积容器,承重为M,有n件待装物品,体积为 nm ,容器承受体积不限制,尽可能的装载物品。

思路

步骤一:确定贪心原则——按照最轻到最重装载
步骤二:确定排序方式:按重量升序
步骤三:判断终止条件——累加物品,达到承重极限,输出。

背包问题

问题模型:载重量为M的背包,有n个物品,第n个物品重量为mn,价值为wn要求尽可能装满背包,且总价值最大
步骤一:确定贪心原则——性价比
步骤二:确定排序方式——按性价比降序
步骤三:判断终止条件——累加至承重极限,输出。

2.区间贪心

区间调度

问题模型:有n项工作,每项工作从Si开始,Ti结束。其中S = {1, 2, 4, 6, 8}, T = {3, 5, 7, 8, 10}。对每项工作,都可以选择参加与否,若参加,则必须全程参与,且时间不能重叠,求最多能参加多少工作。

下面是其甘特图

Mon 04 Mon 11 任务1 任务2 任务3 任务4 任务5 任务 区间调度问题

贪心策略:每次选取结束时间最早,第二权重为每次选取开始时间最早

做作业

问题模型:要做作业,但每次只能做一个,每个作业都有截止时间,超时会扣分,咋让扣分最小呢?
贪心策略:先截止的先安排,扣分多的先安排,如果一定扣分了,替换前面一个已安排但扣分少的
贪心实现:
先对时间排序,如果截止时间相同,哪个扣分大那个放在前
后对时间数据遍历,如果到第i个时已经超过了截止时间,记下这个i和其下标。在这之前找一个扣分最少的来作为要扣的分数

三、代码技能与实现

巧用乘法代替除法 / double之比较

看这样一段代码:

...
struct node{
    double value;
    double weight;
}pool[10010];
...
bool cmp(node a, node b){
    double cp_1, cp_2;
    cp_1 = 1.0 * a.value / a.weight;
    cp_2 = 1.0 * b.value / b.weight;
    return cp_1 > cp_2;
    //第一处
}
...
if(pool[i].value == pool[i + 1].value){
    //第二处
    ...
}

以上代码可能会出现在上面的背包问题(按性价比)里。我们知道,double类型精度不可控,进行比较时,尤其是代码注释第二处的相等比较,很可能会出错。
我们分析一下注释第一处结构:
c p 1 = a . v a l u e a . w e i g h t c p 2 = b . v a l u e b . w e i g h t cp_1 = \frac{a.value}{a.weight}\\ cp_2 = \frac{b.value}{b.weight}\\ cp1=a.weighta.valuecp2=b.weightb.value
那么
c p 1 > c p 2 cp_1 > cp_2 cp1>cp2

a . v a l u e a . w e i g h t ∗ b . v a l u e b . w e i g h t \frac{a.value}{a.weight} * \frac{b.value}{b.weight} a.weighta.valueb.weightb.value
其实可以表示为
a . v a l u e ∗ b . w e i g h t > b . v a l u e ∗ a . w e i g h t a.value*b.weight>b.value*a.weight a.valueb.weight>b.valuea.weight
而且精度判断可以在10-6内。

为此,将以上代码按照如下方式写的话:

...
bool cmp(node a, node b){
    return a.value * b.weight > b.value * a.weight;
}
...
if(pool[i] - pool[i + 1] <= 1e-6){
    ...
}

这样,准确度会大大提高。

struct中的bool

有时需要判断一个元素是不是被用过,我们可以在struct中如下定义:

...
struct node{
    int a;
    int b;
    bool k;
}pool[10010];
...
for(statement){
    ...
    pool[i].k = 1;
    ...
}
...
if(pool[i].k == 1){
    continue;
}
...

pair数组合并

在上面的区间调度问题中过,我们对Si和Ti进行“双排序”。这是使用pair数组即可轻松解决这个问题:

...
pair<int, int> itv[n];
...

pair数组包含两个元素,一个是first,另一个是second。这里我们对S优先,所以把Si放入first,把Ti放入second

...
for(int i = 0; i < n; i++){
    itv[i].first = S[i];
    itv[i].second = T[i];
}
sort(itv, itv + n);
...

四、心得

这周主要讲的贪心算法。贪心算法要求很强的发散思维和数学功底,因此,对待这种问题有时候时候会“不走寻常路”不能拘泥于正向思维,可以试试反向思维
这周是开始做vjudge提的第一周。说实话,vjudge题库(大部分来源于POJ)的难度比OpenJudge提高了一个等级。这主要存在于四个方面:
1.vjudge是全英文题库;
2.vjudge上的题对发散性思维要求十分高;
3.vjudge不给出单题得分,即WA就是0分,导致自己很难分析出到底错在哪里;
4.我经常看错条件。

其实第四条最重要。
要知道为什么我做的这么慢。。。我其实努力做了,但是每次都会因为一点小细节(让输入double我整了个int,用0隔开我没看见之类的)查好几遍甚至好几十遍代码,这很耗时间。所以我代码尽量加上注释,尽量用有意义的变量。其实我在算法上是没有太大问题的。。。

所以对症下药:每次做题一定要注意小细节。

昨天晚上还因为代码的低级错误发了场脾气,班助和同学劝了劝我,我才冷静了下来。
今天又静下心来思考了思考。计算机本来就是出于多年的爱好和兴趣去选的,ACM课也是出于极高的兴趣去选的,那自己还有什么理由在自己所爱好的领域里得到痛苦呢?这就和写算法实现一样,选择了一种算法,就认真地把这种算法写好。既然自己已经迈出了第一步,那就要把剩下的路走完

这周还意外收获了一个人生道理:

寻找最优解。

贪心算法本身就是个寻找最优解的过程,从局部最优扩展至全局最优。生活亦是如此,把握住当下的局部最优,也许在最后会得到整个人生中的全局最优解。
因此,我们在学习算法时,不仅要学习算法的理论知识,还要把算法的核心价值扩展至方方面面,这样一个算法的价值就大大增加了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值