主包最近由于写单测被mt问到随机排列算法的底层原理,只会暴力求随机导致一阵无语,发现还是挺有意思的,以O(n)的时间复杂度就可以完成随机的排列,其中Collections.shuffle(list);这个jdk自带底层也就是采用的Fisher-Yates算法来解决的。
Fisher-Yates简单来说就是一种随机重新排列数组或链表元素的算法,目的就是为了产生一种完全随机的排列,将排列随机化的一种东西,,
实现原理:假设一个长度为n的数组
全排列的数量就是,,n! = n * (n - 1) * (n - 2) *'''* 3 * 2 * 1 ,,那么概率就是 1 / n!
暴力肯定是不行的,数据量一大就寄
对于这个数组,我们倒序遍历,遍历的过程中我们 对于 [i,n - 1)这个范围的元素进行随机交换,随机数由Random生成。
对于n - 1 有 1 种选择机会 概率 1
对于n - 2 有 2 种选择机会 概率 1 / 2
对于n - 3 有 3 种选择机会 概率 1 / 3
对于n - 4 有 4 种选择机会 概率 1 / 4
推导出总概率 p = 1 * (1 / 2) * (1 / 3) * (1 / 4) * (1 / n - 1) = 1 / n!
如下代码,时间复杂度为O(n) 空间复杂度: O(1)
public static void fisherYatesShuffle(int[] arr) {
Random random = new Random();
for (int i = arr.length - 1; i > 0; i--) {
int j = random.nextInt(i + 1); // O(1)
// 交换操作 O(1)
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
再贴一个链表的实现时间复杂度为O(n) 空间复杂度: O(n),需要注意的,对于ArrayLIst这种可以随机获取元素的集合可以不用转换成数组,直接交换即可,对于LinkedList来说,由于基于链表实现,不能随机获取,需要转换成数组
public static ListNode fisherYatesShuffleLinkedList(ListNode head) {
if (head == null) return null;
// 转换为数组 O(n)
List<ListNode> nodes = new ArrayList<>();
ListNode curr = head;
while (curr != null) {
nodes.add(curr);
curr = curr.next;
}
Random random = new Random();
for (int i = nodes.size() - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
int temp = nodes.get(i).val;
nodes.get(i).val = nodes.get(j).val;
nodes.get(j).val = temp;
}
return head;
}
其中还可以对于随机生成器还可以采用ThreadLocalRandom类。jdk7引入的专门用于多线程环境下的随机生成器,Random的增强类,原理是每个线程有独立的Random实例,可以独立设置种子数。 Random 类的问题: 1. 使用原子操作保证线程安全 2. 多线程竞争导致性能下降 3. 全局锁影响并发性能 。
简单说一下场景的应用场景:
1:音乐盒随机播放
2:测试用例随机化
719

被折叠的 条评论
为什么被折叠?



