题目:假设有一个序列,那么如何根据这个序列生成一个乱序的序列?
看到这个题目,那么首先会想到这样一种解法: 我们将序列的下标进行乱序,再根据这个乱序的下标不就生成一个乱序的序列了吗!
如下是生成序列下标的乱序算法:
int length = list.size(); Set<Integer> set = new LinkedHashSet<>(); Random random = new Random(); while (true) { int randomInt = random.nextInt(length); set.add(randomInt); if (set.size() == length) { break; } }
根据这个乱序后的下标的有序集合,就可以生成我们要的乱序序列了。
那么是不是一定要我们自己实现一个这样的算法呢?当然不用了,贴心的J
DK已经提供了这样一个函数,使用起来非常简单,这个函数隐藏在Collections类中
Collections.shuffle(List<?> list); //和 Collections.shuffle(List<?> list, Random rnd);
- 这两个函数都是无返回值得,也就是说是对入参的序列做修改。
- 第二个override函数多了一个入参 rnd ,这是一个随机数生成器,可以选用不同的随机数生成算法函数。
如果只是满足使用,嗯~ 是不是有点low?
为了高大上一些,看下源码吧!
public static void shuffle(List<?> list) { Random rnd = r; if (rnd == null) r = rnd = new Random(); // harmless race. shuffle(list, rnd); } public static void shuffle(List<?> list, Random rnd) { int size = list.size(); if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {//SHUFFLE_THRESHOLD = 5 for (int i=size; i>1; i--) swap(list, i-1, rnd.nextInt(i)); } else { Object arr[] = list.toArray(); // Shuffle array for (int i=size; i>1; i--) swap(arr, i-1, rnd.nextInt(i)); // Dump array back into list // instead of using a raw type here, it's possible to capture // the wildcard but it will require a call to a supplementary // private method ListIterator it = list.listIterator(); for (int i=0; i < arr.length; i++) { it.next(); it.set(arr[i]); } } }
JDK提供的混淆算法的关键是这么几句:
// Shuffle array for (int i=size; i>1; i--) swap(arr, i-1, rnd.nextInt(i));
swap
函数暂且不看,只要知道是用来交换两项的位置的就可以了,感兴趣的可以下来看源码。
这两句什么含义呢:
- 序列使用倒序进行遍历;
- nextInt 会取得[0,i)之间的随机数;
- 进行两项交换。
现在在大脑中对这个算法进行模拟:
- i= size, i-1是序列的最后一项,nextInt 生成了[0,i)之间的一个随机数,并且和序列的最后一项进行了交换;
- i=size-1, i-1是序列的倒数第二项,nextInt生成了[0,size-1)之间的随机数,并且和序列的倒数第二项进行了额交换; ...... 当遍历了序列后就得到了一个乱序序列。
程序是看懂了,可为啥这样设计呢?
其实这是对排列组合的一种运用
排列的定义:从n个不同元素中,任取m(m≤n,m与n均为自然数,下同)个元素按照一定的顺序排成一列,叫做从n个不同元素中取出m个元素的一个排列;从n个不同元素中取出m(m≤n)个元素的所有排列的个数,叫做从n个不同元素中取出m个元素的排列数,用符号 A(n,m)表示。
组合的定义:从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做从n个不同元素中取出m个元素的组合数。用符号 C(n,m) 表示。
当然我们这个算法只用到了组合,也就是从n个不同的元素中取出m(m=n)个元素组成一组。
n个元素,假设是从0~9 10个数 那么 0,1,2,3,4,5,6,7,8,9 和 9,8,7,6,5,4,3,2,1,0 仅仅是组合中的两种,那么在其他组合中任取一个组合也可以构成我们需要的混淆序列。
代码表示的就是这种算法,即从下标中取一个和最后一个交换,在在除最后一项外的序列中取一个下标和倒数第二项交换......以此类推,这样得到的一个序列就是一个混淆序列。