热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

贪心算法在经典问题中的多种应用实例分析

本文深入探讨了贪心算法在多个经典问题中的应用实例,通过具体案例分析,展示了该算法在解决优化问题时的有效性和灵活性。同时,文章还结合Go语言的特点,为Golang开发者提供了实用的编程技巧和经验分享。

什么是贪心算法

贪心算法是一种在解决问题的过程中追求局部最优的算法,对于一个有多种属性的事物来说,贪心算法会优先满足某种条件,追求局部最优的同时希望达到整体最优的效果。以背包问题为例,可以放在背包中的物体有它的重量和价值两种属性,背包的容量也是有限的,我们希望得到一种价值最大的物品摆放方式,如果我们倾向于重量贪心,那么在摆放物品的时候会优先放重量小的,但这和我们追求的价值最优没有关系,自然不能采用;如果倾向于价值贪心,而忽略了物品的重量,可能会导致摆放物品的数量不多,总价值很小;如果是以价值和重量的比值设计贪心算法求解,便可以实现最优的方案。下面我们举一些例子来说明在实际运用中如何实践贪心算法。

例题1:钱币找零问题

1、题目:指定币值和相应的数量,用最少的数量凑齐某金额。
2、思路:利用贪心算法,我们优先选择面值大的钱币,以此类推,直到凑齐总金额。
3、算法实现:

   /**
     * 贪心算法1:钱币找零问题
     */
	public void greedy1(){        
                       //面额
			int[] values = { 1, 2, 5, 10, 20, 50, 100 };
			//数量
			int[] counts = { 3, 3, 2, 1, 1, 3, 3 };
			//获取需要各种面值多少张
			int[] result = getNumber1(446, values, counts);             
			System.out.println("各币值的数量:"+Arrays.toString(result));
	}

	/**
	 * 贪心算法1:钱币找零问题
	 * @param sum
	 * @param values
	 * @param counts
	 * @return
	 */
	public int[] getNumber1(int sum , int[] values, int[] counts)
	{
		int[] result = new int[7];
		int add=0; //当前凑的金额
		for(int i=values.length-1;i>=0;i--)
		{
		    int num = (sum-add)/values[i];
		    if(num>counts[i])
		    {
		    	num=counts[i];
		    }
			add=add+num*values[i];
	        result[i]=num;
		}
		return result;		
	}

例题2:活动选择问题

1、题目: 有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi ,一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,ai和aj两个活动就可以被安排在这一天,该问题就是要安排这些活动使得尽量多的活动能不冲突的举行。例如下图所示的活动集合S,其中各项活动按照结束时间单调递增排序。
在这里插入图片描述
2、思路:使用贪心算法,目标是实现安排尽可能多的活动,那么我们优先找那些结束时间早的活动,为后面的活动留出更多时间,即以结束时间为贪心。
3、算法实现:
注:这里我们稍打乱了顺序,在代码中采用了插入排序的方法对数据简单整理,使得结束时间从小到大排列。

	/**
	 * 贪心算法2:活动选择问题
	 */
	public void greedy2(){		  
			   int [] st = {1,5,0,5,3,3,6,8,8,2,12};
			   int [] et = {4,9,6,7,8,5,10,12,11,13,14};			
			   int num = getNumber2(st,et);
			   System.out.println("活动数量:"+num);		
	}
	/**
	 * 贪心算法2:活动选择问题
	 * @param a
	 * @param b
	 * @return
	 */
	public int getNumber2(int[] a , int[] b)  //优先选择结束时间早的
	{
		int num=0;
		int tempa=0;
		int tempb=0;
		int endTime=0;
		int j=0;
		for(int i=1;i<b.length;i++)//如果顺序混乱,则调整为结束时间从小到大的顺序,直接插入排序
		{
			    tempb=b[i];
				tempa=a[i];
			for(j=i-1;j>=0&&tempb<b[j];j--)
			{								
					b[j+1]=b[j];
					a[j+1]=a[j];
					if(j==0)
					{
						j--;
						break;
					}
			}
			    b[j+1]=tempb;
				a[j+1]=tempa;				
		}
		System.out.println(Arrays.toString(a));
		System.out.println(Arrays.toString(b));		
		num++;
		endTime=b[0];
		for(int k=1;k<b.length;k++)
		{
			if(a[k]>endTime)
			{
				num++;
				endTime=b[k];				
			}
		}						
		return num;								
	   }	
     }

例题3:背包问题

