分治策略之最大子数组问题
2016-06-09 19:56
507 查看
分治策略的说明
分治策略是将一个大问题,不断分解成多个容易解决、与大问题形式相同的小问题,然后将小问题的解组合一起来得出最终大问题的解。在分治策略中将执行如下三个步骤:
分解:将大问题分解成多个与大问题形式相同的小问题
解决:如果问题的规模不够小则继续将小问题分解成更小的问题,如果问题的规模足够小,直接求解
合并:将小问题的解一层层地合并,最终得到大问题的解
实现分治策略的技术,通常是递归。
最大子数组问题
问题简单叙述:给一个连续的数组,数组中既有正数也有复数。
数组中一个数或者连续的一组数称为子数组,求其和。
和最大的子数组,称为最大子数组。
暴力求解
最简单直接的方法就是逐个逐个子数组去检查,但时间复杂度是Θ(n²)int find_max_subarray(int *arr, int len) { int max_sum = arr[0]; for (int i = 0; i < len; i++) { int sum = 0; for (int j = i; j < len; j++) { sum += arr[j]; if (sum > max_sum) max_sum = sum; } } return max_sum; }
分治策略求解
分治策略要求将一个大问题分解成形式相同的小问题。那么,在分解这个大数组的时候,要尽可能将大数组分解成两个长度相同的小数组,即从中间开始划分。
然后在左数组中寻找最大子数组,在右数组中寻找最大子数组,不断递归下去。
但是,又有另一个问题,如果最大子数组跨越了中间点,那又要单独考虑。
因此,在每次递归的时候需要解决三个问题:寻找左数组中的最大子数组,寻找右数组中的最大子数组,寻找跨越中间点的最大子数组。
这三个问题的解,最大的那个,就是最终的解。
利用分治策略求解,时间复杂度降到了O(nlgn)
int find_max_crossing_subarray(int *arr, int low, int mid, int high) { int sum = 0; int left_sum = arr[mid]; sum = 0; for (int i = mid; i >= low; i--) { sum += arr[i]; if (sum > left_sum) left_sum = sum; } int right_sum = arr[mid + 1]; sum = 0; for (int i = mid + 1; i <= high; i++) { sum += arr[i]; if (sum > right_sum) right_sum = sum; } return (left_sum + right_sum); } int find_max_subarray(int *arr, int low, int high) { if (low == high) return arr[low]; int mid = (low + high) / 2; int left_max = find_max_subarray(arr, low, mid); int right_max = find_max_subarray(arr, mid + 1, high); int cross_max = find_max_crossing_subarray(arr, low, mid, high); if (left_max > right_max && left_max > cross_max) return left_max; else if (right_max > left_max && right_max > cross_max) return right_max; else return cross_max; }
对比两种求解方式的差异
利用下面一段完整的代码计算两者的时间消耗#include <cstdlib> #include <ctime> #include <iostream> using namespace std; int find_max_subarray(int *arr, int len) { int max_sum = arr[0]; for (int i = 0; i < len; i++) { int sum = 0; for (int j = i; j < len; j++) { sum += arr[j]; if (sum > max_sum) max_sum = sum; } } return max_sum; } int find_max_crossing_subarray(int *arr, int low, int mid, int high) { int sum = 0; int left_sum = arr[mid]; sum = 0; for (int i = mid; i >= low; i--) { sum += arr[i]; if (sum > left_sum) left_sum = sum; } int right_sum = arr[mid + 1]; sum = 0; for (int i = mid + 1; i <= high; i++) { sum += arr[i]; if (sum > right_sum) right_sum = sum; } return (left_sum + right_sum); } int find_max_subarray(int *arr, int low, int high) { if (low == high) return arr[low]; int mid = (low + high) / 2; int left_max = find_max_subarray(arr, low, mid); int right_max = find_max_subarray(arr, mid + 1, high); int cross_max = find_max_crossing_subarray(arr, low, mid, high); if (left_max > right_max && left_max > cross_max) return left_max; else if (right_max > left_max && right_max > cross_max) return right_max; else return cross_max; } int main() { const int MAX = 100000; srand(time(0)); int *arr = new int[MAX]; for (int i = 0; i < MAX; i++) { arr[i] = rand() % 1000; } clock_t start_1 = clock(); int result_1 = find_max_subarray(arr, MAX); clock_t end_1 = clock(); cout << "The result: " << result_1 << endl; cout << "Time consuming: " << static_cast<double>(end_1 - start_1) / CLOCKS_PER_SEC << endl; clock_t start_2 = clock(); int result_2 = find_max_subarray(arr, 0, MAX - 1); clock_t end_2 = clock(); cout << "The result: " << result_2 << endl; cout << "Time consuming: " << static_cast<double>(end_2 - start_2) / CLOCKS_PER_SEC << endl; return 0; }输出结果是
在数据规模是10万的情况下,就能看出两者的巨大差异。
至此,能够初步看到分治策略解决问题的强大。