回溯算法

回溯算法已经在前面详细的分析过了,详见猛击此处。

简单的讲:

回溯算法是一种局部暴力的枚举算法

循环中,若条件满足,进入递归,开启下一次流程,若条件不满足,就不进行递归,转而进行上一次流程。这个过程称之为回溯

结构往往是在一个循环中,嵌套一个条件判断,条件判断中,嵌套一个自身递归。三层结构,相互嵌套,缺一不可

【例子】在一个7*7的棋盘中,指定某个起始位置,如何才能使遵循马走日规则的棋子,将36个棋盘位置全部走一遍,其走过的位置不能再走

算法分析:

马走日是规则,极限情况下,一个棋子有如下图8种可选方案,每种方案都对应着一个if语句,8种方案组成我们马走日的行走策略

采用回溯算法需要先找到当前棋子所有的可选方案(代码中由next方法实现),然后遍历每个方案,每个方案采取上述相同的流程,直到找到可以符合题目要求的行走路线,或者是被判定无法再行走而终止递归(代码中由 traversalChessboard 方法实现)

通过上述回溯算法就能完成马踏棋盘问题求解

代码实现:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.backtrace;import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;public class 回溯算法_backtrace_马踏棋盘 {private static int X;//棋盘宽度private static int Y;//棋盘高度private static boolean[] visited;//用于记录棋牌被访问情况private static boolean finish=false;//用于记录是否完成马踏棋盘public static void main(String[] args) {//初始化数据X=7;Y=7;int row=3;//起始点为3行3列int col=3;visited=new boolean[X*Y];int[][] chessboard=new int[X][Y];long start=System.currentTimeMillis();//计时开始//调用马踏棋盘解决方法traversalChessboard(chessboard,row-1,col-1,1);//为了人机交互方便,需要减-1操作long end=System.currentTimeMillis();//计时结束System.out.println("运算总耗时:"+(end-start));;//输出结果for (int[] temp:chessboard){System.out.println(Arrays.toString(temp));}}/*** 马踏棋盘解决方案主体(回溯算法)* @param chessboard 棋盘* @param row 行数* @param col 列数* @param step 步数*/public static void traversalChessboard(int[][] chessboard,int row,int col,int step){chessboard[row][col]=step;//将步数赋值到对应棋盘位置上visited[row*X+col]=true;//将该处设置为已访问(row被-1)//调用next来获得可选方案,注意next方法处理的时候先处理列,再处理行,所以传入的point对象为point(y,x)ArrayList<Point> allow = next(new Point(col,row));//用于存储目前可以进入的棋盘(存储可选方案)//回溯算法核心:回溯式的找到所有位置while (!allow.isEmpty()){//可选方案非空时Point p=allow.remove(0);//拿取可选方案中的头一个位置if (!visited[p.y*X+p.x]){//当该方案位置没有被访问时traversalChessboard(chessboard,p.y,p.x,step+1);//以方案位置再进行上述查找}}//终止回溯条件if (step<X*Y && !finish){//当步数不够且未完成所有位置时chessboard[row][col]=0;//初始化本次回溯过程中棋盘数据visited[row*X+col]=false;//将该位置仍然设置为未访问}else {//解决问题,可以跳出回溯了finish=true;}}/*** 把当前位置棋子的所有可访问棋盘位置以集合的形式输出* @param curPoint 当前棋子* @return 当前位置棋子的所有可访问棋盘位置*/public static ArrayList<Point> next(Point curPoint){ArrayList<Point> allowPoint=new ArrayList<>();//存储当前位置可以进入的位置Point allow=new Point();//下面的策略是先计算列,再计算行//走位置5if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y-1)>=0){allowPoint.add(new Point(allow));}//走位置6if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y-2)>=0){allowPoint.add(new Point(allow));}//走位置7if ((allow.x=curPoint.x+1)<X && (allow.y=curPoint.y-2)>=0){allowPoint.add(new Point(allow));}//走位置0if ((allow.x=curPoint.x+2)<X && (allow.y=curPoint.y-1)>=0){allowPoint.add(new Point(allow));}//走位置1if ((allow.x=curPoint.x+2)<X && (allow.y=curPoint.y+1)<Y){allowPoint.add(new Point(allow));}//走位置2if ((allow.x=curPoint.x+1)<X && (allow.y=curPoint.y+2)<Y){allowPoint.add(new Point(allow));}//走位置3if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y+2)<Y){allowPoint.add(new Point(allow));}//走位置4if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y+1)<Y){allowPoint.add(new Point(allow));}return allowPoint;}
}
运算总耗时:13322
[11, 8, 3, 20, 23, 16, 5]
[2, 21, 10, 7, 4, 19, 24]
[9, 12, 1, 22, 15, 6, 17]
[40, 33, 44, 13, 18, 25, 36]
[43, 30, 41, 34, 37, 14, 47]
[32, 39, 28, 45, 48, 35, 26]
[29, 42, 31, 38, 27, 46, 49]

上述代码使用了回溯算法,虽然回溯算法是小规模的暴力,本身已经相较于枚举算法有了提升。但上述实例中,在7*7的棋盘的3行3列上出发的棋子需要运算13322ms(结果不唯一)。这表明这种算法在处理大量数据时是低效率的(回溯了大量无用的步骤)。

