您的位置:首页 > 其它

分治策略之最大子数组问题

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万的情况下,就能看出两者的巨大差异。

至此,能够初步看到分治策略解决问题的强大。

参考书籍

《算法导论》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  分治策略