JavaScript数组随机排序的三种核心算法解析
本文深入解析JavaScript中实现数组随机排序的三种主流方法。首先分析利用Array.prototype.sort结合Math.random的便捷性与局限性;其次详解经典的Fisher-Yates洗牌算法,包括通过splice删除元素的原始实现及其性能瓶颈;最后探讨通过元素交换或末尾覆盖优化的无副作用洗牌方案,并提供完整的代码示例与性能对比思路,帮助开发者根据数据规模选择最优解。
1.利用数组方法sort实现随机排序
实现数组随机排序的方式多种多样,既可以通过for循环手动编写逻辑,也可以借助Lodash等第三方JS库。但从便捷性角度考量,使用sort方法更为直接。不过,该方法存在明显的不足,即随机性并不完全均匀。深入分析sort的运行机制可知,它是基于比较器进行两两元素比较来排序的,这种机制导致其无法保证完美的随机分布。
var arr = [1, 2, 3, 4, 5]
arr.sort(function () {
return Math.random() - 0.5
})
console.log(arr);
2.洗牌算法实现随机排序
核心思路是从原始数组中每次随机选取一个元素,将其放入新数组,随后从原数组中移除该元素(通常使用splice方法),使原数组长度减一,重复此过程直至原数组为空。
举例:
假设手中有红桃K、黑桃A和小王三张牌,目标是打乱顺序。操作者可以从三张牌中任意抽取一张,假设抽到小王,此时手中剩余两张牌,而操作者手中持有已抽取的一张牌。依此类推,直到所有牌都被抽取完毕,从而实现随机排序。
代码实现:
function shuffle(array){
let res = [], random;
while(array.length>0){
random = Math.floor(Math.random()*array.length);
res.push(array[random]);
array.splice(random, 1);
}
return res;
}
console.log(shuffle([1,2,3,4,5]));
3.洗牌算法深入分析
基本思路是模拟一副牌,通过随机抽取的方式处理,每抽取一张,剩余牌的数量相应减少。
function shuffle(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为数组长度),交换次数越多,结果越接近完全随机。
基于此思路,稍作调整即可:让第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>到此这篇关于JS实现数组随机排序的方法详解的文章就介绍到这了,更多相关JS数组随机排序内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
