贪心算法之区间覆盖问题

问题描述:用i表示x轴上坐标为[i-1,i]的区间(区间长度为1),并给出M个不同的整数来表示M个这样的区间。现在要求画出几条线段覆盖住所有的区间,条件是:每条线段可任意长,但要求所画线段长度之和最小,并且线段的数目不超过N

举例:给出M=61,2,4,5,7,11,分别表示6个长度为1的区间,要求用不超过N=3条线段将其覆盖。

 

线段区间的覆盖问题

上图给出了一种可行的覆盖方案,用三段长线段覆盖住6个长度为1的小线段,使得3短线段长度之和最小


例如:M=5,整数1、3、4、8、11表示区间。
    要求:所用线段不超过 N=3条。

bb



分析
    1) 整型数组p[M]表示所有从0开始的区间长度,假设p[M]已经
按从小到大的顺序排好


    2) 如果N>=M,
那么用 M条长度为 1 的线段可以覆盖住所有的区间,所求的线段总长为 M;


3)如果N<M,可以按照如下的贪心准则解决:


① 如果N=1,即要用一条线段覆盖住所有区间。


      线段总长:p[M-1]-p[0]+1。


bb


其中:p[0]=1  ,  p[1]=3  ,   p[2]=4 , ……,p[M-1]=M--区间总长度


② 如果N=2,即要用 2 条线段覆盖住所有区间,相当于把N=1中的线段分为两部分,各覆盖左、右区间。


如果线段在M个区间中不相邻的区间之间断开(如在p[0]与p[1]之间),这样总长度小于断开之前。【不相邻的很多怎么办?】


线段总长度减少: p[1]-p[0]-1


cc


回顾我们的题目要求:求最小线段总长。



找到间隔最大的两个相邻区间,将之断开。


   这一过程相当于找:d[i]=p[i]-p[i-1]-1(1<i<M)的最大值



如果N=3,相当于在N=2的方案下,将某条线段断成两截,并作可能的端点调整。


为了得到当前情况下最小的总长度,同样应该在间隔最大的两个相邻区间之间断开


    如果原来的方案是N=2时总长最小的方案,这一操作是N=3时总长最小的方案。



当N=k(k>1)时依此类推,只需在N=k-1时最小总长的覆盖方案下,找到被同一条线段覆盖的间隔最大的两个相邻区间


------“贪心”地从间隔处断开并调整两边线段的端点,就可以得到总长最小的方案



贪心策略就是每次从找到当前间隔最大的相邻区间


下面给出一个最简单的Java实现对于优化是简单的,利用排序


import java.util.Scanner;



public class Qujianfugai {
	
	
	public static int searchmax(int []d)
	{
		int max=0;
		for(int i=0;i<d.length;i++)
		{
			if(d[i]>d[max])
			{
				max=i;
			}
		}
		return max;
	}
	
	
	
	public static void search(int []p,int n)
	{
		int length=p.length;
		int sum=p[length-1]-p[0]+1;
		int []d=new int[length-1];
		for(int i=0;i<d.length;i++)
		{
			d[i]=p[i+1]-p[i]-1;
		}
		for(int i=n;i>1;i--)
		{
			int max=searchmax(d);
			int maxvalue=d[max];
			sum=sum-maxvalue;
			if(maxvalue!=0)
			{
				d[max]=0;
			}
			else
			{
				break;
			}
		}
		System.out.println(sum);
	}
	
	
	public static void main(String args[])
	{
		Scanner input=new Scanner(System.in);
		int m=input.nextInt();
		int n=input.nextInt();
		int []p=new int[m];
		for(int i=0;i<p.length;i++)
		{
			p[i]=input.nextInt();
		}
		search(p,n);
		
		
		
	}

}


  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
区间覆盖问题是指在一条数轴上,给定若干个区间,选取尽可能少的区间,使得这些区间能够覆盖整个数轴。贪心算法是一种自下而上的贪心策略,每次选择能够覆盖尽可能多未被覆盖区域的区间加入解集合中。 具体算法步骤如下: 1. 将所有区间按照右端点从小到大排序。 2. 初始化当前右端点为负无穷。 3. 遍历所有区间,对于每个区间: a. 如果当前右端点小于等于该区间的左端点,将该区间加入解集合中,更新当前右端点为该区间的右端点。 4. 返回解集合。 算法时间复杂度为O(nlogn),其中n为区间个数。以下是Python实现: ```python def interval_cover(intervals): intervals.sort(key=lambda x: x[1]) # 按右端点排序 right = float('-inf') # 初始化当前右端点 chosen = [] # 解集合 for interval in intervals: if right <= interval[0]: # 如果当前右端点小于等于该区间的左端点 chosen.append(interval) # 将该区间加入解集合中 right = interval[1] # 更新当前右端点为该区间的右端点 return chosen ``` 其中,intervals是一个包含区间左右端点的列表,如[(1, 3), (2, 4), (3, 5), (4, 6)]。函数返回一个解集合,如[(1, 3), (3, 5), (4, 6)],它们能够覆盖整个数轴。 该算法的正确性可以通过反证法证明。假设存在一个更优的解集合S',但该解集合与算法得到的解集合S不同,也就是说它们至少有一个区间不同。我们可以假设在S中覆盖最左边的未被覆盖区域时选择了一个区间I1,而在S'中覆盖同一区域时选择了另一个区间I2。由于I2的右端点在I1的右端点右侧,所以I2能够覆盖的未被覆盖区域一定比I1小,因此在S中选择I1比选择I2更优。这与假设矛盾,因此S即为最优解。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值