1. 算法思想
贪心算法是采用逐步构造最优解的问题解决方法,即在每个阶段都做出在当前状态下看上去最优的选择,并希望通过每次的贪心选择而使最终结果是问题的最优解。其中,做出贪婪选择的依据称为贪婪准则。贪心算法并不一定保证能得到最优解,但在很多情况下确实能达到预期的结果或者与预期的效果或者最优解很接近。
2. 应用
2.1 例1:跳跃游戏
https://leetcode-cn.com/problems/jump-game-ii/
思路分析:运用贪心的思想,每一步都要选择当前情况下最好的选择,什么是最好的选择?这时贪心算法的关键问题,回到题目本身,对于每一个位置,最好的选择是这一次跳跃的距离和下一次跳跃的距离之和最大。解决这个问题就解决了这道题的大部分,最后是边界条件。
代码如下:
class Solution {
public:
int jump(vector<int>& nums) {
int cnt=0,n=nums.size();
int i=0;
while(i<(n-1))
{
int j,maxjump=0,max=0;
for(j=1;j<=nums[i]&&(i+j)<(n-1);j++) //最好的选择是这一次跳跃的距离和下一次跳跃的距离之和最大
//找到下一次跳跃的位置
{
if((j+nums[i+j])>maxjump)
{
max=i+j;
maxjump=j+nums[i+j];
}
}
if((nums[i]+i)>=(n-1)) //最后一次如果能直接跳出去就不用了选择了
max=n-1;
cnt++;
i=max;
}
return cnt;
}
};
2.2 例2 买卖股票的最佳时机
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
思路分析:
- 分为两种情况:手中有股票、手中无股票
- 手中有股票:此时应该分析股票是涨还是跌,如果股票涨了,就应该继续持有,如果股票跌了,就应该在昨天将股票卖出
- 手中无股票:此时应该分析股票是涨还是跌,如果股票涨了,就应该在昨天买入,如果股票跌了,就什么都不做
代码实现:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int p=0,inp=-1,n=prices.size();
for(int i=0;i<n;i++){
if(inp!=-1){ //判断手中有无股票 若有 则inp不为-1
if(prices[i]<prices[i-1]){ //股票跌则昨天卖出
p+=prices[i-1]-prices[inp];
inp=-1;
}
}else{
if(i>0&&prices[i]>prices[i-1]){ //股票涨则昨天买入
inp=i-1;
}
if(i==0){ //第一天先买
inp=0;
}
}
}
if(inp!=-1&&prices[n-1]>prices[inp]){ //最后一天还持股
p+=prices[n-1]-prices[inp];
}
return p;
}
};
2.3 例3 拓扑排序
贪婪算法的思想在很多地方都有应用,例如拓扑排序、最小生成树的Prim算法和Kruskal算法、单源最短路径的Dijstra算法等等。
- 拓扑序:如果图中从V 到W有一条有向路径,则有一条有向路径,则V 一定排在W之前。满足此条件的顶点序列称为一个拓扑序。
- 获得一个拓扑序的过程就是拓扑排序。
- AOV如果有合理的拓扑序,则必定是有向无环图。
/* 邻接表存储 - 拓扑排序算法 */
bool TopSort( LGraph Graph, Vertex TopOrder[] )
{ /* 对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标 */
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
Queue Q = CreateQueue( Graph->Nv );
/* 初始化Indegree[] */
for (V=0; V<Graph->Nv; V++)
Indegree[V] = 0;
/* 遍历图,得到Indegree[] */
for (V=0; V<Graph->Nv; V++)
for (W=Graph->G[V].FirstEdge; W; W=W->Next)
Indegree[W->AdjV]++; /* 对有向边<V, W->AdjV>累计终点的入度 */
/* 将所有入度为0的顶点入列 */
for (V=0; V<Graph->Nv; V++)
if ( Indegree[V]==0 )
AddQ(Q, V);
/* 下面进入拓扑排序 */
cnt = 0;
while( !IsEmpty(Q) ){
V = DeleteQ(Q); /* 弹出一个入度为0的顶点 */
TopOrder[cnt++] = V; /* 将之存为结果序列的下一个元素 */
/* 对V的每个邻接点W->AdjV */
for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
if ( --Indegree[W->AdjV] == 0 )/* 若删除V使得W->AdjV入度为0 */
AddQ(Q, W->AdjV); /* 则该顶点入列 */
} /* while结束*/
if ( cnt != Graph->Nv )
return false; /* 说明图中有回路, 返回不成功标志 */
else
return true;
}
3. 总结
3.1 步骤
- 理清问题的关键所在
- 将全局的问题分解为局部的问题
- 取得局部问题的最优解
- 将所有局部问题的最优解合并为全局的解
3.2 想法
- 贪婪算法得到的解只是最优解的近似解
- 将大的问题分解为小问题是一种思维方式,需要熟悉
- 解决局部问题时不同的贪婪策略可能会得到不同的结果。