为了提高算法效率,就要减少无用步骤的回溯次数

由于一个棋子可能存在多个可进入的位置,而进入这些位置中又可能会存在多个可进入位置。那么我们按照这些位置的可能进入位置数升序排列,先运算可能性小的,再运算可能性大的。即更改了回溯的策略。这样的优化一定程度上(与运气有关)可以大大优化算法效率。

优化代码:

package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.backtrace;import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;public class 回溯算法_backtrace_马踏棋盘 {private static int X;//棋盘宽度private static int Y;//棋盘高度private static boolean[] visited;//用于记录棋牌被访问情况private static boolean finish=false;//用于记录是否完成马踏棋盘public static void main(String[] args) {//初始化数据X=7;Y=7;int row=3;//起始点为3行3列int col=3;visited=new boolean[X*Y];int[][] chessboard=new int[X][Y];long start=System.currentTimeMillis();//计时开始//调用马踏棋盘解决方法traversalChessboard(chessboard,row-1,col-1,1);//为了人机交互方便,需要减-1操作long end=System.currentTimeMillis();//计时结束System.out.println("运算总耗时:"+(end-start));;//输出结果for (int[] temp:chessboard){System.out.println(Arrays.toString(temp));}}/*** 马踏棋盘解决方案主体(回溯算法)* @param chessboard 棋盘* @param row 行数* @param col 列数* @param step 步数*/public static void traversalChessboard(int[][] chessboard,int row,int col,int step){chessboard[row][col]=step;//将步数赋值到对应棋盘位置上visited[row*X+col]=true;//将该处设置为已访问(row被-1)//调用next来获得可选方案,注意next方法处理的时候先处理列,再处理行,所以传入的point对象为point(y,x)ArrayList<Point> allow = next(new Point(col,row));//用于存储目前可以进入的棋盘(存储可选方案)sort(allow);//!!!对结果进行排序优化!!!//回溯算法核心:回溯式的找到所有位置while (!allow.isEmpty()){//可选方案非空时Point p=allow.remove(0);//拿取可选方案中的头一个位置if (!visited[p.y*X+p.x]){//当该方案位置没有被访问时traversalChessboard(chessboard,p.y,p.x,step+1);//以方案位置再进行上述查找}}//终止回溯条件if (step<X*Y && !finish){//当步数不够且未完成所有位置时chessboard[row][col]=0;//初始化本次回溯过程中棋盘数据visited[row*X+col]=false;//将该位置仍然设置为未访问}else {//解决问题,可以跳出回溯了finish=true;}}/*** 把当前位置棋子的所有可访问棋盘位置以集合的形式输出* @param curPoint 当前棋子* @return 当前位置棋子的所有可访问棋盘位置*/public static ArrayList<Point> next(Point curPoint){ArrayList<Point> allowPoint=new ArrayList<>();//存储当前位置可以进入的位置Point allow=new Point();//下面的策略是先计算列,再计算行//走位置5if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y-1)>=0){allowPoint.add(new Point(allow));}//走位置6if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y-2)>=0){allowPoint.add(new Point(allow));}//走位置7if ((allow.x=curPoint.x+1)<X && (allow.y=curPoint.y-2)>=0){allowPoint.add(new Point(allow));}//走位置0if ((allow.x=curPoint.x+2)<X && (allow.y=curPoint.y-1)>=0){allowPoint.add(new Point(allow));}//走位置1if ((allow.x=curPoint.x+2)<X && (allow.y=curPoint.y+1)<Y){allowPoint.add(new Point(allow));}//走位置2if ((allow.x=curPoint.x+1)<X && (allow.y=curPoint.y+2)<Y){allowPoint.add(new Point(allow));}//走位置3if ((allow.x=curPoint.x-1)>=0 && (allow.y=curPoint.y+2)<Y){allowPoint.add(new Point(allow));}//走位置4if ((allow.x=curPoint.x-2)>=0 && (allow.y=curPoint.y+1)<Y){allowPoint.add(new Point(allow));}return allowPoint;}public static void sort(ArrayList<Point> allow){allow.sort(new Comparator<Point>(){//覆写排序方法,使用Lambda表达式完成@Overridepublic int compare(Point p1,Point p2) {return next(p1).size()-next(p2).size();}});}
}
运算总耗时:637
[47, 16, 3, 30, 7, 18, 5]
[2, 29, 46, 17, 4, 31, 8]
[15, 48, 1, 40, 45, 6, 19]
[36, 41, 28, 49, 34, 9, 32]
[27, 14, 35, 44, 39, 20, 23]
[42, 37, 12, 25, 22, 33, 10]
[13, 26, 43, 38, 11, 24, 21]

你看运算时间从13秒优化到不到1秒,这就是编程的魅力(doge)

还有一个值得注意的地方是:两次结果不同。这是由于回溯算法的不确定性组成的(我们更改了回溯的策略)


其他常用算法,见下各链接

【常用十大算法_二分查找算法】

【常用十大算法_分治算法】

【常用十大算法_贪心算法】

