JAVA中如何用shuffle打乱列表并生成乱序序列

引言

在研究用遗传算法等启发式算法解决旅行商问题(Traveling Salesman Problem,TSP)时,首先要解决的问题时如何生成一个初始解,即一个代表顾客位置的编码序列,如有5个顾客,如何生成1,2,3,4,5的乱序序列,一般情况下是这样生成的:

方法一:

/*
	 * @para len represents the length of the solution array
	 * 
	 * function you input a length of an array,this method will return an array 
	 * of number from 1 to len with unsorted.
	 */
	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大小的乱序数组,很显然,上述方式生成一个乱序数组的方式是非常低效的。其实我们可以先生成一个顺序数组再想办法将其顺序打乱,代码如下:

方法二:

/*
	 * @para len represents the length of the solution array
	 * 
	 * function you input a length of an array,this method will return an array
	 * of number from 1 to len with unsorted.
	 */
	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的接口,它可以很方便地将一个有序数组进行乱序处理。

/*
	 * @para len represents the length of the solution array
	 * 
	 * function you input a length of an array,this method will return an array
	 * of number from 1 to len with unsorted.
	 */
	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接口的实现源码:

/**
     * Randomly permute the specified list using the specified source of
     * randomness.  All permutations occur with equal likelihood
     * assuming that the source of randomness is fair.<p>
     *
     * This implementation traverses the list backwards, from the last element
     * up to the second, repeatedly swapping a randomly selected element into
     * the "current position".  Elements are randomly selected from the
     * portion of the list that runs from the first element to the current
     * position, inclusive.<p>
     *
     * This method runs in linear time.  If the specified list does not
     * implement the {@link RandomAccess} interface and is large, this
     * implementation dumps the specified list into an array before shuffling
     * it, and dumps the shuffled array back into the list.  This avoids the
     * quadratic behavior that would result from shuffling a "sequential
     * access" list in place.
     *
     * @param  list the list to be shuffled.
     * @param  rnd the source of randomness to use to shuffle the list.
     * @throws UnsupportedOperationException if the specified list or its
     *         list-iterator does not support the <tt>set</tt> operation.
     */
    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();

            // Shuffle array
            for (int i=size; i>1; i--)
                swap(arr, i-1, rnd.nextInt(i));

            // Dump array back into list
            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]
https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle


你可能感兴趣的:(java,Random,shuffle)