目录
引言
贪心算法和穷举法是两种常用的算法策略,它们在解决实际问题时各有优劣。本文将通过具体的案例来探讨这两种算法的应用,并提供Java实现的代码示例。我们将使用Java 1.8版本,并使用IntelliJ IDEA 2024.1.4作为开发工具。
项目概述
本项目通过Java语言实现了贪心算法和穷举法的具体应用案例:广播覆盖问题和钱币找零问题。通过这些案例,读者可以更好地理解这两种算法的工作原理及其适用场景。
技术栈
- Java 1.8:用于编写程序逻辑。
- JUnit 5:用于编写单元测试。
- HashMap 和 HashSet:用于存储数据结构。
- 递归:用于穷举法中的深度优先搜索。
- 控制流语句:如
for
循环、while
循环等,用以控制程序执行流程。
贪心算法详解
贪心算法是一种在每一步选择中都采取当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法策略。贪心算法特别适用于那些具有最优子结构性质的问题,即局部最优解能决定全局最优解的问题。
特点
- 贪心选择性质:问题的整体最优解可以通过一系列局部的最优解达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。
- 最优子结构性质:问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
局限性
贪心算法并不保证得到全局最优解,有时产生的全局解可能不是最优的。因此,在使用贪心算法时需要评估其适用性和局限性。
穷举法详解
穷举法是一种解决问题的方法,通过遍历所有可能的情况,找到满足条件的结果。虽然穷举法能够保证找到最优解,但其效率往往较低,特别是在问题规模较大时。
特点
- 全面性:能够找到所有可能的解。
- 低效性:当问题规模较大时,计算量呈指数级增长。
广播覆盖问题
问题描述
已知有5个广播电台,每个电台都有自己的覆盖地区。需要选择最少的广播台,让所有的地区都可以接收到信号。
贪心算法解决方案
通过选择每次覆盖最多未覆盖地区的电台,逐步减少未覆盖的地区,直到所有地区都被覆盖。
穷举法解决方案
通过遍历所有可能的电台组合,找到能够覆盖所有地区的最小电台集合。
钱币找零问题
问题描述
给定不同面额的钱币,如何找零使得使用的钱币数量最少。
贪心算法解决方案
从大到小依次选择硬币,直到凑够所需金额或无法继续找零。
穷举法解决方案
通过递归尝试所有可能的找零组合,找到最少的硬币数。
代码示例
以下是完整的Java代码和完整注释,包含了上述提到的所有算法的实现:
package com.chenze.design.practice.greedyandexhaustive;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 贪心算法和穷举法理解和案例
*/
public class GreedyAndExhaustiveAlgorithm {
/**
* 1.贪心算法:
* 贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法策略。
* 贪心算法特别适用于那些具有最优子结构性质的问题,即局部最优解能决定全局最优解的问题。
* 贪心算法的核心思想是在每一步选择中都做出在当前看来是最好的选择,而不考虑各种可能的整体情况。
* 这种方法省去了为找最优解要穷尽所有可能而必须耗费的大量时间。
* 贪心算法采用自顶向下的方法,通过迭代的方式逐步简化问题规模,每一步的选择都基于当前的状态和优化测度,最终得到一个局部最优解。
* 然而,贪心算法并不保证得到全局最优解,有时产生的全局解可能不是最优的。
* 贪心算法的适用场景通常具有以下特征:
* 贪心选择性质:问题的整体最优解可以通过一系列局部的最优解达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。
* 最优子结构性质:问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。这是贪心算法求解的关键所在。
* 贪心算法的经典例子包括:
* 活动选择问题:在多个活动中选择一些活动参加,每个活动有一个结束时间,选择结束时间最早的活动可以使得选择的活动的数量最多。
* 钱币找零问题:给定不同面额的钱币,如何找零使得使用的钱币数量最少。
* 背包问题:在限定重量的背包中装入物品,使得物品的总价值最大。
* 小船过河问题:小船过河需要支付费用,如何安排过河顺序使得总费用最小。
* 区间覆盖问题:给定一系列区间,选择最少的区间覆盖所有的点。
* 贪心算法的局限在于它不保证得到全局最优解,有时产生的解可能不是最优的。因此,在使用贪心算法时需要评估其适用性和局限性。
* 总结:
* 1、每一次都选择当前最优解
* 2、《向前看,别回头》
* 2.穷举法:穷举法是一种解决问题的方法,通过遍历所有可能的情况,找到满足条件的结果。
* 以下我就运用贪心算法和穷举思想解决:广播覆盖问题和钱币找零问题
*/
/**
* 广播覆盖问题:
* 已知有5个广播电台,每个电台都有自己的覆盖地区,其中分别是
* 第一个广播电台为"K1",覆盖地区为"北京", "上海", "天津"
* 第二个广播电台为"K2",覆盖地区为"广州", "北京", "深圳"
* 第三个广播电台为"K3",覆盖地区为"成都", "上海", "杭州"
* 第四个广播电台为"K4",覆盖地区为"上海", "天津"
* 第五个广播电台为"K5",覆盖地区为"杭州", "大连"
* 需要覆盖地区为:北京,上海,天津,广州,深圳,成都,杭州,大连
* 如何选择最少的广播台,让所有的地区都可以接收到信号?
* 1、运用穷举法,就需要进行遍历所有的可能性,但是,若广播的数量和覆盖的地区需要很多时,那么效率就会非常的差,但是穷举法能解决问题。
* 2、运用贪心算法,可以高效的解决问题,但是不一定是最优解,有些时候不能解决所有的问题。
* 下面的代码,实现了使用贪心算法和穷举法解决广播电台覆盖问题,
*/
/**
* 贪心算法解决广播电台覆盖问题
*/
@Test
public void broadcastSelection() {
// 创建广播电台, 放入到Map
HashMap<String, HashSet<String>> broadcasts = createBroadcasts();
// allAreas 存放所有的地区
HashSet<String> allAreas = createAllAreas();
// 创建ArrayList, 存放选择的电台集合
ArrayList<String> selects = new ArrayList<>();
// 定义一个临时的集合, 在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
HashSet<String> tempSet = new HashSet<>();
String maxKey = null;
// 当还有未覆盖的地区时,继续选择电台
while (!allAreas.isEmpty()) {
maxKey = null;
int maxSize = 0;
// 遍历所有电台,寻找能覆盖最多未覆盖地区的电台
for (String key : broadcasts.keySet()) {
tempSet.clear();
HashSet<String> strings = broadcasts.get(key);
tempSet.addAll(strings);
tempSet.retainAll(allAreas);
// 如果当前电台覆盖的未覆盖地区数量大于当前最大值,则更新最大值和对应的电台
if (!tempSet.isEmpty() && tempSet.size() > maxSize) {
maxKey = key;
maxSize = tempSet.size();
}
}
// 如果找到了能覆盖未覆盖地区的电台,则将其添加到选择集合中,并从未覆盖地区中移除已覆盖的地区
if (maxKey != null