成对比较可以返回比 -1、0、+1 更多信息的排序算法

Posted

技术标签:

【中文标题】成对比较可以返回比 -1、0、+1 更多信息的排序算法【英文标题】:sorting algorithm where pairwise-comparison can return more information than -1, 0, +1 【发布时间】:2010-10-29 03:44:50 【问题描述】:

大多数排序算法依靠成对比较来确定 A B。

我正在寻找能够利用成对比较函数的算法(以及奖励积分,Python 中的代码),该函数可以区分少与少或多与多。所以也许不是返回 -1, 0, 1 比较函数返回 -2, -1, 0, 1, 2 或 -5, -4, -3, -2, -1, 0, 1 , 2, 3, 4, 5 甚至是区间 (-1, 1) 上的实数。

对于某些应用程序(例如近似排序或近似排序),这可以通过较少的比较来确定合理的排序。

【问题讨论】:

你能保证对于比较函数 f() 和值 x、y 和 z,距离 f(x,y) + f(y,z) = f(x,z )?那会是 是的,我知道这个问题。在我的应用程序中,我不能保证,但无论如何我只是在寻找近似排序而不是总排序。 如果您仔细阅读,OP 正在寻找由人类专家小组提供的最小化比较,其中比较结果是主观的 【参考方案1】:

您可以使用修改后的快速排序。当你比较函数返回 [-2, -1, 0, 1, 2] 时,让我解释一个例子。比如说,你有一个数组 A 需要排序。

创建 5 个空数组 - Aminus2、Aminus1、A0、Aplus1、Aplus2。

选择A、X中的任意一个元素。

对于数组的每个元素,将其与 X 进行比较。

根据结果,将元素放在 Aminus2、Aminus1、A0、Aplus1、Aplus2 数组之一中。

对 Aminus2、Aminus1、Aplus1、Aplus2 递归应用相同的排序(注意:您不需要对 A0 进行排序,因为那里的所有元素都等于 X)。

连接数组以获得最终结果:A = Aminus2 + Aminus1 + A0 + Aplus1 + Aplus2。

【讨论】:

因此,在一个可爱的、平等的问题传播世界(等于 -2..+2 个桶的命中数)中,这将是一个 log^4 n 的排序解决方案,而不是一个 log^2 n 的解决方案跨度> @Tom,同样的复杂度,对数基数就像一个常数乘数。 另外,您的意思是 log_4 n(以 4 为底的对数),而不是 log^4 n(表示 log-n 的四次方)。 +1 这是一个很好的解决方案,它具有易于实施的优点。【参考方案2】:

似乎使用 Raindog 修改后的快速排序可以让您更快地输出结果,并且可能更快地进入它们。

也许这些功能已经可以通过精心控制的 qsort 操作获得?我没有想太多。

这听起来也有点像基数排序,只是不是查看每个数字(或其他类型的桶规则),而是从丰富的比较中组成桶。我很难想到这样一种情况,即可以进行丰富的比较但数字(或类似的东西)不可用。

【讨论】:

我想到的特定应用是人类实际(主观地)提供成对比较的地方 一个有趣的应用程序。所以从理论上讲,您正在尝试将比较次数减少到尽可能少。 Tom,是的,以接近排序为代价减少比较次数【参考方案3】:

我想不出这会在什么情况下真正有用。即使可以,我怀疑对模糊值进行排序所需的额外 CPU 周期也会比您提到的那些“额外比较”更多。不过我还是提个建议吧。

考虑这种可能性(所有字符串都使用 27 个字符 a-z 和 _):

            11111111112
   12345678901234567890
1/ now_is_the_time
2/ now_is_never
3/ now_we_have_to_go
4/ aaa
5/ ___

显然字符串 1 和 2 比字符串 1 和 3 更相似,并且 非常 比 1 和 4 更相似。

一种方法是缩放每个相同字符位置的差值,并使用第一个不同的字符设置最后一个位置。

暂时把符号放在一边,比较字符串 1 和 2,位置 8 的差异为 'n' - 't'。那是 6 的差异。为了将其转换为单个数字 1-9,我们使用以下公式:

digit = ceiling(9 * abs(diff) / 27)

因为最大差是 26。最小差 1 变成数字 1。最大差 26 变成数字 9。我们的差 6 变成 3。

