活动选择问题的动归和贪心解法

来源:互联网 发布:没权限开通淘宝联盟 编辑:程序博客网 时间:2024/05/17 09:31

1.动归解法


网络上找的代码错误百出,经Debug,正确输出结果。

代码如下:

#include <iostream>  using namespace std; #define N 11void dynamic_activity_selector(int *s,int *f,int c[N+2][N+2],int trace[N+2][N+2]);void print(int i,int j,int trace[][N+2]);int main(){    int s[N+2] = {-1,1,3,0,5,3,5,6,8,8,2,12,65535};    int f[N+2] = {-1,4,5,6,7,8,9,10,11,12,13,14,65536};    int c[N+2][N+2]={0};    int trace[N+2][N+2]={0};    int i,j;    dynamic_activity_selector(s,f,c,trace);    printf("c[i][j]的值如下所示:\n");    for(i=0;i<=N+1;i++)    {        for(j=0;j<=N+1;j++)            printf("%d ",c[i][j]);        printf("\n");    }    printf("最大子集的个数为: %d\n",c[0][N+1]);     for(i=0;i<=N+1;i++)    {        for(j=0;j<=N+1;j++)            printf("%d ",trace[i][j]);  //trace[i][j]表示i和j之间可以选择的活动的序号(因此要想不排除插入1号和11号活动的情形,必须人为扩充0和12号活动)         printf("\n");    }    print(0,N+1,trace);     system("pause");    return 0;}void dynamic_activity_selector(int *s,int *f,int c[N+2][N+2],int trace[N+2][N+2]){    int i,j,k;    int temp;    //当i>=j时候,子问题的解为空,即c[i][j]=0    for(j=1;j<=N+1;j++)      for(i=j+1;i<=N+1;i++)         c[i][j] = 0;    for(j=1;j<=N+1;j++)     for(i=0;i<j;i++)     //c[i][j]是不包括序号为i和j的活动的,如c[1][2]=0(说明1和2之间无活动可选,而c[0][4]=1,是指可以插入1号或2号其中之一的一个活动)     {         //寻找k,将问题分成两个子问题c[i][k]、c[k][j]          for(k=i+1;k<j;k++)            if(s[k] >= f[i] && f[k] <= s[j])   //判断k活动是否满足兼容性 ,即选中i,j之间一个满足条件(使得i,j之间的活动数最多)的k值作为序号            {               temp = c[i][k]+c[k][j]+1;                   if(c[i][j] < temp)               {                  c[i][j] =temp;                  trace[i][j]=k;   //记录本次插入的序号               }            }     }}void print(int i,int j,int trace[][N+2])  {          int k =0;          if(trace[i][j]!=0)          {            k = trace[i][j];              print(i,k,trace);             cout << k << "  ";             print(k,j,trace);   //这句必须有,因为我们不能保证最后插入的活动序号就是最后一个活动,本题是一个例外        } <pre code_snippet_id="616468" snippet_file_name="blog_20150310_1_4407460" name="code" class="cpp">}


2.贪心解法

把两个子问题缩减为一个子问题,每次只要选择结束时间最早的活动,即可保证得到最优解

上面那句话,,直观上理解就是每次选择结束时间最早的活动,可以使得它剩下的资源最多以供尽可能多的活动使用。那么,怎么证明直觉的正确性呢?
问题描述:

为在结束后开始的任务集合。

考虑任意非空子问题

得以证明后,我们可以每次(从剩下的活动中)选择结束时间最早的活动,这样就可以组成最优解——最优子结构性质。

因此,预先将所有活动按结束时间的单调递增顺序排列好后,每次向后寻找结束时间最早的活动(也就是第一个满足s[m]>=f[k]活动兼容性的——它必然是最早结束的),而不必要向前寻找合适的活动(动态规划)——这就就眼看当前活动是怎么选出来的,其前面的活动都不与(被选的)当前活动的(被选的)前一个活动兼容!

伪代码如下:

n=s.length

A={a1}   //因为已经排过序了,所以a1必然是结束时间最早的

k=1

for  m=2 to n

       if  s[m]>=f[k]    //排过序后,第一个与当前活动兼容的,就是结束时间最早

           A=A{am}

           k=m

return A


此外,活动选择还可以按最晚开始时间(逆向)贪心选择,也能得到最优解。但是在剩余兼容活动中贪心选择持续时间最短者和在剩余兼容活动中贪心选择与其他剩余活动重叠最少者均不能得到最大集,见算导课后练习。

0 0
原创粉丝点击