3

我正在寻找一个从 0,1....N 区间接受 1 个数字并从同一区间返回置换值的函数。

0、1、2、3、4、5 和 f(x) 的示例是:

f(0)=5;
f(1)=1;
f(2)=0;
f(3)=4;
f(4)=2;
f(5)=3;

根据我的研究/理解,这是一个循环组,其中 f(x) 是它的全部基础。如果发现函数 f(x) = 911 * x % N 是我想要的一个例子,但是,使用它时会出现模式。911 是一个很大的素数,通过改变它,我得到了另一个排列,但仍然出现了模式。我希望结果是随机的。

我知道我可以使用 [ 0, 1, 2...N ].shuffle() 之类的东西预先生成排列,但就我而言,我不能这样做。

我有一项服务,它接受输入一个数值并返回置换后的值/位置。N 设置为服务器端,我正在寻找的功能也是如此。

4

3 回答 3

4

请记住,我在这里描述的算法是基于列表 [1, 2, ... N-1] (长度为 N-1)。如果您坚持使用列表 [0, 1, ..., N](长度为 N+1),请应用所需的小修改。此外,为简洁起见,我使用 % 操作数的方式与大多数编程语言略有不同:a % b 取值介于 1 和 b 之间,而不是介于 0 和 b - 1 之间,但背后的主要思想当然没有改变, 所以a % b 的值是1和b之间的整数,与a一致,取模b。

如果你通读了这篇文章,你会很明显,生成的 shuffle 根本不是随机的。然而,如果参数选择得当,模式将不容易识别,(我的模幂运算的基本思想来自密码学,其中具有不可识别的模式和不可恢复的函数很重要)。

这更像是算法的语言无关描述,而不是实际的编程解决方案。我不会详细介绍您可能遇到的有效实现和陷阱。我希望它仍然有帮助。我还在 python 中编写了其中的一些部分,因此我可以提供进一步的帮助,甚至在需要时分享我的代码,但这之前需要一些完成和重构。

使用求幂而不是乘法来摆脱模式

您对 f(x) = t * x % N(您选择 t 为 911)的初步试验显示了一些模式,因为乘法保持线性(在它的“模块化”含义中)。

如果你使用指数而不是乘法,你可以给人更多随机的感觉。像 f(x) = t ^ x % N 之类的东西。但是,必须明智地选择 t(就像在乘法的情况下一样,与 N 互质),并且该公式给出的输出不会提供不同的仅在 N 为素数的情况下,不同 x 值的数字。

大学水平的数学即将到来,请耐心等待,我会尽量保持简单。

我们将需要使用原始根。相关的Wikipedia 文章可能有很大帮助,但基本思想是精心选择的基数的余数取 1 到 N-1 之间的所有值。例如

3^1 = 3
3^2 = 9 = 2 (mod 7)
3^3 = 27 = 6 (mod 7)
3^4 = 81 = 4 (mod 7)
3^5 = 243 = 5 (mod 7)
3^6 = 729 = 1 (mod 7)

都是不同的(从这一点开始,值从头开始重复:3^7 = 3^1 (mod 7)、3^8 = 3^2 (mod 7),依此类推)。

所以,如果你的 N 是 7,那么 3 就可以成为 t。您可以将 f(x) = (3 ^ x) % 7 用于 1 到 6 之间的 x 值。

这导致以下 f:

f(1) = 3
f(2) = 2
f(3) = 6
f(4) = 4
f(5) = 5
f(6) = 1

引入移位,提供一些额外的随机效果

如果你稍微玩一下,你会注意到,N-1 总是被洗牌为 1。如果你想避免这种情况,我们可以更进一步,选择一个任意数 k 在求幂后添加。也就是说,使用 g(x) = (f(x) + k) % (N-1) = ((t ^ x) % N + k) % (N-1),在我们的示例中让 k 为 2,导致排列:

g(1) = 5
g(2) = 4
g(3) = 2
g(4) = 6
g(5) = 1
g(6) = 3

如何选择底座

现在你得到了基本的感觉。但是一般如何使用这个,当N不是7时?

问题的关键是选择参数 t,在我们的示例中为 3。

不幸的是,这通常是一个难题(数学家称之为寻找原始根),而且我知道没有任何易于解释、开箱即用的解决方案。

