最大子阵列:分而治之

时间:2013-02-01 02:00:58

标签: c algorithm recursion divide-and-conquer

免责声明:这是作业。我不是要求显式代码,只是帮助理解所涉及的算法,以便我可以修复代码中的错误。

好的,所以你可能熟悉最大的子阵列问题:计算并返回数组中最大的连续整数块。很简单,但这个任务需要我以三种不同的复杂性来做:O(n ^ 3),O(n ^ 2)和O(n log n)。我没有太多麻烦(蛮力)得到了前两个,但第三个让我头疼......字面意思。

我理解该算法应该如何工作:一个数组被传递给一个函数,该函数递归地将它分成两半,然后比较各个组件以找到每一半中的最大子数组。然后,因为最大子阵列必须完全位于左半部分或右半部分,或者在与两者重叠的部分中,所以必须找到与左右重叠的最大子阵列。比较每种情况的最大子阵列,最大值将是您的返回值。

我相信我已经编写了充分执行该任务的代码,但在评估中我似乎错了。我一直试图联系教练和助教,但我不觉得我和其中任何一个人在一起。下面是我迄今为止设法编写的代码。如果你发现任何明显错误,请告诉我。同样,我不是在寻找明确的代码或答案,而是帮助理解我做错了什么。我已经查看了这里提供的所有类似案例,但没有找到任何可以帮助我的东西。我也做了大量的谷歌搜索指导,但这也没有多大帮助。无论如何,这里是有问题的代码:

int conquer(int arr[], int first, int mid, int last) {

    int i = 0;
    int maxLeft = 0;
    int maxRight = 0;

    int temp = 0;
    for (i = mid; i >= first; i--) {
        temp = temp + arr[i];
        if (maxLeft < temp) {
            maxLeft = temp;
        }
    }
    temp = 0;
    for (i = (mid + 1); i <= last; i++) {
        temp = temp + arr[i];
        if (maxRight < temp) {
            maxRight = temp;
        }
    }

    return (maxLeft + maxRight);
}

int divide(int arr[], int start, int end) {

    int i;
    int maxSum;
    int maxLeftSum;
    int maxRightSum;
    int maxOverlapSum;

    if (start == end) {
        return arr[start];
    } else {
        int first = start;
        int mid = (end / 2);
        int last = end;

        maxSum = 0;
        maxLeftSum = 0;

        for (i = first; i < mid; i++) {
            maxSum = maxSum + arr[i];
            if (maxLeftSum < maxSum) {
                maxLeftSum = maxSum;
            }
        }
        for (i = (mid + 1); i < last; i++) {
            maxSum = maxSum + arr[i];
            if (maxRightSum < maxSum) {
                maxRightSum = maxSum;
            }
        }

        maxOverlapSum = conquer(arr, first, mid, last);
    }

    if ((maxLeftSum > maxRightSum) && (maxLeftSum > maxOverlapSum)) {
        return maxLeftSum;
    } else if ((maxRightSum > maxLeftSum) && (maxRightSum > maxOverlapSum)) {
        return maxRightSum;
    } else
        return maxOverlapSum;
}

编辑:我得到的错误是错误的结果。我的两个算法之间的结果一致且正确,但这个算法不正确。

编辑#2:这是我的代码的更新版本,稍微减少了一些,我修复了一些问题。它仍然没有正确运行,但它应该更接近......

#include <stdio.h>
#include <stdlib.h>

int numarr[] = {22, -27, 38, -34, 49, 40, 13, -44, -13, 28, 46, 7, -26, 42,
        29, 0, -6, 35, 23, -37, 10, 12, -2, 18, -12, -49, -10, 37, -5, 17,
        6, -11, -22, -17, -50, -40, 44, 14, -41, 19, -15, 45, -23, 48, -1,
        -39, -46, 15, 3, -32, -29, -48, -19, 27, -33, -8, 11, 21, -43, 24,
        5, 34, -36, -9, 16, -31, -7, -24, -47, -14, -16, -18, 39, -30, 33,
        -45, -38, 41, -3, 4, -25, 20, -35, 32, 26, 47, 2, -4, 8, 9, 31, -28,
        36, 1, -21, 30, 43, 25, -20, -42};

int length = ((sizeof(numarr))/(sizeof(int)));

int divide(int left, int right) {

    int mid, i, temp, mLeft, mRight, mCross = 0;

    if (left == right) {
        return left;
    } else if (left > right) {
        return 0;
    } else {
        mid = (left + right) / 2;

        divide(left, mid);
        divide(mid + 1, right);

        for (i = mid; i >= left; i--) {
            temp = temp + numarr[i];
            if (mLeft < temp) {
                mLeft = temp;
            }
        }

        for (i = mid + 1; i <= right; i++) {
            temp = temp + numarr[i];
            if (mRight < temp) {
                mRight = temp;
            }
        }

        mCross = (mLeft + mRight);

        printf("mLeft:  %d\n", mLeft);
        printf("mRight: %d\n", mRight);
        printf("mCross: %d\n", mCross);

        return 0;
    }
}

