一、贪心算法介绍
1.贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解
2.贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择
二、算法思路
1.贪心算法一般按照如下步骤进行:
-
建立数学模型来描述问题
-
把求解的问题分成若干个子问题
-
对每个子问题求解,得到子问题的局部最优解
-
把子问题的解局部最优解合成原来解问题的一个解
结论:贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。贪心算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解。虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪心算法不要回溯。
三、算法特性
1.有一个以最优方式来解决的问题。为了构造问题的解决方案,有一个候选的对象的集合:比如不同面值的硬币
2.随着算法的进行,将积累起其他两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。
3.有一个函数来检查一个候选对象的集合是否提供了问题的解答,该函数不考虑此时的问题解决方法是否最优。
4.还有一个函数来检查是否一个候选对象的集合是可行的,即是否可能往集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。
5.选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。
6.最后,目标函数给出解的值。
四、贪心算法的最佳应用–集合覆盖
- 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有 的地区都可以接收到信号。
- 如何找出覆盖所有地区的广播台的集合呢,使用穷举法实现,列出每个可能的广播台的集合,这被称为幂集。假 设总的有 n 个广播台,则广播台的组合总共有 2ⁿ -1 个,假设每秒可以计算 10 个子集, 如图:
综合考虑使用贪心算法,效率高
-
目前并没有算法可以快速计算得到准备的值, 使用贪心算法,则可以得到非常接近的解,并且效率高。选择 策略上,因为需要覆盖全部地区的最小集合:
-
遍历所有的广播电台, 找到一个覆盖了最多未覆盖的地区的电台(此电台可能包含一些已覆盖的地区,但没有关 系)
-
将这个电台加入到一个集合中(比如 ArrayList), 想办法把该电台覆盖的地区在下次比较时去掉。
-
重复第 1 步直到覆盖了全部的地区
五、代码实现
package cn.zzw.algorithm.greedy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class GreedyAlgorithm {
public static void main(String[] args) {
//创建一个HashMap集合,将广播电台以及所覆盖的地址放入到HashMap中
HashMap<String, HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
//将每一个电台覆盖的地区放入到broadcasts中
HashSet<String> hashSet1=new HashSet<String>();
hashSet1.add("北京");
hashSet1.add("上海");
hashSet1.add("天津");
HashSet<String> hashSet2=new HashSet<String>();
hashSet2.add("北京");
hashSet2.add("广州");
hashSet2.add("杭州");
HashSet<String> hashSet3=new HashSet<String>();
hashSet3.add("成都");
hashSet3.add("上海");
hashSet3.add("杭州");
HashSet<String> hashSet4=new HashSet<String>();
hashSet4.add("上海");
hashSet4.add("天津");
HashSet<String> hashSet5=new HashSet<String>();
hashSet5.add("杭州");
hashSet5.add("大连");
//将电台和电台所覆盖的地区全部放入到HashMap中
broadcasts.put("K1",hashSet1);
broadcasts.put("k2",hashSet2);
broadcasts.put("k3",hashSet3);
broadcasts.put("k4",hashSet4);
broadcasts.put("k5",hashSet5);
//存放所有的地区
HashSet<String> allAreas=new HashSet<String>();
allAreas.add("北京");
allAreas.add("上海");
allAreas.add("天津");
allAreas.add("广州");
allAreas.add("深圳");
allAreas.add("成都");
allAreas.add("杭州");
allAreas.add("大连");
//创建ArrayList集合,存放已经选择好的电台的集合
ArrayList<String> selects=new ArrayList<String>();
//定义一个临时的集合,存放遍历过程中电台覆盖的地区和当前还没有覆盖的地区的交集
HashSet<String> tempSet=new HashSet<String>();
//定义一个maxKey,保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
//如果maxKey不为null,则会加入到selects中
String maxKey=null;
//如果allAreas不为0,则表示还没有覆盖到所有地区
while (allAreas.size()!=0)
{
int count=0;
maxKey=null;
//遍历broadcasts,取出对应的key
for (String key:broadcasts.keySet())
{
//每进行一次for循环,需要对tempSet进行一次清空
tempSet.clear();
//获取当前key能够覆盖的地区
HashSet<String> areas=broadcasts.get(key);
tempSet.addAll(areas);
//求出tempSet集合与allAreas集合的交集,并把交集部分赋值给tempSet
tempSet.retainAll(allAreas);
//如果当前这个集合包含的未覆盖地区的数量,比maxKey指向的地区还多,就需要重置maxKey
//tempSet.size()>broadcasts.get(maxKey).size()体现出贪心算法的核心
if (tempSet.size()>0 && (maxKey==null || tempSet.size()>broadcasts.get(maxKey).size()))
{
maxKey=key;
}
}
if (maxKey!=null)
{
//将maxKey加入到selects中
selects.add(maxKey);
System.out.println("每次加入到集合中的电台是:"+selects);
//并且需要将maxKey所指向的集合所覆盖的地区从allAreas中去除
allAreas.removeAll(broadcasts.get(maxKey));
}
}
//!!!为什么这里输出不了最终的电台
System.out.println("最终的电台是:"+selects);
}
}
六、测试结果
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:D:\IntelliJ IDEA\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=52382:D:\IntelliJ IDEA\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Users\1\IdeaProjects\algorithm\out\production\algorithm" cn.zzw.algorithm.greedy.GreedyAlgorithm
每次加入到集合中的电台是:[K1]
每次加入到集合中的电台是:[K1, k2]
每次加入到集合中的电台是:[K1, k2, k3]
每次加入到集合中的电台是:[K1, k2, k3, k5]
七、贪心算法的使用条件
1.贪心选择性质
一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择,这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最终导致的整体最优解。
2.最优子结构性质
当一个问题的最优解包含其子问题的最优解时,此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心算法求解的关键所在。在实际应用中,至于什么问题具有什么样的贪心选择性质是不确定的,需要具体问题具体分析。
七、解题策略
贪心算法不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优选择。使用贪心策略要注意局部最优与全局最优的关系,选择当前的局部最优并不一定能推导出问题的全局最优。贪心策略解题需要解决以下两个问题:
1、该问题是否适合使用贪心策略求解,也就是该问题是否具有贪心选择性质 ;
2、制定贪心策略,以达到问题的最优解或较优解 。
要确定一个问题是否适合用贪心算法求解,必须证明每一步所作的贪心选择最终导致问题的整体最优解。证明的大致过程为:首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始,做了贪心选择后,原问题简化为规模更小的类似子问题。然后用数学归纳法证明通过每一步做贪心选择,最终可得到问题的整体最优解。