算法与数据结构——时间复杂度验证:最大子序列问题 Max Subsequence Sum

最近在学数据结构,书看的是《数据结构与算法分析 C语言描述》Mark Allen Weiss的中译本。

第二章中的最大子序列和问题:

给定整数A1,A2,......AN(可能有负数),求∑(k=i~j)     Ak的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为0)。

看到问题后我自己先写了一个笨笨的算法(想了好久啊- - 跟书上的例程相比无论哪方面高下立判啊)。。。时间复杂度是O(N2)的,记为Algorithm0。书上给出的四个算法分别记为Algorithm 1~4,时间复杂度分别为O(N3), O(N2), O(NlogN), O(N)(其中第三个递归真是惊呆我,本来以为递归效率都低的。。),记录当N分别为10,100,1000,2000,5000,10000,20000,50000等时的运行时间。N达到50000以上的时候不论哪个算法都显示停止工作了。。。估计内存的关系吧。。具体原因也不明白。。

具体代码如下,init()调用rand()产生(大小为-9~10)随机数并初始化至数组,通过修改#define SIZE的值改变N的大小,输出10次结果。由于每次显示数组的内容耗费时间长,可以把show()函数调用注释掉来只输出最大子序列和的结果。运行时间嘛。。最小好像就0.063, 0.078啥的,不过大体还是可以反映算法之间的优劣。

主程序:

/* test.c */

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include "Algorithms.h"

#define SIZE 50000

void init(int (*p) [SIZE],int n);
void show(const int *p,int n);


int main(void)
{
    int ans;
    int i;
    int source[10][SIZE];
    init(source,10);
    printf("N= %d\n\n",SIZE);
    for(i=0;i<10;i++)
    {
        ans=MaxSubsequenceSum4(source[i],SIZE);
        show(source[i],SIZE);   // 把这句注释掉可不显示具体序列只显示结果
        printf("%d\n",ans);
    }
        return 0;
}
void show(const int *p,int n)
{
    int i=0;
    for(i=0;i<n;i++)
    {
        printf("%d\t",p[i]);
    }
    putchar('\n');
    return;
}

void init(int (*p)[SIZE],int n)
{
    int i,j;
    srand((int)time(0));
    for(i=0;i<n;i++)
    {
        for(j=0;j<SIZE;j++)
        p[i][j]=10-rand()%20;
    }
    return;
}

算法:

/* Algorithms.c */

#include "Algorithms.h"

static int MaxSubSum(const int *a,int left,int right);
static int Max3(int a,int b,int c);

int MaxSubsequenceSum0(const int *p,int n)
{
    int i,j,sum=0,ans=0,ThisSum=0;
    _Bool last_is_pos=0;
    for(i=0;i<n;i++)
    {
        if(p[i]<0||last_is_pos)
        {
            last_is_pos=0;
            continue;
        }
        else
        {
            sum=p[i];
            last_is_pos=1;
        }
        for(j=i+1,ThisSum=sum;j<n;j++)
        {
            ThisSum+=p[j];
            if(p[j]>0)
            {
                sum= sum>ThisSum?sum:ThisSum;
            }
        }
        ans=ans>sum?ans:sum;

        ThisSum=0;
    }
    return ans;
}

int MaxSubsequenceSum1(const int *p,int n)
{
    int ThisSum,MaxSum,i,j,k;
    MaxSum=0;
    for(i=0;i<n;i++)
    {
        for(j=i;j<n;j++)
        {
            ThisSum=0;
            for(k=i;k<=j;k++)
                ThisSum+=p[k];
            if(ThisSum>MaxSum) MaxSum=ThisSum;
        }
    }
    return MaxSum;
}


int MaxSubsequenceSum2(const int *p,int n)
{
    int ThisSum,MaxSum,i,j;
    MaxSum=0;
    for(i=0;i<n;i++)
    {
        ThisSum=0;
        for(j=i;j<n;j++)
        {
            ThisSum+=p[j];
            if(ThisSum>MaxSum) MaxSum=ThisSum;
        }
    }
    return MaxSum;
}

int MaxSubsequenceSum3(const int *p,int n)
{
    return MaxSubSum(p,0,n-1);
}

