【算法学习——贪心算法】(补充了大小根堆,sort()与bool cmp()配合的用法,getchar()函数的用法)

1.简介

贪心算法是计算机用来模拟一个"贪心“人负责***决策***的过程,这个人十分贪婪,每次决策都要选择最优的解
具体看OI链接:https://oi-wiki.org/basic/greedy/

2.贪心算法的适用范围

主要就是做选择,通过一些解法,去选择最优的方案

3.贪心算法的常见解法

①排序解法

  1. 思路:用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。
  2. 注意:此时排序的方法是要根据题目自己去定义的,常见的排序方法有以下几种
    • 降序(要根据题目具体分析按照哪个部分来升降序)
    • 升序(要根据题目具体分析按照哪个部分来升降序)
    • 相邻比较排序 即两两元素比较,如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的排序已经是最优解了

②后悔解法

  1. 思路:无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复。

4.贪心算法的常见题型

在提高组难度以下的题目中,最常见的贪心有两种。

①「我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)选择。」。
②「我们每次都取 XXX 中最大/小的东西,并更新 XXX。」(有时「XXX 中最大/小的东西」可以优化,比如用优先队列维护)

二者的区别在于一种是离线的,先处理后选择;一种是在线的,边处理边选择。

5.贪心算法与动态规划的区别

贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能

6.贪心算法的例题

①排序解法

例1:恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

a.解题思路

有些题的排序方法非常明显,但这道题不是。如果凭直觉而错误地以 或 为关键字排序,过样例之后提交就发现报 WA 了。一个常见办法就是尝试交换数组相邻的两个元素来 推导 出正确的排序方法。我们假设这题输入的两个数用一个结构体来保存

struct {
  int a, b;
} v[n];

在这里插入图片描述

struct uv {
  int a, b;
  bool operator<(const uv &x) const {
    return max(x.b, a * b) < max(b, x.a * x.b);
  }
};

b.注意事项

①利用元素交换排序法的时候,一定要先分别算出交换前和交换后的结果,然后对其进行筛选比较,判断交换前后哪个是最符合题意得那个解
②要学会刚刚那个解题思路里面图片max推导得过程,它是先求出交换前的解然后再求出交换后的解,当交换前的最大值小于交换后的最大值时,即为最优解
③Max函数分析时可以直接同乘分母将分式化整

c.完整代码

完整题解代码请看:
https://www.luogu.com.cn/blog/league/solution-p1080
注意,将数组结构体排完序后不一定要像这个题解的帮法那样,直接遍历整个结构体数组,求出每个大臣获得的金币数,然后比较就行了,题解太麻烦了

②后悔解法

例1:
在这里插入图片描述
输入:

3 
2 10 
1 5 
1 7 

输出:

17 

a.解题思路
这题是一道带反悔的贪心,第一次遇到这种思路的题赶紧发个题解纪念一下。 首先思路就是先按时间排序(是截止日期),然后如果一个工作有时间去做,就先做了它,然后把它的价值压入一个小根堆。当我们找到一个没法做却价值比当前堆顶高的工作时(注意此时没法做的只能是截至时间相同的那部分,因为我们已经按照截至时间从小到大排列了),我们就放弃那个最小的工作,用做它的时间去做这个价值更高的工作。

b.知识点补充:
①大小根堆知识点:
https://www.cnblogs.com/WindSun/p/11444446.html

②C++实现小根堆方法:
https://blog.csdn.net/fnzsjt/article/details/40118365

③sort()函数用法和与 bool cmp()结合可以自定义排序方式
https://blog.csdn.net/kuimzzs/article/details/81395347
注意!!!sort(a,b)中,第一个为需要排序的数组的起始位置,第二个参数为需要排序的数组的末端位置,注意,此时这个位置不是只下标,而是指个数!如下例子:

