最大字段和的扩展---最大子矩阵和及最大m字段和问题

  关于最大字段和,已有4中方法对其进行求解,现对其进行扩展,得到两个扩展的问题:

  一、最大子矩阵问题

  1、问题描述:给定一个m行n列的子矩阵A,试求出矩阵A的一个子矩阵,使其各元素之和为最大。

  2、求解策略:对该问题,如果用穷举法求解,时间复杂度将为O(m2n2),利用其为最大字段和问题的扩展,将其划归成最大字段和问题,然后再用最大字段和的最优方法进行求解,则可降低时间复杂到O(m2n) 。即先长度为m的维度上求出第i行到第j行每一个的元素和存储到一个n维的数组中,再对该数组进行最大字段和的动态规划法求解,求出结果。

  3、程序如下所示:

 

float maxSum2(float (*a)[4], int row, int col)
{
    int i = 0, j = 0, k = 0;
    float maxs = 0, maxSoFar = 0, maxendinghere = 0, * b = new float[col];
    for (i = 0; i < row; ++i)
    {
        for (k = 0; k < col; ++k)
            b[k] = 0;
        for (j = i; j < row; ++ j)
        {
            for (k = 0; k < col; ++k)
                b[k] += a[j][k]; //b[k]存储的是第i行到第j行每列的元素和

            //动态规划法求解b中的最大子和
            maxendinghere = 0;
            maxSoFar = 0;
            for (k = 0; k < col; ++k)
            {
                maxendinghere = maxendinghere + b[k];
                maxendinghere = maxendinghere > 0 ? maxendinghere : 0;
                maxSoFar = maxSoFar < maxendinghere ? maxendinghere : maxSoFar;
            }
maxs
= maxs < maxSoFar ? maxSoFar : maxs; } } delete b; return maxs; }

  二、最大m字段和问题

  1、问题描述:给定n个数(可能是负数)组成的序列a1,a2,...,an以及一个正整数m,要去确定该序列m个互不相交字段,使这m个字段的总和达到最大。

  2、求解策略:显然最大m字段和问题是最大字段和问题在字段个数上的推广。同样可以考虑用动态规划方法求解。设b(i,j)表示数组a的前j项中i个字段和的最大值,且第i个字段含a[j],则所求的最优值为max b(m,j)(m≤j≤n)。该动态规划的转移方程则为:

                          b(i,j) = max{b(i,j-1)+a[j],max b(i-1,t)+a[j]},(i-1≤t<j,1≤i≤j≤m,1≤j≤n,)

且初始时,b(0,j)=0,b(i,0)=0.

 3、根据2得到的动态规划,对于源程序设计,记住并不是所有的表b[m+1][n+1]都要求的,由i≤j可知下三角部分不需要计算,且对角线上的元素为所有i个元素的和。又根据b的最后一行只有b(m,j)共n-m+1个元素,且根据b(i,j)的计算公式中的第二项知道其只要b(m-1,j)共n-m+1个,依次类推知道每行都只要计算j=i~j=i+n-m个元素。源程序如下:

