算法学习(14):贪心算法
贪心算法
什么是贪心算法
在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最后得到一个答案的算法,叫做贪心算法。
也就是说,不从整体最优上加以考虑,所作出的是在某种意义上的局部最优解
局部最优到整体最优
在写贪心算法的时候,往往考虑的是某种标准下的局部最优,按照这个标准最后在整体上获得一个答案,那么如何证明由局部最优的标准得到的答案是整体最优解。
- 数学推导(不推荐或不可取)。太过麻烦复杂,需要很强的数学基础,如果在笔试过程中是千万不可取的做法,时间会根本来不及
- 对数器。先实现一个由穷举方式得到一定正确答案的算法,然后再举出多种贪心策略并实现其算法,一 一通过对数器来验证这种策略是否正确。
用对数器验证的方法是相对而言更好的办法,但是它要求对穷举方式得到最正确的算法的熟练度要求较高,对实现对数器的熟练度要求较高,所以自己在下面要多练对数器写法,写堆,写排序,多练穷举模板多写总结,还要多练贪心题目积累感觉
贪心算法题目
题目1:会议安排
一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。
正确的贪心策略:谁最早结束先安排谁
C++代码实现:
class Program
{
public:
Program(int start, int end)
{
this->m_Start = start;
this->m_End = end;
}
public:
int m_Start;
int m_End;
};
class ProgramCompare
{
public:
bool operator()(Program p1, Program p2)
{
return p1.m_End < p2.m_End;
}
};
int bestArrange(vector<Program>& programs, int timePoint) //timePoint初始值代表会议室开门时间,假设所有会议预约的结束时间都不会超过关门时间
{
sort(programs.begin(), programs.end(), ProgramCompare());
int result = 0;
for (int i = 0; i < programs.size(); i++)
{
if (timePoint <= programs[i].m_Start)
{
result++;
timePoint = programs[i].m_End;
}
}
return result;
}
题目2:拼接字符串字典序最小
有N个字符串,把这N个字符串串起来,要求最终串成的字符串字典序最小
什么是字典序,假设只有字母,可以把这些字符串看成是26进制的数字,abc就小于acb,最后一位相当于十进制的个位,中间那一位相当于十进制的十位,最左边的那一位相当于十进制的百位,这样看起来就很清楚了。
假设有两个字符串分别叫A1和A2,什么样的排序策略会使得两者的结合字典序更小
- 错误的贪心策略:在排序时,字典序A1<=A2,就把A1排在前面
- 正确的贪心策略:在排序时,字典序(A1结合A2)<=(A2结合A1),就把A1排在前面
那么为什么下面这种排序策略是正确的,上面的是错误的,具体证明看https://www.bilibili.com/video/BV13g41157hK?p=10&vd_source=77d06bb648c4cce91c6939baa0595bcd P10 01:29:23
C++代码实现:
class StringCompare
{
public:
bool operator()(string s1, string s2)
{
return (s1 + s2) < (s2 + s1);
}
};
string lowestString(vector<string> &str)
{
if (str.size() == 0)
{
return "";
}
sort(str.begin(), str.end(), StringCompare());
string res = "";
for (int i = 0; i < str.size(); i++)
{
res += str[i];
}
return res;
}
题目3:金条(哈夫曼树)
一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。
几个人想分整块金条,怎么分最省铜板
例如给定一个数组{10,20,30},则有三个人分金条,数组中的数字则是每个人想要的长度,金条的总长度就是数组中数字的总和。
解题思路:其实就是哈夫曼树,先选两个最小的值,它俩的和为根节点,两个值就是孩子节点,再把根节点放回数组,继续选两个最小的重复前面的步骤
int lessMoney(vector<int> arr)
{
if (arr.size() == 0)
{
return 0;
}
priority_queue<int, vector<int>, greater<int>> priorityQueue; //申请一个小根堆
for (int i = 0; i < arr.size(); i++)
{
priorityQueue.push(arr[i]);
}
int result = 0;
while (priorityQueue.size() > 1)
{
//每次弹出两个数,并相加
int cur = 0;
cur += priorityQueue.top();
priorityQueue.pop();
cur += priorityQueue.top();
priorityQueue.pop();
result += cur;
priorityQueue.push(cur); //记录完本次循环花费的钱之后放回小根堆
}
return result;
}
题目4:做项目(按花费的小根堆,利润大根堆)
输入:
正数数组costs
正数数组profits
正数k
正数m
含义:
costs[i]表示i号项目的花费
profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
k表示你只能最多做k个项目
m表示你初始的资金
说明:
你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
输出:
你最后获得的最大钱数。
解题思路:贪心策略就是每次做资金足够的项目中利润最大的。准备一个小根堆和大根堆,先把所有项目入小根堆。根据现在的资金,把所有可以做的项目弹出进大根堆,做大根堆堆顶的项目并弹出,更新现在的资金。重复前面的步骤直到达到可做的项目个数最大值或者小根堆大根堆都空了。
class Project
{
public:
Project(int costs, int profits)
{
this->m_Costs = costs;
this->m_Profits = profits;
}
public:
int m_Profits;
int m_Costs;
};
class MinCostCompare
{
public:
bool operator()(Project p1, Project p2)
{
return p1.m_Costs < p2.m_Costs;
}
};
class MaxProfitCompare
{
public:
bool operator()(Project p1, Project p2)
{
return p1.m_Profits > p2.m_Profits;
}
};
int findMaxProfit(int k, int w, vector<int>costs, vector<int>profits)
{
priority_queue<Project, vector<Project>, MinCostCompare> minCostQ;
priority_queue<Project, vector<Project>, MaxProfitCompare> MaxProfitQ;
for (int i = 0; i < costs.size(); i++)
{
Project temp(costs[i], profits[i]);
minCostQ.push(temp);
}
for (int i = 0; i < k; i++)
{
while (!minCostQ.empty() && minCostQ.top().m_Costs < w)
{
MaxProfitQ.push(minCostQ.top());
minCostQ.pop();
}
if (MaxProfitQ.empty())
{
return w;
}
w += MaxProfitQ.top().m_Profits;
MaxProfitQ.pop();
}
return w;
}
浙公网安备 33010602011771号