最可靠的方法是Fisher-Yates洗牌算法,时间复杂度O(n)、原地操作且均匀;不能用sort(() => Math.random() - 0.5),因其违反比较一致性导致分布不均。

最常用也最可靠的方法是使用 Fisher-Yates 洗牌算法(又称 Knuth 洗牌),它能保证每个排列出现的概率完全相等,时间复杂度 O(n),且原地操作、空间高效。
为什么不能用 sort() 随机比较?
常见误区是写 arr.sort(() => Math.random() - 0.5)。这种写法看似随机,但实际会因排序算法实现(如 V8 的 TimSort)导致分布严重不均——短数组偏差明显,长数组某些排列几乎不会出现。它违背了排序函数“比较结果需具有一致性”的前提,属于未定义行为。
正确实现 Fisher-Yates 原地洗牌
从数组末尾开始,每次随机选取前面(含当前位)的一个索引,与当前位交换:
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 随机选 [0, i] 之间索引
[arr[i], arr[j]] = [arr[j], arr[i]]; // 解构交换
}
return arr;
}- 循环从
length - 1开始,到i = 1结束(i > 0),共执行 n−1 次交换 -
Math.random() * (i + 1)生成 [0, i+1) 区间浮点数,取整后得到 [0, i] 的整数索引 - 每次只在尚未确定位置的元素中随机挑选,确保每种排列概率均为 1/n!
需要返回新数组时怎么处理?
如果不想修改原数组,先拷贝再洗牌:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
function shuffled(arr) {
return shuffle([...arr]); // 使用展开语法创建浅拷贝
}-
[...arr]或arr.slice()均可,推荐展开语法更直观 - 注意:该方法对对象数组只做浅拷贝,若需深拷贝需额外处理
简单验证是否均匀?
小数组(如 [0,1,2])可跑万次统计各排列频次,理想情况下 6 种排列应各占 ≈16.67%。用以下代码快速检验:
const counts = {};
for (let i = 0; i < 10000; i++) {
const s = shuffle([0,1,2]).join('');
counts[s] = (counts[s] || 0) + 1;
}
console.log(counts); // 应接近 { '012':1667, '021':1667, ... }不复杂但容易忽略细节。

