float maxMSum(float* a, int m, int n)
//求含有n个元素的序列a的最大m字段和
{
    int i = 0, j = 0, k = 0;
    float sum = 0;
    if (n < m || m < 1)
            return 0;

    //因为初值b[i][0]和b[0][j],所以数组大小为(m+1)*(n+1)
    float** b = new float* [m+1];//声明b为指向指针数组的指针(指针的指针)
    for (i = 0; i <= m; ++i)
        b[i] = new float[n+1]; //b[i]指向的是一个n+1维大小的数组

    //赋初值
    for (i = 0; i <= m; ++i)
        b[i][0] = 0;
    for (j = 1; j <= n; ++j)
        b[0][j] = 0;

    //根据转移方程依次求出b[i][j]
    for (i = 1; i <= m; ++i)
        for (j = i; j <= n-m+i; ++j)
        //n-m+i避免多余的运算,当i=m时,j最大为n,最小为m,而依照递归公式
        //其只要求出b[m-1][m-1],...,b[m-1][n-1]共n-m个元素,依次类推
        {
            if (j > i)//元素个数大于字段个数
            {
                b[i][j] = b[i][j-1] + a[j-1]; //第i个字段含a[j-1]的情况,这里及以下a都不是a[j]而是a[j-1]因为a从0开始
                for (k = i-1; k < j; ++k); //第i个字段仅含a[j]
                    if (b[i][j] < b[i-1][k]+a[j-1])
                        b[i][j] = b[i-1][k]+a[j-1];
            }
            else//元素个数等于字段个数,字段和即为序列的和
                b[i][j] = b[i-1][j-1]+a[j-1];
        }

    //扫描b[m][j](m<=j<=n)求出最大值
    for (j = m; j < n; ++j)
        if (sum < b[m][j])
            sum = b[m][j];

    for (i= 0; i<= m; ++i){
        for (j = 0; j <= n; ++j)
            cout << b[i][j] << ' ';
        cout << endl;
    }
    delete [] b;
    return sum;
}

4、上述方法的改进,显然,上述方法的时间复杂度为O(mn2),空间复杂度为O(mn)。注意到每次计算b(i,j)时只利用了本行i和上一行的一个最大值进行求解,故算法中只要存储数组b的当前行以及上一行的最大值max (i-1,t)(i-1≤t<j)即可。故只要两个数组的额外空间b[n+1],c[n+1],且c(i)=max b(i,t) (i≤t<j),b(i,j)=max(b(i,j-1),c(i-1)) + a[j].此时的时间复杂度为O(m(n-m)),空间复杂度为O(n).

5、改进后的源程序和测试程序如下:

float maxMSumImp(float* a, int m, int n)
{
    if (n < m || m < 1)
        return 0;
    int i = 0, j = 0;
    float sum = 0, maxb = 0;
    float* b = new float[n+1],//对i个字段和都只存储n-m+1个有用的值,虽然大小为n+1
         * c = new float[n+1];//额外数组,c[i][j]=max b(i,t)(i <= t < j)的值,开始应该都赋值为-inf
    b[0] = 0;
    c[1] = 0;
    for (i = 1; i <= m; ++i) //依次求出i个子段时的情况
    {
        b[i] = b[i-1] + a[i-1];//i个元素的i子段和为其序列的全部和,其实为b[i][i]=...
        maxb = b[i]; //其实为maxb=max b(i,t) (i <= t < j=i+1),此时只存储了一个有效值(i个字段时)
        //cout << b[i] << ' ';
        for (j = i+1; j <= n-m+i; ++j) //一个这样的循环对应着i个字段时求相应的值
        {    
            cout << c[j-1] << ' ';
            b[j] = (b[j-1] > c[j-1] ? b[j-1] : c[j-1])+a[j-1];//这里的a[j-1]同样是因为a从0开始
            c[j-1] = maxb; //其实为c[i][j-1],为第i+1子段做准备
            if (maxb < b[j])
                maxb = b[j];
            
        }
        c[n-m+i] = maxb;    
        cout << endl;
    }
    for (j = m; j <= n; ++j)
        if (sum < b[j])
            sum = b[j];
    delete[] b;
    delete[] c;
    return sum;
}

测试程序:

 

int main()
{
    int a[] = {31, -41, 59, 26, -53, 58, 97, -93, -23, 84},
        n, besti = 0, bestj = 0, maxSum = 0;
    float b[3][4] = {31, -41, 59, 26,
                    -1, 16, -27, 18,
                    64, -28, 36, -19};
    float c[] = {-1, 3, -1, 4, -2, -3, 5, -2, 3, -1};
    int m = 2,n3;
    n3 = sizeof(c)/sizeof(c[0]);
    cout << "序列C的最大 " << m << " 字段和为:" << maxMSumImp(c,m,n3) << endl;
    return 0;
}

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              

posted @ 2013-04-22 15:39  busyfruit  阅读(1609)  评论(0编辑  收藏  举报