最全面的排序算法的比较

149 阅读7分钟

欢迎来到我们的排序算法比较文章。在这里,我们将根据几个基本因素对各种排序算法进行比较。

  • 时间复杂度
  • 空间复杂度
  • 稳定的/不稳定的
  • 实际字段测试

我们将通过尝试描述每种算法最适合的地方,以及它们的强项和弱项来完成这一切。每种算法都是独一无二的,并在其特有的某些情况下表现得最好。


时间复杂度的比较

一个表格显示了一些最常用的排序算法的时间复杂度。在比较两种排序算法时,时间复杂度是你需要检查的第一件事。时间复杂度越低越好。

排序算法平均情况最佳案例最差情况
泡沫排序O(n2)O(n)O(n2)
插入式排序O(n2)O(n)O(n2)
选择排序O(n2)O(n2)O(n2)
快速排序O(n.log(n))O(n.log(n))O(n2)
合并排序O(n.log(n))O(n.log(n))O(n.log(n))
堆排序O(n.log(n))O(n.log(n))O(n.log(n))
计数排序O(n+k)O(n+k)O(n+k)
径向排序O(n*k)O(n*k)O(n*k)
桶式排序O(n+k)O(n+k)O(n2)

我们在上表中使用了一种颜色方案,以帮助我们进行排序算法的比较。红色是最差的,O(n2)算法位于其下。接下来是O(n.log(n))算法,这是一个中间地带。最好的时间复杂度是O(n),这是最快的算法。

以后我们做实际的现场测试时,你可以用这个表作为参考。你会发现时间复杂度对性能的影响有多大。


空间复杂度的比较

虽然速度很重要,而且通常是你的首要任务,但有时在有内存限制的地方,具有低内存成本的算法是首选。

下表显示了各种排序算法的空间复杂度。你可能会注意到,空间复杂度较高的算法是那些 "不在原地 "的算法,而空间复杂度最低的则是在原地的算法。当然,这是因为 "离位 "算法创建了额外的数组来存储数据,而 "就位 "算法则使用相同的数组。

不言而喻,最好的空间复杂度是O(1)。

排序算法空间复杂度
泡沫排序O(1)
插入式排序O(1)
选择排序O(1)
快速排序O(log(n))
合并排序O(n)
堆排序O(1)
计数排序O(k)
矩阵排序O(n + k)
桶式排序O(n)

稳定的和不稳定的算法

这是一个相当小众的用途,只在某些类型的数据中产生实际的差异。然而,它仍然是这些特定场景所需要的一个重要要求。

排序算法稳定的排序?
泡沫排序是的
插入式排序是的
选择排序没有
快速排序没有
合并排序
堆积排序没有
计数排序
径向排序
桶状分类是的

然而,重要的是要注意,你通常可以创建上述算法的稳定版本。上图中提到的那些算法是 "经典 "版本的算法。


你也可以查看我们关于排序算法的YouTube系列


排序算法--字段测试

最后,我们要测量的是主要部分,即性能。我们已经在各种情况下测试了这里的9种算法。从100个数字到10,000个数字,以及使用已经排序的数据进行测试,这些测试将揭示相当多的东西。

测试方法

我使用Google Collab来运行这些测试,以确保一个稳定和公平的测试环境。要明确的是,这些排序算法的代码是用Python写的,而且是以相当标准的方式写的。没有进行大规模的优化,而且只使用了标准(经典)版本的算法。

Pythontimeit随机库被用来生成随机数据,并对每个算法进行连续和重复测试。这也是为了确保公平的结果。随机库用于生成从1到10,000的数字,timeit库总共进行了5次测试,并返回所有5次的列表。我们同时显示了5次测试的最大值和最小值,所以你可以看到次数的位移。

5次测试中的每一次实际上都是在运行代码10次,(数字参数,默认值为1,000,000)。通过做大量的测试并将数值相加来平均化,这就提高了准确性。如果你想要一个单独的排序的时间,请将最小/最大值除以10。重复次数是由重复参数控制的(默认值为5)。

你可以在下面的代码中看到测试功能的代码。如果你跟随timeit和随机库的链接,你可以了解更多关于这里发生的事情。

