贪心算法详解:原理、应用场景及代码实现
创作时间:
作者:
@小白创作中心
贪心算法详解:原理、应用场景及代码实现
引用
CSDN
1.
https://blog.csdn.net/2301_79602614/article/details/138244968
贪心算法是一种在每一步选择中都采取当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。这种算法通常用于求解优化问题,如最小生成树、背包问题等。
贪心算法的基本概念
贪心算法的核心思想可以概括为以下三个步骤:
- 将寻找最优解的问题分为若干个步骤
- 每一步骤都采用贪心原则,选取当前最优解
- 因为没有考虑所有可能,局部最优的堆叠不一定让最终解最优
贪心算法的应用场景包括:
- 背包问题:给定一组物品和一个背包,每个物品有一定的重量和价值,要求在不超过背包容量的情况下,尽可能多地装入物品。
- 活动选择问题:在一个活动集合中,每次只能参加一个活动,问如何安排时间以最大化所有活动的收益。
- 编辑距离问题:给定两个字符串,求它们之间的最小编辑距离(即将一个字符串转换为另一个字符串所需的最少操作次数)。
- 网络流问题:给定一张有向图和一些起点和终点,求最大流量。
- 找零问题:给定一定数量的硬币和需要找零的金额,求使用最少的硬币数。
贪心算法的常见问题及解答
- 贪心算法一定会找到最优解吗?
- 答:不一定。贪心算法只保证在每一步选择中都是最优的,但并不能保证整个问题的最优解。例如,背包问题中的贪心算法可能会导致最后一个物品没有被装入背包。
- 如何判断一个问题是否适合用贪心算法解决?
- 答:一个问题如果可以用递归的方式分解成若干个子问题,且每个子问题都有明确的最优解(即局部最优),那么这个问题就可以用贪心算法解决。
- 贪心算法的时间复杂度是多少?
- 答:贪心算法的时间复杂度取决于问题的规模和具体实现。一般来说,对于规模较小的问题,贪心算法的时间复杂度可以达到O(nlogn)或O(n^2);对于规模较大的问题,可能需要O(n^3)或更高。
贪心算法的具体实现
Dijkstra算法
while (!list.isEmpty()) {
// 选取当前【距离最小】的顶点
Vertex curr = chooseMinDistVertex(list);
// 更新当前顶点邻居距离
updateNeighboursDist(curr);
// 移除当前顶点
list.remove(curr);
// 标记当前顶点已经处理过
curr.visited = true;
}
需要注意的是,当图中存在负边时,Dijkstra算法可能无法得到正确的最短路径解。这是因为贪心原则会认为本次已经找到了该顶点的最短路径,下次不会再处理它(curr.visited = true)。相比之下,Bellman-Ford算法不会出错,因为它每次都处理所有边,但效率不如Dijkstra算法。
Prim算法
while (!list.isEmpty()) {
// 选取当前【距离最小】的顶点
Vertex curr = chooseMinDistVertex(list);
// 更新当前顶点邻居距离
updateNeighboursDist(curr);
// 移除当前顶点
list.remove(curr);
// 标记当前顶点已经处理过
curr.visited = true;
}
Kruskal算法
while (list.size() < size - 1) {
// 选取当前【距离最短】的边
Edge poll = queue.poll();
// 判断两个集合是否相交
int i = set.find(poll.start);
int j = set.find(poll.end);
if (i != j) { // 未相交
list.add(poll);
set.union(i, j); // 相交
}
}
其他贪心算法的例子
- 选择排序、堆排序
- 拓扑排序
- 并查集合中的 union by size 和 union by height
- 哈夫曼编码
- 钱币找零(change-making problem)
- 任务编排
- 求复杂问题的近似解
零钱兑换问题
零钱兑换II(LeetCode 518)
这是一个典型的动态规划问题,但也可以用贪心算法来尝试解决。下面是一个暴力递归的实现:
public int rec(int index, int[] coins, int remainder) {
// 1.情况1:剩余金额 < 0 - 无解
// 2.情况2:剩余金额 > 0 - 继续递归
// 3.情况3:剩余金额 = 0 - 有解
if (remainder < 0) {
return 0;
} else if (remainder == 0) {
return 1;
} else {
int count = 0;
for (int i = index; i < coins.length; i++) {
count += rec(i, coins, remainder - coins[i]);
}
return count;
}
}
但是这个代码在LeetCode上运行会超时,因为重复处理了很多次相同的操作。我们可以考虑用记忆化搜索或动态规划来优化。
零钱兑换(LeetCode 322)
这是一个求最小硬币数的问题,可以用动态规划来解决。下面是一个暴力递归的实现:
static int min = -1; // 需要的最少硬币数 2 3
public int coinChange(int[] coins, int amount) {
rec(0, coins, amount, new AtomicInteger(-1), new LinkedList<>(), true);
return min;
}
// count 代表某一组合 钱币的总数 可变的整数对象
public void rec(int index, int[] coins, int remainder, AtomicInteger count, LinkedList<Integer> stack, boolean first) {
if (!first) {
stack.push(coins[index]);
}
count.incrementAndGet(); // count++
if (remainder == 0) {
System.out.println(stack);
if (min == -1) {
min = count.get();
} else {
min = Integer.min(min, count.get());
}
} else if (remainder > 0) {
for (int i = index; i < coins.length; i++) {
rec(i, coins, remainder - coins[i], count, stack, false);
}
}
count.decrementAndGet(); // count--
if (!stack.isEmpty()) {
stack.pop();
}
}
贪心法求解零钱兑换(LeetCode 322)
贪心法在某些情况下可以快速得到解,但并不保证总是能得到最优解。下面是一个贪心算法的实现:
public int coinChange(int[] coins, int amount) {
int remainder = amount;
int count = 0;
for (int coin : coins) {
while (remainder - coin > 0) {
remainder -= coin;
count++;
}
if (remainder - coin == 0) {
remainder = 0;
count++;
break;
}
}
if (remainder > 0) {
return -1;
} else {
return count;
}
}
需要注意的是,贪心算法在某些情况下可能会得到错误的解,因为它没有考虑所有可能的组合。例如,对于输入[15, 10, 1]
和金额21,贪心算法会得到错误的解,因为它没有“回头”机制来检查更优的组合。
热门推荐
智能道闸、停车场道闸解决方案、自动收费道闸系统
买卖合同逾期付款可以要求支付违约金吗
什么是数据库的索引?如何利用索引提高查询性能?
签了协议书当事人死了有效吗
如何训练狗狗定点大小便?(培养狗狗良好的卫生习惯,让你的家更整洁)
定点定量投喂,寻找领养家庭,这样救助流浪猫邻里都满意
十二星座恋爱观大揭秘:你的星座如何影响你的爱情观?
新加坡薪酬行业竞争及其实证分析
js怎么在图片上放汉字
全球数据分析师薪酬地图:哪些国家和地区最“壕”?
脚踝酸痛怎么办?7种科学缓解方法
鱼缸水质检测有哪些项目?
电视剧大宋提刑官的结局深度解析
炎炎夏日,正确冰敷原来还有这么多讲究
《经济学方法论》课件
弗洛伊德|生本能和死本能。
弗洛伊德的生本能与死本能理论
如何了解城市的房产市场?这类市场的需求特点是什么?
你的左右手为何对称?埃及“狮身人面像”给了答案……
微信小程序调用复制API报错解决方案
AMD CPU型号命名规则
白月光的出处
车辆动力学模型推导【看了也不一定会】
朊病毒传染途径及预防措施
急性蜂窝织炎治疗用抗生素吗
下腹猛的刺痛的原因是什么
电脑自动关机命令:让您的电脑定时关机不再烦恼
入木三分原本用来形容什么
科举制度:各民族交往交流交融历史的典型例证
海星的养殖方法和注意事项