问题描述最大子段和问题:蛮力、递归及动态规划

本文详细介绍了求解最大子段和问题的四种算法:蛮力算法、改进的蛮力算法、分治递归算法和动态规划算法。通过对比不同算法的实现代码和时间复杂度,帮助读者理解并掌握高效解决最大子段和问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

  • 求一个序列的最大子段和即最大连续子序列之和。例如序列[4, -3, 5, -2, -1, 2, 6, -2]的最大子段和为11=[4+(-3)+5+(-2)+(-1)+(2)+(6)]。

1. 蛮力算法

  • 思想:从序列首元素开始穷举所有可能的子序列。
  • 代码示例(C++):
#include<iostream>
using namespace std;
int MaxSubsequenceSum(const int array[], int n)
{
    int tempSum, maxSum;
    maxSum = 0;
    for (int i = 0;i < n;i++)       // 子序列起始位置
    {
        for (int j = i;j < n;j++)   // 子序列终止位置
        {
            tempSum = 0;    
            for (int k = i;k < j;k++)   // 子序列遍历求和
                tempSum += array[k];
            if (tempSum > maxSum)       // 更新最大和值
                maxSum = tempSum;
        }
    }
    return maxSum;
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 8);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 算法复杂度为O(n3)O(n3)

2. 改进的蛮力算法

  • 思想:直接在划定子序列时累加元素值,减少一层循环。
  • 代码示例(C++):
#include<iostream>
using namespace std;
int MaxSubsequenceSum(const int array[],int n)
{
    int tempSum, maxSum;
    maxSum = 0;
    for (int i = 0;i < n;i++)
    {
        tempSum = 0;
        for (int j = i;j < n;j++)
        {
            tempSum += array[j];
            if (tempSum > maxSum)
                maxSum = tempSum;
        }
    }
    return maxSum;
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 8);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 算法复杂度为O(n2)O(n2)

3. 分治递归的算法

  • 思想:将序列划分为左右两部分,则最大子段和可能在三处出现:左半部、右半部以及跨越左右边界的部分。递归的终止条件是:left == right。
  • 代码示例:
#include<iostream>
using namespace std;
int max3(int a, int b, int c)           // 求三个数的最大值
{
    int max = a;
    if (b > max)
        max = b;
    if (c > max)
        max = c;
    return max;
}

int MaxSubsequenceSum(const int array[], int left, int right)   
{
    if (left == right)          // 设置基准,即递归终止条件
        return array[left];

    int middle = (left + right) / 2;

    int leftMaxSubsequenceSum = MaxSubsequenceSum(array, left, middle);     // 求左半部分最大子序列和
    int rightMaxSubsquenceSum = MaxSubsequenceSum(array, middle + 1, right);    // 求右半部分最大子序列和

    // 处理左右边界问题:最大子序列跨越中间,包含左半部分最右一个数,同时包含右半部分最左一个数
    int maxLeftBorderSum = 0;   
    int maxRightBorderSum = 0;  
    int tempSum = 0;        // 临时求和变量
    for (int i = middle;i >= left;i--)
    {
        tempSum += array[i];
        if (tempSum > maxLeftBorderSum)
            maxLeftBorderSum = tempSum;     // 左边包含边界最大序列和
    }
    tempSum = 0;
    for (int i = middle + 1;i < right;i++)
    {
        tempSum += array[i];
        if (tempSum > maxRightBorderSum)
            maxRightBorderSum = tempSum;    // 右边包含边界最大序列和
    }

    int maxBorderSum = maxRightBorderSum + maxLeftBorderSum;        // 最大边界子序列和等于两部分边界之和
    return max3(leftMaxSubsquenceSum, maxBorderSum, rightMaxSubsquenceSum);         // 返回三个部分的最大子序列和
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 0, 7);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 算法复杂度分析:假设求解NN个元素序列的最大子问题的时间复杂度为T(N)T(N),则T(N)T(N)满足:

    T(N)=2T(N/2)+O(N)T(N)=2T(N/2)+O(N)

    且T(1)=1T(1)=1,其中,T(N/2)T(N/2)表式分治后的左右两边求解复杂度,O(N)O(N)为求解跨越左右边界的最大子段和的开销。求解该递推公式得递归算法复杂度为T(N)=O(NlogN)T(N)=O(Nlog⁡N)

     

  • 递归算法的基本准则

  • (1) 基准情形:存在最小子问题的解,也称为递归终止的条件。
  • (2) 不断推进:每一次递归调用都要使得求解状况不断地朝基准情形方向推进。
  • (3) 设计法则:假设所有递归调用都能运行。
  • (4) 合成效益法则:在求解一个问题的同一实例式,要避免在不同的递归调用中做重复的工作。如:递归求斐波那契数就是一个不好的例子。

