动态规划之活动取舍-算法分析和描述

动态规划之活动选择--算法分析和描述
   最近复习了算法导论部分内容,感觉好陌生啊。记得研一老师只上了算法入门,其余的章节班级分组学习讲解,我只记得我们那一组负责的内容,有关图论、Dijkstra之类的。这两天看了动态、贪心算法,感觉这个真的很不错,以前只忙于准备自己的章节,也没有细细学习和体会。
   总体感觉(个人的理解,不一定准确),动态规划的核心步骤是分析问题,描述出解的递推公式,这个类似于高中学得数列只是更加复杂一些,毕竟数列大部分能笔算出来,而动态规划中就必须借助程序来解了。然后自底向上计算出解。所谓自底向上,从初始值开始计算各个值,并维护一个table来存储这些值,后面计算的值往往会用到前面计算的值,有点空间换时间的意思。
   直接写例子吧,这个例子实际上用贪心算法更加简单易解,这里用动态规划实现旨在学习动态规划了。假设有n个活动集合S = {a1,a2,.....an},每个活动ai有开始时间si小于其结束时间fi,即ai活动的时间区间[si, fi),如果[si, fi)和[sj, fj)互不重叠,则称ai和aj两个活动为兼容,问题:选出一组最大(包含最多的活动个数)的一组兼容活动。

    1.首先为简化问题,我们对集合S以结束时间先后排序,即有
       f0 < f1 < f2 ... < fn+1 时间复杂度为 nlg(n)
    2.找最优子结构:
       定义 S(ij)是S中活动的子集,其中每个活动在ai结束之后开始,且在aj开始之前结束。并引入虚拟活动a0与an+1,f0 = 0, sn+1 = NaN(暂且表示无穷大吧)。Sij = empty(暂且表示空集)。于是有:
    a) 当 i >= j 时,Sij = empty
    b) 假设Sij的解包含ak,即有fi <= sk < fk <= fj,则 Sij = Sik ∪ ak ∪ Skj
       若用Cij表示Sij的活动个数,有Cij = Cik + 1 + Ckj

    3. 于是有递归解:
             Cij = 0  如果 Sij = empty
             Cij = max{Cik + Ckj + 1}  i < k < j,ak属于Sij   如果Sij != empty
   
     用C[i][j]来记录中间计算的Cij的值,A[i][j]来记录中间计算的Sij集合,所以可以写出算法了:

     SELECT_ACTIVITY(s,f,n)
       for i <- 0 to n+1
         do for j <- 0 to i
              do c[i][j] <- 0;
                 A[i][j] <- empty;
             //一半的table值已填满,然后以与对角线平行一行一行填写剩下的半个table

       for t <- 1 to n+1
         do for i <- 0 to n+1-t
              do j = i + t;
                 if f[i] >= s[j] //第i个活动结束之前第j个活动已开始
                   then c[i][j] <- 0;
                        A[i][j] <- empty;
                 else
                    for k <- i+1 to j-1
                      do
                        if s[k] >= f[i] and f[k] <= s[j]
                          then
                             if c[i][j]< c[i][k] + c[k][j] + 1
                              then c[i][j] <- c[i][k] + c[k][j] + 1;
                                   A[i][j] <- A[i][k] ∪ k ∪ A[k][j];
                             
    这个本是贪心算法的一个例子,这里是学习动态规划,所以用了比贪心算法繁琐的解法。呵呵,明天将伪代码改称java代码实现并分析之。