三十六、贪心算法--集合覆盖问题

一、贪心算法介绍

1.贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解

2.贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择

二、算法思路

1.贪心算法一般按照如下步骤进行:

  1. 建立数学模型来描述问题

  2. 把求解的问题分成若干个子问题

  3. 对每个子问题求解,得到子问题的局部最优解

  4. 把子问题的解局部最优解合成原来解问题的一个解

结论:贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。贪心算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪心算法采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解。虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪心算法不要回溯。

三、算法特性

1.有一个以最优方式来解决的问题。为了构造问题的解决方案,有一个候选的对象的集合:比如不同面值的硬币

2.随着算法的进行,将积累起其他两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。

3.有一个函数来检查一个候选对象的集合是否提供了问题的解答,该函数不考虑此时的问题解决方法是否最优。

4.还有一个函数来检查是否一个候选对象的集合是可行的,即是否可能往集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。

5.选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。

6.最后,目标函数给出解的值。

四、贪心算法的最佳应用–集合覆盖

  1. 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有 的地区都可以接收到信号。

三十六、贪心算法--集合覆盖问题_第1张图片

  1. 如何找出覆盖所有地区的广播台的集合呢,使用穷举法实现,列出每个可能的广播台的集合,这被称为幂集。假 设总的有 n 个广播台,则广播台的组合总共有 2ⁿ -1 个,假设每秒可以计算 10 个子集, 如图:
    三十六、贪心算法--集合覆盖问题_第2张图片

综合考虑使用贪心算法,效率高

  1. 目前并没有算法可以快速计算得到准备的值, 使用贪心算法,则可以得到非常接近的解,并且效率高。选择 策略上,因为需要覆盖全部地区的最小集合:

  2. 遍历所有的广播电台, 找到一个覆盖了最多未覆盖的地区的电台(此电台可能包含一些已覆盖的地区,但没有关 系)

  3. 将这个电台加入到一个集合中(比如 ArrayList), 想办法把该电台覆盖的地区在下次比较时去掉。

  4. 重复第 1 步直到覆盖了全部的地区

三十六、贪心算法--集合覆盖问题_第3张图片

五、代码实现

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、制定贪心策略,以达到问题的最优解或较优解 。

要确定一个问题是否适合用贪心算法求解,必须证明每一步所作的贪心选择最终导致问题的整体最优解。证明的大致过程为:首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始,做了贪心选择后,原问题简化为规模更小的类似子问题。然后用数学归纳法证明通过每一步做贪心选择,最终可得到问题的整体最优解。

你可能感兴趣的:(数据结构与算法设计,贪心算法,集合覆盖,数据结构,算法)