扫描算法求最大子序列的一次简单非严格证明

先给出实现程序,如下:

   1:  int max_sub_sum2(const int v[],int n)
   2:  {
   3:     int max=0;
   4:     int currentSum=0;
   5:     int i=0;
   6:   
   7:     for(i=0;i<n;i++)
   8:     {
   9:         currentSum+=v[i];
  10:         if(currentSum>max)
  11:         {
  12:             max=currentSum;
  13:         }
  14:         else if(currentSum<0)
  15:         {
  16:             currentSum=0;
  17:         }
  18:     }
  19:   
  20:     return max;
  21:  }

扫描算法是Jon Bentley在编程珠玑里给出的名字,程序看起来非常简洁,不过简介并非意味着简单,至少在当时,问题的发现与提出者Ulf Grenander只想到了O(n2)的算法,而之后的Michael Shamos给出了分治算法,把复杂度降低到O(NlogN),当Bentley以及其它数学家都以为Shamos的算法最好的时候,卡梅隆大学的统计学家Jay Kadane发现了本文算法。

在这里我们至少能获得关于算法学习的一个重要启示:任何一个看似简单的算法都来之不易,只有经过广泛的研究和实践,你才能熟练地运用算法设计技术。这也是Bentley在编程珠玑八章深入阅读一节种所给出的建议。

该算法的复杂度很明显是O(N),甚至无需证明。

但是为理解该算法,我们仍有必要简单分析证明一下该算法的正确性。

image

现在命题为:如上图所示序列A1~AN,最大子序列Ai-Aj,则该最大子序列一定可以由上述算法求得。

证明:

A[i~j]为最大子序列,则意味着A[i-1]<=0与A[j+1]>=02,论点(1)

且更可推导出A[1]+A[2]+…+A[i]<=0与A[j+1]+A[j+2]+…+A[N]<=0; 论点(2)

论点(2)亦可简单地由反证法推得,如二者任意之一若大于零,则很明显A[i~j]不是最大子序列,这与命题所给条件矛盾。

由论点(2)也可推导出A[i-1]与A[1~i-2]之间的任意子集之和皆<=0,也即|A[i-1]|>A[1]+…+A[i-2],论点(3)

而论点(2)(3)的情况恰可由程序14~17行规避。

而程序10~13行计算的又是论点(2)(3)情况之后的子序列的最大值。max用来保存该最大值,currentSum则用来判断是否会出现下一个符合(2)(3)的位置出现,如出现,则该值被清零,并从新位置寻找下一个可能会超过max值的位置,以此归纳,可值命题得证,也即max就是最大子序列的值。

posted @ 2012-06-17 13:33  Dance With Automation  Views(340)  Comments(0Edit  收藏  举报