对于贪心算法,进行几个简单问题的探讨和学习。
package com.xpn.question;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 1、删数问题(贪心)deleteK
* 2、活动安排问题(贪心GreedySelector)arrange
* 3、会场安排,arrangeMetting
* @author xpn
*
*/
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
//1 test
/*Integer[] A={1,7,8,5,4,3};
Object[] res=deleteK(A,4);
for (Object integer : res) {
System.out.print(integer);
}*/
//2 test
TimeNode[] times = new TimeNode[11];
times[0] = new TimeNode(1, 4);
times[1] = new TimeNode(3, 5);
times[2] = new TimeNode(0, 6);
times[3] = new TimeNode(5, 7);
times[4] = new TimeNode(3, 8);
times[5] = new TimeNode(5, 9);
times[6] = new TimeNode(6, 10);
times[7] = new TimeNode(8, 11);
times[8] = new TimeNode(8, 12);
times[9] = new TimeNode(2, 13);
times[10] = new TimeNode(12, 14);
arrange(times);
}
/*1、删树问题(贪心)deleteK
问题:给定n位整数q,去掉其中任意k<=n个数字后,剩下的数字按原次序排列组成一个新的正整数。
对于给定的n位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案。
比如,178543删除四个数字后,最小的新数是13。*/
//思路:贪心,每次删除第一个非递增的数字
private static Object[] deleteK(Integer[] A,int k){
if(A==null||A.length<=0||k<=0)
return null;
List<Integer> list=new ArrayList<Integer>();
Collections.addAll(list, A);
int n=A.length;
if(k<=n){
while(k>0){
int i=0;
for(;i<list.size()-1;i++){
if(list.get(i)>list.get(i+1))
break;
}
list.remove(i);
k--;
}
return list.toArray();
}
return null;
}
/*2、活动安排问题(贪心)
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi 。如果选择了活动i,则它在半开时间区间[si, fi]内占用资源。若区间[si, fi]与区间[sj, fj]不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。
由于输入的活动以其完成时间的非减序排列,所以算法greedySelector每次总是选择具有最早完成时间的相容活动加入集合A中。直观上,按这种方法选择相容活动为未安排活动留下尽可能多的时间。也就是说,该算法的贪心选择的意义是使剩余的可安排时间段极大化,以便安排尽可能多的相容活动。
此算法的效率极高。当输入的活动已按结束时间的非减序排列,算法只需O(n)的时间安排n个活动,使最多的活动能相容地使用公共资源。如果所给出的活动未按非减序排列,可以用O(nlogn)的时间重排。
*/
private static class TimeNode{
int s;//开始时间
int f;//结束时间
public TimeNode(int s, int f) {
super();
this.s = s;
this.f = f;
}
}
//测试A[]={1,2,3,4,。。。14}
//TimeNode[] times={}
private static void arrange(TimeNode[] times){
Arrays.sort(times, new Comparator<TimeNode>() {
@Override
public int compare(TimeNode o1, TimeNode o2) {
// TODO Auto-generated method stub
return o1.f-o2.f;//按照结束时间进行排序
}
});
int j=0;//保存上一个安排的活动
int i,n=times.length;
int counts=1;//保存安排的活动个数
System.out.println("安排活动"+j+":("+times[j].s+","+times[j].f+")");
for(i=1;i<n;i++){
if(times[i].s>times[j].f){
j=i;
counts++;
System.out.println("安排活动"+j+":("+times[j].s+","+times[j].f+")");
}
}
System.out.println(counts);
}
//3、会场安排,arrangeMetting
/*实际上,可以设计出一个更有效的算法。将n个活动1,2,....,n看做实直线上的n个半闭活动区间[s[i],f[i]],i=1~n。所讨论的问题实际上是求这n个半闭区间的最大重叠数。因为重叠的活动区间所相应的活动是互不相容的。若这n个活动区间的最大重叠数为m,则这m个重叠区间所对应的活动互不相容,因此至少要安排m个会场来容纳这m个活动。
为了有效地对这n个活动进行安排,首先将n个活动区间的2n个端点排序。然后,用扫描算法,从左到右扫描整个直线。在每个事件点处(即活动的开始时刻或结束时刻)作会场安排。当遇到一个开始时刻s[i],就将活动i安排在一个空闲的会场中。遇到一个结束时刻f[i],就将活动i占用的会场释放到空闲会场栈中,已备使用。
经过这样一遍扫描后,最多安排了m个会场(m是最大重叠区间数)。因此所作的会场安排是最优的。上述算法所需的计算时间主要用于对2n个区间端点的排序,这需要O(nlogn)计算时间。
*/
private static class MeetNode{
int flag;//标识初始时间(1)还是结束时间(0)
int data;
public MeetNode(int flag, int data) {
super();
this.flag = flag;
this.data = data;
}
}
private static int arrangeMetting(TimeNode[] times){
List<MeetNode> meets=new ArrayList<MeetNode>();
for (TimeNode time : times) {
meets.add(new MeetNode(1, time.s));
meets.add(new MeetNode(0, time.f));
}
Collections.sort(meets, new Comparator<MeetNode>() {
@Override
public int compare(MeetNode o1, MeetNode o2) {
// TODO Auto-generated method stub
return o1.data-o2.data;
}
});
int current=0;
int sum=0;//类似于左括号,右括号的问题,保存最大的重叠数目
for(int i=0;i<meets.size();i++){
if(meets.get(i).flag==1){
current++;
if(current>sum)
sum=current;
}else {
current--;
}
}
return sum;
}
}