这是一个Scala中的示例解决方案,我将详细解释:
/**
example: index:=15, list:=(1, 2, 3, 4)
*/
def permutationIndex (index: Int, list: List [Int]) : List [Int] =
if (list.isEmpty) list else {
val len = list.size // len = 4
val max = fac (len) // max = 24
val divisor = max / len // divisor = 6
val i = index / divisor // i = 2
val el = list (i)
el :: permutationIndex (index - divisor * i, list.filter (_ != el)) }
由于 Scala 并不为人所知,我想我必须解释一下算法的最后一行,除此之外,它是非常不言自明的。
el :: elist
从元素 el 和列表 elist 构造一个新列表。 Elist 是一个递归调用。
list.filter (_ != el)
只是没有元素 el 的列表。
用一个小列表彻底测试它:
(0 to fac (4) - 1).map (permutationIndex (_, List (1, 2, 3, 4))).mkString ("\n")
用 2 个例子测试更大 List 的速度:
scala> permutationIndex (123456789, (1 to 12).toList)
res45: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 6, 9, 3)
scala> permutationIndex (123456790, (1 to 12).toList)
res46: List[Int] = List(4, 2, 1, 5, 12, 7, 10, 8, 11, 9, 3, 6)
结果会立即出现在一台 5 年前的笔记本电脑上。 12 个元素的列表有 479 001 600 个排列,但是对于 100 或 1000 个元素,这个解决方案应该仍然可以快速工作 - 你只需要使用 BigInt 作为索引。
它是如何工作的?我制作了一个图形,以可视化示例,一个 (1, 2, 3, 4) 的列表和一个 15 的索引:
4 个元素的列表产生 4 个!排列(=24)。我们选择从 0 到 4 的任意索引!-1,比如说 15。
我们可以可视化一棵树中的所有排列,第一个节点来自 1..4。我们分4!乘以 4 可以看出,每个第一个节点都会导致 6 个子树。如果我们将索引 15 除以 6,则结果为 2,并且索引为 2 的从零开始的 List 中的值是 3。所以第一个 Node 是 3,而 List 的其余部分是 (1, 2, 4) .这是一个表格,显示了 15 如何导致 Array/List/whatever 中索引为 2 的元素:
0 1 2 3 4 5 | 6 ... 11 | 12 13 14 15 16 17 | 18 ... 23
0 | 1 | 2 | 3
| | 0 1 2 3 4 5 |
我们现在减去 12,即最后 3 个元素的第一个元素 (12...17),它们有 6 种可能的排列,看看 15 如何映射到 3。数字 3 导致数组索引 1现在,它是元素 2,所以到目前为止的结果是 List (3, 2, ...)。
| 0 1 | 2 3 | 4 5 |
| 0 | 1 | 3 |
| 0 1 |
同样,我们减去 2,并以 2 个剩余元素结束,具有 2 个排列和索引 (0, 3),映射到值 (1, 4)。我们看到,第二个元素,属于从顶部开始的 15,映射到值 3,最后一步的剩余元素是另一个:
| 0 | 1 |
| 0 | 3 |
| 3 | 0 |
我们的结果是 List(3, 2, 4, 1) 或索引 (2, 1, 3, 0)。按顺序测试所有索引表明,它们按顺序产生所有排列。