贪心算法
一、前言
-
贪心算法(greedy algorithm),又称贪婪算法,是一种在每一步选择中都采取 在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。比如在旅行推销员问题中,如果旅行员每次都选择最近的城市,那这就是一种贪心算法。
-
贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。
一、基本思路
1. 思想
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度(贪心策略),每一步都要确保能获得 局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
2. 过程
- 建立数学模型来描述问题;
- 把求解的问题分成若干个子问题;
- 对每一子问题求解,得到子问题的局部最优解;
- 把子问题的解局部最优解合成原来解问题的一个解。
(1) 算法实现过程
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do,求出可行解的一个解元素;
最后,由所有解元素组合成问题的一个可行解。
二、算法对比
贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
-
贪心法可以解决一些最优化问题,如:求图中的最小生成树、求哈夫曼编码……对于其他问题,贪心法一般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。
-
对于大部分的问题,贪心法通常都不能找出最佳解(不过也有例外),因为他们一般没有测试所有可能的解。贪心法容易过早做决定,因而没法达到最佳解。例如,所有对图着色问题。
贪心法在系统故障诊断策略生成乃至高校的排课系统中都可使用。
三、经典问题
1. 股票收益最大问题
来自LeetCode:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
贪心策略:
- 单独交易日:
后一天与前一天的收益:P2 - P1,可能为正,为负,为零。 - 连续上涨交易日:
设开始涨买入价格为 P1,结束涨卖出价格为 Pn,(P1 < P2 < … < Pn),则收益为 Pn - P1,也可表示为( (P2 - P1) + ( P3 - P2 ) + … + ( Pn - Pn-1 ) )
选择所有收益为正的。
package indi.pentiumcm.leetcode;
/**
* @projName: algorithm
* @packgeName: indi.pentiumcm.leetcode
* @className: Q17
* @author: pentiumCM
* @email: 842679178@qq.com
* @date: 2020/4/2 15:38
* @describe: LeetCode-122. 买卖股票的最佳时机 II
*/
public class Q17 {
/**
* 贪心策略来实现
*
* @param prices
* @return
*/
public int maxProfit(int[] prices) {
int maxIncome = 0;
for (int i = 0; i < prices.length - 1; i++) {
int income = prices[i + 1] - prices[i] > 0 ? prices[i + 1] - prices[i] : 0;
maxIncome += income;
}
return maxIncome;
}
public static void main(String[] args) {
int[] arr = {7, 1, 5, 3, 6, 4, 5};
int income = new Q17().maxProfit(arr);
}
}
补充:
对于可以使用贪心算法解决的问题,一般都可以使用动态规划来解决,因为两种都是基于最优子结构性质,但是如果贪心算法可以解决的问题,使用动态规划,就有点杀鸡用牛刀了。
这道题目动态规划的解法:
/**
* 动态规划来实现
*
* @param prices
* @return
*/
public int maxProfitV2(int[] prices) {
// 剪枝处理
if (prices.length <= 1) {
return 0;
}
if (prices.length == 2) {
return prices[1] - prices[0] > 0 ? prices[1] - prices[0] : 0;
}
// 1. 原问题划分为子问题
// 原问题:n天中股票最大收益
// 子问题:n-1中股票最大收益
// 子问题:n-2天中股票最大收益
// 2. 定义状态:dp[i] - 第 i 天的股票最大收益
int[] dp = new int[prices.length + 1];
// 3. 定义初始状态
dp[0] = 0;
dp[1] = 0;
// 4. 状态转移方程:dp[i] = dp[i-1] + 第i天当天的收益
for (int i = 2; i <= prices.length; i++) {
int income = prices[i - 1] - prices[i - 2] > 0 ? prices[i - 1] - prices[i - 2] : 0;
dp[i] = dp[i - 1] + income;
}
return dp[prices.length];
}
四、相关题目
- leetcode: