【问题标题】:How to efficiently *nearly* sort a list?如何有效地*几乎*对列表进行排序?
【发布时间】:2023-03-20 20:59:01
【问题描述】:

我有一个物品清单;我想对它们进行排序,但我想要一个小的随机元素,所以它们不是严格按顺序排列的,只是平均排序。

我怎样才能最有效地做到这一点?

我不介意随机的质量不是特别好,例如它只是基于输入的机会排序,例如提前终止的不完全排序。

上下文通过引入一个非常轻微的不精确元素来实现近乎贪婪的搜索;这是一个紧密的循环,因此要考虑排序和调用random() 的速度

我当前的代码是做一个std::sort(这是C++),然后在数组的早期部分做一个非常短的洗牌:

for(int i=0; i<3; i++) // I know I have more than 6 elements
    std::swap(order[i],order[i+rand()%3]);

【问题讨论】:

  • 排序列表,然后移动一些元素?
  • 想问:为什么? - 你不能使用你选择的现有排序算法,然后通过提前退出来“打破它”吗?
  • 您是否希望该方法比完整排序更有效?换句话说,sort-then-slightly-permute 是一个可接受的解决方案吗?
  • 我会对列表进行排序,然后在其元素之间进行一些随机数的随机交换。
  • @Will 我认为我的建议不值得成为一个真正的答案,因为这是最幼稚的做法,其效率分析是微不足道的。

标签: c++ algorithm sorting random


【解决方案1】:

使用JSort 的前两次传递。两次构建堆,但不执行插入排序。如果随机元素不够小,则重复。


有一种方法(与不完整的 JSort 不同)允许对生成的随机性进行更精细的控制,并且时间复杂度取决于随机性(需要的随机结果越多,时间复杂度越低)。对Soft heap 使用堆排序。软堆的详细说明见pdf 1pdf 2

【讨论】:

    【解决方案2】:

    假设您希望数组按升序排序,我会执行以下操作:

    for M iterations
      pick a random index i
      pick a random index k
      if (i<k)!=(array[i]<array[k]) then swap(array[i],array[k])
    

    M 控制数组的“排序”——随着 M 的增加,数组变得越来越有序。我会说 M 的合理值是 n^2 其中 n 是数组的长度。如果选择随机元素太慢,那么您可以预先计算它们的索引。如果该方法仍然太慢,那么您始终可以减少 M,但代价是获得更差的排序。

    【讨论】:

      【解决方案3】:

      将列表分成大小相等的两个部分。使用任何常用算法分别对每个部分进行排序。然后合并这些部分。像往常一样执行一些合并迭代,比较合并的元素。对于其他合并迭代,不要比较元素,而是从同一部分中选择元素,就像上一步一样。没有必要使用 RNG 来决定如何处理每个元素。只需忽略每个第 N 个元素的排序顺序。

      这种方法的其他变体几乎可以排序数组几乎就地。将数组拆分为具有奇数/偶数索引的两部分。对它们进行排序。 (甚至可以使用带有适当修改的迭代器的标准 C++ 算法,例如 boost::permutation_iterator)。在阵列的末端保留一些有限的空间。合并零件,从头开始。如果合并的部分要覆盖其中一个未合并的元素,只需选择该元素。否则按排序顺序选择元素。随机程度由预留空间量决定。

      【讨论】:

        【解决方案4】:

        如果您确定该元素至多k 远离它们应该在的位置,您可以将快速排序N log(N) 排序时间复杂度降低到N log(k)....

        编辑

        更具体地说,您将创建 k 个存储桶,每个存储桶包含 N/k 个元素。

        您可以对每个存储桶进行快速排序,这需要k * log(k) 次,然后对N/k 存储桶进行排序,这需要N/k log(N/k) 时间。将这两者相乘,就可以在N log(max(N/k,k))进行排序了

        这很有用,因为您可以对每个存储桶并行运行排序,从而减少总运行时间。

        如果您确定列表中的任何元素在排序后距其正确位置最多 k 个索引,则此方法有效。

        但我不认为你的意思是任何限制。

        【讨论】:

        • 这不是我的意思,但我很好奇你的意思是什么;请进一步解释或链接?
        • @Will:我认为他的意思是当你当前正在排序的块大小达到k 或更少时进行快速排序except
        【解决方案5】:

        冒泡排序来救援!

        对于未排序的数组,您可以选择一些随机元素并将它们向上或向下冒泡。 (也许通过轮换,效率更高一些)(dis)order的数量很难控制,即使你选择所有N个元素,你也不确定整个数组是否会被排序,因为元素被移动了而且你不能确保你只碰过每个元素一次。

        顺便说一句:这种问题往往发生在游戏引擎中,其中包含候选动作的列表或多或少地保持排序(因为加权采样),并且每次迭代后排序太昂贵,并且只有一个或一些元素预计会移动。

        【讨论】:

          【解决方案6】:

          需要更多空间但可以保证无需修改即可使用现有排序算法的一种可能性是创建排序值的副本,然后在排序之前以某种方式修改它们(然后使用排序的修改值)。

          例如,如果要排序的数据是一个简单的字符字段Name[N],则添加一个名为NameMod[N] 的字段(假设数据在结构或类中)。用Name 的副本填写NameMod,但添加一些随机化。然后 3% 的时间(或一些适当的数量)更改名称的第一个字符(例如,将其更改 +/- 一个或两个字符)。然后 10% 的时间更改第二个字符 +/- 几个字符。

          然后通过您喜欢的任何排序算法运行它。好处是您可以轻松更改这些百分比和随机性。并且排序算法仍然有效(例如,比较函数返回不一致的结果不会有问题)。

          【讨论】:

            【解决方案7】:

            随机抽取一小部分数据并对其进行排序。您可以将其用作地图,以估计每个元素应出现在最终几乎排序的列表中的位置。您现在可以浏览完整列表并移动/交换位置不佳的元素。

            这基本上是 O(n),假设子集的小的初始排序不需要很长时间。希望您可以构建地图,以便可以快速提取估计值。

            【讨论】:

              【解决方案8】:

              您可以使用标准排序算法(是否有可用的标准库?)并传递一个“知道”的谓词,给定两个小于另一个的元素,或者如果它们相等(返回 -1、0 或1)。然后在谓词中引入一个罕见的(可配置的)情况,其中答案是随机的,通过使用随机数:

              伪代码:

              if random(1000) == 0 then
                return = random(2)-1   <-- -1,0,-1 randomly choosen
              

              这里我们有 1/1000 的机会“打乱”两个元素,但这个数字完全取决于要排序的容器的大小。

              在 1000 案例中添加的另一件事可能是删除“正确”答案,因为这不会打乱结果!

              编辑:

              if random(100 * container_size) == 0 then <-- here I consider the container size
              {
                 if element_1 < element_2
                    return random(1); <-- do not return the "correct" value of -1
                 else if element_1 > element_2
                    return random(1)-1; <-- do not return the "correct" value of 1
                 else
                    return random(1)==0 ? -1  : 1; <-- do not return 0
              }
              

              在我的伪代码中: 随机(x)= y 其中 0

              【讨论】:

              • 根据算法的不同,编写搜索以抵抗比较器的不一致结果可能会很尴尬。您可以轻松地跑出您认为正在处理的区域的尽头,或者认为您已经完成了分类,而实际上您几乎没有做任何事情。显然细节有所不同,但例如在 C 或 C++ 中,将不一致的比较器传递给 qsortstd::sort 是未定义的行为。
              • @Steve 它在哪种意义上是未定义的?你没有得到一个排序的容器(这就是他想要的),而不是程序崩溃!
              • 对于 C 和 C++,在标准中定义的意义上,它是未定义的行为。是的,如果你给它一个不一致的比较器,标准允许实现崩溃,我已经看到了这种情况。在其他语言(例如 Java)中,您遇到的最糟糕的情况是异常。
              猜你喜欢
              • 1970-01-01
              • 2021-03-14
              • 2015-02-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-11-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多