最大子数组,给定数组,在这个数组中找到这样的子数组:子数组的和是所有子数组中最大的(子数组必须是连续的)。
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。(对归并排序,a和b都为2,然而,我们将看到在许多分治算法中,a≠b。)为了求解一个规模为n/b的子问题,需要T(n/b)的时间,所以需要aT(n/b)的时间来求解a个子问题。如果分解问题成子问题需要时间D(n),合并子问题的解成原问题的解需要时间C(n),那么得到递归式:
如何求解递归式,有3中方法,代入法(先猜测,然后数学归纳法证明),画递归树(可以猜测结果,然后用代入法求解),主定理。
5:递归式主定理: