此篇文章源自对一个有趣问题的思考,在我的另一篇博文《一个有趣的 5 X 5 方阵一笔画问题》中有详细介绍。在已知其结论的情况下,作为程序员的我,还是想利用该问题当做出发点,写一个可以遍历所有“可能路线”的寻路算法,当做学习“图”相关算法的练习。如果对那个原始问题有兴趣,点击上面的文章链接,出门右转便是。

一、问题回顾

  还是要简单描述一下问题:有一个 5 X 5 的点方阵,如下图,要想用一笔画将所有的蓝色点连起来,是否有可行路线。需要满足3点要求:

  1、笔画必须水平或垂直。

  2、笔画不可以超出方阵边界之外,

  3、每个点只经过一次,且不可以经过黄色点。

  这个问题的数学证明已经在开头的文章中给出,在此不做赘述。这里只考虑它的算法实现。



  在思考算法之前,需要先对问题进行抽象分析,我们可以将上面的问题理解成下图这样 :

二、抽象分析

  由于题目已经被数学证明没有一条路线能够满足题目要求,所以算法的目的不是为了找到所谓符合要求的路线,而是遍历从出发点向网格中所有点可形成的所有路线。可以假设上面的网格是一个地图,小明同学按照地图在网格中行走,目的是找出从出发点到所有点的所有路线。小明先在地图中规定坐标,网格底边作为x轴,左侧的竖边作为y轴,那么红色五角星坐标就是(1,4)。小明从原点开始出发,刚开始时地图中所有的交叉点都被标记“未走过”标识。小明随机选择下一个点,假如依次经过(0,0)、(1,0)、(1,1)、(2,1)、(2,2)、(3,2)、(4,2)、(4,1)、(4,0)、(3,0)、(3,1),每往前走一个点,他都会在地图的相应位置标记已经走过。走到(3,1)时,由于四周的(2,1)、(3,2)、(4,1)都已经走到过,所以往前是无路可走的,只能逐步回退,回退之前小明为了下次不再重新走这个线路,他将这个线路记到了本子上,并标记本路线为历史路线,然后回退,每回退一步小明都会在离开的那个位置将“已走过”的标记涂抹掉并改为“未走过”标识,因为这样当下次从其他路线走时还可以走那个位置。当回退到(3,0)时,(3,1)位置就会由标记“已走过”改为标记“未走过”的状态,并发现临近的(2,0)还没有走过,于是不再回退,转向(2,0)。到达(2,0)时,又出现了无路可走的情况,于是记录当下的路线,然后回退并标记地图。当回退到(3,0)时,虽然会发现(3,1)是“未走过”状态,但由于如果走(3,1)的话就会发现这条路线在笔记本的历史路线中已经出现过,所以依然不能走(3,1),继续回退,按照这个原则一直走下去,直到遍历完从起点出发到达地图上所有点的所有路线,小明最终一定会回到起点。此时他笔记本中的历史路线列表,就是从起点达地图上所有点的所有路线。

三、算法分析

  考虑到算法的实现,这显然是利用了数据结构中的“图”。每一次路线尝试,都是对图的深度遍历,算法要做的事情,无非就是随机选择起点,然后不断地深度遍历新的路线。每往前走一步,就将新位置放进栈。这里要注意的是,针对每一次尝试,当走到无路可走时,并不意味着24个点全部已经遍历到了,因为根据规则,无路可走的原因可能是当下点的四周位置(下面统称为“邻居”)已经全部走过了,即它们已经在栈中了,或者是走到了边角的位置。那么此时无路可走该怎么办呢?是重新从起点开始呢,还是怎样。理论上都可行,不过考虑到效率,即让算法尽量少做重复的事情,我选择在无路可走时,将当下的路线(一个由点序列组成的数组)做记录(存入一个集合中,这个集合记录着所有历史路线),然后回退并将离开的位置从栈弹出,每次回退都将当下路线记录为历史路线,直到周围存在“没有在栈中的点”时不再回退,而是转向新的位置。此时,最近一次回退的那个历史路线决定了下次不会再走这个路线的方向。(假如连续回退了N步,那么最近一次回退的那个历史路线则是临近前N-1次回退历史路线的子集)。不断以上面的方式进行下去,由于“地图”是有限的,当所有可能路线全部遍历过时,最终一定会回到起点。此时历史路线集合列表,记录了从起点达地图上所有点的所有路线。

四、算法描述:

  1、输入:一个二维数组M[26][5]。(第一维度角标1到4分别代表上右下左四个方向,第二维度角标1到25分别代表图的每个点,一次从地图的左上到右下为1到25。数组中的每个值,比如M[20][1]=15 表示地图编号为20的点的上边是编号为15的点,M[20][2]=0表示地图编号为20的点的右方没有通路。其中M[i][0]=0,M[0][j]=0)

  2、初始化:定义栈Q = {1} 和 集合H = {} .(Q中记录了当下路径中依次走过的所有点,H记录着所有的历史路径。)

  3、重复以下动作,直到 Q = {}:

  根据Q的栈顶元素d,和邻接表M找出d的所有临近点元素,从其中随机选择一个元素,且Q中不存在该元素,若找到,将该元素放入Q中。若找不到,则将Q的副本存入H中,并将d从Q中弹出。

  4、输出:H。

五、算法实现

  到这里,对原始问题的抽象思考过程和算法的理论分析过程已经描述完毕。下面就是用实际的代码实现该算法并做到可视化。如果不考虑可视化,那么随便一个编程语言都可以很方便的实现它,我之前用java写过,将结果输出在控制台那种。不过感觉没啥意思,还是想要可视化的感觉。当时很长一段时间我都不知道如何将它可视化,直到我遇到了React,深入了解后,发现用React来完成该算法的可视化,真是再合适不过了。

  由于React是面向组件开发的模式,并且可以很容易根据状态来自动渲染页面,所以可视化部分的设计,就变成对原始“地图”的拆解和状态的定义。我设计的最基础的组件和整体样式如下图这样:

  每个位置(点)都有上下左右四个方向,点与点之间,如果存在连线,则将对应方向的线条用CSS渲染成“block”,其他渲染成“none”.这个过程在定义好CSS样式后,根据数据状态,React会自动帮助界面渲染,不需要反复用js写渲染逻辑。

  最终效果请点击这个 程序链接1程序链接2 查看。以下附带部分代码。另外关于实现过程中对性能的优化,我已经做了一些努力,以后也会不断对其优化,欢迎有兴趣的朋友提出高见。

  1. import React, { Component } from 'react';
  2. import './AppDemo.css';
  3. import Grid from './Grid';
  4. class AppDemo extends Component {
  5. constructor(proprs) {
  6. super(proprs);
  7. var width = 5;
  8. var height = 5;
  9. var matrix = this.init(width,height);
  10. var x = 50;
  11. var start = Math.floor(Math.random() * width * height + 1);
  12. this.state = {
  13. matrix: matrix,
  14. start: start,
  15. arr: [start],
  16. historyPath: [],
  17. width: width,
  18. height: height,
  19. timeID: 0,
  20. speed: 5,
  21. random: true,
  22. x: x,
  23. time: 0
  24. }
  25. }
  26. init(width,height){
  27. var matrix = [[0, 1, 2, 3, 4]];
  28. for (var numb = 1; numb <= width * height; numb++) {
  29. var up = numb > width ? numb - width : 0;
  30. var right = (numb % width) !== 0 ? numb + 1 : 0;
  31. var down = numb <= width * (height - 1) ? numb + width : 0;
  32. var left = ((numb - 1) % width) !== 0 ? numb - 1 : 0;
  33. var arr = [numb];
  34. arr.push(up);
  35. arr.push(right);
  36. arr.push(down);
  37. arr.push(left);
  38. matrix.push(arr);
  39. }
  40. return matrix;
  41. }
  42. handle() {
  43. //var beginTime1=0;
  44. //var beginTime2=0;
  45. //beginTime1 = new Date().getTime();
  46. var nowRow = this.state.arr[this.state.arr.length - 1];//获取当下的位置编号
  47. var arr = this.state.arr;//路径编号
  48. var matrix = this.state.matrix;//矩阵存储结构
  49. var historyPath = this.state.historyPath;//历史路径
  50. if (arr.length > 0) {//如果路径长度>0
  51. var next = false;//默认找不到路径
  52. var ran = 1
  53. if (this.state.random) {
  54. ran = Math.floor(Math.random() * 4 + 1);
  55. }
  56. //var beginTime4 = new Date().getTime();
  57. for (var i = 0; i < 4; i++) {
  58. var nextNumb = matrix[nowRow][ran];
  59. if (nextNumb !== 0 && !this.containNowPath(nextNumb)) {//找到路径
  60. arr.push(nextNumb);//将新元素入栈
  61. if (!this.containHistoryPath(arr)) {//若新路径没有在历史路径中出现过,则走该路径
  62. this.setState({
  63. arr: arr
  64. });
  65. next = true;
  66. break;
  67. } else {//若新路径在历史路径中出现过,则跳过该路径
  68. arr.pop();//放弃该位置
  69. ran = ran + 1 > 4 ? 1 : ran + 1;
  70. }
  71. } else {
  72. ran = ran + 1 > 4 ? 1 : ran + 1;
  73. }
  74. }
  75. //var beginTime5 = new Date().getTime();
  76. if (!next) {//如果无路可走
  77. //判断当下路径(未退步之前)是否包含于历史记录。
  78. if (!this.containHistoryPath(arr)) {
  79. historyPath.push(arr.slice());//若没有包含与历史中,则将新的尝试路径保存进历史路径集中
  80. }
  81. arr.pop();//将最后一个元素弹出,相当于后退一步
  82. this.setState({//修改当前改变了的状态
  83. arr: arr,
  84. historyPath: historyPath
  85. });
  86. }
  87. } else {//若路径遍历结束,则换一个起点继续遍历。
  88. this.stop();
  89. // this.setState({
  90. // start: this.state.start + 1,
  91. // arr: [this.state.start + 1],
  92. // historyPath: [],
  93. // len: 5
  94. // });
  95. }
  96. // beginTime2 = new Date().getTime();
  97. // var time = beginTime2 - beginTime1 ;
  98. // if(time> this.state.time){
  99. // this.setState({//修改当前改变了的状态
  100. // time: time
  101. // });
  102. // }
  103. //alert(time);
  104. }
  105. containNowPath(row) {//判断下一个位置是否已经存在当下路径中。
  106. var r = false;
  107. for (var i = 0; i < this.state.arr.length; i++) {
  108. r = this.state.arr[i] === row;
  109. if (r) {
  110. break;
  111. }
  112. }
  113. return r;
  114. }
  115. containHistoryPath(arr) {//从历史路径中查找是否已经存在下一步要走的路径
  116. var r = false;
  117. var historyPath = this.state.historyPath;
  118. for (var i = historyPath.length - 1; i >= 0; i--) {
  119. r = historyPath[i].toString().indexOf(arr.toString()) !== -1;
  120. if (r) {
  121. break;
  122. }
  123. }
  124. return r;
  125. }
  126. render() {
  127. return (
  128. <div>
  129. <div style={{ margin: "50px auto 0px auto", width: (this.state.width * this.state.x) + "px", minWidth: "700px" }}>
  130. <div className="control">
  131. <button type="button" onClick={() => this.start()}>开始</button>
  132. <button type="button" onClick={() => this.stop()}>暂停</button>
  133. <button type="button" onClick={() => this.start()}>继续</button>
  134. <button type="button" onClick={() => this.step()}>单步</button>
  135. <apan style={{ margin: "0px auto 0px 10px", width: "120px", display: "inline-block" }}>尝试次数:{this.state.historyPath.length}</apan>
  136. <apan style={{ margin: "0px auto 0px 10px" }}>速度:</apan>
  137. <button style={this.state.speed === 1000 ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.speed(1000)}>极慢</button>
  138. <button style={this.state.speed === 500 ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.speed(500)}>慢</button>
  139. <button style={this.state.speed === 100 ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.speed(100)}>中</button>
  140. <button style={this.state.speed === 50 ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.speed(50)}>快</button>
  141. <button style={this.state.speed === 5 ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.speed(5)}>极快</button>
  142. <apan style={{ margin: "0px auto 0px 10px", width: "50px", display: "inline-block" }}></apan>
  143. <button style={this.state.random ? { backgroundColor: "#61dafb" } : {}} type="button" onClick={() => this.random()}>随机</button><br />
  144. </div>
  145. <div className="control2">
  146. <button style={{ width: "80px" }} type="button" onClick={() => this.addheight(1)}>增加行+</button>
  147. <button style={{ width: "80px" }} type="button" onClick={() => this.addwidth(1)}>增加列+</button>
  148. <button style={{ width: "80px" }} type="button" onClick={() => this.big(1)}>放大+</button><br />
  149. <button style={{ width: "80px" }} type="button" onClick={() => this.addheight(-1)}>减少行-</button>
  150. <button style={{ width: "80px" }} type="button" onClick={() => this.addwidth(-1)}>减少列-</button>
  151. <button style={{ width: "80px" }} type="button" onClick={() => this.big(-1)}>缩小-</button>
  152. </div>
  153. <Grid x={this.state.x} width={this.state.width} height={this.state.height} arr={this.state.arr} />
  154. </div>
  155. </div >
  156. )
  157. }
  158. addwidth(n) {
  159. this.stop();
  160. var width = this.state.width;
  161. width = width + n;
  162. if (width > 0 && width * this.state.x <= 1000) {
  163. var matrix = this.init(width,this.state.height);
  164. var start = Math.floor(Math.random() * width * this.state.height + 1);
  165. this.setState({
  166. matrix : matrix,
  167. start: start,
  168. arr: [start],
  169. historyPath: [],
  170. width: width
  171. });
  172. } else {
  173. this.setState({
  174. width: Math.floor(1000 / this.state.x),
  175. });
  176. }
  177. }
  178. addheight(n) {
  179. this.stop();
  180. var height = this.state.height;
  181. height = height + n;
  182. if (height > 0 && height * this.state.x <= 500) {
  183. var matrix = this.init(this.state.width,height);
  184. var start = Math.floor(Math.random() * this.state.width * height + 1);
  185. this.setState({
  186. matrix:matrix,
  187. start: start,
  188. arr: [start],
  189. historyPath: [],
  190. height: height
  191. });
  192. } else {
  193. this.setState({
  194. height: Math.floor(500 / this.state.x),
  195. });
  196. }
  197. }
  198. big(n) {
  199. var x = this.state.x;
  200. x = x + n;
  201. if (x > 0 && ((x * this.state.width <= 1000) || (x*this.state.height<=500)) ){
  202. this.setState({
  203. x: x
  204. });
  205. } else {
  206. this.setState({
  207. x: Math.floor(x * this.state.width > x*this.state.height?1000/this.state.width:500/this.state.height)
  208. });
  209. }
  210. }
  211. // shouldComponentUpdate(nextProps, nextState){
  212. // return nextState.arr.length>100;
  213. // }
  214. step() {
  215. this.stop();
  216. this.handle();
  217. }
  218. start() {
  219. var timeID = this.state.timeID;
  220. if (timeID === 0) {
  221. timeID = setInterval(
  222. () => this.handle(),
  223. this.state.speed
  224. );
  225. }
  226. this.setState({
  227. timeID: timeID
  228. });
  229. }
  230. stop() {
  231. var timeID = this.state.timeID;
  232. if (timeID !== 0) {
  233. clearInterval(timeID);
  234. }
  235. this.setState({
  236. timeID: 0
  237. });
  238. }
  239. componentDidMount() {
  240. var matrix = [[0, 1, 2, 3, 4]];
  241. var width = this.state.width;
  242. var height = this.state.height;
  243. for (var numb = 1; numb <= width * height; numb++) {
  244. var up = numb > width ? numb - width : 0;
  245. var right = (numb % width) !== 0 ? numb + 1 : 0;
  246. var down = numb <= width * (height - 1) ? numb + width : 0;
  247. var left = ((numb - 1) % width) !== 0 ? numb - 1 : 0;
  248. var arr = [numb];
  249. arr.push(up);
  250. arr.push(right);
  251. arr.push(down);
  252. arr.push(left);
  253. matrix.push(arr);
  254. }
  255. }
  256. speed(n) {
  257. var timeID = this.state.timeID;
  258. clearInterval(timeID);
  259. timeID = 0;
  260. timeID = setInterval(
  261. () => this.handle(),
  262. n
  263. );
  264. this.setState({
  265. timeID: timeID,
  266. speed: n
  267. });
  268. }
  269. random() {
  270. this.setState({
  271. random: !this.state.random
  272. });
  273. }
  274. sleep(numberMillis) {
  275. var now = new Date();
  276. var exitTime = now.getTime() + numberMillis;
  277. while (true) {
  278. now = new Date();
  279. if (now.getTime() > exitTime)
  280. return true;
  281. }
  282. }
  283. }
  284. export default AppDemo;

5 X 5 方阵引出的寻路算法 之 路径遍历(完结)的更多相关文章

  1. JS算法之A*(A星)寻路算法

    今天写一个连连看的游戏的时候,接触到了一些寻路算法,我就大概讲讲其中的A*算法. 这个是我学习后的一点个人理解,有错误欢迎各位看官指正. 寻路模式主要有三种:广度游戏搜索.深度优先搜索和启发式搜索. ...

  2. 寻路算法之A*算法详解

    前言 在实际开发中我们会经常用到寻路算法,例如MMOARPG游戏魔兽中,里面的人物行走为了模仿真实人物行走的体验,会选择最近路线达到目的地,期间会避开高山或者湖水,绕过箱子或者树林,直到走到你所选定的 ...

  3. A星寻路算法介绍

    你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...

  4. A*寻路算法探究

    A*寻路算法探究 A*算法常用在游戏的寻路,是一种静态网路中求解最短路径的搜索方法,也是解决很多搜索问题的算法.相对于Dijkstra,BFS这些算法在复杂的搜索更有效率.本文在U3D中进行代码的测试 ...

  5. A*寻路算法

    对于初学者而言,A*寻路已经是个比较复杂的算法了,为了便于理解,本文降低了A*算法的难度,规定只能横竖(四方向)寻路,而无法直接走对角线,使得整个算法更好理解. 简而言之,A*寻路就是计算从起点经过该 ...

  6. 算法:Astar寻路算法改进,双向A*寻路算法

    早前写了一篇关于A*算法的文章:<算法:Astar寻路算法改进> 最近在写个js的UI框架,顺便实现了一个js版本的A*算法,与之前不同的是,该A*算法是个双向A*. 双向A*有什么好处呢 ...

  7. 算法:Astar寻路算法改进

    早前写了一篇<RCP:gef智能寻路算法(A star)> 出现了一点问题. 在AStar算法中,默认寻路起点和终点都是N x N的方格,但如果用在路由上,就会出现问题. 如果,需要连线的 ...

  8. js实现A*寻路算法

    这两天在做百度前端技术学院的题目,其中有涉及到寻路相关的,于是就找来相关博客进行阅读. 看了Create Chen写的理解A*寻路算法具体过程之后,我很快就理解A*算法的原理.不得不说作者写的很好,通 ...

  9. 用简单直白的方式讲解A星寻路算法原理

    很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高的,因此也成为游戏中 ...

