是否有排序算法尊重最终位置限制并在O(n log n)时间内运行?

时间:2015-03-03 18:40:03

标签: algorithm sorting big-o

我正在寻找一种排序算法,该算法遵循每个元素 1 的最小和最大范围。问题域是一种推荐引擎,它将一组业务规则(限制)与推荐得分(值)相结合。如果我们有推荐(例如特殊产品或交易)或我们希望出现在列表顶部附近的公告(例如"这非常重要,请记住验证您的电子邮件地址以参与即将到来的促销!")或在列表底部附近(例如"如果您喜欢这些推荐,请点击此处了解更多..."),他们将受到一定的位置限制地点。例如,这应该始终位于顶部位置,这些位置应位于前10位或中间位置5等。此策展步骤提前完成并在给定时间段内保持固定,并且出于商业原因必须保持非常灵活。 / p>

请不要质疑商业目的,用户界面或输入验证。我只是试图在我给出的约束条件下实现算法。请将此视为学术问题。我将努力提供严格的问题陈述,非常欢迎对问题的所有其他方面的反馈。


因此,如果我们对char进行排序,我们的数据将具有

的结构
struct {
  char value;
  Integer minPosition;
  Integer maxPosition;
}

minPositionmaxPosition可能为空(不受限制)。如果在所有位置限制为空的算法上调用它,或者所有minPosition s为0或更小并且所有maxPositions等于或大于列表的大小,那么输出将只是按升序排列char

如果两个元素的minPositionmaxPosition不会被新位置侵犯,则此算法仅重新排序两个元素。基于插入的算法将项目提升到列表顶部并重新排序其余部分具有明显的问题,即每次迭代后必须重新验证每个后续元素;在我看来,这排除了 O(n 3 复杂性的算法,但我不排除这些算法而不考虑相反的证据,如果出现。

在输出列表中,当且仅当位置约束集指示它时,某些元素的值才会出现故障。这些输出仍然有效

  • 有效列表是所有元素都处于不与其约束条件冲突的位置的任何列表。
  • 最优列表是一个列表,在不违反一个或多个位置约束的情况下,无法重新排序以更接近地匹配自然顺序。无效列表永远不是最佳的。我没有一个严格的定义,我可以拼出来更紧密地匹配'在一个或另一个订单之间。但是,我认为让直觉引导你,或选择与distance metric类似的东西相当容易。

    如果多个输入具有相同的值,则可能存在多个最佳排序。你可以提出一个论点,上面的段落因此是不正确的,因为任何一个都可以重新排序到另一个而不违反约束,因此两者都不是最优的。但是,任何严格的距离函数都会将这些列表视为相同,与自然顺序的距离相同,因此允许重新排序相同的元素(因为它是无操作)。

    我会将这些输出调用为尊重位置约束的正确排序顺序,但是有几位评论员指出我们并非真的返回排序列表,所以让我们坚持使用& #39;最优'

例如,以下是输入列表(以<char>(<minPosition>:<maxPosition>)的形式,其中Z(1:1)表示必须位于列表前面的Z M(-:-) }表示M可能位于最终列表中的任何位置,自然顺序(仅按值排序)为A...M...Z)及其最佳顺序。

Input order
A(1:1) D(-:-) C(-:-) E(-:-) B(-:-)
Optimal order
A      B      C      D      E

这是一个简单的例子,表明自然顺序在没有约束的列表中占优势。


Input order
E(1:1) D(2:2) C(3:3) B(4:4) A(5:5)
Optimal order
E      D      C      B      A

此示例显示完全约束列表的输出顺序与给定的顺序相同。输入已是有效最佳列表。对于此类输入,该算法仍应在 O(n log n)时间内运行。 (我们的初始解决方案能够将任何完全受约束的列表短路以在线性时间内运行;我添加了示例,以便将最佳和有效的定义驱动回家,并且因为我认为一些基于交换的算法将此处理为更糟糕的情况。 )


Input order
E(1:1) C(-:-) B(1:5) A(4:4) D(2:3)
Optimal Order
E      B      D      A      C

