简体   繁体   English

如何提高 python 中的合并排序速度

[英]How to improve Merge Sort speed in python

Yes, it is homework, but I ended up doing it in Java just to get it done, but now the python implementation is bothering me.是的,这是作业,但我最终在 Java 完成它只是为了完成它,但现在 python 实现让我感到困扰。 I'm pretty sure I've implemented it correctly, but it takes way longer than it should.我很确定我已经正确地实施了它,但它花费的时间比它应该的要长。 On 3 million inputs it take on anywhere from 25 to 32 seconds.在 300 万个输入上,它需要 25 到 32 秒的时间。 I'm assuming it has something to do with the way I'm splicing, and appending to the list.我假设它与我拼接和附加到列表的方式有关。 I have the source code here, let me know if you see anything.我这里有源代码,如果你看到什么请告诉我。

def merge_sort(seq):
    if len(seq) == 1:
        return seq
    left = merge_sort(seq[:len(seq) // 2])
    right = merge_sort(seq[len(seq) // 2:])

    return merge(left, right)


def merge(left, right):
    result = []
    left_count = 0
    right_count = 0
    while len(left) > left_count and len(right) > right_count:
        if left[left_count] > right[right_count]:
            result.append(right[right_count])
            right_count += 1
        else:
            result.append(left[left_count])
            left_count += 1

    while len(left) > left_count:
        result.append(left[left_count])
        left_count += 1

    while len(right) > right_count:
        steps += 1
        result.append(right[right_count])
        right_count += 1

    return result

I think you are correct. 我认为你是对的。 Slicing creates a new list containing the elements sliced. 切片会创建一个包含切片元素的新列表。 This is necessarily a costly operation. 这必然是一项昂贵的操作。

In Java, there is no general slicing feature. 在Java中,没有一般的切片功能。 However if you used List.subList that would return a view of the original instead of a copy and I would presume be much faster. 但是,如果您使用List.subList将返回原始视图而不是副本,我认为会更快。 In-place array manipulation, would be faster still. 就地阵列操作,会更快。

Using 运用

while True:

instead of 代替

while len(left) > left_count and len(right) > right_count:

makes it about 40-45% faster for me: 让我大约快40-45%:

def merge(left, right):
    result = []
    left_count = 0
    right_count = 0
    try:
        while True:
            if left[left_count] > right[right_count]:
                result.append(right[right_count])
                right_count += 1
            else:
                result.append(left[left_count])
                left_count += 1
    except:
        return result + left[left_count:] + right[right_count:]

That last line doesn't seem to make it faster, but I like it a lot better. 最后一行似乎没有让它更快,但我更喜欢它。

From the prior thread linked to by Rishav Kundu: 来自Rishav Kundu链接的先前帖子:


You can initialise the whole result list in the top level call to mergesort: 您可以在对mergesort的顶级调用中初始化整个结果列表:

result = [0]*len(x)   # replace 0 with a suitable default element if necessary. 
                      # or just copy x (result = x[:])

Then for the recursive calls you can use a helper function to which you pass not sublists, but indices into x . 然后对于递归调用,您可以使用辅助函数,而不是将子列表传递给x ,而将索引传递给x And the bottom level calls read their values from x and write into result directly. 底层调用从x读取它们的值并直接写入result


For this to work, the parameter to the seq array needs to be a reference to seq and also the helper array. 为此,seq数组的参数需要是对seq和辅助数组的引用。

You can also add a parameter to keep track of what direction to merge, so that the copy back step is avoided. 您还可以添加参数以跟踪要合并的方向,以避免复制步骤。 C example using mtoa flag that means merge from b to a (if false, it means merge a to b). C示例使用mtoa标志,表示从b合并到a(如果为false,则表示将a合并到b)。 On my system, Intel 2600K 3.4ghz, this code sorts 4 million pseudo random 32 bit unsigned integers in about 0.36 seconds, and 16 million in about 1.6 seconds. 在我的系统上,Intel 2600K 3.4ghz,此代码在大约0.36秒内分配400万个伪随机32位无符号整数,在大约1.6秒内分类1600万。

void TopDownMergeSort(int seq[], size_t n)
{
int * b;
    if(n < 2)
        return;
    b = malloc(n * sizeof(seq[0]));
    TopDownSplitMerge(seq, b, 0, n, true);
    free(b);
}

void TopDownSplitMerge(int a[], int b[], size_t ll, size_t ee, bool mtoa)
{
size_t rr;
    if ((ee - ll) == 1){                    // if size == 1
        if(!mtoa)                           //  copy to b if merging a to b
            b[ll] = a[ll];
        return;
    }
    rr = (ll + ee)>>1;                      // midpoint, start of right half
    TopDownSplitMerge(a, b, ll, rr, !mtoa);
    TopDownSplitMerge(a, b, rr, ee, !mtoa);
    if(mtoa)                                // if merging to a, merge b to a
        Merge(b, a, ll, rr, ee);
    else                                    // else merge a to b
        Merge(a, b, ll, rr, ee);
}

Another option would be to use bottom up merge sort, which skips the recursion steps and just starts merging even runs with odd runs, with an initial run size of 1. 另一种选择是使用自下而上的合并排序,它会跳过递归步骤,只是开始合并甚至是奇数运行的运行,初始运行大小为1。

This is 2.7x faster than the op's code and 2x faster than @Stefan Pochmann 's

def merge(left, right):
    result = []
    left_count = 0
    right_count = 0

    try:
        while True:
            result.append(right[right_count] if left[left_count] > right[right_count] else left[left_count])
            right_count += left[left_count] > right[right_count]
            left_count += left[left_count] <= right[right_count]
    except:
        return result + (left[left_count:] if len(left) > left_count else right[right_count:])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM