随机排列算法(Fisher-Yates)

之前写随机的时候,不管是在打OI的那段时间还是在写游戏的那段时间,用的随机选择(不允许重复)算法都是很笨的。这里总结一下我用过的随机选择算法并且提一下Fisher-Yates洗牌算法。

(前三种做法都是我自己用过的, 估计也不会是什么有趣有用的算法,不过这里写出来权当开拓眼界和记录思路,实在懒的写并且没有什么特别严格的要求的话这些做法也能勉强达到效果)

暴力

没啥技术含量,就是维护一个vis数组,每次rand一个1到n的数,然后判断是不是已经vis过了,据此判断下一位应该选谁。

这样的思路有很大的问题。当所需取出的元素数量m很接近总数n时,比如,m=99,n=100时,在这种情况下,越往后选rand出重复数字的概率越大,因此效率上可能不太乐观。当然如果在m和n相比很小的时候,这种算法还是可以用的。最大的好处是写起来太方便了,完全没什么思考。

排序

假如这n个数的大小范围在0~a-1,那么可以根据这样的方法生成一个随机序列:

将每个数加上rand*a,然后对数组进行排序,然后每个数再对a取模。

这个方法希望的是通过给每个数加上一个随机的权值,然后根据这样的随机来获得一个排列。这样的做法不会像上边的暴力算法一样因为重复而耗费大量时间,但是同时它也有很严重的问题。举个例子:

假如要对 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4进行一次随机排列。现在对他们五个分别加上rand*5。假如现在得到了这样的数列: 15 , 26 , 27 , 33 , 4 15,26,27,33,4 15,26,27,33,4,那么排序后的结果就是 4 , 15 , 26 , 27 , 33 4,15,26,27,33 4,15,26,27,33,然后再把这个数列还原回去,得到的就是 4 , 0 , 1 , 2 , 3 4,0,1,2,3 4,0,1,2,3

这里我们只对其中两个数的顺序提出质疑:既然1和2都是加上了3*5,凭什么就要把1排到2前边啊?

这就是这种算法的问题所在。虽然实际操作时rand的范围可能比较大,但是也无法保证两个数随机到的权值一定是不同的。而一旦两个数的权值相同,就会导致原本在前面的数还在前面。这就不太符合期望得到的随机性,至少感觉上来说是这样。

当然如果真的想用这种方法,我觉得也可以再赋一个权值,总之就是尽量消除上边提到的那种偶然现象呗。

全排列

之前还考虑过一种方法,就是dfs求一下全排列。通过rand来确定要取哪个排列。

比如我要对 0 , 1 , 2 , 3 , 4 0,1,2,3,4 0,1,2,3,4求一个排列,那么我就先rand取一个数,而这个数应当小于5个数的全排列数目,即120。比如这个随机到的数是3,那么就表示要取第3个得到的排列,即 0 , 1 , 3 , 2 , 4 0,1,3,2,4 0,1,3,2,4

不过求全排列的复杂度实在是太高,n稍微大一点就没法玩了。而且这种方法会得到很多冗余的信息,比如没有取到的排列,我们耗费时间求出来,但是实际上和我们的需求几乎没关系。

不过快速求指定位置排列的算法好像也有,所以应该还是有优化空间的。

Fisher-Yates

Fisher-Yates得到的排列就很靠谱了。

它的流程是这样的:从数组的开头开始一位一位往后移,在到达第i位时,随机一个i到n-1的数字k,交换a[i]和a[k]。

由上边的流程可以看出,每经过一位之后,交换到这一位的数字就不会再被用到,也就是会确定a[i]的值。而每一次进行交换实际上就是在从剩下的数里选一个加入已选择的序列。

OK,结束。

猜你喜欢

转载自blog.csdn.net/m0_49792815/article/details/118270121
今日推荐