- import java.util.*;
-
- public class ShuffleTest {
- public static void main(String[] args) {
- List<Integer> list = new ArrayList<Integer>();
- for (int i = 0; i < 10; i++)
- list.add(new Integer(i));
- System.out.println("打乱前:");
- System.out.println(list);
-
- for (int i = 0; i < 5; i++) {
- System.out.println("第" + i + "次打乱:");
- Collections.shuffle(list);
- System.out.println(list);
- }
- }
- }
输出结果:
打乱前:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
第0次打乱:
[6, 3, 2, 0, 8, 1, 7, 5, 4, 9]
第1次打乱:
[6, 2, 3, 0, 8, 5, 7, 4, 9, 1]
第2次打乱:
[1, 7, 9, 4, 6, 0, 2, 5, 3, 8]
第3次打乱:
[0, 4, 2, 8, 9, 1, 3, 7, 5, 6]
第4次打乱:
[8, 1, 3, 0, 7, 9, 4, 2, 5, 6]
在研究用遗传算法等启发式算法解决旅行商问题(Traveling Salesman Problem,TSP)时,首先要解决的问题时如何生成一个初始解,即一个代表顾客位置的编码序列,如有5个顾客,如何生成1,2,3,4,5的乱序序列,一般情况下是这样生成的:
方法一:
-
-
-
-
-
-
- public int[] createSolution(int len) {
- int solutionArr[] = new int[len];
- Random random = new Random();
- int j = 0;
- solutionArr[0] = random.nextInt(len) + 1;
- for (int i = 1; i < len; i++) {
- j = 0;
- while (j != i) {
- j = 0;
- solutionArr[i] = random.nextInt(len) + 1;
- while (j < i && solutionArr[j] != solutionArr[i])
- j++;
- }
- }
- return solutionArr;
- }
如上述代码所示,给定一个参数len,上述方法可以返回一个返回从1到len的一个len大小的乱序数组,很显然,上述方式生成一个乱序数组的方式是非常低效的。其实我们可以先生成一个顺序数组再想办法将其顺序打乱,代码如下:
方法二:
-
-
-
-
-
-
- public int[] createSolution1(int len) {
- int solutionArr[] = new int[len];
- Random random = new Random();
- for (int i = 0; i < len; i++)
- solutionArr[i] = i + 1;
- int endIndex = len / 2, ranIndex1 = 0, ranIndex2 = 0;
- for (int i = 0; i <= endIndex; i++) {
- ranIndex1 = random.nextInt(len);
- ranIndex2 = random.nextInt(len);
- while (ranIndex1 == ranIndex2)
- ranIndex2 = random.nextInt(len);
- swap(solutionArr, ranIndex1, ranIndex2);
- }
- return solutionArr;
- }
用上述方式比第一种方式减少了许多运算量,但是整个数组的乱序效果却不是很好,有没有一种效果很好的同时运算量又非常小的乱序方式呢。不用同时生成两个randomIndex,生成一个就行,然后从头到尾与randomIndex进行交换即可,代码如下:
方法二的改进版:
- public int[] createSolution3(int len) {
- int solutionArr[] = new int[len];
- int ranIndex=0;
- Random random = new Random();
- for (int i = 0; i < len; i++)
- solutionArr[i] = i + 1;
- for (int i = 0; i <len; i++) {
- ranIndex = random.nextInt(len);
- swap(solutionArr, ranIndex, i);
- }
- return solutionArr;
- }
方法二的改进版的实现方式为从头到尾将每个元素与随机生成的下标所对应的元素进行交换以达到相应的乱序效果。
方法三:
其实Java.util.Collections
里面提供了一个shuffle的接口,它可以很方便地将一个有序数组进行乱序处理。
-
-
-
-
-
-
- public Integer[] createSolution2(int len) {
- Integer solutionArr[] = new Integer[len];
- List list=new ArrayList<Integer>();
- for (int i = 0; i < len; i++)
- list.add(i+1);
- Collections.shuffle(list);
- list.toArray(solutionArr);
- return solutionArr;
- }
从eclipse查看shuffle接口的实现源码:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public static void shuffle(List<?> list, Random rnd) {
- int size = list.size();
- if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
- for (int i=size; i>1; i--)
- swap(list, i-1, rnd.nextInt(i));
- } else {
- Object arr[] = list.toArray();
-
-
- for (int i=size; i>1; i--)
- swap(arr, i-1, rnd.nextInt(i));
-
-
- ListIterator it = list.listIterator();
- for (int i=0; i<arr.length; i++) {
- it.next();
- it.set(arr[i]);
- }
- }
- }
从上述代码可以知道,shuffle的参数为一个List列表和一个Random对象,当List比较大时,选择首先将list通过list.toArray()转换成数组,然后按方法二的改进形式交换数组中元素的值,最后将list中的值依次替换为数组中的值,返回list对象。
其实,方法二的改进方法和方法三种shuffle的在JAVA中的实现方式是类似的,很简单不是吗?而这种思路就来自 Ronald Fisher 和 Frank Yates首先提出的Fisher–Yates shuffle洗牌算法,但该算法的适合计算机运算的版本是由Richard Durstenfeld和Donald E. Knuth发扬光大的,Durstenfeld将该算法的时间复杂度由Fisher–Yates 提出算法的O(n*n)降低到O(n),其算法的伪码如下:
- -- To shuffle an array a of n elements (indices 0..n-1):
- for i from n−1 downto 1 do
- j ← random integer such that 0 ≤ j ≤ i
- exchange a[j] and a[i]
该算法的另外一个版本为从最小的index开始至最高的index的过程:
- -- To shuffle an array a of n elements (indices 0..n-1):
- for i from 0 to n−2 do
- j ← random integer such that 0 ≤ j < n-i
- exchange a[i] and a[i+j]