比较元素时我可以使用哪些排序技术?

时间:2013-08-22 13:06:53

标签: performance algorithm sorting comparison

问题

我有一个应用程序,我想对数组 a a 0 1 ,... ,一个<子> N-1 的。我有一个比较函数 cmp(i,j)比较元素 a i a j 和交换函数 swap(i,j),交换元素 a i a j 数组。在应用程序中, cmp(i,j)函数的执行可能非常昂贵,以至于执行 cmp(i,j)的时间比任何时间都长排序中的其他步骤(当然除了其他 cmp(i,j)调用之外)。你可能会认为 cmp(i,j)是一个相当冗长的IO操作。

请假设为了这个问题,没有办法让 cmp(i,j)更快。假设所有可能使 cmp(i,j)更快的优化已经完成。

问题

  • 是否有一种排序算法可以最大限度地减少对 cmp(i,j)的调用次数?

  • 在我的应用程序中可以写一个谓词昂贵的(i,j),如果调用 cmp(i,j),则为真需要很长时间。 昂贵的(i,j)价格便宜且昂贵(i,j)∧昂贵(j,k)→昂贵(i,k)主要适用于我目前的应用。但这并不能保证。

    昂贵(i,j)的存在是否允许更好的算法试图避免昂贵的比较操作?如果是的话,你能指点我这样的算法吗?

  • 我想指出有关此主题的更多资料。

实施例

这是一个与我的应用程序完全不同的例子。

考虑一组可能很大的文件。在此应用程序中,目标是在其中查找重复文件。这基本上归结为通过一些任意的标准对文件进行排序,然后按顺序遍历它们,输出遇到的相同文件的序列。

当然,大量数据中的读取器很昂贵,因此,例如,只能读取每个文件的第一兆字节并计算该数据的散列函数。如果文件比较相等,则哈希值相等,但反向可能不成立。两个大文件只能在接近结尾的一个字节中有所不同。

在这种情况下,昂贵(i,j)的实现只是检查哈希值是否相等。如果是,则需要进行昂贵的深度比较。

9 个答案:

答案 0 :(得分:9)

我会尽力回答每个问题。

  • 是否有一种排序算法可以最大限度地减少对 cmp(i,j)的调用次数?

传统的排序方法可能有一些变化,但一般来说,对列表排序所需的最小比较数量存在数学限制,大多数算法都会利用这一点,因为比较通常并不便宜。您可以尝试按其他方式进行排序,或尝试使用可能更接近真实解决方案的快捷方式。

  • 昂贵(i,j)的存在是否允许更好的算法试图避免昂贵的比较操作?如果是的话,你能指点我这样的算法吗?

我认为你不能解决至少进行最低数量比较的必要性,但你可以改变你比较的东西。如果您可以比较数据的哈希值或子集而不是整个事物,那肯定会有所帮助。您可以采取的任何简化比较操作的方法都会产生很大的不同,但如果不了解数据的具体细节,就很难提出具体的解决方案。

  • 我想指出有关此主题的更多资料。

检查这些:

答案 1 :(得分:7)

平均排序n个元素数组所需的理论最小比较次数是lg(n!),约为n lg n - n。如果您使用比较来对元素进行排序,那么平均没有办法比这更好。

在标准的O(n log n)基于比较的排序算法中,mergesort进行最低比较次数(仅约n lg n,而快速排序约为1.44 n lg n,而heapsort约为n lg n + 2n) ),因此它可能是一个很好的算法用作起点。通常mergesort比heapsort和quicksort慢,但这通常假设比较快。

如果你确实使用了mergesort,我建议使用像自然mergesort这样的mergesort的自适应变体,这样如果数据大部分被排序,那么比较的数量就更接近于线性。

还有其他一些选择。如果您知道数据已经大部分已排序,您可以使用插入排序或标准版本的heapsort来尝试加快排序。或者,您可以使用mergesort,但在n很小时使用最佳排序网络作为基本情况。这可能会减少足够的比较,从而为您带来显着的性能提升。

希望这有帮助!

答案 2 :(得分:4)

一种称为Schwartzian transform的技术可用于将任何排序问题减少到排序整数的问题。它要求您将函数f应用于每个输入项,其中f(x) < f(y)当且仅当x < y


