1.Collections.shuffler
最近有个需求是生成十万级至百万级的所有随机数,最简单的思路是一个个生成,生成新的时候排重,但是这样时间复杂度是o(n^2),网上看了几个博客的解决方法都不是很理想
因为是要求生成所有随机数,可以换个思路,即生成顺序数,然后打乱即可。最后用到了shuffler方法,效率很高,百万级的数据毫秒就能打乱完, 其实这个算法也可以用于生成范围内一定量的随机数。
先介绍下源码实现吧,其实思路很简单。
jdk:
shuffle
public static void shuffle(List<?> list)使用默认随机源对指定列表进行置换。所有置换发生的可能性都是大致相等的。
前面描述中使用了不确定的词“大致”,因为随机源只是大致上独立选择位的无偏源。如果它是一个随机选择位的最佳源,那么算法将完全一致的选择置换。
此实现向后遍历列表,从最后一个元素一直到第二个元素,将随机选择的元素重复交换到“当前位置”。元素是从列表的一部分随机选择的,该部分列表从第一个元素一直到当前位置(包括)。
此方法以线性时间运行。如果指定列表没有实现 RandomAccess 接口并且是一个大型列表,则此实现在改组列表前将指定列表转储到数组中,并将改组后的数组转储回列表中。这避免了二次行为,该行为是原地改组一个“有序访问”列表引起的。
参数:
list - 要改组的列表。
抛出:
UnsupportedOperationException - 如果指定列表或其列表迭代器不支持 set 操作。
shuffle
public static void shuffle(List<?> list,
Random rnd)使用指定的随机源对指定列表进行置换。所有置换发生的可能性都是相等的,假定随机源是公平的。
此实现向后遍历列表,从最后一个元素一直到第二个元素,将随机选择的元素重复交换到“当前位置”。元素是从列表的一部分随机选择的,该部分列表从第一个元素一直到当前位置(包括)。
此方法以线性时间运行。如果指定列表没有实现 RandomAccess 接口并且是一个大型列表,则此实现在改组列表前将指定列表转储到一个数组中,并将改组后的数组转储回列表中。这避免了二次行为,该行为是原地改组一个“有序访问”列表引起的。
参数:
list - 要改组的列表。
rnd - 用来改组列表的随机源。
抛出:
UnsupportedOperationException - 如果指定列表或其列表迭代器不支持 set 操作。
重载函数的思路是一样的, 如果没有穿随机类就生成一个调第二个函数。
java.util.Collections
public static void shuffle(List<?> list) {
if (r == null) {
r = new Random();
}
shuffle(list, r);
}
private static Random r;
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]);
}
}
}
核心就是遍历,首先获取集合的元素个数,如果小于5个或者实现了RandomAccess
接口,就循环一遍,随机交换集合中两个相邻的元素的位置,RandomAccess
是一个标记接口,如果实现了这个接口就表示支持快速的随机访问操作,类似于数组。
如果大于等于5
个元素也没有实现RandomAccess
接口,那么就转为数组,之后也是循环,随机交换集合中两个相邻的元素的位置,最后再将数组放回原来的list中。