洗牌算法是我们罕见的随机题目,在玩游戏、随机排序时经常会遇到。它能够笼统成如许一个题目。
获得一个M之内的一切自然数的随机递次数组。
在百度搜“洗牌算法”,第一个效果是《百度文库 — 洗牌算法》。扫了一下内里的内容,许多内容都轻易误导他人走上邪路,包含末了用链表替代数组,也只是一个有限的优化(链表也引入了读取效力的丧失)。
抽牌思绪
基础算法
该文里的第一种方法,能够简朴形貌成:随机抽牌,放在另一组;再次抽取,抽到空牌则反复抽。
“抽到空牌则反复抽”这会致使背面抽到空牌的时机愈来愈大,显然是不合理的。能够优化一步成:牌抽走后,原牌变少(而不是留下空牌)。代码以下:
function shuffle_pick_1(m) {
// 天生m张牌
var arr = new Array(m);
for (var i = 0; i < m; i++) {
arr[i] = i;
}
// 每次抽出一张牌,放在另一堆。由于要在数组里抽出元素,把背面的一切元素向前拉一名,所以很耗时
var arr2 = new Array();
for (var i = m; i > 0; i--) {
var rnd = Math.floor(Math.random() * i);
arr2.push(arr[rnd]);
arr.splice(rnd, 1);
}
return arr2;
}
这个也显著有题目,由于数组假如很大的话,删除中心的某个元素,会致使背面的列队向前走一步,这是一个很耗时的行动。
优化算法
追念一下“我们为何要删除谁人元素?”目标就是为了不发生空牌。除了删除谁人元素以外,我们是不是是另有别的体式格局来去除空牌?有的,我们把末了一张未抽的牌放在谁人抽走的位置上就能够了。所以,这个思绪我们能够优化成如许:
function shuffle_pick(m) {
// 天生m张牌
var arr = new Array(m);
for (var i = 0; i < m; i++) {
arr[i] = i;
}
// 每次抽出一张牌,放在另一堆。把末了一张未抽的牌放在空位子上。
var arr2 = new Array();
for (var i = m; i > 0;) {
var rnd = Math.floor(Math.random() * i);
arr2.push(arr[rnd]);
arr[rnd] = arr[--i];
}
return arr2;
}
换牌思绪
除了抽牌思绪,我们还能够用换牌思绪。
《百度文库-洗牌算法》提到一种换牌思绪:“随机交流两个位置,共交流n次,n越大,越靠近随机”。
这个做法是不对的,就算n很大(比方10张牌,举行10次换取),也还存在很大能够“有的牌基础没换位置”。
顺着这个思绪,做一点小调解就能够了:第i张与恣意一张牌换位子,换完一轮即可。代码以下:
function shuffle_swap(m) {
// 天生m张牌
var arr = new Array(m);
for (var i = 0; i < m; i++) {
arr[i] = i;
}
// 第i张与恣意一张牌换位子,换完一轮即可
for (var i = 0; i < m; i++) {
var rnd = Math.floor(Math.random() * (i + 1)),
temp = arr[rnd];
arr[rnd] = arr[i];
arr[i] = temp;
}
return arr;
}
插牌思绪
除了抽牌与换牌的思绪,我们还能够用插牌的思绪:先有一张牌,第二张牌有两个位置可随机插进去(第一张牌前,或后),第三张牌有三个位置可随机插进去(放在背面,或插在第一名,或插在第二位),依此类推
代码以下:
function shuffle_insert_1(m) //洗牌 //插牌法
{
//每次天生一张最大的牌,插在随机的某张牌前。由于要在数组里插进去元素,把背面的一切元素向后挤一名,所以很耗时。
var arr = [0];
for (var i=1; i<m; i++) {
arr.splice(Math.floor(Math.random()*(i+1)),0,i);
}
return arr;
}
以上的代码也会有一些题目:就是跟着牌数的增加,插牌变得愈来愈难题,由于插牌会致使背面的许多牌都今后推一步。
固然,我们也能够恰当的优化一下:先有n-1张牌,第n张牌放在末了,然后与恣意一张牌交换位置。
代码以下:
function shuffle_insert(m) //洗牌 //插牌法优化版,能够用数学归纳法证实,这类洗牌是匀称的。
{
//每次天生一张最大的牌,与随机的某张牌换位子
var arr = new Array(m);
arr[0] = 0;
for (var i=1; i<m; i++) {
var rnd = Math.floor(Math.random()*(i+1));
arr[i] = arr[rnd];
arr[rnd] = i;
}
return arr;
}
好的,悉数的代码以下,有兴致的同砚能够在本身的机械上试下,看下他们各自的实行效力、以及末了的效果是不是是理论随机。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>JK:javascript 洗牌算法 </title>
</head>
<body>
<script type="text/javascript">
function shuffle_pick_1(m) //洗牌 //抽牌法
{
//天生m张牌
var arr = new Array(m);
for (var i=0; i<m; i++) {
arr[i] = i;
}
//每次抽出一张牌,放在另一堆。由于要在数组里抽出元素,把背面的一切元素向前拉一名,所以很耗时。
var arr2 = new Array();
for (var i=m; i>0; i--) {
var rnd = Math.floor(Math.random()*i);
arr2.push(arr[rnd]);
arr.splice(rnd,1);
}
return arr2;
}
function shuffle_pick(m) //洗牌 //抽牌法优化牌
{
//天生m张牌
var arr = new Array(m);
for (var i=0; i<m; i++) {
arr[i] = i;
}
//每次抽出一张牌,放在另一堆。把末了一张未抽的牌放在空位子上。
var arr2 = new Array();
for (var i=m; i>0;) {
var rnd = Math.floor(Math.random()*i);
arr2.push(arr[rnd]);
arr[rnd] = arr[--i];
}
return arr2;
}
function shuffle_swap(m) //洗牌 //换牌法
{
//天生m张牌
var arr = new Array(m);
for (var i=0; i<m; i++) {
arr[i] = i;
}
//第i张与恣意一张牌换位子,换完一轮即可
for (var i=0; i<m; i++) {
var rnd = Math.floor(Math.random()*(i+1)),
temp = arr[rnd];
arr[rnd] = arr[i];
arr[i]=temp;
}
return arr;
}
function shuffle_insert_1(m) //洗牌 //插牌法
{
//每次天生一张最大的牌,插在随机的某张牌前。由于要在数组里插进去元素,把背面的一切元素向后挤一名,所以很耗时。
var arr = [0];
for (var i=1; i<m; i++) {
arr.splice(Math.floor(Math.random()*(i+1)),0,i);
}
return arr;
}
function shuffle_insert(m) //洗牌 //插牌法优化版,能够用数学归纳法证实,这类洗牌是匀称的。
{
//每次天生一张最大的牌,与随机的某张牌换位子
var arr = new Array(m);
arr[0] = 0;
for (var i=1; i<m; i++) {
var rnd = Math.floor(Math.random()*(i+1));
arr[i] = arr[rnd];
arr[rnd] = i;
}
return arr;
}
//alert(shuffle_pick(10))
var funcs = [shuffle_pick_1, shuffle_pick, shuffle_swap, shuffle_insert_1, shuffle_insert],
funcNames = ["抽牌", "抽牌优化", "换牌", "插牌", "插牌优化"]
m = 10000,
times=[];
for(var i = 0; i < funcs.length; i++){
var d0= new Date();
funcs[i](m);
funcNames[i] = (new Date() - d0) + '\t' + funcNames[i];
}
alert(funcNames.join('\n'));
</script>
</body>
</html>
文章转载自《随机题目之–洗牌算法》