当前位置: 首页 > Web前端 > JavaScript

JS算法回溯法

时间:2023-03-27 16:10:38 JavaScript

弱点和无知不是生存的障碍,狂妄才是--《三体·死神永生》大家好,我叫七八九。今天,我们继续探索JS算法相关的知识点。下面说一下回溯法的相关知识点和具体算法。如果想了解其他数据结构的算法介绍,可以参考我们发表的文章。以下是算法系列的前几篇文章。文章列表整数常规排序算法ArrayStringLinkedListStackQueueBinaryTree好了,时间不早了,我们言归正传。你能学到的知识点什么是回溯法的组合和排列用回溯算法解决其他问题什么是回溯法回溯法可以看成是蛮力法的升级版。选项来找到所有可能的解决方案。回溯非常适合解决由多个步骤组成的问题,每个步骤都有多个选项。回溯法求解问题的过程可以用树形结构形象化,求解问题的每一步都可以看作是树中的一个节点。如果在某个步骤有n个可能的选项,那么每个选项都是树中的一条边,通过它可以到达该节点的n个子节点。在使用回溯法解决问题时,如果到达树结构的叶子节点,则说明已经找到了问题的解。如果想找到更多解,可以回到当前节点的父节点,再尝试父节点的其他选项。如果父节点所有可能的选项都试过了,则回到父节点的父节点,继续尝试其他选项,从而逐层回溯到树的根节点。因此,使用回溯法求解问题的过程本质上是从树结构中的根节点开始进行深度优先遍历。通常,回溯法的深度优先遍历是用递归代码实现的。如果在去到一个节点时对问题解的状态进行了修改,则在返回到其父节点时相应的修改将被清除。剪枝由于回溯法是对所有选项组成的树进行深度优先遍历,如果解决问题的步骤很多或者每一步都面临多个选项,遍历整棵树会花费更多的时间。如果明确有些子树是不需要遍历的,那么在遍历的时候就应该避开这些子树,以优化效率。使用回溯时避免遍历不必要的子树的方法通常称为剪枝。集合的组合与排列从包含m个元素的集合中选择n个元素(0≤n≤m)组成一个{subset|子集。子集也称为组合。如果两个子集(组合)的元素完全相同但顺序不同,则可以认为它们是相同的子集(组合)。从包含m个元素的集合中选出n个(0≤n≤m)元素,按一定顺序组成排列。m等于n的排列称为完全排列。如果两个排列的元素完全相同但顺序不同,则两个排列是两个不同的排列。排列与元素的顺序有关。所有子集题目描述:输入一个没有重复数字的数据集,请找出它的所有子集输入:nums=[1,2,3]输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]分析子集就是从一个集合中选择若干个元素。如果集合包含n个元素,那么子集的生成可以分为n步,每一步从集合中取一个数,此时有两种选择,将数加到子集和不将数加到thesubset生成一个子集集合可以分为几个步骤,每个步骤面临几个选择--回溯方法代码实现函数subsets(nums){letresult=[];if(nums.length==0)返回结果;(functionhelper(nums,index,subset,result){if(index===nums.length){//基线条件result.push([...subset])}elseif(index0&&index0&&indexa-b);让结果=[];(functionhelper(nums,target,index,subset,result){if(target==0){//基线条件result.push([...subset]);}elseif(target>0&&index[nums[i],nums[j]]=[nums[j],nums[i]];代码解释函数执行过程中,total数组nums保存的是函数助手生成排列时当前排列的状态,下标为index的个数时,从0到index-1的数字已经被选中,但是数组nums中index到n-1的数(假设数组的长度为n)可以放在排列的下标index的位置,函数helper中有一个for循环与后面的数进行交互它与数字下标索引一一对应。for循环包括带有下标索引的数字本身,因为它也可以放在数组本身索引的位置。swap(nums,index,j)交互后,调用递归函数生成数组中的数字index+1。helper(nums,index+1,result)需要在函数退出前清除排列状态的修改swap(nums,index,j)包含一组重复元素的完整排列数据,请求输出其所有全排列Input:nums=[1,1,2]Output:[[1,1,2],[1,2,1],[2,1,1]]分析是否存在在集合数中是重复的,则集合中重复数交换得到的全排列是同一个全排列。在处理满排列的第i个数时,如果已经用一个值为m的数交换了排列的第i个数,则如果遇到其他有m值的数,跳过代码,执行函数permuteUnique(nums){让结果=[];(functionhelper(nums,index,result){if(index===nums.length){result.push([...nums]);//基线条件}else{letmap={};for(letj=index;j[nums[i],nums[j]]=[nums[j],nums[i]];代码解释使用一个对象映射,来保存所有交换到数组索引位置的值。只有当一个值之前没有交换到索引位置时,才进行交换,否则在for循环中直接跳过,多加一层判断if(!map[nums[j]])解决除解决其他问题集合排列,结合相关问题,回溯法也可以解决很多问题。如果解决一个问题需要几个步骤,每一步都面临几个选择,在某个步骤做出某个选择后,还有几个选项可以进入下一步。那么可以考虑使用回溯法。通常,回溯法可以用递归代码来实现。生成匹配括号题目描述:输入一个正整数n,请输出所有包含n个左括号和n个右括号的组合,要求每个组合的左右括号匹配。输入:n=3输出:["((()))","(()())","(())()","()(())","()()()"]对输入n进行分析,生成的括号组合包含n个左括号和n个右括号。因此,生成这样的组合需要2n个步骤。每一步都会生成一个括号。每一步都面临两个选择。可以生成左括号或右括号。回溯法解决括号组合的生成,需要注意每一步需要满足两个A限制左括号或右括号的个数不能超过n个括号匹配原则:即任意一步生成的右括号的个数不能超过左括号个数代码实现函数generateParenthesis(n){letresult=[];(functionhelper(left,right,subset,result){if(left==0&&right==0){result.push(subset);//基线条件返回;}//生成的左括号数量少For`n`if(left>0){helper(left-1,right,subset+"(",result)}//生成的右括号数量小于生成的左括号数量if(left0)就生成一个左括号只要生成的右括号的个数小于生成的左括号的个数(left