【问题标题】:Merging in merge sort algorithm合并排序算法中的合并
【发布时间】:2013-08-04 10:12:30
【问题描述】:

我知道归并排序算法的基本概念,但是在通过递归实现它时,我很难理解它是如何工作的。据我了解,合并排序函数将我们当前的数组分成两半,并使用递归,我们一直这样做,直到每边只剩下 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

然后在我们的第二个子程序中,我们转到下一行: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

然后在我们的第二个子例程中,我们继续下一行:result = merge(left, right) 它调用合并函数对我们的左右数组进行排序,这些数组只有 38 和 27。合并函数对我们的两个值进行排序基于哪个更小,然后它将第一个添加到数组中两个先前的数组?)然后合并函数将“结果”返回给我们的合并排序函数中的另一个结果变量,因为调用了合并函数。我假设这个结果是按顺序排列了 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() 函数有几种变体;一种可能性是:

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

【问题讨论】:

    标签: algorithm sorting mergesort


    【解决方案1】:

    我不确定它是否是您要查找的内容,但您可以通过在主要条件中将 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
    

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


    基于伪代码的工作 C99 代码

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

    1. 文档进入和退出函数
    2. 创建一个诊断打印函数(此处为 dump_array(),其中一个参数是“标签”(用于识别正在使用的调用),其他参数是要打印的数据结构。
    3. 在适当的时候调用诊断打印函数。
    4. 轻松启用或禁用诊断。

    对于生产质量代码,我的诊断打印函数也采用FILE *fp 参数并写入给定文件;我在这里作弊并使用了stdout。额外的通用性意味着该函数可用于写入stderr 或日志文件,以及或代替stdout

    空间管理

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

    难道不应该每次合并之前的两个数组都有一个新数组吗?

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

    我的另一个问题是代码中的什么过程允许我们回到递归之前,我们拆分数组的左侧,这样我们就可以在右侧工作以获得 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
    

    【讨论】:

    • 我对将这些单个元素合并在一起的过程感到困惑。我用什么数组来合并它们?它是一个新数组吗?然后我也对我们返回“结果”感到困惑,因为这不会结束整个过程吗?那么 43 和 3 (最后是原始数组的右侧)呢?子程序仍然暂停。
    • 我需要好好想想——这是个好问题。我在这台机器上的合并排序示例是针对链表的,您不会遇到此问题,因为输出列表的存储空间与输入列表的存储空间相同;下一个指针只是重新定位到正确的列表。在我的另一台机器上(距离我坐的地方大约 6,000 英里,并关闭),我想我还有一些其他工作的合并排序示例。
    • 在此页面的 RHS 中,我看到了指向以下问题的链接:How to sort in-place using the merge sort algorithm?Space requirements of a merge sort? — 这些或其中列出的任何其他链接对您有帮助吗?
    • 我不这么认为,那些似乎与运行时间有关。也许这张照片可以更好地说明我的一个问题。我用了3种颜色imageshack.us/f/209/dp4w.jpg
    • 您在普林斯顿的网页链接讨论了就地合并排序。请注意,该算法使用列表,并将项目添加到列表中。列表不仅仅是数组。除其他事项外,伪代码假定您可以确定列表的长度(您不能在 C 中使用数组),并且您始终可以将项目添加到列表中(您不能总是这样做用 C 中的一个数组)。因此,处理伪代码需要做大量工作。然而,一旦每个“节点”都在一个列表中,所需的额外空间是 O(1)——当然,列表的空间是 O(N)。
    猜你喜欢
    • 2021-06-27
    • 1970-01-01
    • 2013-03-27
    • 2015-12-10
    • 2021-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多