算法部分 基础6

一、贪心算法简述

  贪心算法的每一步行动总是按照某种指标选取最优的操作来进行该指标,只看眼前并不考虑以后可能造成的影响。证明方法通过替换法和数学归纳法实现。

二、贪心算法例子

1. 圣诞老人的礼物

  问题描述:圣诞节来临了,中圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果都有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿雪橇最多只能装下重量为 W 的糖果,请问圣诞老人最多能带走多大价值的糖果。

输入:第一行由两个部分组成,分别为糖果箱数正整数 n (1 <= n <= 100),
驯鹿能承受的最大重量正整数 w (0 < w < 10000), 两个数用空格隔开。其余
n 行每行对应一箱糖果,由两部分组成,分别为一箱糖果的价值正整数 v 和重量
正整数 w , 中间用空格隔开。
输出:输出为圣诞老人能带走的糖果的最大总价值,保留 1 位小数。输出为一
行,以换行符结束。样例输入
输入:
4 15
100 4
412 8
266 7
591 2输出:
1193.0

  分析和程序如下

按礼物的价值/重量比从大到小一次选取礼物,对选取的礼物尽可能地多装,直到
重量为 w , 复杂度 O(nlogn).程序如下:
#include <stdio.h>
#include <iostream>
#include <algorithm>using namespace std;const double eps = 1e-6;
struct Candy{int v; int w;
}candies[110];//箱子按单位重量价值排序
bool operator < (const Candy & a, const Candy & b) {return float(a.v) / a.w - float(b.v) / b.w > eps;
}int main(){int n, w;scanf("%d%d", & n, & w);for(int i = 0; i < n; i++){scanf("%d%d", & candies[i].v, & candies[i].w);}sort(candies, candies + n);int totalW = 0;double totalV = 0;// 排好序,按照顺序取就行for(int i = 0; i < n; i++){if(totalW + candies[i].w <= w){totalW += candies[i].w;totalV += candies[i].v;}else{totalV += candies[i].v *double(w - totalW) / candies[i].w;break;}}printf("%.1f", totalV);return 0;
}

  结果如下,

  对于这个题,若糖果只能整箱拿,则贪心法得到结果是错误的。考虑以下例子,只能整箱拿, 3 个箱子 (8, 6), (5, 5), (5, 5), 雪橇总容量为 10 。如果按照贪心算法,拿的是 (8, 6) ,就不能拿了。但是 (5, 5), (5, 5) 是最优的。

2. 电影节

  问题描述:大学生电影节在北大举办!这天,在北大各地放了多部电影,给定每部电影的放映时间区间,区间重叠的电影不可能同时看(端点可以重合),问李雷最多可以看多少部电影。

输入:多组数据。每组数据开头都是 n (n <= 100), 表示共 n 场电影,接下
来 n 行,每行两个整数 (均是小于 1000) , 表示一场电影的放映区间,n = 0
则数据结束。
输出:对每组数据输出最多能看几部电影样例如下
输入:
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输出:
5

  分析

贪心解法:将所有电影按结束时间从小到大排序,第一步结束时间最早的那部电影。然
后,每步都选和上一部选中的电影不冲突且结束时间最早的电影。复杂度:O(nlogn)

  程序如下

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>struct Film{int starts;int ends;
}a[100];
// 使用快速排序需要一种比较方式
// void * 类型指针可以强制转化为任何类型指针
int cmp(const void * a,const void * b){struct Film * pa = (struct Film *) a;struct Film * pb = (struct Film *) b;if( pa -> ends >= pb -> ends) return 1;else return -1;
}
int main(){int n;int numFilm;int endTime;scanf("%d", & n);while(n != 0){int i = 0;for(i = 0; i < n; i++){scanf("%d%d", & a[i].starts, & a[i].ends);}// 使用快排,用 cmp 函数作为比较方式qsort(a, n, sizeof(a[0]), cmp);numFilm = 1;endTime = a[0].ends;for(i = 1; i < n; i++){if(a[i].starts >= endTime){numFilm ++;endTime = a[i].ends;}}printf("Out: %d\n", numFilm);}return 0;
}

  结果如下

  这个也比较好理解,通过对电影放映结束时间进行排序,这样的策略来实现贪心算法。

3. 分配畜栏

  有 n 头牛 (1 <= n <= 50, 000) 要挤奶。给定每头牛挤奶的时间区间 [A, B] (1 <= A <= B <= 1, 000, 000,A,B 为整数) 。
  牛需要待蓄栏同一时间只能容纳一头牛。问至少需要多少个蓄栏,才能完成全程挤奶工作,以及每头牛都放在哪个蓄栏里。
  去同一个蓄栏的两头牛,它们挤奶时间区间哪怕只在端点重合也是不可以的。
  分析

所有奶牛都必须挤奶。到了一个奶牛的挤奶开始时间,就必须为这个奶牛找畜栏。因此按照奶牛
的开始时间逐个处理它们,是必然的。S(x) 表示奶牛 x 的开始时间。E(x) 表示 x 的结束时间。对 E(x), x 可以是奶牛,也可
以是畜栏。畜栏的结束时间,就是正在其里面挤奶的奶牛的结束时间。同一个畜栏的结束时间是
不断在变的。1) 把所有奶牛按开始时间从小到大排序。
2) 为第一头奶牛分配一个畜栏。
3) 依次处理后面每头奶牛 i 。处理 i 时,考虑已分配畜栏中,结束时间最早的畜栏 x .若 E(x) < S(i) , 则不用分配新畜栏,i 可进入 x , 并修改 E(x) 为 E(i)若 E(x) >= S(i) , 则分配新畜栏 y ,记 E(y) = E(i)
直到所有奶牛结束需要用优先队列存放已经分配的畜栏,并使得结束时间最早的畜栏始终位于队列头部。复杂度 O(nlogn)

  程序如下,