E被约束为1:1,因此即使它具有最低值,它也是列表中的第一个。 A类似地约束为4:4,因此它也不符合自然顺序。 BC具有基本相同的约束,并且可能出现在最终列表中的任何位置,但由于价值,B将在C之前。 D可能位于第2或第3位,因此在B之后由于自然顺序而在C之前因为其约束而显示。

请注意,最终订单是正确的,尽管与自然顺序(仍为ABCD,{{1}完全不同})。如前一段所述,此列表中的任何内容都不能在不违反一个或多个项目约束的情况下重新排序。


E

Input order B(-:-) C(2:2) A(-:-) A(-:-) Optimal order A(-:-) C(2:2) A(-:-) B(-:-) 仍然无动于衷,因为它已经处于唯一有效的位置。 C被重新排序到最后,因为它的值小于B两个值。实际上,将会有其他字段区分两个A,但从算法的角度来看,它们是相同的并且保留OR反转它们的输入顺序是最佳解决方案。


A

此输入无效,原因有两个:1)Input order A(1:1) B(1:1) C(3:4) D(3:4) E(3:4) Undefined output A都被约束到位置1和2)BC和{{1}被约束到一个范围而不能只容纳2个元素。换句话说,范围DE 过度约束。但是,约束的一致性和合法性由UI验证强制执行,因此如果它们不正确,它正式不是算法问题,并且算法可以返回尽力排序或在该情况下的原始排序。将这样的输入传递给算法可以被认为是undefined behavior;什么都可能发生。所以,对于其余问题......


  • 所有输入列表都包含最初位于有效位置的元素。
  • 排序算法本身可以假设约束有效并且存在最佳顺序。 2

我们目前已经确定了自定义选择排序(运行时复杂度为 O(n 2 )并且合理地证明它适用于所有输入位置限制是有效且一致的(例如,对于给定的位置或位置范围,不会超额预订)。

是否有一种排序算法可以保证返回最优最终顺序并且运行时间优于 O(n 2 时间复杂度? 3

我觉得可以通过提供一个接受每个元素的候选目标位置的自定义比较器来修改库标准排序算法来处理这些约束。这相当于每个元素的当前位置,因此可能修改值保持类以包含元素的当前位置,并在比较(1:1)和交换方法中进行额外记帐就足够了。 / p>

但是,我想的越多,在 O(n log n)时间运行的算法就无法正常使用这些限制。直观地说,这些算法基于运行 n 比较 log n 次。 log n 是通过利用分而治之的机制实现的,该机制只比较某些职位的某些候选人。

换句话说,对于任何 O(n log n)排序算法,存在具有有效位置约束的输入列表(即反例),其中候选元素将与元素(或其中的范围)进行比较Quicksort和变体的情况)与/它无法交换,因此永远不会移动到正确的最终位置。如果这太模糊了,我可以为mergesort和quicksort提出一个反例。

相比之下, O(n 2 排序算法进行了详尽的比较,并且始终可以将元素移动到正确的最终位置。

提出一个实际问题:当我认为 O(n log n)排序无法保证找到有效订单时,我的直觉是否正确?如果是这样,你能提供更具体的证据吗?如果没有,为什么不呢?还有其他关于这类问题的研究吗?


1 :我找不到一组搜索词,指出我对这种排序算法或约束的任何具体分类的方向;这就是为什么我要问一些关于复杂性的基本问题。如果存在此类问题的术语,请将其发布。

2 :验证是一个单独的问题,值得自己研究和算法。我非常确定有效订单的存在可以在线性时间内证明:

  1. 分配长度等于列表的元组数组。每个元组都是一个整数计数器 k ,对于相对赋值权重,它是一个双值 v
  2. 遍历列表,将每个元素位置约束的小数值添加到相应的范围,并将其计数器递增1(例如,10列表中的范围2:5将2,3,4和5中的每一个加上0.4)在我们的元组列表中,也增加每个的计数器)
  3. 走元组列表和
  4. 如果没有条目的值 v 大于 1 / k 的1到 k 系列的总和,则存在有效的订单
  5. 如果有这样的元组,它所处的位置就会过度约束;抛出异常,记录错误,使用双精度数组来纠正问题元素等。
  6. 编辑:此验证算法本身实际上是 O(n 2 。最糟糕的情况是,每个元素都有约束3:4,您最终会走 n 元组列表 n 次。这仍然与问题的范围无关,因为在实际问题域中,约束被强制执行一次并且不会发生变化。

    确定给定列表的有效顺序更加容易。只需根据约束检查每个元素的当前位置。

    3 :这无疑是一个不成熟的过早优化。我们最初的用途是针对相当小的列表,但我们正在考虑扩展到更长的列表,因此如果我们现在可以进行优化,我们现在可以获得较小的性能提升,并且稍后可以获得较大的性能提升。此外,我的好奇心被激发了,如果有关于这个主题的研究,我希望看到它并(希望)从中学习。

3 个答案:

答案 0 :(得分:1)

关于解的存在:你可以将其视为二分图,其中一组顶点(U)是k值,另一组(V)是k级(1到k),还有一个弧从U中的每个顶点到V中的有效等级。然后,解的存在等同于最大匹配是双射。检查这一点的一种方法是在U中为每个顶点添加一个带弧的源顶点,在V中为每个顶点添加一个弧顶点。为每个边分配一个容量为1,然后找到最大流量。如果它是k,则有解决方案,否则不是。

http://en.wikipedia.org/wiki/Maximum_flow_problem

- edit-- O(k ^ 3)解决方案:首先排序找到每个顶点的排序等级(1-k)。接下来,将您的值和等级考虑为2组k个顶点U和V,其中U的每个顶点的加权边到V中的所有合法等级。分配每个边的权重是距离顶点排名的距离订购。例如,如果U是10到20,则自然等级10是1.从值10到等级1的边缘将具有零权重,等级3将具有权重2.接下来,假设存在所有缺失边缘给他们无限的重量。最后,找到&#34; MINIMUM WEIGHT PERFECT MATCHING&#34;在O(k ^ 3)。

http://www-math.mit.edu/~goemans/18433S09/matching-notes.pdf

这并没有利用U中每个元素的合法等级是连续的这一事实,这可能有助于将运行时间降低到O(k ^ 2)。

答案 1 :(得分:1)

这是我和同事想出来的。我认为它是 O(n 2 解决方案,如果存在,则返回有效的最优订单,如果初始范围是过度约束。我刚刚调整了一些关于实现的内容,我们仍在编写测试,所以它有可能不像宣传的那样工作。这种过度约束的情况在发生时很容易被发现。

首先,如果您将输入规范化以具有所有非空约束,则会简化操作。在线性时间,即:

    输入中的每个项目
  • 如果某个商品没有最低排名,请将其设为1
  • 如果某个商品没有最高排名,请将其设置为列表的长度

下一个目标是构建一个范围列表,每个范围包含具有该范围的所有候选元素,并按范围的剩余容量排序,上升所以剩余的最少点的范围先打开,然后开始范围的位置,然后是范围的结束位置。这可以通过创建一组这样的范围,然后使用简单的比较器在 O(n log n)时间内对它们进行排序来完成。

对于本答案的其余部分,范围将是一个简单的对象,如此

class Range<T> implements Collection<T> {
   int startPosition;
   int endPosition;
   Collection<T> items;

   public int remainingCapacity() {
       return endPosition - startPosition + 1 - items.size();
   }

   // implement Collection<T> methods, passing through to the items collection
   public void add(T item) {
       // Validity checking here exposes some simple cases of over-constraining
       // We'll catch these cases with the tricky stuff later anyways, so don't choke
       items.add(item);
   }
}

如果元素A的范围为1:5,则构造一个range(1,5)对象并将A添加到其元素中。此范围的剩余容量为 5 - 1 + 1 - 1 (最大 - 最小+ 1 - 大小) = 4 。如果元素B的范围为1:5,请将其添加到现有范围,现在范围为3。

然后,选择适合每个位置1 => k的最佳元素是一个相对简单的问题。按照排序顺序迭代您的范围,跟踪最符合条件的元素,如果您已达到剩余大小无法适应其余位置的范围,则可以停止查看。这相当于简单的计算 range.max - 当前位置+ 1&gt; range.size (可能会简化,但我认为这种形式最容易理解)。从选择的范围中删除每个元素。从清单中清除每个范围(可选;迭代空范围将不会产生候选人。这是一个不好的解释,所以让我们从问题中做一个例子。注意C(-:-)已经如上所述更新为已清理的C(1:5)

Input order
E(1:1)    C(1:5)    B(1:5)    A(4:4)    D(2:3)
Built ranges (min:max) <remaining capacity> [elements]
(1:1)0[E] (4:4)0[A] (2:3)1[D] (1:5)3[C,B]

最适合1

  Consider (1:1), best element from its list is E
  Consider further ranges?
    range.max - current position + 1 > range.size ?
    range.max = 1; current position = 1; range.size = 1;
    1 - 1 + 1 > 1 = false; do not consider subsequent ranges
Remove E from range, add to output list

最好找2;当前范围列表是:

(4:4)0[A] (2:3)1[D] (1:5)3[C,B]
  Consider (4:4); skip it because it is not eligible for position 2
  Consider (2:3); best element is D
  Consider further ranges?
     3 - 2 + 1 > 1 = true; check next range
  Consider (2:5); best element is B
End of range list; remove B from range, add to output list

增加的简化因素是不需要更新容量或重新排序范围。仅当其他较高排序范围不会受到干扰时,才会删除项目。初始排序后永远不会检查剩余容量。

最好找3;输出现在为EB;当前范围列表是:

(4:4)0[A] (2:3)1[D] (1:5)3[C]
  Consider (4:4); skip it because it is not eligible for position 3
  Consider (2:3); best element is D
  Consider further ranges?
     same as previous check, but current position is now 3
     3 - 3 + 1 > 1 = false; don't check next range
Remove D from range, add to output list

最好找4;输出现在为EBD;当前范围列表是:

(4:4)0[A] (1:5)3[C]
  Consider (4:4); best element is A
  Consider further ranges?
     4 - 4 + 1 > 1 = false; don't check next range
Remove A from range, add to output list

现在输出EBDA,还有一个要检查的元素,因此它会附加到结尾。这是我们希望拥有的输出列表。

这个构建过程是最长的部分。从本质上讲,它是一个简单的 n 2 选择排序算法。范围约束仅用于缩短内部循环,并且没有环回或递归;但最坏的情况我认为仍然是 sum i = 0 < sup> n n - i ),即n2/2 - n/2

如果当前位置超出该范围最大位置的末尾,则通过不排除候选范围来发挥检测步骤。您必须跟踪最佳候选人来自的范围才能将其删除,因此当您执行删除操作时,只需检查您提取候选人的位置是否大于endPosition范围。< / p>

我还有其他几个反例,它们挫败了我之前的算法,包括一个很好的例子,它显示了同一输入列表上的几个过度约束检测,以及最终输出如何在约束允许的情况下最接近最优。同时,请发布您可以看到的任何优化,特别是此算法做出客观错误选择的任何计数器示例(即,如果存在,则到达无效或次优输出)。


我不接受这个答案,因为我明确询问是否可以比 O(n 2 更好地完成。在@ DaveGalvin的答案中,我还没有完全理解约束满足的方法,而且我从来没有做过最大流量问题,但我认为这可能对其他人有所帮助。< / p>

另外,我发现提出有效测试数据的最佳方法是从有效列表开始并随机化它:0 - &gt; i,创建随机值和约束,使得min&lt;我&lt;最大。 (同样,发布它是因为它花了我的时间比它应该提出的更长,其他人可能会发现它有用。)

答案 2 :(得分:0)

不太可能*。我假设您的平均运行时间为O(n log n)就地,非稳定,离线。大多数排序算法改进了泡沫排序平均运行时间O(n ^ 2),如tim sort,依赖于假设比较子集中的2个元素将在超集中产生相同的结果。 Quicksort的较慢变体对于范围约束来说是一个很好的方法。最坏的情况不会改变,但平均情况可能会减少,算法将有一个有效排序的额外约束。

是否... O(n log n)排序不能保证找到有效的订单?

只要满足约束条件,我所知道的所有流行排序算法都可以保证找到订单。正式分析(具体证明)是每种类型的算法wikepedia页面。

是否有其他关于此类问题的研究?

是;有many期刊,如IJCSEA,有分类研究。

*但这取决于您的平均数据集。