但这只是问题的一部分。更可悲的是,如果 N 是合数,则原根甚至都不起作用。例如,如果 N=6,则表达式 t^x 以 6 取 1 到 5 之间的所有值都不存在任何数字 t。

但这部分解决起来并不难。

如何处理复合 N

如果 N 是复合的,我们应该找到一个稍大一点的素数 P,并在基于该算法的算法的基础上,通过用它们的洗牌后值替换超出范围的数字(如果需要,迭代) .

例如,如果 N 为 6,我们可以选择 P 为 7 并使用我们之前构建的 g(x)。

g(1) = 5 ok (5<=N-1 holds)                          h(1) = 5
g(2) = 4 ok                                         h(2) = 4
g(3) = 2 ok                                    =>   h(3) = 2
g(4) = 6 too large, using g(g(4)) = g(6) = 3        h(4) = 3
g(5) = 1 ok                                         h(5) = 1

为了安全起见,我举了另一个 N=4 的例子,我们使用之前计算的 P=7 的解决方案。

g(1) = 5, g(5) = 1                                  h(1) = 1
g(2) = 4, g(4) = 6, g(6) = 3                   =>   h(2) = 3
g(3) = 2                                            h(3) = 2

现在应该很清楚了。选择接近 N 的 P 是明智的,因此对于 g 的越界值不需要太多的重新计算。

回到寻找 t

所以我们剩下的唯一问题是找到可以用作求幂基础的原根。

如果我之前链接的页面上的数学引起了一些内心的厌恶,我有一些好消息要告诉你:t 的可能好的值在区间 [2, N-1] 中是密集的,所以即使是随机猜测也可能会有所帮助。

有一些详细信息如何有效地验证随机选择的 t 在链接页面上是否真的对我们有好处,但是除非您使用非常大的数字,否则您可以只进行求幂并检查数字 1 是否早于 ( t 的 N-1) 次方(也许你记得我注意到 t^x=1 (mod N) 在 x=N-1 的情况下始终成立,因此 1 的早期出现会破坏唯一性)。

我建议在 N/2 附近寻找合适的 t(意味着数量级 - 对于 P=91367,t=54949 效果很好)。如果您选择 t 太低(例如 t=2),您可以很容易地发现一些相邻 x 值(2+k, 4+k, 8+k, ... 会出现在彼此)。如果 t 太接近 N,如果在相同奇偶性的连续 x 值中查看 f(x),可能会出现类似的现象。一个好的 t 选择应该涵盖这些模式,并以足够随机的结果结束。

概括

所以再一次,这里是算法的步骤

(N 给定)

找到一个比 N 稍大的 P 素数

选择 1 到 P-1 之间的任意数字 k

找到 t,它是 P 的原根

(对于给定的 x,输出 shuffle h(x) 是)

计算

f(x) = (t ^ x) % P

计算

g(x) = (f(x) + k) % (P-1)

计算

h(x) = g(x)                                       if g(x)<=N-1,
       iterate the calculations with x = g(x)     otherwise
于 2013-05-07T22:08:44.140 回答
2

这个非常相关的问题(如何在不预先生成整个序列的情况下生成可预测的序列改组?)及其接受的答案(以及其中链接的博客文章及其参考论文)也可能很有趣,应该可以解决您的问题。

于 2013-05-08T12:44:52.097 回答
0

根据您的评论:

Function accepts 1 number =x, math happens, 1 number is returned which is a random position AND UNIQUE within the same interval where x comes from

我可能仍然不明白这个问题,但我再试一次。我希望它会给你一些线索/帮助!

<?php
$control = array();

$f = array(5,1,0,4,2,3); //Example

$x = 5; //Function accepts 1 number =x


for($n=1;$n<41;$n++) {
    echo doTheMath($control, $f, $x); //Echo just for example
}

function doTheMath(Array &$control, Array &$f, $x) {
    $n = count($f); //Interval count

    //1 number is returned 
    //AND
    //UNIQUE within the same interval where x comes from.

    while (!in_array($random = array_rand($f, 1), $control)) { //Get another random value from array-element if it's not in control-array

        if (!in_array($random, $control)) {
            $control[] = $random;

            //The are no more unique values, stop executing php
            if (count($control) == $n) {
                exit;
            }

            $result = 911 * $random * $n;
            return $result . '<hr />';
        }
    }


    return null;

}
?>

示例输出:

10932
0
21864
27330
16398
于 2013-05-07T10:11:26.813 回答