int MaxSubsequenceSum4(const int *p,int n)
{
    int MaxSum=0,ThisSum=0;
    int i;
    for(i=0;i<n;i++)
    {
        ThisSum+=p[i];
        if(ThisSum>MaxSum)
            MaxSum=ThisSum;
        else if(ThisSum<0)
            ThisSum=0;
    }
    return MaxSum;
}
static int MaxSubSum(const int *a,int Left,int Right)
{
    int MaxLeftSum, MaxRightSum;
    int MaxLeftBorderSum, MaxRightBorderSum;
    int LeftBorderSum, RightBorderSum;
    int Center, i;

    if(Left==Right) //Base case
    {
        if(a[Left]>0)
        return a[Left];
    else
        return 0;
    }

    Center=(Left+Right)/2;
    MaxLeftSum=MaxSubSum(a,Left,Center);
    MaxRightSum=MaxSubSum(a,Center+1,Right);

    MaxLeftBorderSum=0;LeftBorderSum=0;
    for(i=Center;i>=Left;i--)
    {
        LeftBorderSum+=a[i];
        if(LeftBorderSum>MaxLeftBorderSum)
            MaxLeftBorderSum=LeftBorderSum;
    }
    MaxRightBorderSum=0;RightBorderSum=0;
    for(i=Center+1;i<=Right;i++)
    {
        RightBorderSum+=a[i];
        if(RightBorderSum>MaxRightBorderSum)
            MaxRightBorderSum=RightBorderSum;
    }
    return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}

static int Max3(int a,int b,int c)
{
    int ans;
    ans=a>b?a:b;
    ans=ans>c?ans:c;
    return ans;
}

头文件:

/* Algorithms.h */

int MaxSubsequenceSum0(const int *p,int n);
int MaxSubsequenceSum1(const int *p,int n);
int MaxSubsequenceSum2(const int *p,int n);
int MaxSubsequenceSum3(const int *p,int n);
int MaxSubSequenceSum4(const int *p,int n);

运行结果如下(运行时间单位为s):

元素个数N 算法0  O(N2)算法1  O(N3)算法2  O(N2)算法3  O(NlogN)算法4  O(N)
10 0.0630.0470.0630.0630.063
100 0.0630.0630.0630.0630.063
1000 0.0634.9690.0780.0630.063
2000 0.09439.0510.1410.0630.063
5000 0.313609.4640.4530.0630.063
10000 1.313------------1.5470.0780.063
20000 5.704-----------5.8600.0940.063
30000 13.220-----------13.1110.1090.063
50000 37.879-----------36.2540.1410.063

大体上还是验证了这几个算法的效率区别,也算第一次切身体会到算法的重要性,虽然只是一个简单的问题,同一个问题不同解决方案,结果相同但效率的差别是惊人的。不过递归那个不是很理解哪!另外这也只是一个粗略的测试,这个过程本身肯定有很多问题。。。还是继续看书吧= =

关于运行时间中的对数

书中的翻译是这样的:如果一个算法用常数时间(O(1))将问题的大小削减为其一部分(通常是1/2),那么该算法就是O(logN)。另一方面,如果使用常数时间只是把问题减少一个常数(如将问题减少1),那么这种算法就是O(N)的。

翻译很拗口,不过好在下面给出了三个例子:对分查找、欧几里得算法和取幂运算,再结合前面的最大子序列和问题,我的理解是这样的:无论是用循环还是递归,程序每次执行时都将变量减半(上面说的削减为一部分),并且循环和递归之外的语句是常数阶复杂度O(1),也就是有确定的执行次数,这样的程序往往是对数阶的。

至于原理,直观上来讲,比如开始变量为N,每次变为1/2,需要logN次就可以让变量从N变到1,此时也就是基准情况,所以执行次数就是logN了。更加准确的分析是使用递推公式,例如上面的最大子序列和,递归之外的语句复杂度为O(N),设求解大小为N时时间为T(N),可以列出递推公式:

T(1)=1;

T(N)=2T(N/2)+O(N);

把这里的O(N)用N代替可以算出T(N)=NlogN+N=O(NlogN)

当然这个程序是所谓的“分治”方法,复杂度为NlogN的,并不是logN,但分析方法都是类似的,即通过递推公式。

例如:最后的取幂运算问题,算法如下:

long int pow(long int x, unsigned int N)
{
if(N==0) return 1;
if(N==1) return X;
if(IsEven(N)) return Pow(X*X,N/2);
else return pow(X*X,N/2)*X;
}

这里面如果用乘法的次数衡量运行时间,或者连判断也算上,总之每次递归内部都是O(1),列出的递推式:

T(1)=1;

T(N)=T(N/2)+O(1);

把O(1)当1来算,可以得出来T(N)=logN+1=O(logN)。

不过这里通过递推得到公式都是写出前几项归纳的。。。至于真正怎么去算这个公式是数学问题。。书里预言了第七章会讲。。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值