随机推荐

  1. Python入门基础(9)__面向对象编程_1

    定义一个只包含方法的类 class 类名: def 方法1(self,参数列表): pass def 方法2(self,参数列表): pass 当一个类定义之后,要使用这个类来创键对象.语法如下: 对 ...

  2. 洛谷P4995 跳跳!题解

    求关注,求赞,求评论QAQ 题目:https://www.luogu.org/problemnew/show/P4995 简单描述一下吧,就是说有n块石头,起始可以跳到任何一块上面,接着也是,只不过每 ...

  3. [leetcode]375 Guess Number Higher or Lower II (Medium)

    原题 思路: miniMax+DP dp[i][j]保存在i到j范围内,猜中这个数字需要花费的最少 money. "至少需要的花费",就要我们 "做最坏的打算,尽最大的努 ...

  4. python递归-三元表达式-列表生成式-字典生成式-匿名函数-部分内置函数-04

    递归 递归: # 函数在调用阶段直接或间接地又调用了自身 应用场景: # 将列表中的数字依次打印出来(循环的层数是你必须要考虑的点)   -->  l = [1, [2, [3, [4, [5, ...

  5. mongodb的索引原理

    首先说一下为什么要有索引,大家都知道mongdb是非关系型文档类型数据库,用过的人都有同一种感受,查询的效率太低,当你想提高查询效率的时候可以就需要使用索引了. 哈哈,本来想写一篇的,在网上看到了一篇 ...

  6. 装饰器和"开放-封闭"原则

    装饰器和"开放-封闭"原则 "开放-封闭"原则 软件开发中的"开放-封闭"原则,它规定已经实现的功能代码不应该被修改,但可以被扩展,即: 封 ...

  7. js - 原生ajax访问后台读取数据并显示在页面上

    1.前台调用ajax访问后台方法,并接收数据 <%@ page contentType="text/html;charset=UTF-8" language="ja ...

  8. WebGL着色器32位浮点数精度损失问题

    问题 WebGL浮点数精度最大的问题是就是因为js是64位精度的,js往着色器里面穿的时候只能是32位浮点数,有效数是8位,精度丢失比较严重. 这篇文章里讲了一些处理方式,但是视坐标这种方式放在我们的 ...

  9. 计算机原理以及PythonIDE配置和使用

    计算机基础 在巩固了昨日学习知识的基础上,增加了新的内容 整个关于计算机基础的学习可以浓缩为五个问题 什么是编程? 人与计算机之间的交互操作,使人可以奴役计算机从而让其代替人类工作的行为 操作系统有什 ...

  10. SQL语句中的as