用于排序的重新洗牌次数

Number of reshuffles used to sort

最近发现了一个有趣的问题,大概是这样的:

有一个枯燥的排序算法从数组中取第一个数,它找到一个比第一个元素低1的元素(或者它取最高的元素当没有下一个时),并将其放在前面。 将索引为 x(从 0 开始计数)的元素放在前面的成本等于它的索引。 它会继续这个过程,直到数组被排序。 任务是统计排序所有n!从 1 到 n 的数字排列。答案可能很大,所以答案应该是模 m(n 和 m 在输入中给出)

示例:

Input (n,m): 3 40
Answer: 15

There are permutations of numbers from 1 to 3. The costs of sorting them are:
(1,2,3)->0
(1,3,2)->5
(2,1,3)->1
(2,3,1)->2
(3,1,2)->4
(3,2,1)->3
  sum = 15

我的程序生成了所有可能的数组,并将它们一一排序。它的复杂度是 O(n!*n^2),太高了。我被我所有的想法和这个蛮力解决方案困住了。

还有一些有趣的发现:

  1. 当我根据这些成本的数量对排序排列的成本进行分组时,我得到了一个奇怪的数字: n=7 https://cdn.discordapp.com/attachments/503942261051228160/905511062546292807/unknown.png (每行有 2 个数字 x * y,在它们之前有 y 个空格。x-排序成本,y-排序成本等于 y 的排列数 (1-n))。分数是所有行 x*y 的总和。如果需要,我可以为其他 n 提供此图。
  2. 可能是关于斐波那契数列,只是一种感觉。
  3. 正如您在 1. 中看到的那样,有一个部分的所有 y 都相同。它从 x=n^2-4n+3 开始。

问题:如何更有效地解决这个问题?

从排序后的排列 123...n,您可以使用规则的反向构建树,并获得所有排列。看到这棵树 n=4.

现在,观察一下

if node==1234 then cost(node) = 0

if node!=1234 then cost(node) = blue_label(node) + cost(parent)

你需要的是制定反向规则来生成树。 也许使用一些记忆技术来避免每次都重新计算成本。

排序算法有两个阶段:首先对排列进行排序 进入身份的某种旋转,然后将其旋转到身份。 我们分别计算这些阶段的成本。

第一阶段最多包含n−2步。在 n−1−j 移动之后, 排列由 n−j 个值 x, x+1, x+2, … mod n 后跟 a 剩余 j 值的排列,假设我们从 一个随机排列,同样可能是在任何特定的顺序。 我们必须移动 x−1 mod n 的预期距离是 ((n−j)+(n−1))/2。 但是等一下,如果我们仍处于第一阶段,我们只会计算移动。 因此,我们需要对排列已经是 a 的情况进行打折 回转。有 n!/j!他们中的,他们最后都有 x−1,所以 每个的折扣是 n−1。

第二阶段平均由 (n−1)/2 次移动组成 到开头的排列,每个排列的成本为 n−1。平均费用 总而言之!因此排列是 (n−1)²/2.

我将 modular arithmetic/strength 减少 Python 下面 作为练习。

from itertools import permutations
from math import factorial


# Returns the total cost of sorting all n-element permutations.
def fast_total_sort_cost(n):
    cost = 0
    for j in range(n - 1, 0, -1):
        cost += factorial(n) * ((n - j) + (n - 1)) // 2
        cost -= (factorial(n) // factorial(j)) * (n - 1)
    return cost + factorial(n) * (n - 1) ** 2 // 2


# Reference implementation and test.
def reference_total_sort_cost(n):
    return sum(sort_cost(perm) for perm in permutations(range(n)))


def sort_cost(perm):
    cost = 0
    perm = list(perm)
    while not is_sorted(perm):
        i = perm.index((perm[0] - 1) % len(perm))
        cost += i
        perm.insert(0, perm.pop(i))
    return cost


def is_sorted(perm):
    return all(perm[i - 1] <= perm[i] for i in range(1, len(perm)))


if __name__ == "__main__":
    for m in range(1, 9):
        print(fast_total_sort_cost(m), reference_total_sort_cost(m))