关于排序:合并排序算法中的合并

Merging in merge sort algorithm

我知道合并排序算法的基本概念,但是当它通过递归实现时,我很难理解它是如何工作的。据我所知,merge sort函数将当前数组拆分为两半,使用递归,我们一直这样做,直到每边有一个元素为止。

如果我们的数组是38、27、43、3、9、82、10,那么递归将从使用子数组(原始数组的左侧)调用自身开始,并每次重复该过程,将数组减半并存储最左侧,直到我们到达一个元素:

1
2
3
4
5
6
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

然后,在第二个子例程中,我们转到下一行:right=merge_sort(right),它再次调用自身来拆分子数组并存储最右侧:

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

然后在第二个子例程中,我们转到下一行:result=merge(左,右),它调用merge函数来排序我们的左数组和右数组,它们只有38和27个。merge函数根据较小的值对两个值进行排序,然后将第一个值添加到数组中,尽管我不确定哪个数组。(我需要这方面的规范;我们不应该每次合并两个以前的数组时都有一个新的数组吗?)然后,merge函数将"result"从调用merge函数返回到merge sort函数中的另一个result变量。我假设这个结果是按顺序排序的新数组38和27。然后,看起来我们再次将结果返回到任何称为合并排序函数的函数,但是我很困惑,因为这不会结束所有的操作吗?对于为左侧递归而暂停的第一个子例程呢?我不知道会发生什么:

1
2
3
4
5
6
38 27 43 3
      43 3
      43
and
      43 3
         3

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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()函数有几种变体;一种可能是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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


我不确定您是否在寻找它,但是您可以通过在主要条件下用and替换or来简化合并循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

是的,有三个循环,但最后两个循环中只有一个被执行。

在伪代码的基础上紧密地工作C99代码

因此,代码使用C99可变长度数组(C11中的可选功能)。如果使用-DDEBUG编译,那么在程序运行时将得到广泛的跟踪。如果没有编译,则只打印输入(未排序)和输出(排序)数组。我需要它来诊断一个愚蠢的打字错误(一个r_pos,其中显然需要l_pos)。注意一般技术:

  • 文档进入和退出功能
  • 创建一个诊断打印函数(此处为dump_array(),其中一个参数是"tag"(用于标识正在使用的调用),另一个参数是要打印的数据结构。
  • 在适当的位置调用诊断打印功能。
  • 使启用或禁用诊断变得容易。
  • 对于生产质量代码,我的诊断打印功能还接受一个FILE *fp参数并写入给定的文件;我在这里欺骗并使用stdout。额外的通用性意味着该函数可以用于写入stderr或日志文件,也可以代替stdout

    空间管理

    merge_sort()代码将完整的输入数组复制到两个较小的数组(leftright中,然后对较小的数组(递归)进行排序,并将排序后的较小数组合并到输入数组中。这发生在递归的每个log n级别。一些经验测试表明,使用的空间约为2n项,即O(n)空间使用。

    Shouldn't we have a new array every time we merge two previous arrays?

    在函数式编程语言中,您将拥有新的数组。在C语言中,也使用输入数组作为输出数组。代码将原始输入数组复制到单独的较小数组中,对这些较小的数组进行排序,并将排序后的较小数组合并到原始数组中。

    My other question is what procedure in the code allows us to go back to before the recursion where we split the left side of our array so we can work on the right side to get 43 a 3 in order to merge them as well.

    拆分过程将创建输入数组的副本(因此原始数据中的信息暂时多余)。合并过程将(现在已排序)拆分的数组复制回原始数组。(主要是重复自己的话。)

    来源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    #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
    ", func);
    }

    static void exit_func(const char *func)
    {
        printf("<<-- %s
    ", 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('
    ');
    }

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

    样本输出

    非调试

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

    调试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    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