两种改组方法中哪一种效果更好?

时间:2016-03-12 02:22:02

标签: java list random collections

我正在尝试在List中随机洗牌整数集合,我想出了两种洗牌方法来完成这项工作。但是,我不确定哪一个效果更好?有没有人有任何意见或建议?

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);
    }
}

2 个答案:

答案 0 :(得分:6)

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

未注释的算法理论上较差,这很容易证明。维基百科关于Fisher-Yates shuffle的文章中给出了解释原因的原因。引用该文章,(为清晰起见而编辑)

  

实施Fisher-Yates shuffle时常见的错误是从错误的范围中选择随机数。有缺陷的算法似乎可以正常工作,但它不会以相同的概率产生每个可能的排列。始终在每次迭代时从整个有效数组索引范围中选择 j 会产生偏差的结果,尽管不那么明显。这可以从这样的事实看出,即这样做产生n^n个不同的可能的交换序列,而n元素阵列只有n!个可能的排列。由于n^n n!永远不能被n > 2整除,因为n−1可以被n)整除,而n^ni - 1没有共同因素,因此会有一些排列必须由更多的list.size()掉期序列产生。

在此上下文中, j 是目标槽,用于交换元素{{1}}。看起来,将当前元素与0和{{1}}之间的所有元素进行交换可能会提供更好的随机播放,因为它似乎可以“更多”地改组。确实存在比Fisher-Yates更多不同的可能序列,但额外的序列增加了偏差,降低了混洗的质量。

维基百科的文章详细说明了为什么会这样,并且它显示了改变3元素数组的每个结果的概率。

这很容易证明。取一个3元素阵列或列表并将其洗牌,比如说,一百万次,并计算每个可能的六个排列的出现频率。我用Fisher-Yates做了这个,结果都在±0.3%之内。在全范围洗牌的情况下,其中三种排列比其他三种排列频率高出约20%。

Fisher-Yates shuffle算法,你标注为“替代版本”,显然是一种优越的方法。

答案 1 :(得分:4)

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

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

或者你自己写一个特定的目的吗?