使用 Haskell 按字典顺序获取排列

Get permutations in lexicographic order using Haskell

我正在研究 Project Euler 中的问题 24,如下所示:

A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all of the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1 and 2 are:

012 021 102 120 201 210

What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9?

我正在尝试使用 Haskell 解决此问题,并从暴力方法开始:

import Data.List
(sort . permutation) [0..9] !! 999999

但这花费的时间太长了,我想知道是否是因为程序首先获取所有排列,然后对它们进行排序,最后取第 100 万个元素,这比它需要做的工作多得多做。

所以我在想,如果我要编写一个函数来枚举已经按字典顺序排列的排列,我可以加快速度,这样我们就可以停在百万分之一的元素上并得到答案。

我想到的算法是首先对输入列表 x 进行排序,然后取第一个(最小)元素并将其添加到按字典顺序排列的其余元素的排列中。这些顺序将通过在 x 现在已经排序的尾部递归调用原始函数来找到(这意味着我们的原始函数应该有一种标记输入列表是否排序的方法)。然后我们继续 x 的下一个最大元素,依此类推,直到我们得到完整的有序列表。不幸的是,我仍然是 Haskell 初学者,我尝试编写此函数的尝试失败了。关于如何完成此操作的任何提示?

我的想法太长了,无法发表评论,但这不是一个完整的可行解决方案。不过,这是一个几乎可以立即生效的计划。

从按字典顺序生成排列开始。使用递归算法很容易做到这一点。首先,select 可用的最少元素,递归地生成剩余元素的排列,将 selected 元素添加到每个排列之前。然后 select 字典序的第二个元素并继续向上。

就其价值而言,如果输入列表按升序排序,这是您经常在 Haskell 教学材料中找到的基于标准非确定性 select 的置换算法。它 不是 Data.List.permutations 使用的算法,它旨在通过无限输入更快、更高效。

但你可以做得更好。您不需要在目标排列之前生成所有排列。可以跳过,原来真的很简单

您需要做的就是查看您要定位的排列数,我们称之为 k,并使用它来对排列进行索引。如果输入按字典顺序排序,则结果的第一个元素是索引 q 处的元素,然后是索引 r 处剩余元素的排列,给定 (q, r) = divMod k (fact(n - 1)).

我敢肯定有比这更快的方法,但是对于像一百万这样的小数字来说,这应该基本上是即时的。