1、题目:现有几种拥有一定重量和价值两个属性的物品,需要放到一个容量一定(能承受的重量一定)的包中,物品放入包中时,物品可以不完全放入包中,而放入一部分,求价值最大的方案。
2、思路: 背包问题一般不能使用贪心算法。 然而我们考虑这样一种背包问题:在选择物品i装入背包时,可以选择物品的一部分,而不一定要全部装入背包。这时便可以使用贪心算法求解了。 计算每种物品的单位重量价值作为贪心选择的依据指标,选择单位重量价值最高的物品,将尽可能多的该物品装入背包,依此策略一直地进行下去,直到背包装满为止。 在零一背包问题中贪心选择之所以不能得到最优解原因是贪心选择无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。 在程序中已经事先将单位重量价值按照从大到小的顺序排好。
3、算法实现:

	/**
	 * 贪心算法3:背包问题
	 */
	public void greedy3(){
			    float M=50;  
			    //背包所能容纳的重量     
			    float[] w={0,10,30,20,5};  
			    //每种物品的重量    
			    float[] v={0,200,400,450,20};    
			    //每种物品的价值   
				   
				float num = getNumber3(M,w,v);   
				 
				System.out.println("物品数量:"+num);
	}
	/**
	 * 贪心算法3:背包问题
	 * @return
	 */
	public float getNumber3(float M,float[] w ,float[] v)
	{
		float num=0;
		int i=0;
		float max=0;
		float weight=0;
		for(i=0;i<w.length;i++)
		{
			if(v[i]/w[i]>max)
			{
				max=v[i]/w[i];
				weight=w[i];
			}
		}
		num=M/weight;
		return num;	
	}

例题4:多机调度问题

1、题目:n个作业组成的作业集,可由m台相同机器加工处理。要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。
2、思路:作业不能拆分成更小的子作业;每个作业均可在任何一台机器上加工处理。 这个问题是NP完全问题,还没有有效的解法(求最优解),但是可以用贪心选择策略设计出较好的近似算法(求次优解)。当n<=m时,只要将作业时间区间分配给作业即可;当n>m时,首先将n个作业从大到小排序,然后依此顺序将作业分配给空闲的处理机。也就是说从剩下的作业中,选择需要处理时间最长的,把它分配给当前总累计需要工作时长最短的机器。这样一来,这个调度问题可以理解为一个分配问题,我们通过这种方案,使得几台机器获得接近的工作总时长,达到整体的最短的工作时长的效果。
3、算法实现:

	/**
	 * 贪心算法4:多机调度问题
	 */
	public void greedy4()
	{
		       int[] time= {9,7,8,4,2,1,3};
		       int number = 3;
		       
		       int Sumtime = getNumber4(time,number);
		
		       System.out.println("花费的最小总时间:"+Sumtime);		
	}
	 /**
	 * 贪心算法4:多机调度问题
	 * @param time
	 * @param number
	 * @return
	 */
	public int getNumber4(int[] time, int number)
	{
		int usedTime=0;  //最长时间为总时间
		int[] fin = new int[number]; //单机处理时间		
		for(int k=0;k<number;k++) //初始时间清零
		{
			fin[k]=0;
		}		
		if(number>time.length)
			return time[0];
		else 
		{			
			for( int i=0 ; i<time.length-1 ;i++)
			{  
				   for( int j=0;j<time.length-i-1;j++) //冒泡选出任务时间最大的
				   {
					   if(time[j]>time[j+1])
					   {
						   int temp = time[j+1];
						   time[j+1]=time[j];
						   time[j]=temp;
					   }
				   }
					   int min=0;; 
					   int value=100;
					   for(int k=0;k<fin.length;k++)  //选出当前累计工时最小的机子
					   {
						   if(fin[k]<value)
						   {
							   min=k;
							   value=fin[k];
						   }						  
					   }					   						
					   fin[min]+=time[time.length-1-i];							   
			} 
			   int min=0;; 
			   int value=100;
			   for(int k=0;k<fin.length;k++)  //选出当前累计工时最小的机子
			   {
				   if(fin[k]<value)
				   {
					   min=k;
					   value=fin[k];
				   }				  
			   }
			   fin[min]+=time[0];
			for( int n=0;n<fin.length;n++)
			{
				if(fin[n]>usedTime)
				{
					usedTime=fin[n];
				}
			}
			return usedTime;
		}		
	}

贪心算法5:小船过河问题

1、题目:N个人过河,船每次只能坐两个人,船载每个人过河的所需时间不同t[i],每次过河的时间为船上的人的较慢的那个,求最快的过河时间。(船划过去要有一个人划回来)
2、思路:本题的最优选择是先将所有人过河所需的时间按照升序排序。优先把速度慢的人带到对岸,返回由速度快的人来完成,节省时间,在剩余人数大于3时,有两种方式: 1.最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来,所需时间为:t[0]+2t[1]+t[n-1];2.最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来,所需时间为:2t[0]+t[n-2]+t[n-1]。最后还需处理一下人数小于等于3的边界问题。
3、算法实现:

	/**
	 * 贪心算法5:小船过河问题
	 */
	public void greedy5()
	{
		       int[] v = {1,3,4,8,4,3,9}; //按照不同的人的速度过河所需的时间
		       int timeSum=getNumber5(v);
		       System.out.println("过河总时间:"+timeSum);
	}
          /** 
	 * 贪心算法5:小船过河问题
	 */
	public int getNumber5(int[] v)
	{
		int time =0;;
		Arrays.sort(v);//降序排列
		int N = v.length; //N表示当前人数
		while(N>3)
		{
			if(2*v[0]+v[N-1]+v[N-2]>2*v[1]+v[0]+v[N-1])
				time+=2*v[1]+v[0]+v[N-1];
			else
				time+=2*v[0]+v[N-1]+v[N-2];
			N-=2;
		}
		else if(N==3) //处理边界
		{
			time+=v[2]+v[0]+v[1];
		}
		else if(N==2)
		{
			time+=v[1];
		}
		else if(N==1)
		{
			time+=v[0];
		}	
		return time;		
	}

