有效地计算随机排列中的第n项

Efficient calculation of nth item in a random permutation

本文关键字:计算 随机 排列 有效地      更新时间:2023-10-16

想象一下,我可以使用Knuth洗牌和一个带有密钥的种子随机数生成器来洗牌0到2^32之间的所有数字。

从概念上讲,我需要两个数组(为简洁起见,使用Z5而不是Z232):
[2, 0, 1, 4, 3] // perm
[1, 2, 0, 4, 3] // inv === p^-1

如果我有这些数组,我可以有效地查找排列中的第n个元素以及找出排列值v中的第1个元素;

v = perm[n];
n == inv[v]; // true

我不想存储两个16 GB的int数组来表示这个洗牌集合,因为我在任何时候都不会对整个洗牌序列感兴趣。我只对第n个元素的值感兴趣

理想情况下,我想写两个这样的纯函数:

uint nthShuffled = permutate<uint>(key, n); // O(log n)
uint n == invert<uint>(key, nthShuffled); // O(log n)

要求:

  • 每个32位的值映射到一个唯一的不同的32位值。
  • 对排列中前100个元素的了解并不能提供排列中第101个元素的信息。

我明白,理论上必须有至少232!唯一键来表示任何可能的排列,但我相信在实践中我可以把这个问题隐藏在一个好的哈希函数后面。

有什么东西能接近这个吗?

任何分组密码实际上都是一个伪随机排列。32位分组密码将02 ^ 32 - 1之间的整数进行排列。

给定一个密钥,用这个密钥加密N-th,得到CC_4的伪随机数。

唯一的问题是找到一个好的32位分组密码。我唯一知道的是SKIP32,但我对它的强度一无所知。

SKIP32的密钥大小为80位。如果它是一个好的密码,那就足够了。

但是,我还是不知道密码。

如果将范围增加到2 ^ 64 - 1整数是您的选择,您可以简单地使用众所周知的64位分组密码,如Triple-DES或Blowfish。

"知道了排列中的前100个元素,就无法知道排列中的第101个元素是什么。"

需要将整个数组存储在内存中。我建议使用stxxl,它是通过将容器的大部分存储在磁盘上而为大型数据类型设计的。根据随机排列的本质,在给定[n]的情况下,你不能推断[n-1]或[n+1]的值。所以看起来空间不像是可以优化的

从密码学的角度来看,您需要使用32位块的分组密码。

在任意(通常很小)域上的加密(又名"键控排列")问题就是保持格式加密的问题。

对于这个特定的问题,有一个通用的"完美"解决方案——但是计算涉及到通过超几何分布进行采样,这意味着大量的浮点数和任意精度数的混淆,这是昂贵的。

也存在"近似"解,其中排列不是严格地在所有可能的排列中均匀选择的,但差异可以任意小,以至于无法区分实现的排列和真正随机选择的排列。具体参见索普洗牌。

没有标准和安全的32位分组密码,因为32位不足以确保在通常使用分组密码的情况下的安全性(加密长数据流,例如作为SSL的一部分);64位块已经不受欢迎了。所以你得靠自己了

哈希不能解决随机数序列

存储2^32位。那是0.5 GB。

运行fisher - yates洗牌,并在你进行的过程中"划掉"比特。如果你想知道第5个元素的内容,你可以划掉4,第5个随机值就是你的数字。

要得到第n个排列,你需要回溯。运行n次算法,得到如下数字:

Find 5th index after 4 permutations:
First iteration:
1st : skip (run through the RNG)
2nd : skip
3rd : skip
4th : 7th index to 5th index
Second iteration: (run using same seed as 1st iteration)
1st : skip
2nd : skip
3rd : 3rd index to 7th index
4th : 7th index to 5th index
Third iteration:
1st : skip
2nd : 4th index to 7th index
3rd : 3rd index to 7th index
4th : 7th index to 5th index
Fourth iteration:
1st : 8th index to 4th index
2nd : 4th index to 7th index
3rd : 3rd index to 7th index
4th : 7th index to 5th index

在最后一次迭代时,你知道第8个索引变成了第5个索引。

编辑:我写了一个快速的程序来测试速度。每个排列都需要几分钟。虽然速度很慢,但仍然可用。