算法思想:贪心算法

实际问题:活动安排问题

编写语言:Java


问题描述

  设有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 相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。

  将活动按照结束时间进行从小到大排序。然后用 i 代表第 i 个活动,s[i] 代表第 i 个活动开始时间,f[i] 代表第 i 个活动的结束时间。挑选出结束时间尽量早的活动(活动结束时间最早的活动),并且满足后一个活动的起始时间晚于前一个活动的结束时间,全部找出这些活动就是最大的相容活动子集合。即有活动 i,j 为 i 的下一个活动。f[i]最小,s[j] >= f[i]。

想法证明

  上述思路的第一步是在最大相容活动子集合中加入最早结束的活动(以下称第一个活动)。实际上,总存在一个最优安排,其包含第一个活动。

  证明如下:

  设 E ={0,1,2,…,n-1}为所给的活动集合。由于 E 中活动安排安结束时间的非减序排列,所以活动 1 具有最早完成时间。首先证明活动安排问题有一个最优解以贪心选择开始(选择了活动 1)。设 A 是所给的活动安排问题的一个最优解,且 B 中活动也按结束时间非减序排列,A 中的第一个活动是活动 k。若 k = 1,则 A 就是一个以贪心选择开始的最优解。若 k > 1,则我们设 B = A -{k}∪{1}。由于 end[1] ≤ end[k](非减序排列),且 A 中活动是互为相容的,故 B 中的活动也是互为相容的。又由于 B 中的活动个数与 A 中活动个数相同,且 A 是最优的,故 B 也是最优的。也就是说 B 是一个以贪心选择活动 1 开始的最优活动安排。因此,证明了总存在一个以贪心选择开始的最优活动安排方案,也就是算法具有贪心选择性质。

Java代码

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class ActivityArrangement {
    public static void main(String[] args) {
        ArrayList<Time> list = new ArrayList<>();
        list.add(new Time(3, 5));
        list.add(new Time(1, 4));
        list.add(new Time(5, 7));
        list.add(new Time(0, 6));
        list.add(new Time(6, 10));
        list.add(new Time(3, 8));
        list.add(new Time(5, 9));
        list.add(new Time(8, 12));
        list.add(new Time(8, 11));
        list.add(new Time(2, 13));
        list.add(new Time(12, 14));
        
        //将数组按照结束时间排序
        Collections.sort(list, new Comparator<Time>(){
            @Override
            public int compare(Time t1, Time t2) {
                return t1.end - t2.end;
            }
        });
        
        //选出局部最优解,返回结果数组
        boolean[] r = greedySelector(list);
        
        System.out.print("被安排上的活动为:");
        for(int i = 0; i < list.size(); i++)
        {
            if(r[i] == true)
                System.out.print("[" + list.get(i).start + ", " 
                    + list.get(i).end + "] ");
        }
        System.out.println();
    }
    
    /**
      * 利用贪心性质选出活动安排的最优解
      * @param list 存储活动的列表
      * @return r 最终返回的结果数组
    */
    public static boolean[] greedySelector(ArrayList<Time> list) {
        int n = list.size();
        //存储结果的数组
        boolean[] r = new boolean[n];
        
        //将第一个活动放入活动表中
        r[0] = true;
        //记录最近一次加入到r中的活动
        int j = 0;
        
        //依次检查活动i是否与当前已选择的活动相容
        for(int i = 1; i < n; i++) {
            if(list.get(i).start >= list.get(j).end) {
                r[i] = true;
                j = i;
            }
            else
                r[i] = false;
        }
        
        return r;
    }
}

class Time {
    public int start;
    public int end;
    public Time(int start, int end) {
        this.start = start;
        this.end = end;
    }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class ActivityArrangement {
    public static void main(String[] args) {
        ArrayList<Time> list = new ArrayList<>();
        list.add(new Time(3, 5));
        list.add(new Time(1, 4));
        list.add(new Time(5, 7));
        list.add(new Time(0, 6));
        list.add(new Time(6, 10));
        list.add(new Time(3, 8));
        list.add(new Time(5, 9));
        list.add(new Time(8, 12));
        list.add(new Time(8, 11));
        list.add(new Time(2, 13));
        list.add(new Time(12, 14));
        
        //将数组按照结束时间排序
        Collections.sort(list, new Comparator<Time>(){
            @Override
            public int compare(Time t1, Time t2) {
                return t1.end - t2.end;
            }
        });
        
        //选出局部最优解,返回结果数组
        boolean[] r = greedySelector(list);
        
        System.out.print("被安排上的活动为:");
        for(int i = 0; i < list.size(); i++)
        {
            if(r[i] == true)
                System.out.print("[" + list.get(i).start + ", " 
                    + list.get(i).end + "] ");
        }
        System.out.println();
    }
    
    /**
      * 利用贪心性质选出活动安排的最优解
      * @param list 存储活动的列表
      * @return r 最终返回的结果数组
    */
    public static boolean[] greedySelector(ArrayList<Time> list) {
        int n = list.size();
        //存储结果的数组
        boolean[] r = new boolean[n];
        
        //将第一个活动放入活动表中
        r[0] = true;
        //记录最近一次加入到r中的活动
        int j = 0;
        
        //依次检查活动i是否与当前已选择的活动相容
        for(int i = 1; i < n; i++) {
            if(list.get(i).start >= list.get(j).end) {
                r[i] = true;
                j = i;
            }
            else
                r[i] = false;
        }
        
        return r;
    }
}

class Time {
    public int start;
    public int end;
    public Time(int start, int end) {
        this.start = start;
        this.end = end;
    }
}

运行结果

用python贪心算法编程求解以下任务安排问题 python贪心算法活动安排_数据结构与算法