2

マージソートアルゴリズムの基本的な概念は知っていますが、再帰を介して実装するとなると、その仕組みを理解するのに苦労します。私が理解していることから、マージソート関数は現在の配列を2つの半分に分割し、再帰を使用して、各側に1つの要素が残るまでこれを続けます。

配列が {38, 27, 43, 3, 9, 82, 10} の場合、再帰はサブ配列 (元の配列の左側) を使用して自分自身を呼び出すことから始まり、毎回プロセスを繰り返し、配列を半分にして格納します。 1 つの要素に到達するまで、最も左側:

38 27 43 3 9 82 10
38 27 43 3 <-split
<---first subroutine/recursion
38 27 <-split
<---second subroutine/recursion
38 <---only 1 element left so we return the value back to the first subroutine that called

次に、2 番目のサブルーチンで、次の行に進みます: right = merge_sort(right) これは、サブ配列を分割し、最も右側を格納するために再びそれ自体を呼び出します。

38 27 <-split
<---second subroutine/recursion
   27
<---only 1 element left so we return the value back to the first subroutine that called

次に、2 番目のサブルーチンで次の行に進みます。result = merge(left, right) これは、merge 関数を呼び出して、左右の配列を並べ替えます。この配列は 38 と 27 だけです。merge 関数は、2 つの値に基づいて並べ替えます。小さいので、最初の配列を配列に追加しますが、どの配列かはわかりません。(これについては仕様が必要です。前の 2 つの配列をマージするたびに新しい配列を作成するべきではありませんか?) 次に、マージ関数は、マージ関数を呼び出した結果、マージ ソート関数内の別の結果変数に「結果」を返します。この結果は、38 と 27 が順番に並べ替えられた新しい配列であると想定しています。次に、その結​​果をマージソート関数と呼ばれるものに再度返しているように見えますが、それですべてが終了しないので混乱しています? 左側の再帰のために一時停止した最初のサブルーチンはどうですか? 何が起こるかわかりません:

38 27 43 3
      43 3
      43
and
      43 3
         3

擬似コード:

 function merge_sort(m)
    if length(m) ≤ 1
        return m
    var list left, right, result


    var integer middle = length(m) / 2
    for each x in m up to middle
         add x to left
    for each x in m after middle
         add x to right
    left = merge_sort(left)
    right = merge_sort(right)
    result = merge(left, right)
    return result

merge_sort 関数を記述した後、上で作成した左右のリストをマージする必要があります。merge() 関数にはいくつかのバリエーションがあります。1つの可能性はこれです:

function merge(left,right)
    var list result
    while length(left) > 0 or length(right) > 0
        if length(left) > 0 and length(right) > 0
            if first(left) ≤ first(right)
                append first(left) to result
                left = rest(left)
            else
                append first(right) to result
                right = rest(right)
        else if length(left) > 0
            append first(left) to result
            left = rest(left)             
        else if length(right) > 0
            append first(right) to result
            right = rest(right)
    end while
    return result

http://www.princeton.edu/~achaney/tmve/wiki100k/docs/Merge_sort.html

4

1 に答える 1

3

それがあなたが探しているものかどうかはわかりませんが、主な条件orをに置き換えることでマージ ループを簡素化できます。and

while length(left) > 0 and length(right) > 0
    if first(left) ≤ first(right)
        append first(left) to result
        left = rest(left)
    else
        append first(right) to result
        right = rest(right)
end while

# You know that one of left and right is empty
# Copy the rest of the data from the other
while length(left) > 0
    append first(left) to result
    left = rest(left)             
end while
while length(right) > 0
    append first(right) to result
    right = rest(right)
end while

はい、3 つのループがありますが、最後の 2 つのうち 1 つだけが実行されます。


疑似コードに基づいた C99 コードの動作

したがって、コードは C99 可変長配列 (C11 のオプション機能) を使用します。でコンパイルすると-DDEBUG、プログラムの実行中に広範なトレースが得られます。なしでコンパイルすると、入力 (ソートされていない) 配列と出力 (ソートされた) 配列のみが出力されます。愚かなタイプミスを診断するためにそれが必要でした(aが明らかに必要なr_pos場所)。l_pos一般的なテクニックに注意してください。

  1. ドキュメントの入力と関数の終了
  2. 診断出力関数を作成します (ここdump_array()では、1 つの引数に「タグ」(どの呼び出しが使用されているかを識別するため)、その他の引数には出力するデータ構造を指定します)。
  3. 適切なポイントで診断印刷機能を呼び出します。
  4. 診断を簡単に有効または無効にできます。

製品品質のコードの場合、私の診断出力関数もFILE *fp引数を取り、指定されたファイルに書き込みます。ここでだまされて使用されstdoutました。追加の一般性は、この関数を使用して、stderrまたはログ ファイルに書き込むことができることを意味しますstdout

スペース管理

このmerge_sort()コードは、完全な入力配列を 2 つの小さな配列 (leftright) にコピーしてから、小さな配列を並べ替え (再帰)、並べ替えられた小さな配列を入力配列にマージします。これは、log N レベルの再帰のそれぞれで発生します。いくつかの実験的テストでは、使用されるスペースは約 2N 個のアイテムであることが示されています。これは、O(N) スペースの使用量です。

以前の 2 つの配列をマージするたびに、新しい配列を作成する必要があるのではないでしょうか?

関数型プログラミング言語では、新しい配列があります。C では、入力配列を出力配列としても使用します。このコードは、元の入力配列を別の小さな配列にコピーし、それらの小さな配列を並べ替えて、並べ替えられた小さな配列を元の配列にマージします。