#include <stdlib.h>
#include <algorithm>
#include <queue>
#include <vector>using namespace std;struct Cow{int startT, endT; // 挤奶区间, 开始时间和结束时间int No; // 编号// 定义结构体比较大小的方式bool operator < (const Cow & c) const {return startT < c.startT;}
} cows[50100];
int pos[50100]; // pos[i] 表示标号为 i 的奶牛去的畜栏编号// 畜栏结构体
struct Stall{int ends;   // 结束时间int No;     // 编号bool operator < (const Stall & s) const {return ends > s.ends;}Stall(int e, int n): ends(e), No(n){ }
};int main(){int n;scanf("%d", & n);for(int i = 0; i < n; i++){scanf("%d%d", & cows[i].startT, & cows[i].endT);cows[i].No = i;}sort(cows, cows + n);int total = 0;// 在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。priority_queue <Stall> pq;for(int i = 0; i < n; i++){// 队列代表的是畜栏的个数,FIFOif( pq.empty() ){total++;pq.push(Stall(cows[i].endT, total));pos[cows[i].No] = total; // 某个牛被安排在第 total 的畜栏}else{// 返回 结束时间 最大的畜栏Stall st = pq.top(); // 优先队列能够把最大的放在最上面if(st.ends < cows[i].startT){pq.pop();pos[cows[i].No] = st.No;pq.push(Stall(cows[i].endT, st.No));}else{ // st.ends > cows[i].startT// 开辟新的畜栏total++;pq.push(Stall(cows[i].endT, total));pos[cows[i].No] = total;}}}printf("Stall num: %d\n", total);for(int i = 0; i < n; i++){printf("Cow %d in %d stall.\n", i, pos[i]);}return 0;
}

运行结果如下,

程序分析,

1.
定义两个结构体:
struct Cow    定义了每头牛开始挤奶时间和结束时间,并对牛编号
struct Stall  定义了每个畜栏中编号为 i 的牛结束的时间,并且该牛占用到时间结束
并且两个结构体都对运算符 < 进行了重载,方便比较。2.
// 注意 Cow 对小于号的重载,是对结束时间进行比较
sort(cows, cows + n); // 使用排序的方法,对牛按照结束时间从小到大排序3.
// 注意 Stall 对小于号重载,是对结束时间进行比较,所以结束时间最短的在队头
// 注意,是对 < 重载, return ends > s.ends; 返回是大于号,结束时间最短的在队头
priority_queue <Stall> pq; // 使用优先队列,让结束时间最短的在队头。4.
// 返回 结束时间 最小的畜栏,Stall 对运算符进行了重载
Stall st = pq.top(); // 优先队列能够把优先级最大的放在最上面
// 如果最小的完成了,那新的牛进入队列
if(st.ends < cows[i].startT){pq.pop();// 返回已有畜栏的编号pos[cows[i].No] = st.No;pq.push(Stall(cows[i].endT, st.No));
}else{ // st.ends > cows[i].startT// 开辟新的畜栏total++;pq.push(Stall(cows[i].endT, total));pos[cows[i].No] = total;
}

  这个程序难点和要点是对优先队列的使用和运算符重载,使用的是贪心算法,这个题还是比较有意思的。

