【问题标题】:What is a sorting algorithm that is robust to a faulty comparison?什么是对错误比较具有鲁棒性的排序算法?
【发布时间】:2021-09-17 05:03:30
【问题描述】:

我想使用比较排序对n 项目列表进行排序。但是,该算法进行的比较之一将与应有的情况相反。具体来说,有一对项目的比较器函数始终给出错误的结果。

什么是高效的n*log(n) 排序算法,可以对这种错误的比较保持稳健?稳健,我的意思是每个项目最多偏离其真实位置k 点,对于一些相当小的k

如果可能的话,我希望它在最坏的情况下是稳健的(对抗性选择的错误比较),但我会满足于在平均情况下稳健。

一个稳健的算法示例(效率不高)是进行所有n*(n-1)/2 成对比较,并根据他们赢得的比较次数来放置每个项目。然后,无论对手进行何种比较,每个项目索引的偏差都不会超过k=1

快速排序是非鲁棒算法的一个示例,因为对手可能会选择最大的项目位于第一个枢轴的错误一侧,从而使其平均 n/2 偏离其正确索引。

【问题讨论】:

  • 明确地说,您的意思是在调用比较器函数时得到错误的结果,还是比较器在一对输入上始终给出错误的结果?
  • 一对项目将始终被错误地比较。否则,您可以简单地将每个比较进行 3 次以消除错误比较。
  • 是的,在另一种情况下,您可以进行 3 次比较,但也许还有比这更有效的方法,所以这仍然是一个明智的问题。因此,我要求澄清。
  • 比较不正确的原因是什么?
  • 我相当确定任何项目与真实位置的最大距离是 2,而不是 1。想象比较器说 A

标签: algorithm sorting time-complexity noise robust


【解决方案1】:

TL;DR:可以修改快速排序以获得以下保证:在(预期)时间 O(n log n) 内,我们可以执行以下操作之一,具体取决于翻转的比较。

  • 对数组进行完美排序。
  • 对数组进行完美排序,但数组中某处的相邻项对被交换。
  • 对数组进行完美排序,只是数组中连续三个可以识别的项被置换了。

这保证了最大位移为 2,这在理论上是可能的。


我考虑了这个问题几个小时,我所做的一切都连接到tournaments

我想首先尝试重新构建问题,如下所示。如果您有一组 n 个项目,并且您知道它们之间比较的“真实”结果,则可以将该结果表示为一个有向图,每个项目一个节点,边指示一个项目何时比另一个少。这种类型的有向图称为“锦标赛”,因为您可以将其视为对循环锦标赛的结果进行编码,其中每个玩家与其他玩家比赛。

在诚实比较器的情况下,我们的锦标赛将是非循环的,特别是它具有以下关键属性:每个出度 0、1、2、...、n - 1 恰好有一个节点。这里的想法是最小元素的出度为 n - 1(它比其他所有元素都小),而最大的元素的出度为 0(它比其他所有元素都大)。事实上,有一个定理,当且仅当锦标赛中的每个节点具有不同的出度时,锦标赛才是非循环的。另一个有用的事实:在非循环锦标赛中,当且仅当 outdeg(U) > outdeg(V) 时,从 U 到 V 存在边。

在“不诚实的比较器”的情况下,我们基本上从非循环锦标赛开始,然后翻转单边。你的问题是关于根据这个比较器进行近似排序,但我想退后一步,问一个不同的问题,我认为可以用来更准确地回答你的问题。在什么情况下,你能确定哪条边被翻转了?如果我们能做到这一点,那么我们可以做得比近似排序更好——我们可以“取消翻转”边缘并完美排序。另一方面,在哪些情况下你不能确定哪条边被翻转了,当这种情况发生时,我们离排序还有多远?这对应于必须进行近似排序,因为我们无法恢复原始排序。

这是一个有用的事实:

定理:从非循环锦标赛开始并翻转单边。那么,当且仅当翻转边的两个端点的出度最初相差至少三倍时,才可能确定翻转了哪条边。

为了证明这一点,我们将展示两个隐含方向。

