🥳每日一练-回溯算法之全排列-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 中下一个元素。

你有没有好的优化办法呢

总结

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

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

相关推荐
Duck Bro几秒前
数据结构:顺序表(动态顺序表)
c语言·数据结构·c++·学习·算法
拼图20923 分钟前
Vue.js开发基础——数据绑定/响应式数据绑定
前端·javascript·vue.js
刘志辉28 分钟前
vue反向代理配置及宝塔配置
前端·javascript·vue.js
DK2215136 分钟前
机器学习系列-----主成分分析(PCA)
人工智能·算法·机器学习
oliveira-time42 分钟前
爬虫学习8
开发语言·javascript·爬虫·python·算法
jinmo_C++1 小时前
数据结构——链表(带有头节点)
数据结构·链表
点云侠1 小时前
二维椭圆拟合算法及推导过程
开发语言·c++·算法·计算机视觉·matlab
海绵波波1071 小时前
Webserver(4.5)复用
android·开发语言·javascript
一直学习永不止步1 小时前
LeetCode题练习与总结:迷你语法分析器--385
java·数据结构·算法·leetcode·字符串··深度优先搜索
老胡说前端2 小时前
vue3 elemnetPlus table 数据id 相同的合并单元格
javascript·vue.js·elementui