9

免責事項: これは割り当て用です。私は明示的なコードを求めているのではなく、コードのエラーを修正できるように、関連するアルゴリズムを理解するのに十分な助けを求めているだけです。

さて、あなたはおそらく部分配列の最大化問題に精通しているでしょう: 配列内の連続する整数の最大ブロックを計算して返します。簡単ですが、この割り当てでは、O(n^3)、O(n^2)、O(n log n) という 3 つの異なる複雑さで実行する必要があります。最初の 2 つは問題なく取得できましたが (ブルート フォース)、3 番目は頭痛の種です..文字通り。

アルゴリズムがどのように機能するかを理解しています。配列は関数に渡され、再帰的に半分に分割され、個々のコンポーネントを比較して各半分の最大サブ配列を見つけます。次に、最大の部分配列は完全に左半分または右半分、または 2 つに重なるセグメントに存在する必要があるため、左右に重なる最大の部分配列を見つけなければなりません。各ケースの最大サブ配列を比較すると、最大のものが戻り値になります。

そのタスクを適切に実行するコードを書いたと思いますが、その評価は間違っているようです。インストラクターと TA に助けを求めて連絡を取ろうとしてきましたが、どちらともうまくいっているようには感じません。以下は、これまでになんとか書いたコードです。明らかなエラーがあれば教えてください。繰り返しますが、明示的なコードや回答を探しているわけではありませんが、何が間違っているのかを理解するのに役立ちます。ここで紹介した同様のケースをすべて調べましたが、本当に役立つものは見つかりませんでした。また、ガイダンスを求めて Google 検索を何度も行いましたが、それもあまり役に立ちません。とにかく、問題のコードは次のとおりです。

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 つのアルゴリズム間で一貫した正しい結果が得られましたが、これは正しくありません。

編集#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

4 に答える 4

5

私はまだあなたの問題を見つめていますが、すぐにいくつかのエラーに気付きました.

まず、firstlastがその名前が意図するものである場合、中点を正しく見つけられません。これをして:

mid = end/2;

これが必要な場合:

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

次に、最初の列挙ループが実行されます(右側[first,mid) の除外に注意してください)。midこのループには要素が含まれていません:arr[mid]

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

2 番目は から実行されますが、これにも次の要素[mid+1,last)は含まれません。arr[mid]

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

これにより、1 つの要素、具体的には の穴が残りarr[mid]ます。アルゴリズムを完全に理解しているとは言いませんが、アルゴリズムを読む機会はほとんどあり[first,last)ませんでした。また、SauceMaster によってリンクされた論文によって提示された教科書アルゴリズムは、配列にオフセットし、ポインター減衰を介して関数呼び出しにそれをベースアドレスとして渡すことを許可しない言語を使用するという明確な欠点があります。配列。C ではこれが可能であり、それを利用する必要があります。数値が理解しやすくなり、渡されたインデックスの必要性がなくなることがわかると思います。

例: 配列を受け取り、途中で分割して再帰する関数は、次のようになります。

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
    } 
}

各再帰では、分割ポイントは常に 1 つの範囲の終わりであり、再帰呼び出しで独自の 0 ベースの配列として扱われる 2 番目の範囲をオフセットするために使用されます。あなたがそれを使用できるかどうかはわかりませんが、(少なくとも私にとっては)把握するのが少し簡単になります。

最後に、あなたの除算は、私が見ることができるほど多くの再帰を行っていないようです。これは、結局のところ、再帰アルゴリズムであるため、問題になるでしょう。への呼び出しが少なくとも 1 つ欠けているようですdivide()

私は何かを見逃したかもしれませんが、それは初めてではありませんが、私が言ったように、私はそれにあまり注ぎ込みませんでした (まだ)。

于 2013-02-01T03:53:33.217 に答える
3

John Bentley は 1984 年にこれに関する論文を発表しました。オンラインで PDF を無料で見つけることができます

彼は、2 ページ目で O(n log n) アプローチに関する議論を開始します。

于 2013-02-01T02:20:19.587 に答える
2

必要なコードはほぼすべて揃っていると思いますが、次の 2 つの問題が際立っています。

  • mid変数の計算が疑わしい。
  • あなたのdivide関数は実際には除算を行っていません。

分割して征服するという再帰的な定式化では、配列の下半分で分割関数を再帰的に呼び出し、次に配列の上半分で分割関数を呼び出します。征服ステップは、オーバーラップの合計を計算し、3 つの候補のうち最大のものを返すコードと考えることができます。

編集:問題を再帰的に考えるときは、関数の目的を認識し、それを活用してください。この場合、divide関数は、指定された配列のサブ配列の最大合計を返します。したがって、計算方法maxLeftSumは左側のサブ配列で除算を呼び出すことです。についても同様です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;
}

幸運を!

于 2013-02-01T03:47:39.213 に答える
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;
}
于 2013-06-04T11:19:47.350 に答える