首先,假设我们翻转两个节点 X 和 Y 之间的边,它们的出度相差 1。当我们完成后,我们剩下一个锦标赛,其中所有节点都有不同的出度(所有其他节点的出度不变,如果我们翻转边缘(X,Y),那么 X 和 Y 交换出度,因为一个上升一个和一个下降一个)。我们现在剩下另一个非循环锦标赛。特别是,我们无法判断我们翻转了哪条边,因为我们也可以翻转出度相差 1 的任何一对节点之间的任何边。

接下来,假设我们在节点 X 和 Y 之间翻转一条边,其中 outdeg(X) = k+1 和 outdeg(Y) = k-1。我们现在有 outdeg(X) = k = outdeg(Y),并且在其他地方开始时肯定有某个节点 Z 也有 outdegree k。所以此时,我们有三个出度为 k 的节点(即 X、Y 和 Z),我们知道我们必须翻转它们之间的三个边之一。但我们无法判断它是哪一个。具体来说,翻转 XY 边缘、XZ 边缘或 YZ 边缘都会返回非循环锦标赛。因此,在这种情况下,无法撤消转换。这意味着我们从这个比较器获得的任何排序顺序都会使这两项不合适,因此我们的最大距离至少为 1。

此特殊情况的重要说明:这对应于比较器创建一个包含节点 X、Y 和 Z 的循环的锦标赛。具体而言,它将采用 X、Z、Y、X 的形式。问题是我们无法判断原始排序是(X,Z,Y),还是(Z,Y,X),还是(Y,X,Z),所以我们的最大距离为至少 2 个。

最后,假设我们有两个节点 X 和 Y 并在 outdeg(X) = k、outdeg(Y) = m 和 k ≥ m + 3 的情况下翻转边 XY。我们现在剩下在锦标赛中,两个节点的出度为 k - 1,两个节点的出度为 m + 1。但是在这四个节点中,可以保证只有一对可以翻转以产生非循环锦标赛。一种看待这一点的方法:取现在重复出度的四个节点;称它们为 X 和 Y(如上)以及 W 和 Z,假设我们有循环 X、W、Z、Y、X,其中唯一与原始翻转的边是(Y,X)。这个循环会是什么样子?好吧,因为 (X, W)、(W, Z) 和 (Z, Y) 是锦标赛中没有翻转的边,所以在原始锦标赛中,我们有 outdeg(X) > outdeg(W) > outdeg (Z) > 外度 (Y)。这意味着我们必须让 X 和 W 在新图中具有出度 k - 1 并且 Z 和 Y 在新图中具有出度 m + 1。因此,仅将边缘从 Y 翻转到 X 会将度数-(k-1) 节点之一的度数增加到 k,同时也会将度数-(m+1) 节点之一的度数降低到 m .

总结:

定理:有故障的比较器要么

  1. 表现得像一个真正的比较器,在这种情况下,我们交换了原始序列中的两个相邻元素,我们永远不知道是哪个。
  2. 恰好有一个长度为 3 的元素的循环,其原始顺序永远无法知道,或者
  3. 有一个长度为 4 的循环,在这种情况下,我们可以确定哪个比较被反转。

考虑到这一点,用以下方式重新定义您的问题似乎是合理的:

目标: 设计一种算法,在 O(n log n) 时间内,在给定错误比较器的情况下,对包含 n 个元素的列表执行以下操作之一比较两个固定元素 X 和 Y 时返回错误的结果:

  1. 完美排序列表。
  2. 完美排序列表,除了两个相邻的项目交换。
  3. 完美排序列表,三个相邻项目除外。

这是一种可能的算法,它基于快速排序在预期的 O(n log n) 时间内完成此操作。基本思想如下:我们运行或多或少的常规快速排序,在每个时间点检查是否找到了三角形。如果不是,那么我们要么是情况(1),要么是情况(2)。如果我们确实找到了一个三角形,我们看看我们是否可以确定哪个比较被颠倒了。如果可以,那么我们重新运行快速排序,除了我们在这个损坏的情况下“修复”比较器。如果我们不能,那么我们在情况 (3) 中并像往常一样完成快速排序。

我们将用于检测三角形的特定技术是这样工作的。从常规的普通快速排序开始:选择一个枢轴,将数组划分为小于枢轴的事物和大于枢轴的事物,然后递归地对两个较小的子数组进行排序。然而,在这样做之后,我们又做了一个步骤:假设我们正在排序的子数组中包含三个或更多元素,请查看枢轴 p 以及它之前和之后的元素(将这些 s、p、g 称为“更小”、“枢轴”和“更大”)。那么如果比较器说 s

