用于查找最大子数组的分而治之算法 - 如何同时提供结果子数组索引?

Posted

技术标签:

【中文标题】用于查找最大子数组的分而治之算法 - 如何同时提供结果子数组索引?【英文标题】:Divide and Conquer Algorithm for Finding the Maximum Subarray - How to also provide the result subarray indexes? 【发布时间】:2017-01-13 22:55:07 【问题描述】:

对不起,我有一个任务是使用Brute Force Algorithm O(n^2)、Divide and Conquer O(nlogn) 和Kadane's Algorithm O(n) 解决Maximum Sub Array Problem。 (我的代码不同)。

例如,对于值序列−2, 1, −3, 4, −1, 2, 1, −5, 4,总和最大的连续子数组是[4, −1, 2, 1],总和为6” - 来自维基页面。

我已经完成了 Kadane 和 BruteForce,我需要的输出不仅仅是找到总和,还包括找到的子数组的 起始索引结束索引强>.

我当前的DivideAndConquer 代码让我得到了正确的总和。但是,我看不到跟踪索引的方法,因为我递归地实现了它(当然)。而且我不知道在这种情况下是否唯一的方法是使用全局变量(我不喜欢)..你能帮忙解决这个问题吗?还是我需要改变整个设计?

#include <iostream>

int DivideAndConquer(int[], int);

int main()

    // Example 1
    //const int MyArraySize = 16;
    //int MyArray[MyArraySize] = 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 ; // answer: Index 7 -> 10, sum = 43

    // Example 2
    const int MyArraySize = 8;
    int MyArray[MyArraySize] =  -2, -5, 6, -2, -3, 1, 5, -6 ; // answer: Index 2 -> 6, sum = 7

    int FinalResult;

    FinalResult = DivideAndConquer(MyArray, MyArraySize);
    std::cout << "Using Divide And Conquer: With O(nlogn) Sum = " << FinalResult << "\n\n";

    system("pause");
    return 0;


int DivideAndConquer(int* _myArray, int _myArraySize)

    if (_myArraySize == 1)
        return _myArray[0];

    int middle = _myArraySize / 2;
    int Result_LeftPortion = DivideAndConquer(_myArray, middle);
    int Result_RightPortion = DivideAndConquer(_myArray + middle, _myArraySize - middle);

    int LeftSum = -9999;
    int RightSum = -9999;
    int TotalSum = 0;

    for (int i = middle; i < _myArraySize; i++)
    
        TotalSum += _myArray[i];
        RightSum = TotalSum < RightSum ? RightSum : TotalSum;
    

    TotalSum = 0;

    for (int i = middle - 1; i >= 0; i--)
    
        TotalSum += _myArray[i];
        LeftSum = TotalSum < LeftSum ? LeftSum : TotalSum;
    

    int PartialResult = LeftSum < RightSum ? RightSum : LeftSum;
    int Result= (PartialResult < LeftSum + RightSum ? LeftSum + RightSum : PartialResult);

    return Result;

【问题讨论】:

你的递归函数可以有另一个输出变量(通过引用)吗? @πάνταῥεῖ 是的。 return Tuple&lt;int, int*, int*&gt;&amp; 之类的东西完全没问题。但是我仍然看不到如何跟踪索引,因为我没有迭代地做它.. 你昨天得到了那个答案(或多或少):) @πάνταῥεῖ 工厂的东西还是建议的return Tuple&lt;..&gt; 不,它的逻辑不对。我现在正在写一个完整的答案。 【参考方案1】:

您的算法存在逻辑问题并且不是最优的。您甚至没有使用 Result_LeftPortionResult_RightPortion 值。您的最终结果始终是整个数组中 RightSumLeftSumTotalSum 的最大值。所有其他子数组的值都会被忽略。

用分而治之的方式解决这个问题的一种方法如下。您应该为每个子数组保存四个值:

    包含左元素(s_l)的最大总和 包含正确元素的最大总和 (s_r) 整个数组的总和(t) 上述值的最大值 (mx)

