🥳每日一练-回溯算法之全排列-JS简单版

今天分享回溯算法的系列第一篇--全排列。

回溯算法是什么

回溯算法是一种解决组合问题(如组合问题、子集问题、排列问题等)的方法。它通过不断地尝试和撤销,最终找到问题的解。回溯算法的核心思想是"选择-回退",即每次选择一个元素,然后进行下一次选择,直到找到问题的解。

回溯算法,在我目前看来就是不断地往前尝试各种结果,如果可行就继续往前,如果不可行,就会退。先尝试前进,不行就回退,再在其他方向前进。

其实这就是深度优先遍历嘛,回溯算法也没什么神奇的

对于今天的全排列来说,就只管往前就好了,不需要管什么可行不可行的,因为全排列就是要穷举所有可能的

什么是全排列

排列是指一个集合中的所有元素按照一定的顺序排列起来。排列的数学定义是:对于一个集合A,如果它里面有n个元素,那么由这n个元素组成的排列总数就是n的阶乘,也就是n!。

举个例子:有一个集合{ 'a', 'b', 'c' },这个集合的全排列集合是:

css 复制代码
[ 'a', 'b', 'c' ]
[ 'a', 'c', 'b' ]
[ 'b', 'a', 'c' ]
[ 'b', 'c', 'a' ]
[ 'c', 'a', 'b' ]
[ 'c', 'b', 'a' ]

全排列的个数是 3!,也就是 6 种。没有问题,这就是我们高中数学学到的知识。

集合元素很少的时候,我们手动就可以写出集合的所有排列的可能性。但是一旦元素过多,列出所有排列的可能性就很吃力了。这时候就需要计算机的帮助了

全排列

为什么说求解全排列是一种回溯算法呢?回溯就是采用 DFS 的方法尝试每一种可能性。像下面这张图:

先选排在第一位的数字,第一位可以是 1、2、3,所以有三种可能性,假设第一位选了 2,那第二位有几种可能性呢?两种!要么是 1,要么是 3.假设第二位选了 3,那第三位有几种可能性呢?只有一种!只能是 1

就是这样理解上面的图片内容。理解了之后,相信理解下面代码就不是什么问题了

javascript 复制代码
const set = ["a", "b", "c"]; // 定义一个包含3个元素的集合

const res = []; // 定义一个空数组用于存储排列结果
const completeArrangeMent = (set, currentUse, notUse) => { // 定义一个递归函数,用于完成排列组合
 if (notUse.length == 0) { // 如果剩余未使用的元素为空,则将当前排列组合添加到结果数组中
   res.push([...currentUse]);
   return;
 }
 for (let i = 0; i < notUse.length; i++) { // 遍历剩余未使用的元素
   completeArrangeMent( // 递归调用函数,完成当前元素的所有排列组合
     set,
     [...currentUse, set[notUse[i]]], // 将当前元素添加到当前排列组合中
     notUse.filter((item, index) => index !== i) // 更新剩余未使用的元素
   );
 }
};

上面定义了一个函数 completeArrangeMent,接收三个函数,set 表示需要被排列的数组,currentUse表示数组中已经被使用的内容,notUse 表示数组中还未被使用的内容。

在代码的一开始会先判断notUse.length == 0,来看看数组中的元素是否已经被使用完了。如果使用完了,就将当前排列的可能性放到 res 数组中,res 数组存在着所有可能的排列方式。

如果不理解开头的内容,没关系,继续往下看。

下面会对未使用的元素遍历,分别将其作为排列组合的下一种可能性。

代码使用递归的方式,让代码变得更简洁,也让不常使用递归的人来说,有些难以理解。不过总要接触递归的嘛,如果不熟悉递归,就从二叉树的前中后序遍历开始吧

想了解二叉树的遍历,可以看这些文章:

这些文章都像本篇文章一样通俗易懂。

测试代码

javascript 复制代码
const arrangement = (set) => { // 定义一个函数,用于完成集合的排列组合
 completeArrangeMent(set, [], Object.keys(set)); // 调用递归函数,完成初始排列组合
};

arrangement(set); // 调用函数,完成集合的排列组合,并将结果存储在res数组中

console.log(res); // 输出排列结果
// [
//   [ 'a', 'b', 'c' ],
//   [ 'a', 'c', 'b' ],
//   [ 'b', 'a', 'c' ],
//   [ 'b', 'c', 'a' ],
//   [ 'c', 'a', 'b' ],
//   [ 'c', 'b', 'a' ]
// ]

这里定义了arrangement,用来调用completeArrangeMent

输出结果显而易见,没有问题。再来试试更有难度的:

javascript 复制代码
const set = ["a", "b", "c", "d"];
const res = [];
arrangement(set); // 调用函数,完成集合的排列组合,并将结果存储在res数组中

console.log(res); // 输出排列结果
// [
//   [ 'a', 'b', 'c', 'd' ], [ 'a', 'b', 'd', 'c' ],
//   [ 'a', 'c', 'b', 'd' ], [ 'a', 'c', 'd', 'b' ],
//   [ 'a', 'd', 'b', 'c' ], [ 'a', 'd', 'c', 'b' ],
//   [ 'b', 'a', 'c', 'd' ], [ 'b', 'a', 'd', 'c' ],
//   [ 'b', 'c', 'a', 'd' ], [ 'b', 'c', 'd', 'a' ],
//   [ 'b', 'd', 'a', 'c' ], [ 'b', 'd', 'c', 'a' ],
//   [ 'c', 'a', 'b', 'd' ], [ 'c', 'a', 'd', 'b' ],
//   [ 'c', 'b', 'a', 'd' ], [ 'c', 'b', 'd', 'a' ],
//   [ 'c', 'd', 'a', 'b' ], [ 'c', 'd', 'b', 'a' ],
//   [ 'd', 'a', 'b', 'c' ], [ 'd', 'a', 'c', 'b' ],
//   [ 'd', 'b', 'a', 'c' ], [ 'd', 'b', 'c', 'a' ],
//   [ 'd', 'c', 'a', 'b' ], [ 'd', 'c', 'b', 'a' ]
// ]

总共 24 种可能

优化

上面的代码看着很简单,确有些小缺陷,如果数组中存在重复元素,那么产生的排列结果也会有重复,我们可以做些优化,比如对数组进行查重,或者判断 currentUse 中是否已经存在了将要选择的元素,如果是,就跳过,选在 notUse 中下一个元素。

你有没有好的优化办法呢

总结

这篇文章分享了一种回溯算法--全排列。如果你会了树的深度优先遍历,那理解这个算法就是分分钟的事情了。我也是看了一遍文字描述,就是代码写出来了,相信你也可以。

你觉得这篇文章怎么样?我每天都会分享一篇算法小练习,喜欢就点赞+关注吧

相关推荐
浊酒南街23 分钟前
决策树python实现代码1
python·算法·决策树
冠位观测者2 小时前
【Leetcode 热题 100】208. 实现 Trie (前缀树)
数据结构·算法·leetcode
秋雨凉人心2 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
小王爱吃月亮糖3 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
哥谭居民00014 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿5 小时前
Android native+html5的混合开发
javascript
IT猿手5 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解LRMOP1-LRMOP6及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·算法·matlab·智能优化算法·多目标算法
kittygilr5 小时前
matlab中的cell
开发语言·数据结构·matlab
前端没钱5 小时前
探索 ES6 基础:开启 JavaScript 新篇章
前端·javascript·es6
花心蝴蝶.5 小时前
Map接口 及其 实现类(HashMap, TreeMap)
java·数据结构