【常用十大算法_动态规划算法(DP)】

【常用十大算法_KMP算法】

【常用十大算法_普里姆(prim)算法,克鲁斯卡尔(Kruskal)算法】

【常用十大算法_迪杰斯特拉(Dijkstra)算法,弗洛伊德(Floyd)算法】

【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)

常用十大算法_回溯算法相关推荐

  1. java回溯算法_回溯算法讲解--适用于leetcode绝大多数回溯题目

    什么是回溯算法? 回溯法是一种系统搜索问题解空间的方法.为了实现回溯,需要给问题定义一个解空间. 说到底它是一种搜索算法.只是这里的搜索是在一个叫做解空间的地方搜索. 而往往所谓的dfs,bfs都是在 ...

  2. 回溯算法和贪心算法_回溯算法介绍

    回溯算法和贪心算法 回溯算法 (Backtracking Algorithms) Backtracking is a general algorithm for finding all (or som ...

  3. python回溯算法_回溯算法经典问题及python代码实现

    2. 0-1背包问题 # 0-1 bag problem import sys def f(no, cur_mass, things, num): global cur_max if no == nu ...

  4. 常用十大算法_KMP算法

    KMP算法 FBI提示:KMP算法不好理解, 建议视频+本文+其他博客,别走马观花 KMP算法是用于文本匹配的算法,属于模式搜索(pattern Searching)问题的一种算法,在讲KMP算法之前 ...

  5. 常用十大算法 非递归二分查找、分治法、动态规划、贪心算法、回溯算法(骑士周游为例)、KMP、最小生成树算法:Prim、Kruskal、最短路径算法:Dijkstra、Floyd。

    十大算法 学完数据结构该学什么?当然是来巩固算法,下面介绍了十中比较常用的算法,希望能帮到大家. 包括:非递归二分查找.分治法.动态规划.贪心算法.回溯算法(骑士周游为例).KMP.最小生成树算法:P ...

  6. DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总

    DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台.刷题集合.问题为导向的十大类刷题算法(数组和字符串.栈和队列.二叉树.堆实现.图.哈希表.排序和搜索.动态规划/回溯法/递归/贪心/分治)总 ...

  7. 常用十大算法(七)— 克鲁斯卡尔算法

    常用十大算法(七)- 克鲁斯卡尔算法 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 介绍 克鲁斯卡尔(Kruskal)算法,是 ...

  8. 八皇后时间复杂度_回溯算法 | 追忆那些年曾难倒我们的八皇后问题

    文章收录在公众号:bigsai,关注更多干货和学习资源 记得点赞.在看 前言 说起八皇后问题,它是一道回溯算法类的经典问题,也可能是我们大部分人在上数据结构或者算法课上遇到过的最难的一道题-- 在这里 ...

  9. emlog充值插件_常用十大必备Emlog插件

    常用十大必备Emlog插件 一个建站程序功能完善离不开插件的支持,也就是功能的插件化.特别是开源的程序,其个性化扩展更大,wordpress的强大很大一个原因就是为其开发的插件种类多.网站的基本功能都 ...

最新文章

  1. dynamic.rnn()sequence_len理解
  2. 手术革命:这三家公司如何用AR技术辅助医疗手术
  3. java中utilities类_Java PHUtilities类代码示例
  4. svn 本地仓库使用
  5. 【Asp.Net】:如何处理大量页面的身份验证跳转
  6. 资深程序员:Python中你不知道的那些小工具
  7. 什么是A记录,子域名,CNAME别名,MX记录,TXT记录,SRV 记录,泛域名(泛解析),域名转向,域名绑定...
  8. 专科计算机组成原理大一试题及答案,计算机组成原理专科试题答案.doc
  9. IMAP与POP3的比较
  10. (Keras/监督学习)15分钟搞定最新深度学习车牌OCR
  11. ewebeditor for php任意文件上传漏洞
  12. 免疫算法(Immune Algorithm,IA)实例详解
  13. bmi055六轴传感器获取数据
  14. 无法删除文件 数据错误 循环冗余检查
  15. json数组的遍历(获取属性名和属性值)
  16. Unity3D射线检测墙面前停止移动
  17. 如何正确在轧钢厂中运用无线测温产品
  18. 正点原子探索者stm32f407 rt-thread 文件系统使用
  19. 基于51单片机的智能遥控晾衣架温度湿度光强检测proteus仿真原理图PCB
  20. autojs 自阅 刷宝短视频 脚本源码

热门文章

  1. 【LeetCode】636. 函数的独占时间
  2. 腾讯在海外游戏和短视频广告领域的新增长机会
  3. 双十一「剁手」背后的客服暗战
  4. echarts 图例属性设置
  5. 【数字IC验证快速入门】5、快速上手Linux下的文本编辑神器gvim
  6. 吸顶灯怎么固定天花板_吊灯怎样安装在天花板上?
  7. 20年来最优秀游戏处理器!AMD锐龙7 7800X3D首发评测:大幅超越i9-13900KS
  8. mysql 建表建索引
  9. JS面试(四):对调变量的六种方法
  10. c语言定时延时子程序,C语言延时子程序准确设置