算法基础09-贪心算法深度解析

1 贪心算法

1.1 基本概念

1、最自然智慧的算法

2、用一种局部最功利的标准,总是能做出在当前看来是最好的选择

3、难点在于证明局部最优解最功利的标准可以得到全局最优解

4、对于贪心算法的学习主要是以增加阅历和经验为主

1.2.1 贪心算法解释

正例:通过一个例子来解释,假设一个数组中N个正数,第一个挑选出来的数乘以1,第二个挑选出来的数乘以2,同理,第N次挑选出来的数乘以N,总的加起来是我们的分数。怎么挑选数字使我们达到最大分数?

数组按从小到大的顺序排序,我们按顺序依次挑选,最终结果就是最大的。本质思想是因子随着挑选次数的增加会增大,我们尽量让大数去结合大的因子。

贪心算法有时是无效的,后面会贪心算法无效的例子

1.2.2 贪心算法的证明问题

如何证明贪心算法的有效性?

一般来说,贪心算法不推荐证明,很多时候证明是非常复杂的。通过下面例子来说明贪心算法证明的复杂性,从头到尾讲一道利用贪心算法求解的题目。

例子:给定一个由字符串组成的数组strs,必须把所有的字符串拼接起来,返回所有可能的拼接结果中,字典序最小的结果。

字典序概念:直观理解,两个单词放到字典中,从头开始查找这个单词,哪个先被查找到,哪个字典序小。

字典序严格定义,我们把字符串当成k进制的数,a-z当成26进制的正数,字符长度一样,abk>abc,那么我们说abk的字典序更大。字符长度不一样ac和b,那么我们要把短的用0补齐,0小于a的accil,那么ac<b0,高位b>a即可比较出来大小。

Java中字符串的ComparTo方法,就是比较字典序。

本题思路1:按照单个元素字典序贪心,例如在[ac,bk,sc,ket]字符串数组中,我们拼接出来最终的字符串字典序最小,那么我们依次挑选字典序最小的进行拼接的贪心策略得到acbkketsc。

但是这样的贪心不一定是正确的,例如[ba,b]按照上述思路的贪心结果是bba,但是bab明显是最小的结果

本题思路2:两个元素x和y,x拼接y小于等于x拼接y,那么x放前,否则y放前面。例如x=b,y=ba。bba大于bab的字典与,那么ba放前面

证明:

我们把拼接当成k进制数的数学运算,把a-z的数当成26进制的数,'ks’拼接’ts’实质是ks * 26^2 + te。

目标先证明我们比较的传递性:证明a拼接b小于b拼接a,b拼接c小于等于c拼接b,推出a拼接c小于等于c拼接a。

a拼接b等于a乘以k的b长度次方 + b。我们把k的x长度次方这个操作当成m(x)函数。所以:

a * m(b) + b <= b * m(a) + a  

b * m(c) + c <= c * m(b) + b 

=> 

a * m(b) * c <= b * m(a) * c + ac - bc

b * m(c) * a + ca - ba <= c * m(b) * a 

=>

b * m(c) * a + ca - ba <= b * m(a) * c + ac - bc

=> 

m(c) * a + c <= m(a) * c + a

至此,我们证明出我们的排序具有传递性质。

根据我们排序策略得到的一组序列,证明我们任意交换两个字符的位置,都会得到更大的字典序。

例如按照思路二得到的amnb序列,我们交换a和b。我们先把a和m交换,由于按照思路二得到的序列,满足a.m <= m.a 那么所以manb > amnb,同理得到amnb < bmna。

再证明任意三个交换都会变为更大的字典序,那么最终数学归纳法,得到思路二的正确性

所以贪心算法的证明实质是比较复杂的,我们大可不必每次去证明贪心的正确性

package class09;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;

public class Code01_LowestLexicography {

        // 暴力法穷举,排列组合
	public static String lowestString1(String[] strs) {
		if (strs == null || strs.length == 0) {
			return "";
		}
		ArrayList<String> all = new ArrayList<>();
		HashSet<Integer> use = new HashSet<>();
		process(strs, use, "", all);
		String lowest = all.get(0);
		for (int i = 1; i < all.size(); i++) {
			if (all.get(i).compareTo(lowest) < 0) {
				lowest = all.get(i);
			}
		}
		return lowest;
	}

	// strs里放着所有的字符串
	// 已经使用过的字符串的下标,在use里登记了,不要再使用了
	// 之前使用过的字符串,拼接成了-> path
	// 用all收集所有可能的拼接结果
	public static void process(String[] strs, HashSet<Integer> use, String path, ArrayList<String> all) {
	        // 所有字符串都是用过了
		if (use.size() == strs.length) {
			all.add(path);
		} else {
			for (int i = 0; i < strs.length; i++) {
				if (!use.contains(i)) {
					use.add(i);
					process(strs, use, path 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值