贪心算法基本介绍

应用场景

集合覆盖问题

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

广播台 覆盖地区
K1 “北京”,“上海”,“天津”
K2 “广州”,“北京”,“深圳”
K3 “成都”,“上海”,“杭州”
K4 “上海”,“天津”
K5 “杭州”,“大连”

基本介绍

  1. 贪心算法是指在对问题进行求解时,在每一部选择中都采取最好或最优的选择,从而希望能够导致结果是最好或者最优的算法。
  2. 贪心算法所得到的结果不一定是最优(有时候会是最优解)的结果,但都是相对近似(接近)最优解的结果。

贪心算法解决集合覆盖思路分析

思路分析

穷举法

如何找出覆盖所有地区的广播台的集合呢,使用穷举法实现,列出每个可能的广播台的集合,这被称为幂集。假设总共有n个广播台,则广播台的组合总共有2n2^n-1个,假设每秒可以计算10个子集,如图:

广播台数量n 子集总数2n2^n 需要的时间
5 32 3.2s
10 1024 102.4s
32 4294967296 13.6年
100 1.26*100^30 4*10^23年

随着广播台数量增加,使用穷举法实现,数值非常大,耗时也非常多,并不理想。

贪心算法

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

  1. 遍历所有的广播电台,找到一个覆盖了最多未覆盖地区的电台(此电台可能包含一些已覆盖地区,但没有关系)。
  2. 将这些电台加入到一个集合中(比如ArrayList),想办法把该电台覆盖的地区在下次比较时去掉。
  3. 重复步骤1,直到覆盖了全部地区。

思路图解

key每一次执行都从k1开始依次向下扫描;

maxKey待key扫描完毕,从上面开始往下扫描,选择一个最大覆盖值(如果上下相等,优先选择上面的)

image-20221128094648177

image-20221128094306871

贪心算法解决集合覆盖代码实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.algorithm.greedy;

import java.util.*;

/**
* @author Joker大雄
* @data 2022/11/28 - 9:48
**/
public class GreedyAlgorithm {
public static void main(String[] args) {
// 创建广播电台,放到map中
Map<String, HashSet> broadcasts = new HashMap<>();
// 将电台放入
HashSet<String> hashSet1 = new HashSet<>();
hashSet1.add("北京");
hashSet1.add("上海");
hashSet1.add("天津");
HashSet<String> hashSet2 = new HashSet<>();
hashSet2.add("广州");
hashSet2.add("北京");
hashSet2.add("深圳");
HashSet<String> hashSet3 = new HashSet<>();
hashSet3.add("成都");
hashSet3.add("上海");
hashSet3.add("杭州");
HashSet<String> hashSet4 = new HashSet<>();
hashSet4.add("上海");
hashSet4.add("天津");
HashSet<String> hashSet5 = new HashSet<>();
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);

// 创建allAreas,存放所有地区
HashSet<String> allAreas = new HashSet<>();
allAreas.add("北京");
allAreas.add("上海");
allAreas.add("天津");
allAreas.add("广州");
allAreas.add("深圳");
allAreas.add("成都");
allAreas.add("杭州");
allAreas.add("大连");
// 创建存放选择的电台集合的ArrayList
List<String> selects = new ArrayList<>();
// 定义临时集合,在遍历过程中存放电台覆盖地区和当前没有覆盖地区的交集
HashSet<String> tempSet = new HashSet<>();
// 记录最大的可再覆盖的size
HashSet<String> tempSet2 = new HashSet<>();

// 定义maxKey,保存在一次遍历过程中,能够覆盖最大未覆盖地区对应的电台的key
// 如果maxKey不为null,则会加入到selects
String maxKey = null;
while(allAreas.size()!=0){// 如果allAreas不为0,则表示还没有覆盖到所有的地区
maxKey = null; // 每次执行需要将maxKey置空
// 遍历 broadcasts,取出对应的key
for (String key : broadcasts.keySet()){
tempSet.clear(); // 每进行一次for循环,需要清空tempSet
// 当前这个key能够覆盖的地区
HashSet areas = broadcasts.get(key);
tempSet.addAll(areas); // 地区添加到临时集合
// 求出tempSet和allAreas集合的交集,并将交集赋给tempSet
tempSet.retainAll(allAreas);
// 如果当前这个集合包含的未覆盖地区的数量,比maxKey指向的集合未覆盖的地区还要多
// 就需要重置maxKey
if(tempSet.size() > 0 &&
(maxKey == null|| tempSet.size()>tempSet2.size())){
maxKey = key;
tempSet2 = tempSet; // 如果tempSet大于tempSet2,就把它赋给tempSet2
}
}
// maxKey != null,就将maxKey加入selects
if(maxKey != null){
selects.add(maxKey);
// 将maxKey指向的广播电台覆盖的地区从allAreas中去掉
allAreas.removeAll(broadcasts.get(maxKey));
}
}
System.out.println("所选择的结果为:"+selects);
}
}

运行结果

1
2
3
所选择的结果为:[k1, k2, k3, k5]

Process finished with exit code 0

贪心算法解决集合覆盖注意事项

  1. 贪心算法所得到的结果不一定是最优(有时候会是最优解)的结果,但都是相对近似(接近)最优解的结果。
  2. 例如上题通过算法选出的是k1,k2,k3,k5,符合覆盖了全部地区。
  3. 但是我们发现k2,k3,k4,k5也可以覆盖全部地区,假如k2的使用成本低于k1,那么上题的结果虽然满足条件,但并不是最优解。