算法导论笔记:04最大子数组矩阵乘法以及递归式求解

       最大子数组,给定数组,在这个数组中找到这样的子数组:子数组的和是所有子数组中最大的(子数组必须是连续的)。

 typedef  struct
{
       int  max;
       int  subbegin;
       int  subend;
}SUBMAX;

#define  NEINFINITE  (0x80000000)
#define  true      1
#define  false     0

       1:最普通的解法,根据定义,求所有子数组的和,然后找到最大子数组。时间复杂度为O(n^2)

       从第i个元素开始,连续求得以i为起点,i,i+1,i+2...为终点的子数组的和,得到以i为起点的所有子数组的最大值,然后对于i,0<i<num。代码如下:

SUBMAX normalmaxsub(int *set,  int  num)

{

         int i, j;

 

         int begin, end;

         int max = NEINFINITE;

         int and = 0;

         SUBMAX res;

        

         for(i = 0; i < num; i++)

         {

                  and = set[i];

                  if(max < and)

                  {

                          max = and;

                          begin = i;

                          end = i;

                  }

                  for(j = i+1; j < num; j++)

                  {

                          and = and + set[j];

                          if(max < and)

                          {

                                   max = and;

                                   begin = i;

                                   end = j;

                          }

                  }

         }

         res.max = max;

         res.subbegin = begin;

         res.subend = end;

         return res;

}

 

2:分治算法:将原数组分解为左右两部分,mid为分界点,那么原数组的最大子数组的位置只有3中可能:

       a:在mid左边的子数组中;

       b:在mid右边的子数组中;

       c:穿过中间节点mid的子数组。

 

       因找穿过中间节点的最大子数组的时间复杂度是O(n)。所以,该算法的时间复杂度:T(N) = 2T(N/2) + O(N),即为O(nlgn)。代码如下:

SUBMAX  dividemaxsub(int *set,  int  begin,  int  end)

{

         int mid;

         SUBMAX leftmax, rightmax, midmax;

         leftmax.max = rightmax.max = midmax.max= NEINFINITE;

 

         SUBMAX max;

        

         if(begin == end)

         {

                  max.max = set[begin];

                  max.subbegin = begin;

                  max.subend = begin;

                  return max;

         }

        

         if(begin < end)

         {

                  mid = (begin + end)/2;

                  if(mid < end)

                  {

                          rightmax = dividemaxsub(set, mid+1, end);

                  }

                  if(mid > begin)

                  {

                          leftmax = dividemaxsub(set, begin, mid-1);

                  }

                  midmax = crossmidmaxsub(set, begin, end, mid);

                  max = (leftmax.max < rightmax.max)?rightmax:leftmax;

                  max = (max.max < midmax.max)?midmax:max;

                  return max;

         }

}

 

SUBMAX crossmidmaxsub(int *set,  int  begin,  int  end,  int  mid)

{

         int i,j;

         int max = 0;

         int and = 0;

         SUBMAX res;

 

         and = set[mid];

         max = and;

        

         res.subbegin = mid;

         res.subend = mid;

        

         for(i = mid - 1; i >= begin; i--)

         {

                  and = and + set[i];

                  if(max < and)

                  {

                          max = and;

                          res.subbegin = i;

                  }

         }

        

         and = max;

        

         for(j = mid + 1; j <= end; j++)

         {

                  and = and + set[j];

                  if(max < and)

                  {

                          max = and;

                          res.subend = j;

                  }

         }

         res.max = max;

         return res;

}

 

3:DP解法,时间复杂度为O(n):

       对于数组a[0…i],若已知它的最大子数组,如何求a[0…i+1]的最大子数组?

       先看数组a[0…i],已知它的最大子数组,记为res(i),同时,也可求得包含元素a[i]的最大子数组。记为sum(i)

       对于数组a[0…i+1],它的最大子数组,要么包含元素a[i+1],要么不包含元素a[i+1]

       包含元素a[i+1]的情况是:res(i+1)= sum(i+1)

       不包含元素a[i+1]的情况是res(i+1)= res(i)

 

       现在的问题就是如何求解sum(i+1):如果sum(i) > 0,则sum(i+1) = sum(i) + a[i+1],否则,sum(i+1) = a[i+1]。

 

       这样,依次遍历数组,每遍历一个元素,就可求得sum(i)。具体代码如下:

SUBMAX  DPmaxSum(int *a, int length)

{

         int res =NEINFINITE;

         int sum = 0;

         int i;

 

         int sumbegin = 0;

         int sumend = 0;

         int resbegin = 0;

         int resend = 0;

        

         SUBMAX result;

         for(i = 0; i < length; i++)

         {

                  if(sum >= 0)

                  {

                          sum = sum + a[i];

                          sumend = i;

                  }

                  else

                  {

                          sum = a[i];

                          sumbegin = sumend = i;

                  }

                  res = (sum > res)?sum:res;

                  if(res == sum)

                  {

                          resbegin = sumbegin;

                          resend = sumend;

                  }

         }

         result.max = res;

         result.subbegin = resbegin;

         result.subend = resend;

         return result;

}

 

 

       1:普通的矩阵乘法就是依次扫描两个矩阵的行和列,然后计算结果矩阵的每一个元素,时间复杂度为Ө(n^3)

 

       2:分治法求解矩阵的乘积,就是把一个矩阵依次分为4个部分,对每个部分分别求解。该算法的递归式为:

该算法的时间复杂度为Ө(n^3)

 

       3:Strassen方法:递归方法需要8次乘积,Strassen方法采用矩阵的加减法减少了乘机次数,也就是7次乘积,所以,该算法的时间复杂度为Ө(n^lg7)

 

       4:分治模式在每层递归时都有三个步骤:

分解:原问题为若干子问题,这些子问题是原问题的规模较小的实例。

解决:这些子问题,递归地求解各子问题。然而,若子问题的规模足够小,则直接求解。

合并:这些子问题的解成原问题的解。

 

       分治算法运行时间的递归式来自基本模式的三个步骤。如前所述,我们假设T(n)是规模为n的一个问题的运行时间。若问题规模足够小,如对某个常量c,n≤c,则直接求解需要常量时间,我们将其写作Θ(1)。假设把原问题分解成a个子问题,每个子问题的规模是原问题的1/b。(对归并排序,ab都为2,然而,我们将看到在许多分治算法中,ab。)为了求解一个规模为n/b的子问题,需要T(n/b)的时间,所以需要aT(n/b)的时间来求解a个子问题。如果分解问题成子问题需要时间D(n),合并子问题的解成原问题的解需要时间C(n)那么得到递归式:


 

       如何求解递归式,有3中方法,代入法(先猜测,然后数学归纳法证明),画递归树(可以猜测结果,然后用代入法求解),主定理。

       5:递归式主定理:

 

posted @ 2015-04-19 16:33  gqtc  阅读(241)  评论(0编辑  收藏  举报