《算法图解》-8 贪婪算法

本文属于《算法图解》系列。

一 教室调度问题

   假设有如下课程表,你希望将尽可能多的课程安排在某间教室上。

  

   (1) 选出结束最早的课,它就是要在这间教室上的第一堂课。

  (2) 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要在这间教室上的第二堂课。

   

贪婪算法很简单:每步都采取最优的做法。在这个示例中,你每次都选择结束最早的 课。用专业术语说,就是你每步都选择局部最优解,最终得到的就是全局最优解。

二 背包问题

  背包问题:有一个背包,容量为35磅 , 现有如下物品

物品重量价格
吉他151500
音响303000
笔记本电脑202000

      要求达到的目标为装入的背包的总价值最大,并且重量不超出。 

   音响最贵,但背包没有空间装其他东西了。

 

如果选择装笔记本电脑和吉他,总价将为3500美元!

在这里,贪婪策略显然不能获得最优解,但非常接近。

   从这个示例你得到了如下启示:在有些情况下,完美是优秀的敌人。有时候,你只需找到一个能够大致解决问题的算法,此时贪婪算法正好可派上用场,因为它们实现起来很容易,得到的结果又与正确结果相当接近。

三 集合覆盖问题

  假设你办了个广播节目,要让全美50个州的听众都收听得到。为此,你需要决定在哪些广播台播出。在每个广播台播出都需要支付费用,因此你力图在尽可能少的广播台播出。现有广播台名单如下。

每个广播台都覆盖特定的区域,不同广播台的覆盖区域可能重叠。

如何找出覆盖全美50个州的最小广播台集合呢?听起来很容易,但其实非常难。具体方法如下。

(1) 列出每个可能的广播台集合,这被称为幂集(power set)。可能的子集有2ⁿ 个。

(2)在这些集合中,选出覆盖全美50个州的最小集合。

问题是计算每个可能的广播台子集需要很长时间。由于可能的集合有2ⁿ 个,因此运行时间为 O(2ⁿ )。如果广播台不多,只有510个,这是可行的。但如果广播台很多,结果将如何呢?随着广播台的增多,需要的时间将激增。假设你每秒可计算10个子集,所需的时间将如下。

广播台数量n子集总数2ⁿ需要的时间
5323.2秒
101024102.4秒
32429496729613.6年
1001.26*100³º4x10²³年

 目前并没有算法可以快速计算得到准备的值.
而使用贪婪算法,则可以得到非常接近的解,并且效率高:.

选择策略上,因为需要覆盖全部地区的最小集合:

(1) 选出一个广播台,即它覆盖了最多未覆盖的地区,即便包含一些已覆盖的地区也没关系.
(2) 重复第一步直到覆盖了全部的地区.

这是一种近似算法(approximation algorithm)。在获得精确解需要的时间太长时,可使用近似算法。判断近似算法优劣的标准如下:

这是一种近似算法(approximation algorithm,贪婪算法的一种)。在获取到精确的最优解需要的时间太长时,便可以使用近似算法,判断近似算法的优劣标准如下:

  • 速度有多快
  • 得到的近似解与最优解的接近程度

在本例中贪婪算法是个不错的选择,不仅运行速度快,本例运行时间O(n²),最坏的情况,假设n个广播台,每个广播台就覆盖1个地区,n个地区,总计需要查询n*n=O(n²),下面看下实现过程。

1 准备数据。

首先,创建一个列表,其中包含要覆盖的州。

最后,需要使用一个集合来存储最终选择的广播台。

final_stations = set() 

2 计算答案

  你需要遍历所有的广播台,从中选择覆盖了最多的未覆盖州的广播台。我将这个广播台存储在 best_station中。

states_covered是一个集合,包含该广播台覆盖的所有未覆盖的州。for循环迭代每个广 播台,并确定它是否是最佳的广播台。

这里用到了计算求集合的交集。以及判断你检查该广播台覆盖的州是否比 best_station多。

下面来比较一下贪婪算法和精确算法的运行时间。

看下Java版本的实现。

/**
 * 
 * @author bohu83
 *
 */