4. 安装雷达

  x 轴是海岸线,x 轴上方是海洋。海洋中有 n (1 <= n <= 1000) 个岛屿,可以看作点。
  给定每个岛屿的坐标 (x, y) , x, y 都是整数。
  当一个雷达 (可以看作点) 到岛屿的距离不超多 d (整数), 则认为该雷达覆盖了该岛屿。
  雷达只能放在 x 轴上。问至少需要多少个雷达才可以覆盖全部岛屿。
  如图,

  分析如下,

对于每个岛屿 P , 可以算出,覆盖它的雷达,必须位于 x 轴上的区间 [Ps, Pe].
如果有雷达位于某个 x 轴区间 [a, b], 称该雷达覆盖此区间。问题转换为,至少要在 x 轴
上放几个雷达 (点) , 才能覆盖全部区间 [P1s, P1e], [P2s, P2e] ... [Pns, Pne].重要结论:
如果可以找到一个雷达同时覆盖多个区间,那么把这多个区间按起点坐标从小到大排序,则最后
一个区间(起点靠右的) k 的起点,就能覆盖所有区间。
所以只用找区间的起点来放置雷达

  输出方式

输入:输入包含若干组数据,每组数据的第一行是两个整数 n 和 d (1 <= n <= 1000), 分
别表示小岛的数量和雷达的半径。接下来有 n 行, 每行的两个整数分别表示各个小岛的坐标。
输入以 0 0 结束。
输出:对于每组数据输出一行,为最少的雷达数,如果该组数据无解,则输出 -1 .输出样例
输入:
3   2
1   2
-3  1
2   1
1   2
0   2
0   0
输出:
case 1: 2
case 2: 1

  程序如下

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>using namespace std;#define MAXN 1010// 雷达在[Psi, Pei] 可以探测到改点
struct Node{double left, right;
};int T, n, d;
Node s[MAXN];// 比较左边节点,找到最后一个满足可以覆盖前面一组的点
bool operator < (const Node & a, const Node & b){return a.left < b.left;
}// 求解每个 case
int Solve(){int ans = 1; // 雷达个数double now;  // 标记能够覆盖的点// 按左端点排序sort(s, s + n);// 若当前线段与目前集合中的线段没有交集,则加入一个新雷达now = s[0].right;for(int i = 1; i < n; i++){if(s[i].left <= now) now = min(now, s[i].right);else{ans++;now = s[i].right;}}return ans;
}int main(){int x, y;bool flag;T = 0;while(true){T++;// 雷达总数和雷达半径scanf("%d%d", & n, & d);if(n == 0 && d == 0) break;// 读入flag = true;for(int i = 0; i < n; i++){// x, y 是坐标scanf("%d%d", & x, & y);if(y > d) flag = false;else{// 勾股定理计算该点雷达可以检测两端点s[i].left = x - sqrt(d * d - y * y);s[i].right = x + sqrt(d * d - y * y);}}// 若有小岛距离 x 轴大于 d , 则输出 -1if(flag) printf("Case %d: %d\n", T, Solve());else printf("Case %d: -1\n", T);}return 0;
}

结果如下

  这个程序比较好理解,主要是上面分析过程,关于数学逻辑和推导。感觉贪心算法主要是考虑数学逻辑和对问题分析能力。

三、总结

  现在程序设计与算法部分一,二,三全部刷完了。现在开始正式入门 C++ ,后面算法会继续刷着,我觉得精彩,难理解的会在博客分享。后面应该记录周期会长一些了。不过依旧会坚持更新,深入深入!

算法基础部分6-贪心算法相关推荐

  1. java贪心算法 区间调度_贪心算法-区间调度问题解之证明(示例代码)

    一.贪心算法 定义:一个算法是贪心算法,如果它是通过一些小的步骤来一个求解,并且在每一步根据局部情况选择一个决定,使得某些主要的指标得到优化. 二.区间调度问题 1. 问题:我们有一组需求{1,2,3 ...

  2. (十四)算法设计思想之“贪心算法”

    算法设计思想之"贪心算法" 贪心算法是什么 LeetCode:455.分饼干 LeetCode:122.买卖股票的最佳时机II 思考题 贪心算法是什么 贪心算法是算法设计中的一种方 ...

  3. 疯子的算法总结(四)贪心算法

    一.贪心算法 解决最优化问题的算法一般包含一系列的步骤,每一步都有若干的选择.对于很多最优化问题,只需要采用简单的贪心算法就可以解决,而不需要采用动态规划方法.贪心算法使所做的局部选择看起来都是当前最 ...

  4. _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II 、55.跳跃游戏、45.跳跃游戏II

    _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II .55.跳跃游戏.45.跳跃游戏II 题目列表 122.买卖股票的最佳时机II 55.跳跃游戏 45 ...

  5. 算法——人的天性贪心算法

    相应的练习代码:https://github.com/liuxuan320/Algorithm_Exercises 0. 写在前面 说起贪心算法,可能是我们最为熟悉的算法了.正如我标题所说的,贪心算法 ...

  6. 算法系列(二):贪心算法--Huffman编码

    算法系列(二):贪心算法--Huffman编码 一.分析 问题描述: 哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法.其压缩率通常在20%-90%之间.哈夫曼编码算法使用字符在文件中出现的频率 ...

  7. _32LeetCode代码随想录算法训练营第三十二天-贪心算法 | 738.单调递增的数字 、714.买卖股票的最佳时机含手续费、968.监控二叉树

    _32LeetCode代码随想录算法训练营第三十二天-贪心算法 | 738.单调递增的数字 .714.买卖股票的最佳时机含手续费.968.监控二叉树 题目列表 738.单调递增的数字 714.买卖股票 ...

  8. 算法基础-十大排序算法及其优化(文末有抽奖福利哦)

    算法基础-十大排序算法及其优化 算法基础-十大排序算法及其优化 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kw1LA5Q4-1607527572080)(/uplo ...

  9. 提高篇 第一部分 基础算法 第1章 贪心算法

    一本通提高篇在线提交地址 一本通提高篇在线提交地址_老象的专栏-CSDN博客 [ 贪心 进阶总结 ][ 来自一本通提高篇 ] [ 贪心 进阶总结 ][ 来自一本通提高篇 ]_violinwang-CS ...

  10. 【算法基础26】贪心下——哈夫曼树、排序不等式、绝对值不等式、推公式的思路与应用

    一.合并果子(哈夫曼树) 题目描述:给出n堆不同种类的果子,每堆果子的数量不同,每个果子的重量为1.每次只能合并相邻堆的果子,且花费的体力是果子的重量和.将所有果子合并成一堆,求最小的体力花费. 问题 ...

最新文章

  1. 机器视觉与深度神经网络—洗去浮华,一窥珠玑
  2. silverlight for olap version milestone 07 updated!
  3. .NET Conf 2019 大会上发布.NET Core 3.0
  4. Android之导入项目提示Android requires compiler compliance level 5.0 or 6.0. Found ‘1.8‘ instead解决办法
  5. P6240 好吃的题目(分治+背包)
  6. leetcode979. 在二叉树中分配硬币(dfs)
  7. [html] http中的301、302、307、308有什么区别?
  8. Mac安装Cobalt Strike 4 【亲测有用】
  9. JavaScript 类型判断的那些事
  10. Vs自带的freetextbox无法在远端使用
  11. matlab 深度学习环境配置
  12. 大数据集群安装02之Hadoop配置
  13. 管螺纹如何标注_【专业知识】一次搞全所有螺纹常识,很基础
  14. 实现mysql远程(通过IP地址访问)连接
  15. (诛仙剑C-SKY)1-初识
  16. 5款开源云计算平台推荐
  17. 模拟光源 html5,光照渲染——用canvas模拟光照效果
  18. 基于STM32智能小车蓝牙遥控实验(有代码含上位机)
  19. latch:cache buffers chains
  20. 机房常用动力环境设备远程集中监控及告警方案

热门文章

  1. 万能Prompt句式拆解,人人都是Prompt 工程师
  2. python信息标记与信息提取
  3. word2010首行缩进、设置页码为第几页共几页、图片完全不显示等问题
  4. 多肽定制:钙调蛋白激酶底物,KKALHRQETVDAL
  5. ajax js 轮询请求,ajax的轮询和长轮询
  6. 根据状态栏颜色亮度设置黑白字体(Android 6.0及以上版本)
  7. 计算机网络纳新水粉画,快乐水粉画记录-淮南查查网 - Powered by Discuz!
  8. mysql+主从同步端口_MySQL主从同步配置
  9. mysql 主从同步 速度_MySQL主从同步延迟原因及解决办法
  10. 简述顺序表、链表、栈和队列以及队列实现栈、栈实现队列