程序员都会的五大算法之三(贪心算法),恶补恶补恶补!!!

前言

点击查看算法介绍

五大算法

在这里插入图片描述

WX搜素"Java长征记"对这些算法也有详细介绍。

贪心算法

一、算法概述

   贪心算法也叫贪婪算法,是指在对问题求解时,总是做出在当前看来是最好的选
择。也就是说,不从整体最优解出发来考虑,它所做出的仅是在某种意义上的局部最
优解。(所谓贪心即只看眼前不管未来)

【注意:】

   贪心方法是一种分级处理方法,首先要根据题意,选取一种量度标准(贪心策
略),然后根据量度标准对成本进行排序,再根据题目的约束条件进行选择。对于
一个问题可能会有不同的量度标准,选择其中可以获得最优解的量度标准(贪心
策略)是贪心法设计的核心问题

二、贪心算法的基本思想

  • 建立数学模型来描述问题
  • 把求解的问题分成若干个子问题
  • 对每一子问题求解,得到子问题的局部最优解
  • 把子问题的解局部最优解合成原来解问题的一个解

【注意:贪心法要进行正确性证明,证明其不正确举反例即可】

三、贪心法的正确性证明

个人感觉应该是贪心法的难点之处

   贪心法正确性证明常用的方法有归纳法(对算法步数归纳、对问题归纳)和交换论
   证法(从最优解出发,不变坏地替换,得到贪心策略的解)。下面的例子介绍时
   会详细介绍
  • 活动选择问题——对算法步数归纳
  • 最优装载问题——对问题规模归纳
  • 最小延迟调度——交换论证

四、贪心算法的性质

  • 贪心选择可以依赖以往所做过的选择,但绝不依赖于将来所作的选择,也不依赖于子问题的解。(区别于动态规划)
  • 贪心算法通常以自顶向下的方式进行,不断迭代做出贪心选择,每做一次贪心选择就将问题简化为规模更小的问题。

【你会发现贪心法其实和动态规划有点相似之处,但也有很大区别】

五、贪心法的优势

算法简单,时间和空间复杂性低

话不多说直接上栗子,一目了然

六、经典例子

  • 活动选择问题
  • 最优装载问题
  • 最小延迟调度问题

一一活动选择问题

●问题介绍

S = {1, 2, … , n}为n 项活动的集合, si , fi 分别为活动 i 的开始和结束时间. 活动i与j相容即si ≥fj 或 sj ≥fi .求最大的相容活动集

●贪心策略

策略1:开始时间早的优先排序使 s1≤s2≤…≤sn,从前向后挑选

不正确(反例):

反例:S ={1,2,3 }s1=0, f1=20, s2=2, f2=5, s3=8, f3=15

解(活动1)

在这里插入图片描述

策略2:占用时间少的优先排序使得 f1-s1≤ f2-s2≤…≤fn-sn,从前向后挑选

不正确(反例)

反例:S = { 1, 2, 3 }s1=0, f1=8, s2=7, f2=9, s3=8, f3=15

解(活动2)

在这里插入图片描述

策略3:结束早的优先排序使 f1≤f2≤…≤ fn,从前向后挑选

解(活动1、3)

●伪码:
在这里插入图片描述

●正确性证明(对算法步数归纳)

命题:算法Select执行到第k步,选择k项活动i1=1,i2,…,ik,那么存在最优解 A包含活动 i1=1, i2 ,…, ik .

只要此定理成立,算法至多到第n步得到最优解

归纳证明

设S={1,2,….n}为活动集且f1≤…≤ fn

归纳基础: k=1, 证明存在最优解包含活动 1 ,k=1, 证明存在最优解包含活动 1

证:任取最优解A, A中活动按截止时间递增排列. 如果A的第一个活动为 j,j ≠1, 用1替换A的活动 j 得到解 A’,即A’ = (A−{ j }) ∪{1}, 由于 f1 ≤ fj , A’ 也是最优解,且含有1.

归纳步骤:假设命题对k为真, 证明对k+1也为真.

证:算法执行到第 k 步, 选择了活动i1=1,i2, …, ik , 根据归纳假设存在最优解 A包 含i1=1,i2,…,ik , A中剩下活动选自集合S’
S’ = { i | i∈S, si ≥ fk }
A = { i1, i2, … , ik } ∪ B

在这里插入图片描述

证:B是 S’的最优解.(若不然, S’ 的最优解为B*, B的活动比 B多,那么B∪{1, i2, … , ik } 是 S 的最优解,且比 A的活动多,与 A 的最优性矛盾.)

在这里插入图片描述

证:将S’ 看成子问题,根据归纳基础,存在 S’ 的最优解B’ 有S’ 中的第一个活动 ik+1, 且 |B’ | = |B|, 于是
{ i1, i2, … , ik } ∪ B’= { i1, i2, … , ik , ik+1 } ∪ ( B’−{ ik+1})也是原问题的最优解

在这里插入图片描述

●程序:

//活动选择
/*非递归,一般实现*/
static List<Integer> acivitySelector(int[] s, int[] f) {
		// 假设活动的结束时间已经从小到大排好
		list.add(1);//根据贪心算法的贪心选择性质,肯定会选择第一个活动
		int j=0;//记录被上一次选择的活动
		for(int i=1;i<f.length;i++)
			if(s[i]>=f[j])
			{
				list.add(i+1);
				j=i;//更新
				}
		return list;
	}

【也可以用递归实现,WX搜索"Java长征记"回复算法代码即可获得相关代码】

●时间复杂度

O(n) = O(n)(输入时已排好序)

一一最优装载问题

●问题介绍

n 个集装箱1, 2, … , n 要装上轮船,集装箱 i 的重量为 wi , 轮船装载重量限制为C, 无体积限制.
问如何装使得上船的集装箱最多?假设每个箱子的重量 wi≤C.