import random
import timeit
import sys

def test():
  SETUP_CODE = '''
from __main__ import sort
from random import randint'''
  
  TEST_CODE = '''
array = []
for x in range(1000):
  array.append(x)
sort(array)
  '''
  times = timeit.repeat( setup = SETUP_CODE,
                          stmt = TEST_CODE,  
                          number = 10,
                          repeat = 5) 

  print('Min Time: {}'.format(min(times)))
  print('Max Time: {}'.format(max(times)))


排序算法--性能比较

在本节中,我们将进行三组测试。第一组将有100个随机数,第二组将有1000个,第三组将有10000个。好好看看这个表,比较一下时间的复杂性,并做出你自己的观察。我将在这之后分享我的观察结果。

排序算法测试1 (100)测试2 (1000)测试3 (10000)
泡沫排序最小。0.01008秒最大
:0.0206秒
最小。1.0242秒Max
: 1.0558秒
最小。100.922秒Max
: 102.475秒
插入排序最小。0.00306秒Max
: 0.00650秒
最低限度。0.0369秒Max
: 0.0562秒
最小值。100.422秒Max
: 102.344秒
选择排序最小。0.00556秒Max
: 0.00946秒
最低限度。0.4740秒Max
: 0.4842秒
最小值。40.831秒Max
: 41.218秒
快速排序最小。0.00482秒Max
: 0.01141秒
最低限度。0.0370秒Max
: 0.0383秒
最低限度。0.401秒Max
: 0.420秒
合并排序最小。0.00444秒Max
: 0.00460秒
最小。0.0561秒Max
: 0.0578秒
最小。0.707秒Max
: 0.726秒
堆排序最小。0.00489秒Max
: 0.00510秒
最小。0.0704秒Max
: 0.0747秒
最小。0.928秒Max
: 0.949秒
计数排序最小。0.01929秒Max
: 0.02052秒
最低限度。0.0354秒Max
: 0.0400秒
最小值。0.195秒Max
: 0.203秒
矩阵排序最小。0.00315秒Max
: 0.00394秒
最小。0.0294秒Max
: 0.0309秒
最小。0.313秒Max
: 0.338秒
桶状分类最小。0.00225秒Max
: 0.00241秒
最低限度。0.0335秒Max
: 0.0369秒
最小值。1.854秒Max
: 1.892秒

我还想包括对100,000和1,000,000个数字的测试,但O(n2)算法要花很长时间才能完成,所以我放弃了。

观察结果

  1. O(n2)算法(泡沫和插入排序)在测试数量增加到10,000时反应非常差。在10,000个数字时,其他算法的平均速度超过100倍。
  2. 在只有100个数字的测试案例中,O(n2)算法比O(n.log(n))算法快。
  3. 数字的数量每增加10倍,O(n2)算法的完成时间就增加100倍。
  4. 平均而言,Radix Sort和Counting Sort是最快的算法。
  5. Heapsort是最快的算法,空间复杂度为O(1)。

分类数据的比较

另一个非常有趣的情况是使用分类数据,而不是随机数据。这个测试主要是为了显示哪些排序算法在排序/部分排序的数据中表现良好,哪些表现较差。

排序算法已排序的数据(1000)。
泡沫排序最小。0.542秒最大值
:0.556秒
插入式排序最小。0.790秒最大值
:0.821秒
选择排序最小值。0.434秒Max
: 0.464秒
快速排序最小值。0.812秒最大值
:0.872秒
合并排序最小。0.0289秒Max
: 0.0364秒
堆积排序最小。0.0604秒Max
: 0.0661秒
计数排序最小。0.0055秒Max
: 0.0124秒
弧度排序最小。0.0119秒Max
: 0.0145秒
桶式排序最小。0.0183秒Max
: 0.0247秒

观察结果

  1. 惊喜吧,惊喜吧。快速排序算法名不副实,在上述所有算法中,对于1000个排序的数字,快速排序算法是最慢的。这是因为Quick Sort对这样的退化情况反应不佳,需要进行特殊的优化,如 "随机枢轴"。
  2. 除了快速排序之外,所有算法所需的时间都下降了。
  3. 计数排序的表现最好,其次是Radix和Bucket排序。