【问题标题】:Which of the two shuffling method works better?两种洗牌方法中哪一种效果更好?
【发布时间】:2016-06-27 10:51:58
【问题描述】:

我正在尝试随机打乱列表中的整数集合,并且我想出了两种打乱方法来完成这项工作。但是,我不确定哪一个效果更好?有没有人有任何cmets或建议?

public class TheCollectionInterface {

    public static <E> void swap(List<E> list1, int i, int j) {
        E temp = list1.get(i);
        list1.set(i, list1.get(j));
        list1.set(j, temp);
    }

// Alternative version:    
//    public static void shuffling(List<?> list, Random rnd){
//        for(int i = list.size(); i >= 1; i--){
//            swap(list, i - 1, rnd.nextInt(i));
//        }
//    }


    public static <E> void shuffling(List<E> list1, Random rnd) {
        for (int i = list1.size(); i >= 1; i--){
            swap(list1, i - 1, rnd.nextInt(list1.size()));
        }
    }

    public static void main(String[] args) {

        List<Integer> li2 = Arrays.asList(1,2,3,4,5,6,7,8,9);

        Random r1 = new Random();

        TheCollectionInterface.shuffling(li2, r1);

        System.out.println(li2);
    }
}

【问题讨论】:

标签: java list random collections


【解决方案1】:

如果每个可能的结果都具有相同的可能性,则通常认为洗牌算法是好的。您的改组算法的“替代版本”被称为 Fisher-Yates 算法。给定足够好的随机源,该算法可证明以相等的概率生成输入的每个可能排列。 JDK Collections.shuffle() 方法使用此算法。

未注释的算法在理论上是劣等的,这很容易证明。为什么会这样的解释在这篇关于Fisher-Yates shuffle 的维基百科文章中给出。引用那篇文章,(为清楚起见进行了编辑)

实施 Fisher-Yates 洗牌时的一个常见错误是从错误的范围内选择随机数。有缺陷的算法可能看起来可以正常工作,但它不会以相等的概率产生每个可能的排列。每次迭代时总是从有效数组索引的整个范围中选择 j 会产生有偏差的结果,尽管不太明显。这可以从这样一个事实看出,这样做会产生n^n 不同的可能交换序列,而 n 元素数组只有n! 可能的排列。由于n^nn &gt; 2 时永远不能被n! 整除(因为后者可以被n−1 整除,n)n) 没有质因数,因此必须由更多的n^n 产生一些排列交换序列比其他序列。

在此上下文中,j 是与元素 i - 1 交换的目标插槽。似乎将当前元素与 0 和 list.size() 之间的全部元素交换会提供更好的随机播放,因为它似乎做了“更多”的随机播放。确实有比 Fisher-Yates 更多不同的可能序列,但是额外的序列增加了偏差,降低了 shuffle 的质量。

Wikipedia 文章详细说明了为什么会这样,并显示了对 3 元素数组进行洗牌的每个结果的概率。

这很容易演示。取一个 3 元素数组或列表并将其打乱,例如一百万次,并计算可能的六种排列中每一种排列的出现频率。我和 Fisher-Yates 一起做了这个,结果都在 ±0.3% 之内。使用全范围洗牌时,其中三个排列的发生频率比其他三个高出约 20%。

您标记为“替代版本”的 Fisher-Yates shuffle 算法显然是更好的方法。

【讨论】:

    【解决方案2】:

    您是否考虑过在 Collection 中随机播放的内置方法?

    http://www.tutorialspoint.com/java/util/collections_shuffle.htm

    或者自己写有没有特定的目的?

    【讨论】:

    • 只是想练习一下随机洗牌背后的逻辑
    • 在这种情况下,查看您注释掉的备用版本,它在操作上看起来是相同的。您似乎正在练习编写 JavaDocs 中描述的 Collections.shuffle 方法,所以这很酷。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多