【类似于0-1背包问题】

●建模

设 <x1, x2, … , xn> 表示解向量,xi = 0,1(0代表不装,1代表装)xi = 1表示当且仅当第 i 个集装箱被装上船

在这里插入图片描述

●算法策略

将集装箱按照重量递增顺序进行排列,轻者优先

●正确性证明(对问题规模归纳)

  设集装箱从轻到重记为1, 2, … , n.

命题:对装载问题任何规模为 n 的输入实例,算法得到最优解.

归纳基础:

证:对任何只含 1个箱子的输入实例,贪心法得到最优解. 显然正确.

归纳步骤:

假设对于任何n个箱子的输入实例贪心法都能得到最优解,那么对于任何n+1个箱子的输入实例贪心法也得到最优解.

归纳步骤证明思路
在这里插入图片描述

假设对于 n 个集装箱的输入,贪心法都可以得到最优解,考虑 输入N = { 1, 2, … , n+1 } 其中 w1 ≤ w2 ≤ … ≤
wn+1.

证:由归纳假设,对于N’ = {2, 3, …, n+1},C’ = C− w1,贪心法得到最优解 I’.

令I = I '∪{1}

证:I (算法解)是关于 N 的最优解. 若不然,存在包含 1 的关于 N 的最优解 I*(如果 I* 中没有1,用 1 替换 I* 中的第一个元素得到的解也是最优解), 且 |I*|>| I |;那么I−{1}是 N’ 和C’ 的解且| I− {1}| > | I −{1} | = | I’ | 与 I’是关于N’ 和C’ 的最优解矛盾.

在这里插入图片描述

●程序:
贪心法的程序设计都比较简单,这个留给大家思考,【实在没有思路,WX搜索"Java长征记",回复算法代码可获得相关代码】

一一最小延迟调度问题

●问题介绍

客户集合A,∀i∈A,ti 为服务时间,di 为要求完成时间,ti, di为正整数. 一个调度f : A→N,f(i)为客户 i的开始时间. 求最大延迟达到最小的调度,即求 f 使得

在这里插入图片描述

●贪心策略

按照 di 从小到大安排,即按完成时间从早到晚安排任务,没有空闲.

●伪码
在这里插入图片描述

●正确性证明(交换论证)

没有空闲时间, 没有逆序. 逆序 ( i, j ): f (i) < f (j) 且 di > dj (逆序即i比j结束时间晚却先执行了)

命题:所有没有逆序、没有空闲时间的调度具有相同的最大延迟

证:设 f 没有逆序,在 f 中具有相同完成时间 d 的客户i1, i2, … , ik连续安排, 其开始时刻为 t0,完成这些任务的时刻是 t,最大延迟为最后任务延迟t−d, 与 i1, i2, … , ik的排列次序无关.t = t0+ (ti1+ ti2+ …+ tik)

在这里插入图片描述

证明思想:从一个没有空闲时间的最优解出发,在不改变最优性的条件下,转变为没有逆序的解。

  1. 如果一个最优调度存在逆序,那么存在i < n使得(i,i+1构成一个逆序)
  2. 存在逆序(i,j),j = i + 1,那么交换i和j得到的解的逆序数减一,后面证明这个新的调度仍然最优
  3. 至多经过n(n-1)/2次交换得到一个没有逆序的最优调度

证:交换相邻逆序仍旧最优 设 f1是一个任意最优解,存在相邻逆序( i, j ) . 交换 i 和 j 的顺序, 得到解 f2.那么f2的最大延迟不超过 f1的最大延迟.

  • 交换 i, j 与其他客户延迟时间无关
  • 交换后不增加 j 的延迟,但可能增加 i的延迟
  • i 在 f2的延迟小于 j 在 f1 的延迟 ,因此小于f 1的最大延迟 r
    在这里插入图片描述

●程序:

//最小延迟调度问题
import java.util.Arrays;

class Task implements Comparable<Task>{
	String number;//任务编号
	int servicetime;//服务时间
	int requiredtime;//客户要求完成时间
	public Task(String number,int servicetime,int requiredtime) {
		this.number=number;
		this.servicetime=servicetime;
		this.requiredtime=requiredtime;
	}
	public int compareTo(Task obj) {
		return this.requiredtime-obj.requiredtime;
	}
}
public class Schedule {
	public static void main(String []args) {
		int delaytime[]=new int[5];
		Task task[]=new Task[5]; 
		task[0]=new Task("1",5,10);
		task[1]=new Task("2",8,12);
		task[2]=new Task("3",4,15);
		task[3]=new Task("4",10,11);
		task[4]=new Task("5",3,20);
		Arrays.sort(task);//按照客户要求完成时间从早到晚排序
		int finishtime[]=new int[5];
		finishtime[0]=task[0].servicetime;
		for(int i=1;i<task.length;i++) {
			finishtime[i]=finishtime[i-1]+task[i].servicetime;
			delaytime[i]=finishtime[i]-task[i].requiredtime;
		}
		for(int i=0;i<task.length;i++) {
			System.out.println("第"+(i+1)+"个任务为"+task[i].number+"号任务");
			System.out.println("该任务的完成时间为"+finishtime[i]+",延迟时间为:"+delaytime[i]);
		}
	}
}

●时间复杂度为

O(nlogn) + O(n) = O(nlogn)

【可以看出贪心法的程序设计和思路其实很简单,个人感觉难点就在于证明贪心策略的正确性,如果一个问题可以用贪心法解决的话,首选贪心法】

干货分享

想学习更多的关于Java基础、算法、数据结构等编程知识的WX搜索"Java长征记"。上面的这些在里面也有详细介绍。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

碎涛

感谢您的支持,我会再接再厉

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值