洗牌算法(Fisher-Yates)--用于打乱数组或链表的一种算法

主包最近由于写单测被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:测试用例随机化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值