Java隐藏技-混淆序列

423 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

题目:假设有一个序列,那么如何根据这个序列生成一个乱序的序列?

看到这个题目,那么首先会想到这样一种解法: 我们将序列的下标进行乱序,再根据这个乱序的下标不就生成一个乱序的序列了吗!

如下是生成序列下标的乱序算法:

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

根据这个乱序后的下标的有序集合,就可以生成我们要的乱序序列了。

那么是不是一定要我们自己实现一个这样的算法呢?当然不用了,贴心的JDK已经提供了这样一个函数,使用起来非常简单,这个函数隐藏在Collections类中

Collections.shuffle(List<?> list);
//和
Collections.shuffle(List<?> list, Random rnd);
  1. 这两个函数都是无返回值得,也就是说是对入参的序列做修改。
  2. 第二个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函数暂且不看,只要知道是用来交换两项的位置的就可以了,感兴趣的可以下来看源码。

这两句什么含义呢:

  1. 序列使用倒序进行遍历;
  2. nextInt 会取得[0,i)之间的随机数;
  3. 进行两项交换。

现在在大脑中对这个算法进行模拟:

  1. i= size, i-1是序列的最后一项,nextInt 生成了[0,i)之间的一个随机数,并且和序列的最后一项进行了交换;
  2. 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 仅仅是组合中的两种,那么在其他组合中任取一个组合也可以构成我们需要的混淆序列。

代码表示的就是这种算法,即从下标中取一个和最后一个交换,在在除最后一项外的序列中取一个下标和倒数第二项交换......以此类推,这样得到的一个序列就是一个混淆序列。