分治法
所谓分治法,就是把问题不断分割变小,常见的是把数组分割为两部分,然后分别计算,将大问题变成小问题。
问题分析方向
这一类问题主要用的是递归的方法,所以时间复杂度普遍为O(nlogn)
对时间复杂度的分析
样例分析
接下来分析最大子数组问题
问题分析
分解问题
求最大子数组无非就是求数组内某段连续的数相加,那么通过分治的思想,我们把问题拆开。
将一个数组变成两个数组,然后分别找两个数组中最大的子数组,然后再比较大小。
这么一想就会觉得问题简单了不少。但是仔细一想不对啊,要是最大子数组在两个部分中都有怎么办呢?
于是这样我们就把问题分解为三个部分:
解决问题
对于仅位于两边的子数组来说好解决
- 在分解后的数组长度大于1的时候继续递归
- 要是到了递归尾了,那就直接输出最后剩下那一个数组元素就好了。
但是要是是中间那种情况怎么办呢?
这个好解决,要这么想,既然是经过最中间的元素,那么最大子数组里面必然包括最中间那个元素,那么直接从中间向两边遍历就好了。
通过不断累加计算寻找两边是否有更好的情况。
合并问题
对于某一个子数组来说,递归所返回的子数组就是最大子数组,其中返回的子数组就是目前这个某数组的最大子数组,因为返回的子数组就是从他的左边选出来的数组和右边选出来的子数组以及中间选出来的子数组中挑选的最大数组。
同理对于他的父数组来说也是这样。应该是类似于数学归纳法的想法。
对代码的设想
中间部分的处理
- 伪代码(来自算法导论)
- C语言实现
mid_message find_mid(int A[], int low, int mid, int high)
{
mid_message midx;
int left_sum = -inf;
int sum = 0;
for(int i = mid ; i >= low ; i--)
{
sum = sum + A[i];
if (sum > left_sum)
{
left_sum = sum;
midx.max_left = i;
}
}
int right_sum = -inf;
sum = 0;
for(int j = mid + 1 ; j <= high ; j++)
{
sum = sum + A[j];
if (sum > right_sum)
{
right_sum = sum;
midx.max_right = j;
}
}
midx.sum = left_sum + right_sum;
return midx;
}
递归部分
递归部分很简单,无非就是设置一下递归出口以及选择子数组的条件
-
伪代码(来自算法导论)
-
C语言实现
mid_message find_max(int A[], int low, int high)
{
mid_message midx;
if (high == low)
{
midx.max_left = low;
midx.max_right = high;
midx.sum = A[low];
return midx;
}
else
{
int mid = (low+high) / 2;
mid_message midxl = find_max(A, low, mid);
mid_message midxr = find_max(A, mid+1, high);
mid_message midxm = find_mid(A, low, mid, high);
int max = Max(midxr.sum, midxl.sum, midxm.sum);
if(midxl.sum == max)
return midxl;
else if(midxr.sum == max)
return midxr;
else
return midxm;
}
}
时间复杂度分析
按照分治法的时间复杂度分析,可以写出下列方程
化简得出符合O(nlogn)
完整代码
我加了注释,基本上复制到自己的编辑器就可以看懂
#include<stdio.h>
//设置一个无限大值
#define inf 999999
//定义的一个匿名结构体(我也不大记得匿名结构体怎么用了)
typedef struct mid_message{
//最大子数组的左边界,右边界,最大子数组的总值
int max_left;
int max_right;
int sum;
}midk;
//自己写的一个求3个数中最大值的函数
int Max(int a, int b, int c)
{
int max = a;
if(b > max)
max = b;
if(c > max)
max = c;
return max;
}
//求经过中点的最大子数组,返回结构体
mid_message find_mid(int A[], int low, int mid, int high)
{
mid_message midx;
//初始化中点左边的子数组值
int left_sum = -inf;
int sum = 0;
//通过循环不断向左加,然后一直与left_sum比较
for(int i = mid ; i >= low ; i--)
{
sum = sum + A[i];
if (sum > left_sum)
{
left_sum = sum;
midx.max_left = i;
}
}
//与左边的操作相同
int right_sum = -inf;
sum = 0;
for(int j = mid + 1 ; j <= high ; j++)
{
sum = sum + A[j];
if (sum > right_sum)
{
right_sum = sum;
midx.max_right = j;
}
}
midx.sum = left_sum + right_sum;
return midx;
}
mid_message find_max(int A[], int low, int high)
{
mid_message midx;
//递归出口
if (high == low)
{
midx.max_left = low;
midx.max_right = high;
midx.sum = A[low];
return midx;
}
else
{
int mid = (low+high) / 2;
//左右中,左右用递归,中用 find_mid
mid_message midxl = find_max(A, low, mid);
mid_message midxr = find_max(A, mid+1, high);
mid_message midxm = find_mid(A, low, mid, high);
int max = Max(midxr.sum, midxl.sum, midxm.sum);
//返回最大的子数组
if(midxl.sum == max)
return midxl;
else if(midxr.sum == max)
return midxr;
else
return midxm;
}
}
int main()
{
//测试数据(从别人那复制来的)
int A[] = {-500,10,5,5,2,-3,-29,-10,-50,22,-23,10,150,1,9,-900,-26,3,99,7};
mid_message max = find_max(A, 0, 19);
printf("%d\n",max.max_left);//11
printf("%d\n",max.max_right);//14
printf("%d\n",max.sum);//170
}