int a[]={45,12,34,77,90,11,2,4,5,55};
sort(a,a+10;//如果没有加号则默认从a数组的第一个位置开始,即从45开始,然后到数组的第10个元素结束,即55结束
sort(a+2,a+8);//从a数组的第2开始,即12开始,到a数组的第8个结束,即4结束

另外还要注意,return 的升降序的问题
如果是`

bool func(int i, int j)
{
    return i > j;//降序排列,如果是i<j则是升序排列
}

④getchar()函数的用法:
https://blog.csdn.net/Warddamn/article/details/109362613

⑤memset(a,‘0’,sizeof(a))函数
https://blog.csdn.net/zff13673839907/article/details/81209213

c.源码

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{
	int tim,mny;
}w[100001];//建立一个结构体数组,一个数组包含两个元素tim与mny,其中tim为截止时间,mny为价值
int n,i;
long long ans;//ans用来储存工作价值,longlong为超长整型变量
priority_queue<int,vector<int>,greater<int> > q;//建立一个小根堆
bool cmp(node a,node b){
	return a.tim<b.tim;
}//自定义的排序,将截止时间早的放在前面
int main(){
	scanf("%d",&n);
	for (i=1; i<=n; i++)
		scanf("%d%d",&w[i].tim,&w[i].mny);
	sort(w+1,w+n+1,cmp);//将结构体数组里面的数据按照截止时间早晚排序
	for (i=1; i<=n; i++){
		if (w[i].tim<=q.size()){//q.size就是当前堆内的元素个数
		//注意,由于我们前面已经将数据按照截止时间从早到晚排列了,所以如果会出现冲突的情况,那么一定是这两个数据截至时间相同,当有连续几个截止时间相同的数据时,会先将第一个数据存入到堆内和数组内,然后再去看第二个数据,此时第二个数据的截止时间一定是小于或等于堆内的个数的,因为只有截止时间不同的才可以存入堆内,假如有一组数据是 1 5 /2 7 / 2 9 / 3 0,那么在遍历到第三组数据 2 9时,堆里面已经存了两个数据了,所以tim<=2
			if (w[i].mny>q.top()){
				ans-=q.top();
				q.pop(); q.push(w[i].mny);
				ans+=w[i].mny;
			}
		}
		else{
			q.push(w[i].mny);//当截止时间不冲突时,加进来
			ans+=w[i].mny;//将价值加进来
		}
	}
	printf("%lld",ans);//输出最多价值的算法
	return 0;
}

例2:
“今年暑假不AC?”
“是的。”
“那你干什么呢?”
“看世界杯呀,笨蛋!”
“@#$%^&*%…”

确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视了。
作为球迷,一定想看尽量多的完整的比赛,当然,作为新时代的好青年,你一定还会看一些其它的节目,比如新闻联播(永远不要忘记关心国家大事)、非常6+7、超级女生,以及王小丫的《开心辞典》等等,假设你已经知道了所有你喜欢看的电视节目的转播时间表,你会合理安排吗?(目标是能看尽量多的完整节目)

输入:
输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Ti_s,Ti_e (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。

输出:
对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。

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

a.解题思路
经典的贪心算法,选择决策求最优解的问题,经典的排序解法,因此只要把排序方法搞定即可,此题排序有两种思路

  • 思路1:以节目的结束时间为基准进行升序排列(简单来说就是节目结束早的排在前面,节目结束晚的排在后面,因为如果想要看更多的节目,当然是希望当前的节目越早结束越好了。),然后将第一个节目结束的时间与后面的节目进行比较,找到后面节目开始的时间大于等于这个节目结束的时间,以此类推,每次都把count加1。这样只需要一次遍历就可以计算出最大值了。(注意:在开始的时候需要把count初始化为1,因为这是第一个节目已经计算在内)这种思路就属于贪心算法的排序解法

  • 思路2:按照节目时间长度进行排序,然后新建两个个结构体数组,一个是存原始数据a的,一个是存满足要求选好了的b,对a进行排列,节目时间短的在前面,然后遍历整个结构体数组,如果后面的节目开始时间比前面的节目结束时间晚或者后面节目结束时间比前面节目开始时间要早,那么就符合要求存入到B中,之后遇到的每一个节目都要和b数组进行比较,所以这个时间复杂度为两次循序O(n^2),比较不好,但是也是属于贪心算法

b.思路1源码

#include<stdio.h>
#include<algorithm>
using namespace std;
struct Node
{
    int st,en;
} Node[105];
bool cmp(struct Node a,struct Node b)
{
    return a.en<b.en;
}
/*
注意,这个自定义排序规则可以根据题目进行多样化设置的,比如还可以
bool cmp(struct Node a,struct Node b)
{
    return a.en<b.en,a.st<b.st;
}
还可以

bool cmp(node a, node b) //自定义排序规则
{
    if(a.st == b.st)
        return a.en < b.en;
    return a.st < b.st;

*/
int main()
{
    int n,ans;
    while(~scanf("%d",&n))
    {
        if(n==0)
            break;
        for(int i=0; i<n; i++)
            scanf("%d%d",&Node[i].st,&Node[i].en);
        sort(Node,Node+n,cmp);
        ans=1;//第一个区间一定要选
        int pos=Node[0].en;
        for(int i=1; i<n; i++)
        {
            if(Node[i].st>=pos)//当后面节目的开始时间晚于前面节目的结束时间时,符合要求,计数加一
            {
                ans++;
                pos=Node[i].en;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

例3:从贪心扩展01与dp
https://www.luogu.com.cn/problem/P4377

做完上述几道之后,要掌握结构体的方法和贪心算法了!也可以自己找一些贪心算法来做,巩固一下!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值