int main(int argc, char const *argv[])
{
    divide(0, length);
    return 0;
}

4 个答案:

答案 0 :(得分:5)

我仍在盯着你的问题,但我几乎立即注意到了几个错误。

首先,如果firstlast与其名称相符,则会发现中点不正确。你这样做:

mid = end/2;

应该是这样的:

mid = first + (last-first)/2;

接下来,您的第一个枚举循环从[first,mid)开始运行(请注意右侧的mid除外)。此循环包含arr[mid]元素:

    for (i = first; i < mid; i++) {

您的第二次来自[mid+1,last),也不包含arr[mid]元素:

    for (i = (mid + 1); i < last; i++) {

这留下了一个元素的洞,特别是arr[mid]。现在,我并没有声称我完全理解这个算法,因为我几乎没有机会阅读它,但是如果你的目的是涵盖[first,last)的整个范围,这很可能不会它。另外,由SauceMaster链接的论文所提出的教科书算法的明显缺点是使用一种语言,它不允许你偏移到数组中,并通过指针衰减将它作为一个函数调用传递给一个函数调用。阵列。 C允许你这样做,你应该利用它。我想你会发现它使数字更容易理解,并且不需要你传入的索引。

例如:一个接受数组和mid-splits和recurses的函数看起来像这样:

void midsplit( int arr[], int len)
{
    if (len < 2)
    {
         // base case
    }
    else
    {
        int mid = len/2;
        midsplit(arr, mid);
        midsplit(arr+mid, len-mid);

        // cumulative case
    } 
}

在每次递归中,分割点始终是一个范围的结束,并用于偏移地寻址第二个范围,在递归调用中将其视为自己的基于0的数组。 Dunno,如果你可以使用它,但它确实让它更容易(至少对我来说)掌握。

最后,你的鸿沟似乎并没有做太多的递归,我可以看到,这将是一个问题,因为这毕竟是一个递归算法。看来你至少错过了一次divide()的电话。

我可能错过了一些东西,这不是第一次,但就像我说的那样,我还没有过多地倾注它(还)。

答案 1 :(得分:3)

John Bentley于1984年发表了一篇论文。您可以在线免费找到PDF:http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/05/Bentley84.pdf

他在第二页开始讨论O(n log n)方法。

答案 2 :(得分:2)

我认为你几乎拥有所需的所有代码,但这两个问题对我来说很突出:

  • mid变量计算是可疑的。
  • 您的divide功能实际上没有进行任何划分。

在一个征服的递归公式中,你会递归地在数组的下半部分调用divide函数,然后在数组的上半部分。征服步骤可以被认为是计算重叠总和并返回三个候选者中最大的一个的代码。

编辑:在递归地思考问题时,实现函数的目的,并利用它。在这种情况下,divide函数将返回所提供数组的最大子数组总和。因此,计算maxLeftSum的方法是在左子阵列上调用divide。同样适用于maxRightSum

int divide(int arr[], int start, int end) {
    if (end > start) {
        int mid = (start + end)/2;
        int maxLeftSum = divide(arr, start, mid);
        int maxRightSum = divide(arr, mid+1, end);
        return conquer(arr, start, end, maxLeftSum, maxRightSum);
    }
    return (start == end) ? arr[start] : 0;
}
祝你好运!

答案 3 :(得分:1)

我认为你只专注于交叉子阵列部分。但是,还有左子阵列部分和右子阵列部分,它们都有可能大于交叉子阵列。

因为英语不是我的第一语言,我不擅长。我不能说服我已经表达了我要表达的内容,所以我会粘贴我的代码。 这是我的代码:

int find_max_subarr(int a[],int l,int r)
{
    if(l>r)return 0;
    if(l==r) return a[l];
    int lsum=-1000,rsum=-1000;
    int sum=0;
    if(l<r) {
        int mid=(l+r)/2;
        for(int i=mid;i>=l;i--) {
            sum+=a[i];
            if(lsum<sum)lsum=sum;
        }
        sum=0;
        for(int i=mid+1;i<=r;i++) {
            sum+=a[i];
            if(rsum<sum)rsum=sum;
        }
        int all_sum=lsum+rsum;
        int llsum=find_max_subarr(a,l,mid);
        int rrsum=find_max_subarr(a,mid+1,r);
        if(llsum<all_sum&&rrsum<all_sum) return all_sum;
        if(all_sum<llsum&&rrsum<llsum)return llsum;
        if(all_sum<rrsum&&llsum<rrsum)return rrsum;
    }
}

int main()
{
    int a[SUM]={100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97};
    int b[SUM-1];
    int t=0;
    for(int i=1;i<SUM;i++) {
        b[t]=a[i]-a[i-1];
        t++;
    }
    int sum=find_max_subarr(b,0,t-1);
    cout<<sum<<endl;
    return 0;
}