总结

贪心算法追求局部最优,拿到问题之后先分析我们需要达到什么目标,是否适合采用贪心算法,并且使得什么最优以及实现的方法。


推荐阅读
  • c#学Java–Java基本语法1.类比JAVA .NETJVM CLRJDK  FCL2.java命名约定类名称应以大写字母开头,并成为容易理解的名词或组合。如 ... [详细]
  • 探索Golang中实现MD5加密的两种高效技术
    本文探讨了在Golang中实现MD5加密的两种高效方法。通过详细分析标准库 `crypto/md5` 的使用技巧和第三方库的性能优势,提供了丰富的代码示例和性能对比数据,帮助开发者选择最适合其应用场景的实现方式。此外,文章还讨论了MD5算法的安全性问题及其在现代应用中的局限性,为读者提供了全面的技术参考。 ... [详细]
  • 在处理Java程序时,中文乱码是一个常见的问题。本文将详细探讨导致中文乱码的原因,并分享有效的解决方案,帮助开发者在实际工作中避免这一问题。通过具体的代码示例和最佳实践,本文旨在提供全面的指导,确保中文字符在不同环境下的正确显示。 ... [详细]
  • 本文通过复旦大学自然语言处理课程中的一个具体案例,详细解析了中文词汇分割技术的实现方法。该案例利用Java编程语言,结合词典和算法模型,展示了如何高效地进行中文文本的词汇分割,为相关研究和应用提供了宝贵的参考。 ... [详细]
  • 多进程程序异常退出问题分析与解决 ... [详细]
  • Python初学者入门指南:从基础到实践的全面学习路径本文为Python初学者提供了一条从基础到实践的全面学习路径。特别介绍了Python字典(Dictionary)中的`items()`方法,该方法用于返回字典中所有键值对的视图对象,便于在循环和其他操作中使用。通过实例讲解,帮助读者更好地理解和应用这一重要功能。 ... [详细]
  • 探讨 javax.jms.JMSException 中 getLocalizedMessage 方法的应用与实例代码分析 ... [详细]
  • 利用注解在Spring框架中实现面向切面编程(AOP)
    本文探讨了如何在Spring框架中通过注解实现面向切面编程(AOP)。具体介绍了使用`@Retention(RetentionPolicy.RUNTIME)`和`@Target({ElementType.TYPE, ElementType.METHOD})`等注解来定义切面,以及如何配置Spring AOP以实现对业务逻辑的增强和解耦。通过实例代码,详细展示了注解驱动的AOP在实际项目中的应用,为开发者提供了实用的参考。 ... [详细]
  • OpenCV 2.4.9 源码解析:级联分类器的错误率与尺寸分析 ... [详细]
  • 本文探讨了Node.js Cluster模块在多核CPU环境下的应用及其性能测试。通过安装`async`包并利用Node.js自带的`http`和`cluster`模块,创建了一个名为`cluster.js`的文件,该文件根据系统CPU核心数动态生成多个工作进程,以实现负载均衡和提高应用性能。实验结果表明,使用Cluster模块能够显著提升高并发场景下的响应速度和处理能力。 ... [详细]
  • 基于灰度直方图的水果识别系统开发:MATLAB源代码及图形用户界面设计
    基于灰度直方图的水果识别系统开发:MATLAB源代码及图形用户界面设计 ... [详细]
  • 数据科学笔记26:深入解析随机森林分类算法及其在Python和R中的应用
    ### 摘要随机森林是一种在集成学习领域备受推崇的算法,被誉为“集成学习技术的典范”。该方法因其简洁性、易实现性和较低的计算成本而被广泛应用。本文将深入探讨随机森林的工作原理,特别是其在Python和R中的具体应用。随机森林通过结合多个决策树和Bagging技术,有效提高了模型的准确性和鲁棒性。我们将详细解析其核心机制,并通过实际案例展示如何在不同编程环境中高效实现这一强大的分类算法。 ... [详细]
  • 本文深入解析了Java编译过程,重点探讨了从源代码到字节码文件的转换机制。通过具体示例,如 `Hello.java` 的编译与反编译过程,详细介绍了 `javap -verbose` 命令的使用方法及其在字节码分析中的重要作用。此外,文章还讨论了字节码文件的结构特点及其在不同应用场景中的实际应用,为开发者提供了宝贵的参考。 ... [详细]
  • 在编写数据库应用程序时,常常需要用户自己在控制面板中配置ODBC数据源。然而对一般用户而言,配置ODBC数据源的工作是有一定困难的。因此, ... [详细]
  • 题目1:给定一个非空数组A,包含有N个整数,起始下标为0。数组包含有奇数个元素,其中除了唯一一个元素之外,其他 ... [详细]
author-avatar
你看看我的世界_420
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有