改组数组的以下算法之间有什么区别吗?

时间:2016-09-30 19:24:50

标签: java algorithm shuffle

我的Java教科书说你可以使用以下代码随机洗牌任何给定的数组:

for(int i = myList.length-1; i >=0; i--)
{

    int j = (int)( Math.random() * (i+1) );
    double temp = myList[i];
    myList[i] = myList[j];
    myList[j] = temp;

}


我写的以下代码是否同样有效或有效?

for(int i = 0; i < myList.length; i++)
{

    int j = (int)( Math.random() * (myList.length) );
    double temp = myList[i];
    myList[i] = myList[j];
    myList[j] = temp;

}

我测试了我的代码,它确实正确地改变了元素。有没有理由使用教科书的算法?

4 个答案:

答案 0 :(得分:4)

是的,它们实际上是不同的。

第一种算法是经典Knuth Shuffle的变体。 对于这个算法,我们可以证明(例如,通过归纳),如果我们的随机数生成器(Math.random())是理想的,它将生成n中的每一个! (n阶乘)可能的排列具有相同的概率。

第二种算法没有此属性。 例如,当n = 3时,有3个 3 = 27个可能的结果,并且不会均匀地除以3! = 6,可能的排列数。 实际上,以下是结果的概率(生成统计数据的程序:1 2):

[0, 1, 2] 4/27
[0, 2, 1] 5/27
[1, 0, 2] 5/27
[1, 2, 0] 5/27
[2, 0, 1] 4/27
[2, 1, 0] 4/27

对于n = 4,结果更加不均衡,例如(生成统计数据的程序:3 4):

[1, 0, 3, 2] has probability 15/256
[3, 0, 1, 2] has probability  8/256

如你所能想象的那样,如果你的排列应该是随机的,那么这是一个不受欢迎的属性。

最后,我们通常使用伪随机数生成器而不是真正的随机源的事实不会使上述任何一个无效。 我们的随机数发生器的缺陷(如果有的话)显然无法在后一步修复损坏 - 如果我们选择非均匀算法,那就是。

答案 1 :(得分:3)

第一个例子是首选,因为它确保了元素随机排列的公平性。在第二个例子中,元素是随机的,但不是同等随机的。

第一个示例基于一个不使用Math.random的优化版本。

Random rand = ...
for(int i = myList.length-1; i > 0; i--) {
    int j = rand.nextInt(i+1);
    double temp = myList[i];
    myList[i] = myList[j];
    myList[j] = temp;
}

来自Collections.shuffle

        for (int i=size; i>1; i--)
            swap(list, i-1, rnd.nextInt(i));

这是一回事。

这可能会快得多,因为它不必产生尽可能多的随机性&#34;这意味着更少的计算。生成double比使用0到9之间的小数字要贵得多。

然而,第一个例子没有利用这个并且以任何方式调用Math.random()。只有使用nextInt(n)

才有用

答案 2 :(得分:1)

不,你不能用你的算法代替书的算法。

说明:您的书籍算法从当前元素开始,从最后一个元素开始,然后从[0, current]中的所有元素中选择另一个元素,然后交换它们。这样一个更高的索引元素永远不会再被触及,但它可能仍然最终与自己交换(这是正常的)

但是,在您的算法中,您生成随机索引以与0i - 1之间的所有可能索引进行交换。因此,在shuffle期间,可以将更高的索引元素交换回其原始位置。

以下代码不等同于您图书的算法。它不会留下任何元素,这在你的书的算法的情况下是可能的:

for (int i = myList.length - 1; i > 0; i--) {
    int j = (int)(Math.random() * i);
    swap(myList, i, j);
}

private void swap(double[] myList, int i, int j) {
    double temp = myList[i];
    myList[i] = myList[j];
    myList[j] = temp;
}

答案 3 :(得分:0)

  

有没有理由使用教科书的算法?

不。您的代码和教科书都没有基于乘数值的火箭科学。核心部分是Math.random()函数。您的图书中的乘数为i+1,在获得重复的j值时数学概率较低,而您的代码获得重复j值的概率稍高,但坦率地说,没关系。

虽然,每次执行添加操作时,您的代码会稍微,实际上略微降低,实际上可以忽略不计。