(面向Python的答案,当我认为问题被标记为[python]

如果您可以定义f函数f(x) < f(y)当且仅当x < y时,您可以使用

进行排序
sort(L, key=f)

Python保证为您正在排序的可迭代的每个元素最多调用一次key。这为Schwartzian transform提供了支持。

Python 3不支持指定cmp函数,仅支持key参数。 This page提供了一种轻松将cmp函数转换为key函数的方法。

答案 3 :(得分:2)

  

是否有一种排序算法可以最小化对cmp(i,j)的调用次数?

编辑:啊,对不起。有一些算法可以最大限度地减少比较次数(下面),但不是我所知道的特定元素。

  

昂贵的(i,j)的存在是否允许更好的算法试图避免昂贵的比较操作?如果是的话,你能指点我这样的算法吗?

我不知道,但也许你会在下面的这些论文中找到它。

  

我想指出有关这个主题的更多材料。

On Optimal and Efficient in Place Merging

Stable Minimum Storage Merging by Symmetric Comparisons

Optimal Stable Merging(这个似乎是O(n log 2 n)但是

Practical In-Place Mergesort

如果你实施其中任何一个,在这里发布它们也可能对其他人有用! :)

答案 4 :(得分:1)

  

是否有一种排序算法可以最小化对cmp(i,j)的调用次数?

合并插入算法,在D. Knuth的“计算机编程艺术”第3卷第5.3.1章中描述,比其他基于比较的算法使用更少的比较。但它仍然需要O(N log N)比较。

  

昂贵的(i,j)的存在是否允许更好的算法试图避免昂贵的比较操作?如果是的话,你能指点我这样的算法吗?

我认为可以修改一些现有的排序算法以考虑expensive(i,j)谓词。让我们采用最简单的方法 - 插入排序。其中一个变体,在维基百科中命名为binary insertion sort,仅使用O(N log N)比较。

它使用二进制搜索来确定插入新元素的正确位置。我们可以在每个二进制搜索步骤之后应用expensive(i,j)谓词来确定将插入的元素与二进制搜索步骤中找到的“中间”元素进行比较是否便宜。如果价格昂贵,我们可以尝试“中间”元素的邻居,然后是邻居等。如果没有找到便宜的比较,我们只需返回“中间”元素并执行昂贵的比较。

有几种可能的优化。如果谓词和/或廉价比较不那么便宜,我们可以比所有其他可能性尝试更早地回滚到“中间”元素。此外,如果移动操作不能被认为非常便宜,我们可以使用一些订单统计数据结构(如Indexable skiplist)确实将插入成本降低到O(N log N)。

这种修改后的插入排序需要O(N log N)时间进行数据移动,O(N 2 )谓词计算和廉价比较以及O(N log N)在最坏情况下的昂贵比较。但更有可能只有O(N log N)谓词和便宜的比较以及O(1)昂贵的比较。

  

考虑一组可能很大的文件。在这个应用程序中,目标是在它们中找到重复的文件。

如果唯一的目标是找到重复项,我认为排序(至少是比较排序)是没有必要的。您可以根据为每个文件的第一兆字节数据计算的哈希值在桶之间分配文件。如果某个存储桶中有多个文件,请使用其他10,100,1000,...兆字节。如果某个存储桶中仍有多个文件,请逐个字节地进行比较。实际上,此过程类似于基数排序

答案 5 :(得分:0)

Quicksort和mergesort是最快的排序算法,除非您有关于要排序的元素的其他信息。它们需要O(n log(n))比较,其中n是数组的大小。 从数学上证明,任何通用排序算法都不能比这更有效。

如果你想让程序更快,你可以考虑添加一些元数据来加速计算(除非你这样做,否则不能更精确)。

如果你知道更强大的东西,比如存在最大值和最小值,你可以使用更快的排序算法,例如基数排序或桶排序。

您可以在维基百科上查找所有提到的算法。

据我所知,你不能从昂贵的关系中受益。即使你知道,你仍然需要进行这样的比较。正如我所说,你最好尝试缓存一些结果。


编辑

我花了一些时间来思考它,我想出了一个稍微定制的解决方案,我认为这将尽可能少地进行昂贵的比较,但完全无视比较的总数。它将进行最多(n-m)* log(k)的昂贵比较,其中

  • n是输入向量的大小
  • m是易于在彼此之间进行比较的不同组件的数量
  • k是难以比较且具有连续排名的元素的最大数量。

Here是算法的描述。没有什么可以说它会比简单的合并排序更糟糕,除非m很大而k很小。总运行时间是O [n ^ 4 + E(nm)log(k)],其中E是昂贵比较的成本(我假设E>&gt; n,以防止它从渐近中消失至少在平均情况下,n ^ 4可能会进一步减少。

编辑

我发布的文件包含一些错误。尝试时,我也修复了它们(我忽略了insert_sorted函数的伪代码,但是这个想法是正确的。我制作了一个Java程序,对整数向量进行排序,并按照你的描述添加延迟。即使我持怀疑态度,它实际上也是如此。如果延迟很大,那么它比mergesort更好(我使用1s延迟再次进行整数比较,通常需要几纳秒来执行)

答案 6 :(得分:0)

大多数排序算法尝试最小化排序过程中的比较量。

我的建议: 选择快速排序作为基本算法并记住比较结果,以防您碰巧再次比较相同的问题。这应该可以帮助你解决O(N ^ 2)最快的快速排序问题。请记住,这将使您使用O(N ^ 2)内存。

现在,如果您真的喜欢冒险,可以尝试使用Dual-Pivot快速排序。

答案 7 :(得分:0)

要记住的是,如果您使用新添加的内容不断对列表进行排序,并且保证两个元素之间的比较永远不会改变,您可以记住比较操作,这将导致性能提升。不幸的是,在大多数情况下,这将不适用。

答案 8 :(得分:0)

我们可以从另一个方向看你的问题,看来你的问题与IO有关,那么你可以利用并行排序算法,实际上你可以运行很多线程来运行文件比较,然后按一个排序最着名的并行算法,如Sample sort algorithm