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

你有没有好的优化办法呢

总结

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

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

相关推荐
zhong liu bin10 分钟前
Vue框架技术详解——项目驱动概念理解【前端】【Vue】
前端·javascript·vue.js·vscode·vue
神里流~霜灭14 分钟前
(C++)数据结构初阶(顺序表的实现)
linux·c语言·数据结构·c++·算法·顺序表·单链表
前端 贾公子29 分钟前
ElementUI 中 validateField 对部分表单字段数组进行校验时多次回调问题
前端·javascript·elementui
棒棒的唐29 分钟前
vue2 elementUI 登录页面实现回车提交登录的方法
前端·javascript·elementui
一只乔哇噻35 分钟前
java后端工程师进修ing(研一版 || day41)
java·开发语言·学习·算法
愚润求学37 分钟前
【贪心算法】day7
c++·算法·leetcode·贪心算法
知识分享小能手1 小时前
React学习教程,从入门到精通,React 使用属性(Props)创建组件语法知识点与案例详解(15)
前端·javascript·vue.js·学习·react.js·前端框架·vue
要开心吖ZSH1 小时前
软件设计师备考-(十六)数据结构及算法应用(重要)
java·数据结构·算法·软考·软件设计师
带娃的IT创业者1 小时前
如何开发一个教育性质的多线程密码猜测演示器
网络·python·算法
摸鱼的春哥2 小时前
前端程序员最讨厌的10件事
前端·javascript·后端