更高效的排序算法?
我正在寻找一种比Arrays.sort()
更好的Arrays.sort()
。 我知道这看起来像是一个万次问的愚蠢的问题,但请继续阅读。
让我们有两个实现Comparable
的类,它们的自然顺序基于int值。 第一个compareTo
方法如下所示:
public int compareTo(ComparableInteger o) { return this.value - o.value; }
第二个是:
public int compareTo(ComparableInteger o) { if (this.value > o.value) { return 1; } else { if (this.value == o.value) { return 0; } else { return -1; } } }
当我在这些clases的实例列表上调用Collections.sort
时,它们的表现大致相同。
我的问题是,是否存在排序算法,这将有益于第一个compareTo
方法的附加信息。 在第一个示例中,添加的信息是:
我们有三个ComparableInteger
值:
a == 1 b == 2 c == 3
现在当我们将c
与a
进行比较时,我们得到2,当我们将c
与b
进行比较时,我们得到1.从compareTo
实现很明显, b
应该在a
之后,因为c.compareTo(a) > c.compareTo(b)
所以我们知道正确的顺序。 现有的Comparable
合同不需要这个,需要进行另一次比较。 例如,以下实现也满足(至少我希望)合同,但给出不同的结果(数字排序,但偶数数字在奇数之前)
public int compareTo(ComparableInteger o) { if (value % 2 == o.value % 2){ return value - o.value; } else { if (value % 2 == 1){ return 1; }else{ return -1; } } }
- 我很清楚第一个例子不是声音,因为int可能会溢出
排序算法的效率有很多可以依赖的东西,但有一点需要注意的是,一般来说,如果基于元素之间的比较进行排序,最快的渐近运行时间是Ω(n lg n)
。
但是,可以构建一种场景,其中排序可以比n lg n
更快地完成,但这需要使用更多信息而不仅仅是比较。 这些是所谓的“线性排序”,它通过使用元素的值而不是与另一个元素的比较来排序。 这些示例包括Bucket Sort,Counting Sort和Radix Sort。
您提供的第一个比较方法确实提供了额外的信息,这可能会使分拣速度更快,但只能在受限条件下。 例如,如果您知道没有重复值 ,并且最小值和最大值之间的每个值只使用一次 ,那么您可以执行排序:
- 执行线性搜索以找到最小值。
- 将每个元素与最小值进行比较并放置在比较方法给出的索引处。
该方法应该花费2n = O(n)
时间。 当然,除非对象包含除整数值之外的额外信息,否则您可以直接构造范围min..max
。 此外,如果您可以读取元素的整数值,则可以实现普通存储桶或对其进行计数排序。
tl; dr :基于比较的最快排序是Ω(n lg n)
。 当您可以读取元素的确切值时,可以更快地排序,但线性排序仅适用于某些受限情况。 通常,您应该使用您的编程语言的内置排序。
小心第一个比较,它并不完全一致。
public int compareTo(ComparableInteger o) { return this.value - o.value; //not always correct }
正如Eric Lippert指出的那样 (该文章适用于C#,但仍然适用于Java),您首先比较是不安全的:
特别是,对于输入Int32.MinValue和Int32.MaxValue,差值为1.显然,最小可能的整数小于最大可能的整数,但此方法给出了相反的结果!
如您所述,其他溢出/下溢问题也会出现。
事实上,对于任何排序算法来说,在比较之外需要更多的逻辑开销来尝试使用“额外”信息。 “额外”信息是以一些额外的头痛和角落案件为代价的。
我认为第一个compareTo
的额外信息并不像您想象的那样有用:在您的示例中,您只是通过比较compareTo
结果来替换对象之间的比较,无论排序算法如何,都是如此。
-
正常算法:3次比较
-
您的算法:2个比较+ 1个先前差异的“缓存”值的比较。 (在你的例子中检查2> 1将确定
a
和b
顺序)
至于O
复杂性它们是相同的,但我的感觉是你的实现在实践中会稍微慢一点(而且实现起来有点困难)。
始终坚持使用核心Java Collectionfunction,例如Arrays.sort()
因为它们已经针对目前为止在答案中提到的各种细微差别进行了测试,大多数程序员都不会想到,而且他们也是为性能而调整。 当下一版Java出现时,您不必重新测试自己的排序例程。