私のもう 1 つの質問は、配列の左側を分割する再帰の前に戻ることができるコードの手順です。これにより、右側で作業して 43 a 3 を取得し、それらをマージすることもできます。

分割プロセスにより、入力配列のコピーが作成されます (そのため、元のデータの情報は一時的に不要になります)。マージ プロセスは、(現在ソートされている) 分割された配列を元の配列にコピーします。(主に自分自身を繰り返します。 )

ソース

#include <stddef.h>

extern void merge_sort(int *array, size_t arrlen);

/* Debug */
#ifdef DEBUG
static void dump_array(const char *tag, int *array, size_t len);
static void enter_func(const char *func);
static void exit_func(const char *func);
#else
#define dump_array(t, a, l) ((void)0)
#define enter_func(f)       ((void)0)
#define exit_func(f)        ((void)0)
#endif

/*
function merge(left, right)
   var list result
    while length(left) > 0 and length(right) > 0
        if first(left) ≤ first(right)
            append first(left) to result
            left = rest(left)
        else
            append first(right) to result
            right = rest(right)
    end while

    # You know that one of left and right is empty
    # Copy the rest of the data from the other
    while length(left) > 0
        append first(left) to result
        left = rest(left)             
    end while
    while length(right) > 0
        append first(right) to result
        right = rest(right)
    end while
    return result
end function
*/

static void merge(int *left, size_t l_len, int *right, size_t r_len, int *output)
{
    size_t r_pos = 0;
    size_t l_pos = 0;
    size_t o_pos = 0;
    enter_func(__func__);
    dump_array("Left:", left, l_len);
    dump_array("Right:", right, r_len);
    while (r_pos < r_len && l_pos < l_len)
    {
        if (right[r_pos] < left[l_pos])
            output[o_pos++] = right[r_pos++];
        else
            output[o_pos++] = left[l_pos++];
    }
    while (r_pos < r_len)
        output[o_pos++] = right[r_pos++];
    while (l_pos < l_len)
        output[o_pos++] = left[l_pos++];
    dump_array("Output:", output, r_len + l_len);
    exit_func(__func__);
}

/*
function merge_sort(m)
    if length(m) ≤ 1
        return m
    var list left, right, result

    var integer middle = length(m) / 2
    for each x in m up to middle
        add x to left
    for each x in m after middle
        add x to right
    left = merge_sort(left)
    right = merge_sort(right)
    result = merge(left, right)
    return result
*/

void merge_sort(int *array, size_t len)
{
    if (len <= 1)
        return;
    int left[(len+1)/2];
    int l_pos = 0;
    int right[(len+1)/2];
    int r_pos = 0;
    size_t mid = len / 2;

    enter_func(__func__);
    dump_array("Input:", array, len);
    for (size_t i = 0; i < mid; i++)
        left[l_pos++] = array[i];
    for (size_t i = mid; i < len; i++)
        right[r_pos++] = array[i];
    dump_array("Left:", left, l_pos);
    dump_array("Right:", right, r_pos);
    merge_sort(left, l_pos);
    merge_sort(right, r_pos);
    merge(left, l_pos, right, r_pos, array);
    dump_array("Result:", array, len);
    exit_func(__func__);
}

/* Test code */
#include <stdio.h>

#ifdef DEBUG
static void enter_func(const char *func)
{
    printf("-->> %s\n", func);
}

static void exit_func(const char *func)
{
    printf("<<-- %s\n", func);
}
#endif

/* dump_array is always used */
#undef dump_array

static void dump_array(const char *tag, int *array, size_t len)
{
    printf("%-8s", tag);
    for (size_t i = 0; i < len; i++)
        printf(" %2d", array[i]);
    putchar('\n');
}

int main(void)
{
    int array[] = { 38, 27, 43, 3, 9, 82, 10 };
    size_t arrlen = sizeof(array) / sizeof(array[0]);

    dump_array("Before:", array, arrlen);
    merge_sort(array, arrlen);
    dump_array("After:", array, arrlen);
    return 0;
}

サンプル出力

非デバッグ

Before:  38 27 43  3  9 82 10
After:    3  9 10 27 38 43 82

デバッグ

Before:  38 27 43  3  9 82 10
-->> merge_sort
Input:   38 27 43  3  9 82 10
Left:    38 27 43
Right:    3  9 82 10
-->> merge_sort
Input:   38 27 43
Left:    38
Right:   27 43
-->> merge_sort
Input:   27 43
Left:    27
Right:   43
-->> merge
Left:    27
Right:   43
Output:  27 43
<<-- merge
Result:  27 43
<<-- merge_sort
-->> merge
Left:    38
Right:   27 43
Output:  27 38 43
<<-- merge
Result:  27 38 43
<<-- merge_sort
-->> merge_sort
Input:    3  9 82 10
Left:     3  9
Right:   82 10
-->> merge_sort
Input:    3  9
Left:     3
Right:    9
-->> merge
Left:     3
Right:    9
Output:   3  9
<<-- merge
Result:   3  9
<<-- merge_sort
-->> merge_sort
Input:   82 10
Left:    82
Right:   10
-->> merge
Left:    82
Right:   10
Output:  10 82
<<-- merge
Result:  10 82
<<-- merge_sort
-->> merge
Left:     3  9
Right:   10 82
Output:   3  9 10 82
<<-- merge
Result:   3  9 10 82
<<-- merge_sort
-->> merge
Left:    27 38 43
Right:    3  9 10 82
Output:   3  9 10 27 38 43 82
<<-- merge
Result:   3  9 10 27 38 43 82
<<-- merge_sort
After:    3  9 10 27 38 43 82
于 2013-08-03T03:03:30.687 に答える