因为差异在位置 8,out 比较函数将返回 3x10-8 (实际上它会返回负数,因为字符串 1 字符串 2 .

对字符串 1 和 4 使用类似的过程,比较函数返回 -5x10-1。可能的最高返回值(字符串 4 和 5)在 '-' - 'a' (26) 的位置 1 中存在差异,这会生成数字 9,因此给我们 9x10-1

采纳这些建议并在您认为合适的时候使用它们。我很想知道你的模糊比较代码最终是如何工作的。

【讨论】:

【参考方案4】:

考虑到您希望根据人工比较订购许多商品,您可能希望像体育比赛一样处理这个问题。您可以允许每个人工投票将获胜者的得分增加 3,将较松的得分减少 3、+2 和 -2、+1 和 -1,或者仅 0 0 为平局。

然后您只需根据分数进行常规排序。

另一种选择是单淘汰或双淘汰锦标赛结构。

【讨论】:

我考虑过先进行近似排序,以此作为锦标赛结构的一种播种方式【参考方案5】:

您可以使用两个比较来实现这一点。将更重要的比较乘以 2,然后将它们相加。

这是我在 Perl 中的意思的一个例子。 它按第一个元素比较两个数组引用,然后按第二个元素。

use strict;
use warnings;
use 5.010;

my @array = (
  [a => 2],
  [b => 1],
  [a => 1],
  [c => 0]
);

say "$_->[0] => $_->[1]" for sort 
  ($a->[0] cmp $b->[0]) * 2 +
  ($a->[1] <=> $b->[1]);
 @array;
一个 => 1 一个 => 2 b => 1 c => 0

您可以很容易地将其扩展到任意数量的比较。

【讨论】:

【参考方案6】:

也许这样做是有充分理由的,但我认为它在任何给定情况下都不能胜过其他选择,并且当然不适合一般情况。原因?除非您对输入数据的域和值的分布有所了解,否则您无法真正改进,例如快速排序。如果您确实知道这些事情,通常会有更有效的方法。

反例:假设您的比较对于相差超过 1000 的数字返回“巨大差异”值,并且输入为 0, 10000, 20000, 30000, ...

反例:同上,但输入为 0, 10000, 10001, 10002, 20000, 20001, ...

但是,你说,我知道我的输入看起来不像那样!好吧,在这种情况下,请详细告诉我们您的输入的真实情况。那么也许有人能够真正提供帮助。

例如,有一次我需要对历史数据进行排序。数据保持排序。当添加新数据时,它被附加,然后再次运行列表。我没有新数据附加到哪里的信息。我针对这种情况设计了一种混合排序,通过选择一种对已排序数据快速的排序并在遇到未排序的数据时将其调整为快速(本质上是切换到 qsort),从而轻松击败 qsort 和其他排序。

您要改进通用分类的唯一方法是了解您的数据。如果你想得到答案,你必须在这里很好地沟通。

【讨论】:

任务是人类以成对的方式主观地表达他们对集合中项目的偏好,以便能够根据个人偏好对集合进行近似排序【参考方案7】:

额外的信息确实可以用来最小化比较的总数。对 super_comparison 函数的调用可用于进行相当于对常规比较函数的大量调用的扣除。例如,a much-less-than bc little-less-than b 意味着 a &lt; c &lt; b

可以将扣除项组织成可以单独排序的箱或分区。实际上,这等效于具有 n 路分区的 QuickSort。这是 Python 中的一个实现:

from collections import defaultdict
from random import choice

def quicksort(seq, compare):
    'Stable in-place sort using a 3-or-more-way comparison function'
    # Make an n-way partition on a random pivot value
    segments = defaultdict(list)
    pivot = choice(seq)
    for x in seq:
        ranking = 0 if x is pivot else compare(x, pivot)
        segments[ranking].append(x)
    seq.clear()

    # Recursively sort each segment and store it in the sequence
    for ranking, segment in sorted(segments.items()):
        if ranking and len(segment) > 1:
            quicksort(segment, compare)
        seq += segment

if __name__ == '__main__':
    from random import randrange
    from math import log10

    def super_compare(a, b):
        'Compare with extra logarithmic near/far information'
        c = -1 if a < b else 1 if a > b else 0
        return c * (int(log10(max(abs(a - b), 1.0))) + 1)

    n = 10000
    data = [randrange(4*n) for i in range(n)]
    goal = sorted(data)
    quicksort(data, super_compare)
    print(data == goal)

通过使用 trace 模块检测此代码,可以测量性能增益。在上面的代码中,常规的三路比较使用了 133,000 次比较,而超级比较函数将调用次数减少到了 85,000 次。

该代码还可以轻松地尝试各种比较功能。这将表明,朴素的 n 路比较函数对排序几乎没有帮助。例如,如果比较函数对于大于四的差异返回 +/-2,对于差异小于等于四的差异返回 +/-1,则比较次数仅会适度减少 5%。根本原因是一开始使用的课程粒度分区只有少数“近匹配”,而其他所有内容都属于“远匹配”。

对超级比较的改进是覆盖对数范围(即,如果在十以内,则为 +/-1,如果在一百以内,则为 +/-2,如果在一千以内,则为 +/-。

理想的比较函数应该是自适应的。对于任何给定的序列大小,比较函数应努力将序列细分为大小大致相等的分区。信息论告诉我们,这将使每次比较的信息位数最大化。

自适应方法也具有很好的直观意义。人们应该首先被划分为love vs like,然后再进行更精细的区分,例如love-a-lot vs love-a-little。进一步的划分通道应该会做出越来越精细的区分。

【讨论】:

以上是关于成对比较可以返回比 -1、0、+1 更多信息的排序算法的主要内容,如果未能解决你的问题,请参考以下文章

为啥python处理排序列表比未排序列表花费更多时间

为啥数字数组,更多数据排序比对象数组更快,Javascript中的数据更少?

查询中的 LEFT JOIN 返回比预期更多的记录

C#中对象的更多信息比较

多线程计数排序

算法——快速排序算法