public class GreedyTest {
	// 广播站与地区对应关系
	static HashMap<String, HashSet<String>> broadcasts = new HashMap<String, HashSet<String>>();
	// 需要覆盖的全部地区
	static HashSet<String> statesNeed = 
new HashSet(Arrays.asList(new String[] { "ID", "NV", "UT", "WA", "MT", "OR", "CA", "AZ" }));
	// 所选的广播站
	static List<String> finalStaion = new ArrayList<String>();

	static {
		broadcasts.put("Kone", new HashSet(Arrays.asList(new String[] { "ID", "NV", "UT" })));
		broadcasts.put("Ktwo", new HashSet(Arrays.asList(new String[] { "WA", "ID", "MT" })));
		broadcasts.put("Kthree", new HashSet(Arrays.asList(new String[] { "OR", "NV", "CA" })));
		broadcasts.put("Kfour", new HashSet(Arrays.asList(new String[] { "NV", "UT" })));
		broadcasts.put("Kfive", new HashSet(Arrays.asList(new String[] { "CA", "AZ" })));
	}

	public static void Greedy() {

		while (statesNeed.size() > 0) {
			String bestStation = null;//将覆盖了最多的未覆盖州的广播台存储进去
			//一个集合,包含该广播台覆盖的所有未覆盖的州(有点拗口,可以结合下面的求交集来理解)
			HashSet<String> statesCovered = new HashSet<String>();
			
			//循环迭代每个广播台并确定它是否是最佳的广播台
			for (String key : broadcasts.keySet()) {
				HashSet<String> areas = broadcasts.get(key);
				// 求交集
				areas.retainAll(statesNeed);
				//检查该广播台的州是否比best_station多
				if (areas.size() > 0 && areas.size() > statesCovered.size()) {
					bestStation = key;
					statesCovered = areas;
				}

			}
			// 更新statesneeded
			statesNeed.removeAll(statesCovered);
			// 把best_station添加到最终的广播台列表中
			finalStaion.add(bestStation);
		}
		System.out.print("finalStaion:" + finalStaion);
	}

	public static void main(String[] args) {
		Greedy();

	}

}

输出结果:finalStaion:[Ktwo, Kthree, Kfour, Kfive]

四 NP完全问题

旅行商问题

作者推导一番,假设从两个城市开始,可能的路线有两条。 a->b     . b->a

3个城市,从每个城市出发时,都有两条不同的路线,因此总共有6条路线。

假设有4个城市,你选择一个出发城市后,还余下3个城市。而你知道,涉及3个城市时,可能的路线有6条。可能的出发城市有4个,从每个城市出发时都有6条可能的路线,因此可能的路线有4 × 6 = 24条。

这被称为阶乘函数(factorial function),涉及10个城市时,需要计算的可能路线超过300万条。正如你看到的,可能的路线数增加得非常快!因此,如果涉及的城市很多,根本就无法找出 旅行商问题的正确解。

旅行商问题和集合覆盖问题有一些共同之处:你需要计算所有的解,并从中选出最小/最短 的那个。这两个问题都属于NP完全问题。

而使用贪婪算法,随机选择从一个城市出发,比如A,每次选择从最近的还没去过的城市出发,则可以得到近似最优解。

第一次比较n-1个城市
第二次比较n-2个城市
...
第n-1次比较1个城市
第n次不存在需要比较的了个

0+1+2+3+..+(n-1) ≈ O(n²/2)

   模拟计算时间对比(每秒计算10次)

数量n总数n!穷举需要时间贪婪需要时间
5120120秒12.5秒
103242天50秒

作者还举例挑选橄榄球队员组成球队的问题。

如何判断是NP完全问题的:

1.元素较少时,一般运行速度很快,但随着元素数量增多,速度会变得非常慢
2.涉及到需要计算比较"所有的组合"情况的通常是NP完全问题
3.无法分割成小问题,必须考虑各种可能的情况。这可能是NP完全问题
4.如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题
5.如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题
6.如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题

小结:

贪婪算法寻找局部最优解,企图以这种方式获得全局最优解

对于NP完全问题,还没有找到快速解决方案。

面临NP完全问题时,最佳的做法是使用近似算法。

贪婪算法易于实现、运行速度快,是不错的近似算法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值