如果您正在检查大小为 1 的子数组,所有这些值都等于该元素的值。 当合并两个子数组(sub_left,sub_right)时,这些值将是:

    s_l = max( sub_left.s_l, sub_left.t + sub_right.s_l ) s_r = max(sub_right.s_r, sub_right.t + sub_left.s_r) t = sum( sub_left.t + sub_right.t ) mx = max(s_l, s_r, t, sub_right.mx, sub_left.mx, sub_left.r+sub_right.l)

最终结果将是数组的mx 的值。 为了找到具有最大总和的子数组的位置,您应该为这些值中的每一个保留一个右索引和左索引,并在执行合并时相应地更新它们。考虑这种情况

sub_left.s_r range is (2,5)
sub_right.t range is (6,10)
if ( sub_right.t + sub_left.s_r > sub_right.s_r )
      s_r range = (2,10)

这是我的实现:

#include <iostream>
using namespace std;

struct node 
    //value, right index, left index
    int value,  r,  l;
    node(int _v, int _r, int _l)
        value = _v;
        r = _r;
        l = _l;
    
    node ()

;

struct sub 
    // max node containing left element
    // max node containing right element
    // total node
    // max node
    node s_l, s_r, t, mx;
    sub ( node _l, node _r, node _t, node _mx )
        s_l = _l;
        s_r = _r;
        t = _t;
        mx = _mx;
    
    sub()
;


sub DivideAndConquer(int* _myArray, int left, int right)


    if(right == left)
        node n (_myArray[left],right,left);
        return sub( n, n, n, n);
    
    int mid = (left+right)/2;
    sub sub_left = DivideAndConquer( _myArray, left, mid);
    sub sub_right = DivideAndConquer( _myArray, mid+1, right);

    sub cur;
    if ( sub_left.t.value + sub_right.s_l.value > sub_left.s_l.value )
        cur.s_l.value = sub_left.t.value + sub_right.s_l.value;
        cur.s_l.r = sub_right.s_l.r;
        cur.s_l.l = sub_left.s_l.l;
     else 
        cur.s_l = sub_left.s_l;
    

    if ( sub_right.t.value + sub_left.s_r.value > sub_right.s_r.value )
        cur.s_r.value = sub_right.t.value + sub_left.s_r.value;
        cur.s_r.l = sub_left.s_r.l;
        cur.s_r.r = sub_right.s_r.r;
     else 
        cur.s_r = sub_right.s_r;
    

    cur.t.value = sub_right.t.value + sub_left.t.value;
    cur.t.r = sub_right.t.r;
    cur.t.l = sub_left.t.l;

    if ( cur.s_r.value >= cur.s_l.value &&
         cur.s_r.value >= cur.t.value &&  
         cur.s_r.value >= sub_left.mx.value &&
         cur.s_r.value >= sub_right.mx.value )
        cur.mx = cur.s_r;
     else if ( cur.s_l.value >= cur.s_r.value &&
         cur.s_l.value >= cur.t.value &&  
         cur.s_l.value >= sub_left.mx.value &&
         cur.s_l.value >= sub_right.mx.value )
        cur.mx = cur.s_l;
     else if ( sub_left.mx.value >= cur.s_l.value &&
         sub_left.mx.value >= cur.t.value &&  
         sub_left.mx.value >= cur.s_r.value &&
         sub_left.mx.value >= sub_right.mx.value )
        cur.mx = sub_left.mx;
     else 
        cur.mx = sub_right.mx;
    

    if ( sub_left.s_r.value + sub_right.s_l.value > cur.mx.value )
        cur.mx.value = sub_left.s_r.value + sub_right.s_l.value;
        cur.mx.l = sub_left.s_r.l;
        cur.mx.r = sub_right.s_l.r;
    
    return cur;


