今天看了一个介绍走迷宫算法的视频,就自己动手写了一遍。写一篇日志以免将来忘记。
首先是迷宫的表示。如左下图,起始位置是左上角黄色位置,重点位置是右下角黄色位置。在这个二维矩阵里,0表示道路通畅,可走;1表示有障碍物,不可走。最终计算出来的结果如右下角所示,从左上角0开始,每走一步,累加一次,这样就可以显示出整条路径的先后顺序。要走最短路径,只要从终点位置,不断递减1寻找上一步的位置直到回到起始位置即可。
算法的主要过程:
从矩阵中一点向外探索,总共有四个方向,分别是上下左右,如下图。
当探索了第一步以后,可以继续向外探索,如从上开始,第二步探索只有三个方向,因为根据规则不能往回走。
往上探索结束后,往左探索。
往左探索结束后往下探索。
往下探索结束后往右探索
这样就形成了一圈逆时针方向的探索。
继续进行第三步探索,如下图所示。
一直持续这个过程直到走到终点。
这里要注意的一点是,一定要先探索完1号所有位置后,再探索完2号所有位置。这叫广度优先,不然就成了深度优先了,就得不到最短路径了。
这里我使用队列来存储下一步可走的位置实现广度优先算法。代码如下:
package main
import (
"fmt"
"os"
)
func readMaze(filename string) [][]int {
file, err :=os.Open(filename)
defer file.Close()
if err != nil {
panic(err)
}
var row,col int
_, err = fmt.Fscanf(file,"%d %d\n", &row, &col)
if err != nil {
panic(err)
}
var maze = make([][]int, row)
for i:=range maze {
maze[i] = make([]int, col)
for j:= range maze[i] {
_, err = fmt.Fscanf(file,"%d", &maze[i][j])
if err != nil {
panic(err)
}
}
var ln string
fmt.Fscanln(file, &ln)
}
return maze
}
type point struct {
i,j int
}
//向某个方向探索
func (p point)Explore(direc direction) point {
return point{p.i+direc.i, p.j+direc.j}
}
//判断一个点是否落在迷宫地图内
func (p point)InMaze(maze [][]int) bool {
if p.i < 0 || p.i >=len(maze) {
return false
}
if p.j < 0 || p.j >= len(maze[0]) {
return false
}
return true
}
//判断一个点是否落在障碍物上
func (p point)OnObstacle(maze [][]int) bool {
if maze[p.i][p.j] == 1 {
return true
}
return false
}
func (p point)IsValidPoint(maze [][]int) bool {
if !p.InMaze(maze) {
return false
}
if p.OnObstacle(maze) {
return false
}
return true
}
//判断这个点是不是在往回走
func (p point)IsBack(steps [][]int, start point) bool {
if steps[p.i][p.j] != 0 {
return true
}
//注意,起始点也是0,所以要把起始点也排除在外
if p.i == start.i && p.j == start.j {
return true
}
return false
}
type direction point
var FourDirections = [4]direction {
{-1,0}, {0,-1}, {1,0},{0,1},
}
//返回路径地图和路径结果(true表示找到了最佳路径)
func walk(maze [][]int, start,end point) ([][]int, bool){
//构造路径地图
steps := make([][]int, len(maze))
for i:= range steps {
steps[i] = make([]int, len(maze[0]))
}
//广度优先搜索队列
var q =[]point{start}
for {
//判断是否已经找到最佳路径
if len(q) == 0 {
return steps,false
}
//取出队列首个元素为当前点
cur := q[0]
q = q[1:]
if cur.i == end.i && cur.j == end.j {
return steps,true
}
//求出四个方向上的点
for _, direc := range FourDirections {
nextPoint := cur.Explore(direc)
//查看探索到的点是否合法
//1. 在迷宫地图范围内
//2. 有没有落在墙上
//3. 没有走回头路
if !nextPoint.IsValidPoint(maze) {
continue
}
if nextPoint.IsBack(steps, start) {
continue
}
//合法的step就添加到广度优先搜索队列里去
q = append(q, nextPoint)
//在路径地图上打路径tag
steps[nextPoint.i][nextPoint.j] = steps[cur.i][cur.j]+1
}
}
return steps,false
}
func PrintSteps(steps [][]int) {
for i:= range steps {
for j:= range steps[i] {
fmt.Printf("%2d ",steps[i][j])
}
fmt.Println("")
}
}
//找出最佳路径,注意这里找到的路径顺序是反着的
func FindBestPath(steps [][]int, start,end point) ([]point, bool){
var path = []point{end}
cur := end
for steps[cur.i][cur.j] != 0 {
//向四个方向去搜索
for _,direc := range FourDirections {
lastStep := cur.Explore(direc)
//判断搜索的点是否在地图内
if !lastStep.InMaze(steps) {
continue
}
//判断搜索的点是否是上一步
if steps[lastStep.i][lastStep.j] == steps[cur.i][cur.j]-1 {
path = append(path, lastStep)
cur = lastStep
break
}
}
}
if cur.i == start.i && cur.j == start.j {
return path,true
}
return path,false
}
func main() {
var maze = readMaze("maze.in")
//fmt.Println(maze)
var start = point{0,0}
var end = point{5,4}
steps, find := walk(maze, start, end)
if find {
fmt.Println("find the best path.")
} else {
fmt.Println("no path to the end.")
}
PrintSteps(steps)
path, _ := FindBestPath(steps, start, end)
fmt.Println(path)
}
测试数据如下:
6 5
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0
(全文完)