问题描述
有一个二维数组map[4][4],
[0, 0, 0, 0],
[1, 0, 1, 0],
[1, 0, 0, 0],
[1, 0, 1, 0],
把map[0][0]作为起点,map[4][4]作为终点,如果数组中的值0代表可以通过,1代表无法通过。求出是否能从起点到达终点,如果可以,输出ture,并且输入路径。
一、深度优先搜索(dfs)
① 概念理解:一个人迷路,遇到很多分叉路口,他只有一个人,并且想走出去,所以只能一个个尝试,一条道路走到黑,发现到头了,然后再拐回去走刚才这条路的其他分叉路口,最后发现这条路的所有分叉路口走完了,选择另外一条路继续以上操作,直到所有的路都走过了。实际上就是往深度走,走错了就回来,找没走过的路,直到走到终点。
② 实现方式:堆栈实现,
③ 优点:能找出所有解决方案;相对于bfs内存占用少。
④ 缺点:找到的路径有可能不是最短路径,且在深度较大时效率低。
⑤ 代码实现(JS):
let map = [
[0, 0, 0, 0],
[1, 0, 1, 0],
[1, 0, 0, 0],
[1, 0, 1, 0],
]
let start = [0, 0];
let end = [3, 3];
let vis = { 0: true }; // x_y表示(x,y)坐标已经不能再经过了
let dis = [];
let num = 0;
let n =0;
function dfs(x, y) {
dis.push([x, y]); //在dis列表最后添加一个元素
if (x == end[0] && y == end[1]) { //到达终点
console.log("第", num, "条路:", dis);
num++;
dis.pop(); //截取列表中的最后一个元素
return true;
}
let isFind = false;
for (let info of [[0, 1],[0, -1],[-1, 0], [1, 0]]) { // 遍历所有方向(上,下,左,右)
let X = x + info[0]; // 计算出对应方向的坐标
let Y = y + info[1];
if (X >= 0 && X < 4) { // 判断坐标是否出界
if (Y >= 0 && Y < 4) {
if (map[X][Y] == 0) { // 判断坐标是否能走
if (!vis[X * 10 + Y]) { // 判断坐标是否已走
vis[X * 10 + Y] = true; // 记录坐标已走
if (dfs(X, Y)) { // 进入下一个坐标,当找到终点时,dfs函数的返回值时ture
isFind = true;
}
vis[X * 10 + Y] = false; // 取消上一个走的坐标标记,是在全部完成搜索完以后
}
}
}
}
}
dis.pop();
return isFind;
}
console.log(dfs(start[0], start[1]));
⑥ 实现结果:
1、列表dis[]
中存放的是所有可行的路径,
2、isFind
代表是否从起点start
到达 终点end
二、广度优先搜索(bfs)
① 概念理解:一个人迷路,但是他有技能(分身术)它遇到分叉路口,不是选一个走,而是分身多个人都试试,比如有A、B、C三个分叉路口,它A路走一步,紧接着B路也走一步,然后C路也赶紧走一步,步伐整齐统一,直到所有的路走过了。实际上就是往四周搜索,分开搜索。
② 实现方式:队列实现
③ 优点:找到的路径就是最短路径,效率高。
④ 缺点:内存耗费大。
⑤ 代码实现(JS):let map = [
[0, 0, 0, 0],
[1, 0, 1, 0],
[1, 0, 0, 0],
[1, 0, 1, 0],
]
let start = [0, 0];
let end = [3, 3];
let vis = { 0: true }; // x_y表示(x,y)坐标已经不能再经过了
let dis = [];
function bfs(s, t){
let que = [s];
let isFind = false;
while (que.length > 0){ // 列表不空,循环不完
let cur = que.shift(); // 队首元素出队
if (cur[0] == t[0] && cur[1] == t[1]){ // 判断当前点是否为终点
isFind = true;
break;
}
for (let info of [[-1,0],[1,0],[0,-1],[0,1]]){
let X = cur[0] + info[0];
let Y = cur[1] + info[1];
if (X >= 0 && X < 4) { // 判断坐标是否出界
if (Y >= 0 && Y < 4) {
if (map[X][Y] == 0) { // 判断坐标是否能走
if (!vis[X * 10 + Y]) { // 判断坐标是否已走
vis[X * 10 + Y] = true; // 记录坐标已走
dis[X * 10 + Y] = cur; // 儿子记录老子
que.push([X, Y]);
}
}
}
}
}
}
return isFind;
}
console.log(bfs(start,end));
let c = end;
let list = [];
while(c){
list.push(c);
c = dis[c[0] * 10 +c[1]];
}
console.log(list.reverse());
⑥ 实现结果:
dfs和bfs的最优解情况
① 比较两种算法:广度(bfs)一般无回溯操作,即人栈和出栈的操作,所以运行速度比深度优先搜索法要快些。所以一般情况下,深度(dfs)占内存少但速度较慢,广度(bfs)占内存较多但速度较快,在距离与深度成正比的情况下能较快地求出最优解。
② 如果数据量较大,必须考虑溢出和节省内存空间的问题,使用深度优先搜索法较好,深度优先搜索法有递归以及非递归两种设计方法。一般的,当搜索深度较小、问题递归形式较明显时,用递归方法设计的较好,它可以使得程序结构更简捷易懂。但当搜索深度较大时,当数据量较大时,由于系统堆栈容量的限制,递归易产生溢出,用非递归方法设计比较好
③ 如果数据量较小,且对程序运行的效率要求较高,或者题意是需要找出最短路径,一般使用广度优先搜索法。