int main()

    // Example 1
    //const int MyArraySize = 16;
    //int MyArray[MyArraySize] = 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 ; // answer: Index 7 -> 10, sum = 43

    // Example 2
    const int MyArraySize = 8;
    int MyArray[MyArraySize] =  -2, -5, 6, -2, -3, 1, 5, -6 ; // answer: Index 2 -> 6, sum = 7

    sub FinalResult = DivideAndConquer(MyArray, 0,MyArraySize-1);
    std::cout << "Sum = " << FinalResult.mx.value << std::endl;
    std::cout << "( " << FinalResult.mx.l << " , " << FinalResult.mx.r << ")" << std::endl;

 //   system("pause");
    return 0;

注意:此算法运行时间为O(n)

【讨论】:

这绝对值得更多的支持。它突出显示了我的代码中的一个逻辑错误(我虽然它是完美的),它提供了正确方法的设计,它提供了解决我的问题而不仅仅是原始问题的设计(我的问题是还提供索引)此外,它还提供了一种新的实现,时间复杂度更低!为辛勤工作脱帽致敬:)我仍然会尝试修复我的代码,但这是完美的答案。我正在编辑标题,以便将来使用此 Q 将其他 Q 标记为与此重复。【参考方案2】:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define MINUS_INFINITY -1e10

float Find_max_cross_subarray(int I[],float A[])

    float left_sum = MINUS_INFINITY;
    float right_sum = MINUS_INFINITY;
    float sum = 0;
    int left_max,right_max,i;

    for(i=I[2];i>=I[0];i--)
        sum = sum + A[i];
        if(sum > left_sum)
        left_sum = sum;
        left_max = i;
        
    
     sum = 0;
    for(i=I[2]+1;i<=I[1];i++)
    sum = sum + A[i];
    if(sum > right_sum)
        right_sum = sum;
        right_max = i;
    

I[0] = left_max;
I[1] = right_max;
sum = left_sum + right_sum;
return sum;


float Find_max_subarray(int I[],float A[])

int sum,mid;
int left[2];
int right[2];
int cross[3];
float left_sum,right_sum,cross_sum;
if(I[0] == I[1])
    sum = A[I[0]];

else 
    mid = floor((I[0]+I[1])/2);
    left[0] = I[0];
    left[1] = mid;
    right[0] = mid + 1;
    right[1] = I[1];
    cross[0] = I[0];
    cross[1] = I[1];
    cross[2] = mid;
    left_sum = Find_max_subarray(left,A);
    right_sum = Find_max_subarray(right,A);
    cross_sum = Find_max_cross_subarray(cross,A);
    if((left_sum >= right_sum)&&(left_sum >= cross_sum))
        sum = left_sum;
        I[0] = left[0];
        I[1] = left[1];
    
    else if((right_sum >= left_sum)&&(right_sum >= cross_sum))
        sum = right_sum;
        I[0] = right[0];
        I[1] = right[1];
    
    else
        sum = cross_sum;
        I[0] = cross[0];
        I[1] = cross[1];
    

return sum;


int main()

int n,i;
float max_sum;
int I[2];

float A[100] = 13, -3, -25, 20, -3,-16,-23,18,20,-7,12,-5,-22,15,-4,7; 
n = sizeof(A)/sizeof(A[0]); 
I[0] = 0;
I[1] = n-1;
max_sum = Find_max_subarray(I,A);
printf("Maximum sub array is A[%d ......%d] with sum %f",I[0],I[1],max_sum);


【讨论】:

以上是关于用于查找最大子数组的分而治之算法 - 如何同时提供结果子数组索引?的主要内容,如果未能解决你的问题,请参考以下文章

使用分而治之的最大子数组

最大和子数组 - 返回子数组和总和 - 分而治之

最大子数组:分而治之

在 nxn 的二维数组中查找局部最大值

使用分治算法在未排序的数组中查找最大和

用于查找数组中最小值的分治算法