运行截图
实现原理
这里寻找的走出迷宫的路径并不是最短路径而是可行路径,采用的思想就是暴力搜索,将所有的走法都尝试一下,走到最后就是可行的路径,所以会采用栈这个先进后出的数据结构,因为这样就可以利用栈进行依次回溯
数据结构
PosType只是为了方便的记录x,y的坐标位置,栈中存储的是SElemType这个数据结构,作用是栈顶的元素就是当前的位置,里面的direction就是要走的方向
function PosType() {
this.x = null;
this.y = null;
} //坐标位置
function SElemType() {
this.seat = null; //通道块在迷宫中的"坐标位置"
this.direction = null; //从此通道块走向下一通道块的方向,di=1,2,3,4分别表示东,南,西,北
}
伪代码
STACK = []
curpos = start
//curpos记录的就是下一步的位置
do{
if(Pass(curpos)){
//如果当前的这个格子能走进去就进行记录并将当前的位置生成新的数据结构加入栈中
FootPrint(curpos)
new SElemType()//seat = curpos,direction = 1;//一开始都是向东走
STACK.push(SElemType)
if(curpos == end){
//如果到最后的位置了
return true;//迷宫走完
}
//如果没走完就看下一步走到哪
curpos = nextPos(curpos, 1);
}else{
//如果不能走进去就进行回溯
PrePath = STACK.pop();
while(PreSElemtype.direction == 4&&STACK.length > 0){
//如果尝试完了就进行回溯,一直到栈空或者有新的尝试为止
MarkPrint(PrePath.seat);
PrePath = STACK.pop();
}
if(还有元素可以尝试){
PrePath.direction++;
STACK.push(PrePath);
curpos = nextPos(PrePath.seat,PrePath.direction);//根据回溯到的位置取下个位置
}
}
}while(STACK.length > 0)//栈不空
流程图
实现代码
<!DOCTYPE html>
<html>
<head>
<title>迷宫搜索</title>
<meta charset="UTF-8">
<style>
.container {
left: 200px;
height: 200px;
width: 200px;
position: relative;
}
.lattice {
height: 20px;
width: 20px;
top: 0px;
left: 0px;
border-style: solid;
border-width: 1px;
border-color: #ffffff;
background-color: #5E5E5E;
position: absolute;
color: crimson;
}
.lattice2 {
height: 20px;
width: 20px;
top: 0px;
left: 0px;
border-style: solid;
border-width: 1px;
border-color: #5E5E5E;
background-color: #ffffff;
position: absolute;
color: black;
}
</style>
<script type="text/javascript" src="http://cdn.bootcss.com/lodash.js/4.16.6/lodash.min.js"></script>
<script>
var MAPSIZE = 20;
var WALLNUM = 150;
var START = [1, 1];
var END = [MAPSIZE - 2, MAPSIZE - 2];
var WALL = {};
var MAP = {};
var MAPUSED = [];
function PosType() {
this.x = null;
this.y = null;
} //坐标位置
function SElemType() {
this.seat = null; //通道块在迷宫中的"坐标位置"
this.direction = null; //从此通道块走向下一通道块的方向,di=1,2,3,4分别表示东,南,西,北
}
function nextPos(curpos, direction) {
//判断下一步要走到哪一个位置
switch (direction) {
case 1:
curpos.y++;
break; //向东走,行号不变,列号加1
case 2:
curpos.x++;
break; //向南走,行号加1,列号不变
case 3:
curpos.y--;
break; //向西走,行号不变,列号减1
case 4:
curpos.x--;
break; //向北走,行号减1,列号不变
}
return curpos;
}
function Pass(curpos) {
//果然是通过这里出了问题就栈循环了,死锁,这里除了不是墙之外必须是没有走过的
if (MAPUSED[curpos.x][curpos.y] == " ") {
return true
} else {
return false;
}
}
function MapPathSet() {
for (let i = 0; i < MAPSIZE; i++) {
for (let j = 0; j < MAPSIZE; j++) {
if (MAPUSED[i][j] == "*") {
MAP[i + "_" + j].innerText = "*";
} else if (MAPUSED[i][j] == "@") {
MAP[i + "_" + j].innerText = "@";
}
}
}
}
function Init() {
CreateWall();
CreateMap();
if (MapPath()) {
alert("迷宫出路找寻完毕");
MapPathSet();
} else {
alert("迷宫没有出口");
MapPathSet();
}
}
function CreateWall() {
let wallnum = 0;
for (let i = 0; i < MAPSIZE; i++) {
WALL[i + "_" + 0] = 1;
WALL[0 + "_" + i] = 1;
wallnum += 2;
}
for (let i = 0; i < MAPSIZE; i++) {
WALL[i + "_" + (MAPSIZE - 1)] = 1;
WALL[(MAPSIZE - 1) + "_" + i] = 1;
wallnum += 2;
}
while (wallnum < WALLNUM) {
let x = _.random(1, MAPSIZE - 1);
let y = _.random(1, MAPSIZE - 1);
if (WALL[x + "_" + y] == undefined) {
WALL[x + "_" + y] = 1;
wallnum++;
}
}
}
function CreateMap() {
for (let i = 0; i < MAPSIZE; i++) {
MAPUSED[i] = new Array(MAPSIZE);
for (let j = 0; j < MAPSIZE; j++) {
var divEle = document.createElement("div");
if (WALL[i + "_" + j] == 1) {
divEle.className = "lattice";
divEle.style.top = 20 * i + "px";
divEle.style.left = 20 * j + "px";
} else {
divEle.className = "lattice2";
divEle.style.top = 20 * i + "px";
divEle.style.left = 20 * j + "px";
}
MAP[i + "_" + j] = divEle; //记录每一个块
if (WALL[i + "_" + j] == 1) {
//如果是墙则是#
MAPUSED[i][j] = "#";
} else {
MAPUSED[i][j] = " ";
}
document.getElementById("container").appendChild(divEle);
}
}
}
function FootPrint(curpos) {
//如果已经走过这个地方了就留下标记
MAPUSED[curpos.x][curpos.y] = '*';
}
function MarkPrint(curpos) {
//如果走过这个地方但是这个地方无法抵达出口则标记@
MAPUSED[curpos.x][curpos.y] = '@';
}
function MapPath() {
let stack = [];
let curpos = new PosType();
curpos.x = START[0];
curpos.y = START[1];
if (WALL[curpos.x + "_" + curpos.y] == 1) {
return false;
}
do {
if (Pass(curpos)) {
FootPrint(curpos); //如果当前的块是可抵达的则将当前的块标记
let NextPath = new SElemType();
//这里涉及到js自身语法的原因,所以需要深复制,
let clonecurpos = _.cloneDeep(curpos);
NextPath.seat = clonecurpos;
NextPath.direction = 1;
stack.push(NextPath);
if (curpos.x == END[0] && curpos.y == END[1]) {
//如果抵达了重点了
return true;
}
curpos = nextPos(curpos, 1);
} else {
//如果当前的位置并不能通过,就回溯
let PrePath = stack.pop();
while (PrePath.direction == 4 && stack.length > 0) {
//如果回退后发现回退的已经探索完了继续回退
MarkPrint(PrePath.seat);
PrePath = stack.pop();
}
if (PrePath.direction < 4) {
//如果没有探索结束,继续探索
PrePath.direction++;
stack.push(PrePath);
//这边也要克隆一下不然会让PrePath的值增加
let tempcurpos = new PosType();
tempcurpos.x = PrePath.seat.x;
tempcurpos.y = PrePath.seat.y;
curpos = nextPos(tempcurpos, PrePath.direction);
}
}
} while (stack.length > 0);
return false;
}
</script>
</head>
<body onload="Init()">
<div class="container" id="container">
</div>
</body>
</html>
提示(JS小知识)
1.深复制浅复制
js因为自身的原因很多时候计入数组的只是一个指针,指向了同一个数组,也可以理解成JAVA的深复制和浅复制,总之一点就是,你在入栈的时候不要将你循环改变的量入栈,会造成栈内有关这个值的数都在随着循环改变
let NextPath = new SElemType();
//这里涉及到js自身语法的原因,所以需要深复制,
let clonecurpos = _.cloneDeep(curpos);
NextPath.seat = clonecurpos;
NextPath.direction = 1;
stack.push(NextPath);
比如这里贴的代码中的
NextPath.seat = clonecurpos;
,如果直接写NextPath.seat = curpos;
那么栈中存储的就是这个curpos的指针,curpos自身怎么变,栈内元素就会怎么变,所以要深复制,利用lodash这个库就挺方便
2.函数传参
js使用函数如果带有参数,那么这个参数不是临时的参数,每一次都相当于是c语言中指针传值,意思就是,你在函数中调用另外一个函数想获取返回值,但是你传入的参数会因为这个函数的执行收到影响
PrePath.direction++;
stack.push(PrePath);
//这边也要克隆一下不然会让PrePath的值增加
let tempcurpos = new PosType();
tempcurpos.x = PrePath.seat.x;
tempcurpos.y = PrePath.seat.y;
curpos = nextPos(tempcurpos, PrePath.direction);
比如这里,如果你直接将PrePath作为参数使用nextPos这个函数
curpos = nextPos(PrePath.seat, PrePath.direction);
,那么函数执行完后PrePath.seat的值也会发生改变造成影响