【问题标题】:How to improve Merge Sort speed in python如何提高python中的合并排序速度
【发布时间】:2016-01-20 21:49:57
【问题描述】:

是的,这是作业,但我最终用 Java 完成它只是为了完成它,但现在 python 实现困扰着我。我很确定我已经正确地实现了它,但它需要的时间比它应该的要长。在 300 万次输入上,它需要 25 到 32 秒。我假设它与我拼接和附加到列表的方式有关。我这里有源代码,如果你看到什么,请告诉我。

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

【问题讨论】:

  • 为什么你认为 25-32 秒比应该花费的时间长?
  • 因为,我的教授说它太长了(我要求确定)。 Java 实现大约需要 5 分钟。
  • 所以您可以选择语言?迁移到 Java 的 5-6 倍改进对我来说似乎并不算太​​糟糕。也就是说,您可以通过避免复制数据来加快速度。
  • 是的。我只是想尝试将其缩短到 10 秒以下。并不是说它意味着什么,因为我通常只使用 pythons Tim sort。但更多的是一种教育活动。因为我工作的地方,我们在大多数项目中都使用 python,所以知道如何优化任何涉及列表的东西会很好。

标签: python algorithm sorting python-3.x optimization


【解决方案1】:

我认为你是对的。切片创建一个包含切片元素的新列表。这必然是一项昂贵的操作。

在 Java 中,没有通用的切片功能。但是,如果您使用List.subList,它将返回原始视图而不是副本,我认为会快得多。就地数组操作,会更快。

【讨论】:

  • 我也将尝试完全初始化结果列表,而不是在我回家时使用 append 扩展它。昨天完成了作业,但这一直困扰着我。
【解决方案2】:

来自 Rishav Kundu 链接到的先前线程:


您可以在对mergesort的顶级调用中初始化整个结果列表:

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

然后,对于递归调用,您可以使用一个辅助函数,您可以将其传递给的不是子列表,而是x 的索引。底层调用从x读取它们的值并直接写入result


为此,seq 数组的参数需要是对 seq 和辅助数组的引用。

你还可以添加一个参数来跟踪合并的方向,从而避免复制回步。使用 mtoa 标志的 C 示例,表示从 b 合并到 a(如果为 false,则表示将 a 合并到 b)。在我的系统 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);
}

另一种选择是使用自下而上的合并排序,它跳过递归步骤并开始将偶数运行与奇数运行合并,初始运行大小为 1。

【讨论】:

    【解决方案3】:

    使用

    while True:
    

    而不是

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

    对我来说速度提高了 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:]
    

    最后一行似乎并没有让它更快,但我更喜欢它。

    【讨论】:

    • 这似乎是个糟糕的主意。使用 try catch 块,然后故意抛出 IndexError 来破坏?这只是草率。
    • @DJMcMayhem 不,那是 EAFP。而且速度更快。问题是什么。
    • 使用@StefanPochmann 的这个新代码,100 万个数字的运行时间为 7.2 ~ 7.3 秒。 (PC 是宏碁 Aspire E15)
    猜你喜欢
    • 1970-01-01
    • 2017-10-06
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多