一、用分治法的思想解题
分治法的基本思想:将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。
题目:给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。
e.g.
输入:-2 11 -4 13 -5 -2
输出:20
最大子段和为:11 -4 13
这个问题该如何用分治算法来求解呢?
1、首先分解,划分问题:
将问题规模为n的问题分解为两个问题规模为n /2的问题
2、递归求解两个子问题
3、求解子问题
理解:该问题存在三种情况:
1.最大子段和于[0, 2 / n]区域内
2.最大子段和于[2 / n + 1, n]区域内
3.最大子段和的起点于[0, 2 / n]区域内,终点于[2 / n + 1, n]区域内
ret_left 为第一种情况,ret_right为第二种情况,下面两个for计算第三种情况,最后比较究竟哪种情况的子段和最大,返回最大子段和
函数伪代码如下:
// 求解最大子段和的函数,返回值为最大子段和
int SubArraySum(int* a, int l, int r) { // 数组a的起始下标为l,末尾下标为r
if(l == r) return a[l] < 0 ? 0 : a[l]; // 仅有一个数,不用再分解了,返回,由于没有m-1,故不存在l>r,l == r即可,l >= r也可以
mid = l + (r - l) / 2; // 相当于(l + r) / 2
ret_left = SubArraySum(a, l, m); // 返回[l, m]区域内的最大子段和
ret_right = SubArraySum(a, m+1, r); // 返回[m+1, r]区域内的最大子段和
// 计算起点于[l, m],终点于[m+1, r]的最大子段和
sum = 0;
left_max = 0;
for(i = m; i >= l; i--) { // 计算[l, m]的最大子段和,从a[m]开始向左移动
sum += a[i];
if(sum > left_max)
left_max = sum;
}
sum = 0;
right_max = 0;
for(i = m+1; i <= r; i++) { // 计算[m+1, r]的最大子段和,从a[m+1]开始向右移动
sum += a[i];
if(sum > right_max)
right_max = sum;
}
ret = left_max + right_max; // 起点于[l, m],终点于[m+1, r]的最大子段和是多少
// 三种情况做比较,返回子段和最大的
if(ret_left > ret)
ret = ret_left;
if(ret_rigth > ret)
ret = ret_right;
return ret;
}
二、根据主定理求解时间:
分解问题:T(n) = O(1)
递归求解各子问题:将问题规模为n的问题分解为两个问题规模为n /2的问题, T(n) = 2T(n / 2)
求解子问题:两个for循环,范围相加为n,T(n) = O(n)
综上时间复杂度:T(n) = O(1) + 2T(n / 2) + O(n) = O(nlogn)
三、通过第二章的学习,我对分治法的体会和思考
运用分治法的思想可以巧妙改变求解问题的规模,进而减小时间复杂度,提高程序的运行效率,将问题规模由最初的n分解为更小的子问题来求解,很巧妙的方法使得时间复杂度有所减小。我发现分治法重点放在不同环节会产生不同的算法,如归并排序将重点放在合并这个步骤,简单分解为两个部分,那么时间主要就是耗费在合并的环节;快速排序将重点放在分的部分,将数放入合适的位置来将数组分为两部分,那么时间主要就是耗费在分的环节。同时根据合并排序与快速排序的侧重点不同,各自又有不同的应用,像归并排序可用于求逆序对数目,于合并处做修改,而快排可用于找第k小的元素,利用分的结果。