假设在快速排序比较器的某个时刻确实比较了 X 和 Y,即不匹配的项目。我们假设 X

这里应该发生什么,假设比较器是诚实的,Y 会被发现大于 X,因此将被放入“更大”的子数组中。但是因为比较器是一个撒谎的说谎者,所以 Y 被放置到“较小的”子数组中。如果我们然后递归地对“较小”子数组和“较大”子数组进行排序,请考虑 Y 将在哪里结束。它在“较小”子数组中,但实际上比 X 大,这意味着它将比“较小”子数组中的所有内容都大。因此,Y 将出现在 X 之前。现在,查看“更大”子数组中的项目。有两种选择。第一个是在“真实”排序中,X 和 Y 之间至少有一个值。然后该值将出现在“更大”子数组中,因为它大于 X,尤其是“更大”子数组的第一个元素将比较小于 Y。这意味着 Y,然后是 X,然后是排序后紧跟在 X 之后的项目将形成一个三角形。另一种选择是 X 和 Y 在真正的排序顺序中是相邻的,我们永远不会发现这种情况(如上所述)。结合上述见解,这意味着

定理:假设我们运行快速排序,在对左右子数组进行递归排序后,我们查看由枢轴组成的三个项目,即它之前的项目,以及紧随其后的项目,看看它们是否形成三角形。那么如果这个算法检测到一个三角形,那么就存在一个三角形。此外,如果该算法未检测到三角形,则 (1) 不存在三角形或 (2) 确实存在三角形,但比较器从未应用于坏对 (X, Y),因此排序顺序是正确的.

说了这么多,我们可以说明完整的算法,在预期的 O(n log n) 时间内,尽可能最好地对数组进行排序。

function modifiedQuicksort(array, comparator):
    if array has length 0 or 1, return.
    
    pick a random pivot element from the array.

    use the comparator to form subarrays smaller and greater based on
       how elements compare against the pivot.

    recursively apply modifiedQuicksort to those two arrays.

    if the comparator finds a triangle formed from the last element of
       smaller, the pivot, and the first element of greater, report those
       three items as a triangle.

    return smaller, pivot, greater.

function sortAsBestWeCan(array, comparator):
    run modifiedQuicksort(array, comparator)

    if it didn't report a triangle, return the result of the call.

    otherwise, it reported a triangle A, B, C.

    for each other item D:
        if comparator(A, D) and comparator(D, B)   or
           comparator(B, D) and comparator(D, C)   or
           comparator(C, D) and comparator(D, A):

            you have found a 4-cycle from A, B, C, and D.

            detect which comparison is reversed.

            use that knowledge plus the comparator and your favorite
                O(n log n)-time sorting algorithm to perfectly sort
                the input array.

    otherwise, those three items are the only triangle, and the
       array is sorted as well as it can be. return it.

【讨论】:

    【解决方案2】:

    我想我已经想出了一个解决方案。

    首先,使用您想要的任何合适的排序算法(如快速排序)进行第一次遍历,在最坏的情况下,这应该只会导致一个项目与它应该在的位置相距甚远。

    然后,选择宽度至少为 5 的 h

    for i from 0 to n-h,我们在i, i+1, ..., i+h-1 处查看h 项目组。我们在该组中进行所有h*(h-1)/2 成对比较,并按照赢得最多比较的人重新排列。然后我们增加i 并移动到下一组。

    之后,我们做同样的事情,但从 i=n-h 倒退到 i=0

    这两个额外的通道将冒泡/冒泡置换的项目以位于正确的区域,并使用一组h中的额外比较来覆盖错误的单个比较。

    最终的比较次数为O(n*log(n)) + n*h*(h-1)/2。不知道你能做得更好。

    这种方法也适用于(我认为)不止一个错误的比较。您需要做的就是确保h 足够大以覆盖那些错误的比较。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-24
      • 2013-05-21
      • 1970-01-01
      • 1970-01-01
      • 2014-10-06
      • 2012-01-20
      相关资源
      最近更新 更多