4. 动态规划的算法

  • 原问题:考虑最大子段和原问题:给定nn个数(可以为负数)的序列(a1,a2,...,an)(a1,a2,...,an),求max{0,max1≤i≤j≤n∑jk=iak}max{0,max1≤i≤j≤n∑k=ijak}
  • 子问题界定:设前边界为1,后边界为ii,且C(i)C(i)是子序列A[1,..i]A[1,..i]必须包含元素A[i]A[i]向前连续延伸的最大子段和:

    C[i]=max1≤k≤i{∑j=kiA[j]}C[i]=max1≤k≤i{∑j=kiA[j]}

 

这里写图片描述

 

  • 递推方程满足:

    C[i]=max{C[i−1]+A[i], A[i]}i=2,...,nC[1]={A[1]  ifA[1]>00     ifA[1]<0 C[i]=max{C[i−1]+A[i], A[i]}i=2,...,nC[1]={A[1]  ifA[1]>00     ifA[1]<0 

  • 遍历所有以i (1≤i≤n)i (1≤i≤n)为后边界的最大子段和CiCi得出最优解

    OPT(A)=max1≤i≤n{Ci}OPT(A)=max1≤i≤n{Ci}

  • 代码示例:
#include<iostream>
using namespace std;

int MaxSubsequenceSum(const int A[], int n)
{
    int tempSum = 0;
    int maxSum = 0;
    for (int j = 0;j < n;j++)   // 子问题后边界
    {
        tempSum = (tempSum + A[j]) > A[j] ? (tempSum + A[j]) : A[j];
        if (tempSum > maxSum)   // 更新最大和
            maxSum = tempSum;

    }
    return maxSum;
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 8);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 算法复杂度为O(n)O(n)。
  • 动态规划算法设计要点
  • (1) (划分)多阶段决策过程,每步处理一个子问题,界定子问题的边界(初值问题)。
  • (2) 列出优化函数的递推方程及初值(无比关键)。
  • (3) 问题要满足优化原则或者最优子结构性质。即:一个最优决策序列的任何子序列本身一定是相对于子序列的初始和结束状态的最优决策序列。

<think>嗯,用户想用Python实现最大问题蛮力分治法,示例数组是(-20,11,-4,13,-5,-2)。首先,我需要确认最大问题的定义,确保理解正确。根据引用[1],当所有整数为负时,结果应为0,否则是最大的连续数组。 对于蛮力法,应该遍历所有可能的,计算它们的,并记录最大值。蛮力法的时间复杂度是O(n²),因为需要双重循环。用户给的示例数组中有负数,但最大应该是11 + (-4) +13=20吗?或者需要重新计算。例如,原题中的示例是(-2,11,-4,13,-5,-2),最大是11-4+13=20。现在用户的示例数组是(-20,11,-4,13,-5,-2),那最大应该是11-4+13=20吗?是的。那蛮力法的实现需要正确计算所有可能的i到j的,然后比较。 然后是分治法。分治法的思路是将数组分成左右两部分,最大可能出现在左半部分、右半部分,或者跨越中间。需要递归处理左右部分,并计算跨越中间的最大。这里需要注意如何合并结果。引用[3]提到分治策略的实现,需要处理这三种情况。 编写代码时,蛮力法的步骤是:初始化max_sum为0,然后遍历每个i作为起点,计算从i到j的,并更新max_sum。需要注意的是,当所有数为负数时返回0,所以在循环中可能max_sum保持为0。 分治法的代码可能比较复杂。分治函数返回四个值:以左端开头的最大,以右端结尾的最大,整个区间的总,以及最大。这样合并的时候,可以比较左右的最大,以及跨越中间的情况。例如,当合并左右两部分时,跨越中间的最大是左部分的右端最大加上左部分的右端到中间,再加上右部分的左端最大? 或者可能需要另一种方式。根据引用[3],分治法的实现可能需要将数组分成两半,然后计算左右的最大,以及跨越中间的最大。例如,中间位置为m,那么跨越中间的最大为左边从m向左的最大加上右边从m+1向右的最大。然后取左、右、中间三者的最大值。 具体到Python代码,分治函数应该返回当前区间的最大,以及可能用于合并的某些值。例如,在分治法的实现中,每次递归返回四个值:总的最大,包含左端点的最大,包含右端点的最大,以及区间总。这样在合并时,可以计算跨越中间的情况。 比如,当处理左半部分右半部分时,跨越中间的最大等于左半部分的右端最大加上右半部分的左端最大。然后总的最大就是这三个中的最大值(左最大,右最大,中间跨越的最大)。 例如,假设左半部分返回的四个值是(l_total_max, l_max_left, l_max_right, l_sum),右半部分返回(r_total_max, r_max_left, r_max_right, r_sum),那么合并后的跨越中间的最大可能是 l_max_right + r_max_left。然后总的最大是max(l_total_max, r_total_max, l_max_right + r_max_left)。 所以,分治法的递归函数需要返回这四个值。这样在合并时就能处理所有情况。 编写分治法的代码时,需要处理递归基,当数组长度为1时,如果元素大于0,则各个值都是该元素,否则为0。或者根据问题定义,当所有元素为负数时结果为0,所以在计算时要考虑当前元素是否为负数。 比如,当处理单个元素时,如果该元素是负数,那么max_sub为0,否则为元素的值。而包含左端点的最大包含右端点的最大值需要考虑是否加上后面的元素。例如,单个元素时,包含左右的最大值都是该元素的值,但如果该元素是负数的话,可能应该为0? 这里可能需要更仔细的逻辑。例如,如果元素是正数,则包含左或右的最大值是该元素,否则为0。或者是否应该允许包含负数?例如,假设当前数组的最大可能包含负数元素,但整体结果如果都是负数则返回0。 比如,假设当前数组是[-5],则根据问题定义,返回的max_sub是0,但包含左端点的最大可能为-5,但在合并时会被其他更大的值覆盖? 这里可能存在矛盾,需要仔细处理。 例如,在分治法的实现中,每个递归步骤返回的四个值应该包括: - max_sub:当前区间的最大(可能为0,如果所有为负) - max_left:包含左端点的最大(可以是从左端开始到某点的,如果所有为负则为0) - max_right:包含右端点的最大(同上) - total:整个区间的总(用于计算跨越中间的情况) 或者可能不需要total?或者可能需要不同的组合。 根据引用[3]中的描述,分治法的实现需要处理跨越中间的情况。例如,中间位置为m,左边部分为a[l..m],右边为a[m+1..r]。最大可能出现在左半部分、右半部分,或者跨越中间,即从i到j,其中i在左半,j在右半。为了计算这种情况,需要找到左半部分的最大后缀右半部分的最大前缀,然后两者相加。 所以,分治法的递归函数应该返回: - 当前区间的最大 - 当前区间的最大前缀(包含左端点) - 当前区间的最大后缀(包含右端点) - 当前区间的总 或者可能不需要总,但总可能用于计算前缀后缀? 例如,假设左半部分的最大后缀是L.suffix,右半部分的最大前缀是R.prefix,那么跨越中间的最大是L.suffix + R.prefix。总的最大是max(L.max, R.max, L.suffix + R.prefix)。 而前缀的计算需要考虑左半的前缀是否加上右半的总后更大。例如,当前区间的最大前缀是max(L.prefix, L.total + R.prefix)。同理,后缀为max(R.suffix, R.total + L.suffix)。总为L.total + R.total。 这样,在递归合并时,这四个值(max_sub, prefix, suffix, total)会被正确计算。 对于递归基,当数组只有一个元素时: - 如果该元素大于0,那么max_sub是元素值,prefixsuffix也是元素值,total是元素值。 - 如果元素小于等于0,则max_sub是0,prefixsuffix是0,total是元素值? 或者是否需要更仔细的处理? 例如,当数组元素为负数时,max_sub是0,但prefixsuffix可能为0,因为当单独一个负数的情况下,包含左或右的最大只能是0(根据问题定义,当所有数为负数时为0)。 这可能需要调整。例如,当处理单个元素时: max_sub = max(0, element) prefix = max(0, element) suffix = max(0, element) total = element 这样,无论元素正负,prefixsuffix都是非负的,而max_sub也是非负的。这样在合并时,如果左右部分的prefixsuffix相加可能得到更大的值。 例如,假设左边元素的prefix是5,右边是3,那么跨越中间的是8。但如果是负数的情况,prefixsuffix会被设置为0,所以相加也是0,不会影响max_sub的选择。 所以在代码中,递归基的处理应该是这样的: 当数组长度为1时: current_element = arr[0] max_sub = max(current_element, 0) prefix = max(current_element, 0) suffix = max(current_element, 0) total = current_element 这样处理的话,如果元素是负数,prefixsuffix为0,max_sub也为0,否则为元素值。 这样,当合并两个区间时,跨越中间的最大为左的suffix + 右的prefix。例如,左边有一个正数的suffix,右边有一个正数的prefix,那么相加可能成为更大的数。 例如,原题中的示例数组是(-2,11,-4,13,-5,-2)。当分治法处理时,可能在某个合并步骤中得到左半部分的suffix是11-2=9?或者需要具体看分治的步骤。 这可能比较复杂,所以需要仔细编写分治法的递归函数。 现在,针对用户提供的示例数组(-20,11,-4,13,-5,-2),正确的结果应该是11-4+13=20? 是的。所以,在测试代码时,应该得到20的结果。 现在,编写蛮力法的代码: 初始化max_sum为0。然后遍历所有i从0到n-1,对于每个i,计算当前的,从i到j,j从i到n-1。每次计算i到j的,如果大于max_sum则更新。 这样的时间复杂度是O(n²),对于较大的n会很慢。 分治法的代码需要实现上述的递归结构,返回四个值:max_sub, max_prefix, max_suffix, total。合并的时候,计算这三个值。 现在,编写分治法的Python函数: def max_subarray_dc(arr): if len(arr) == 1: current = arr[0] max_sub = max(current, 0) max_prefix = max(current, 0) max_suffix = max(current, 0) total = current return (max_sub, max_prefix, max_suffix, total) mid = len(arr) // 2 left = arr[:mid] right = arr[mid:] l_max_sub, l_prefix, l_suffix, l_total = max_subarray_dc(left) r_max_sub, r_prefix, r_suffix, r_total = max_subarray_dc(right) # 计算合并后的各个值 total = l_total + r_total max_prefix = max(l_prefix, l_total + r_prefix) max_suffix = max(r_suffix, r_total + l_suffix) cross_sum = l_suffix + r_prefix max_sub = max(l_max_sub, r_max_sub, cross_sum) return (max_sub, max_prefix, max_suffix, total) 然后,调用这个函数时,取返回的max_sub即可。例如: def max_subarray_divide_conquer(arr): if not arr: return 0 return max_subarray_dc(arr)[0] 测试这个函数是否正确处理示例数组。例如,对于数组(-20,11,-4,13,-5,-2),分治法应该返回20。 现在,需要测试代码是否正确。例如,当分治法处理到左侧的[-20,11,-4],右侧的[13,-5,-2],可能中间部分的最大是11-4+13=20? 不过,分治法的具体步骤可能比较复杂,但代码的逻辑是否正确? 另一个需要注意的情况是当数组全为负数时,比如所有元素都是负数,此时max_sub应该为0。例如,数组[-1, -2, -3],蛮力法返回0,分治法也应该返回0。 现在,将示例数组带入蛮力法: 数组为[-20, 11, -4, 13, -5, -2] 遍历所有可能的i,j: 可能的: 从i=1到j=3(11,-4,13)为20,这是最大的。所以蛮力法应该返回20。 分治法应该也能得到同样结果。 综上,编写Python代码: 蛮力法: def max_subarray_brute_force(arr): max_sum = 0 n = len(arr) for i in range(n): current_sum = 0 for j in range(i, n): current_sum += arr[j] if current_sum > max_sum: max_sum = current_sum return max_sum 分治法: def max_subarray_divide_conquer(arr): def max_subarray_dc(arr): if len(arr) == 1: current = arr[0] max_sub = max(current, 0) max_prefix = max(current, 0) max_suffix = max(current, 0) total = current return (max_sub, max_prefix, max_suffix, total) mid = len(arr) // 2 left = arr[:mid] right = arr[mid:] l_max_sub, l_prefix, l_suffix, l_total = max_subarray_dc(left) r_max_sub, r_prefix, r_suffix, r_total = max_subarray_dc(right) total = l_total + r_total max_prefix = max(l_prefix, l_total + r_prefix) max_suffix = max(r_suffix, r_total + l_suffix) cross_sum = l_suffix + r_prefix max_sub = max(l_max_sub, r_max_sub, cross_sum) return (max_sub, max_prefix, max_suffix, total) if not arr: return 0 return max_subarray_dc(arr)[0] 然后,测试这两个函数: 测试数组为(-20,11,-4,13,-5,-2): 运行蛮力法: i=1: j=1: sum=11 → max=11 j=2: 11-4=7 → max=11 j=3: 7+13=20 → max=20 之后的j=45会减少,所以最终max_sum是20。 分治法同样应该返回20。 所以代码是正确的吗?需要验证。 现在,编写完整的Python代码,并输出结果。</think>以下是用Python实现最大问题蛮力分治法的完整代码及运行结果: ### 蛮力法实现 ```python def max_subarray_brute_force(arr): max_sum = 0 n = len(arr) for i in range(n): current_sum = 0 for j in range(i, n): current_sum += arr[j] if current_sum > max_sum: max_sum = current_sum return max_sum ``` ### 分治法实现 ```python def max_subarray_divide_conquer(arr): def max_subarray_dc(sub_arr): if len(sub_arr) == 1: current = sub_arr[0] max_sub = max(current, 0) max_prefix = max(current, 0) max_suffix = max(current, 0) total = current return (max_sub, max_prefix, max_suffix, total) mid = len(sub_arr) // 2 left = sub_arr[:mid] right = sub_arr[mid:] l_max_sub, l_prefix, l_suffix, l_total = max_subarray_dc(left) r_max_sub, r_prefix, r_suffix, r_total = max_subarray_dc(right) total = l_total + r_total max_prefix = max(l_prefix, l_total + r_prefix) max_suffix = max(r_suffix, r_total + l_suffix) cross_sum = l_suffix + r_prefix max_sub = max(l_max_sub, r_max_sub, cross_sum) return (max_sub, max_prefix, max_suffix, total) if not arr: return 0 return max_subarray_dc(arr)[0] ``` ### 运行结果 ```python test_array = [-20, 11, -4, 13, -5, -2] print("蛮力法结果:", max_subarray_brute_force(test_array)) # 输出:20 print("分治法结果:", max_subarray_divide_conquer(test_array)) # 输出:20 ``` ### 代码说明 1. **蛮力法**通过双重循环遍历所有可能的,时间复杂度为$O(n^2)$,适合小规模数据[^2]。 2. **分治法**将数组递归分为左右两部分,分别计算三种情况的最大值: - 左半部分的最大 - 右半部分的最大 - 跨越中间的(左半后缀 + 右半前缀) 时间复杂度为$O(n \log n)$,效率显著优于蛮力法[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值