贪心算法讲解

    贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。最小生成子图里的普利姆算法迪杰斯特拉算法就是用的贪心算法的思想。

基本思路:

⒈ 建立数学模型来描述问题。

⒉ 把求解的问题分成若干个子问题。

⒊ 对每一子问题求解,得到子问题的局部最优解。

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

该算法存在问题:

 1. 不能保证求得的最后解是最佳的;

 2. 不能用来求最大或最小解问题;

 3. 只能求满足某些约束条件的可行解的范围。

贪心选择性质: 

    所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,换句话说,当考虑做何种选择的时候,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。这是贪心算法可行的第一个基本要素。贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

案例讲解

 一、活动安排问题

活动安排问题是可以用贪心算法有效求解的一个很好的例子。该问题要求高效地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。

设有n个活动的集合e={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si< fi。如果选择了活动i,则它在半开时间区间[si,fi]内占用资源。若区间[si,fi]与区间[sj,fj]不相交,则称活动i与活动j是相容的。

目的:活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。

预处理:各活动的起始时间和结束时间存储于数组se{中且按结束时间的非减序:.e1≤e2≤…≤en排列,如果结束时间一样则开始时间小的在前。如果所给出的活动未按此序排列,我们可以用o(nlogn)的时间将它重排。 

思想:按照结束时间排序然后尽可能的可以往后多安排几个活动。

public class Tx1
{
	public static void main(String[] args)
	{
		int[] start = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12};
		// 已按照end 排序好
		int[] end = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
		List<Integer> results = arrangeActivity(start, end);
		for (int i = 0; i < results.size(); i++)
		{
			Integer integer = results.get(i);
			System.out.println("开始时间:" + start[integer] + " 结束时间:" + end[integer]);
		}

	}

	public static List<Integer> arrangeActivity(int[] s, int[] e)
	{
		int total = s.length;
		int endFlag = e[0];
		ArrayList<Integer> results = new ArrayList<>();
		results.add(0);
		for (int i = 0; i < total; i++)
		{
			if (s[i] > endFlag)
			{
				results.add(i);
				endFlag = e[i];
			}
		}
		return results;
	}
}

二、找零钱问题

假如老板要找给我99分钱,他有上面的面值分别为25,10,5,1的硬币数,为了找给我最少的硬币数,那么他是不是该这样找呢,先看看该找多少个25分的,诶99/25=3,好像是3个,要是4个的话,我们还得再给老板一个1分的,我不干,那么老板只能给我3个25分的拉,由于还少给我24,所以还得给我2个10分的和4个1分。

public class Tx2
{
	public static void main(String[] args)
	{
		int[] m = {25,10,5,1};
		int target = 99;
		int[] result = giveMoney(m,target);
		for (int i = 0; i < result.length; i++)
		{
			System.out.println(result[i] + "枚" + m[i] + "面值");
		}
	}
	public static int[] giveMoney(int[] m,int target){
		int[] num = new int[m.length];
		for (int i = 0; i < m.length; i++)
		{
			num[i] = target /m[i];
			target = target%m[i];
		}
		return  num;
	}
}

三、背包问题

有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。 要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

物品ABCDEFG
重量35306050401025
价值 10403050354030

目标函数: ∑pi最大,约束条件是装入的物品总重量不超过背包容量,即∑wi<=M( M=150) 。

贪心算法是很常见的算法之一,这是由于它简单易行,构造贪心策略简单。但是,它需要证明后才能真正运用到题目的算法中。一般来说,贪心算法的证明围绕着整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。 

1、按照单位容量价格从高到低排序。

2、从排序号好的数组中拿物品。拿不到了则依次往后顺延,最终切割补充添满。

四、移动纸牌

有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.可以在任一堆上取若干张纸牌,然后移动。移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6 移动三次可以达到目的:

  1. 从③取4张牌放到④
  2. 从③区3张放到②
  3. 从②去1张放到①

我们用贪心算法,按照从左到右的顺序移动纸牌。如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种情况移动: 
1.若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆; 
2.若a[i]< v,则将v-a[i]张从第I+1堆移动到第I堆

为了设计的方便,我们把这两种情况统一看作是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v; a[I+1]=a[I+1]+a[i]-v. 
在从第I+1堆取出纸牌补充第I堆的过程中可能回出现第I+1堆的纸牌小于零的情况。 
如n=3,三堆指派数为1 2 27 ,这时v=10,为了使第一堆为10,要从第二堆移9张到第一堆,而第二堆只有2张可以移,这是不是意味着刚才使用贪心法是错误的呢? 
我们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张,第二堆剩下-7张,在从第三堆移动17张到第二堆,刚好三堆纸牌都是10,最后结果是对的,我们在移动过程中,只是改变了移动的顺序,而移动次数不便,因此此题使用贪心法可行的。

@Test
public void testMoveCard() {
    //总共4堆
    int heap = 4;
//        int[] cards = {9, 8, 17, 6};
    int[] cards = {10, 10, 20, 0};
    int count = moveCards(cards, heap);
    System.out.println("移动次数:" + count);
    for (int i = 0; i < cards.length; i++) {
        System.out.println("第" + (i + 1) + "堆牌数:" + cards[i]);
    }
}
 
/**
 * 均分纸牌
 * @param cards
 * @param heap
 * @return
 */
public int moveCards(int[] cards, int heap) {
    //总牌数
    int sum = 0;
    for (int i = 0; i < cards.length; i++) {
        sum += cards[i];
    }
    //每堆平均牌数
    int avg = sum / heap;
    //移动次数
    int count = 0;
    for (int i = 0; i < cards.length; i++) {
        if(cards[i] != avg) {
            int moveCards = cards[i] - avg;
            cards[i] -= moveCards;
            cards[i + 1] += moveCards;
            count++;
        }
    }
    return count;
}

五、最大整数

设有n个正整数,将它们连接成一排,组成一个最大的多位整数。 
例如:n=3时,3个整数13,312,343,连成的最大整数为343 312 13。 
又如:n=4时,4个整数7,13,4,246,连成的最大整数为7 4 246 13。 
输入:n 
输出:连成的多位数 


算法分析:此题很容易想到使用贪心法,在考试时有很多同学把整数按从大到小的顺序连接起来,测试题目的例子也都符合,但最后测试的结果却不全对。按这种标准,我们很容易找到反例:12,121应该组成12121而非12112,那么是不是相互包含的时候就从小到大呢?也不一定,如12,123就是12312而非12123,这种情况就有很多种了。是不是此题不能用贪心法呢? 
其实此题可以用贪心法来求解,只是刚才的标准不对,正确的标准是:先把整数转换成字符串,然后在比较a+b和b+a,如果a+b>=b+a,就把a排在b的前面,反之则把a排在b的后面。几乎就是冒泡排序的思想了

public class Tx3
{
	public static void main(String[] args)
	{
		int[] nums = {12, 123};
		System.out.println(maxNum(nums));
	}

	public static String maxNum(int[] nums)
	{
		String result = "";
		for (int i = 0; i < nums.length; i++)
		{
			for (int j = i + 1; j < nums.length; j++)
			{
				String num1 = nums[i] + "";
				String num2 = nums[j] + "";
				if ((num1 + num2).compareTo(num2 + num1) < 0)
				{
					int temp = nums[j];
					nums[j] = nums[i];
					nums[i] = temp;
				}
			}
		}
		for (int i = 0; i < nums.length; i++)
		{
			result += nums[i];
		}
		return result;
	}
}

 

SoWhat1412 CSDN认证博客专家 CSDN签约作者 后端coder
微信搜索【SoWhat1412】,第一时间阅读原创干货文章。人之患、在好为人师、不实知、谨慎言。点点滴滴、皆是学问、看到了、学到了、便是收获、便是进步。
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页