走迷宫问题(启发式搜索)

520 阅读3分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

最近刷到过许多迷宫问题,大多数都可以用深度优先遍历或者广度优先遍历来做。偶然间看到了启发式搜索。用启发式搜索来能做迷宫问题可以大大提高搜索效率。

启发式搜索

这里对启发式搜索做一个简单的介绍,需要更深的了解大家可以网上自行查找。对于深度优先搜索,就是把所有的情况全部列出来然后得出答案。而启发式搜索是可以回溯的一个算法,觉得这一条路走下去可能并不值得,于是会换一条路,如果走其他的走下去也不行,那么可能就再接着走原来的路。大大提高了搜索的效率。启发式搜索需要两个要点。一个是当前代价,也就是从原起点走到现在这个位子花费了多少。另一个是未来代价,从现在走起,走到终点可能需要花费多少代价。将这个两个代价加在一起为总代价。我们选择总代价最小的点走下去。每走一步,都要通过总代价的大小来判断是否值得走下去。

当前代价

从原起点开始,每走一步,当前代价加一。

未来代价

通过选取合适的代价函数来计算出未来代价。

曼哈顿距离

如果迷宫内只能上下左右四个方向移动,那么可以选取曼哈顿距离来计算未来代价。

欧几里得距离

如果迷宫内可以选择任意方向移动,那么可以选取欧几里得距离来计算未来代价。这样话可能会产生小数,可能会影响最优点选取。

迷宫实例

给定一个正方形迷宫,迷宫中存在障碍,无障碍的点记为0,有障碍的点记为1。起点为s,终点为e。

迷宫准备

#include<bits/stdc++.h>
using namespace std;
int R,C; //迷宫的长和宽
int Load[100][100]; //迷宫规模
int Loaded[100][100];//标记迷宫已经走过的点
int Loadend[100][100];//最终迷宫所有扩展了的路径
int S1,S2;//起始位子
int End1,End2//最终位子
struct load{
    int x;
    int y;
    int cost;//总代价
    int S;//用于计算到原起点所花代价。
    //总代价大小的判断	
    bool operator  < (const load &p)const 
    {
        return cost > p.cost;
    }
    load(int x1,int y1,int S1){
        x=x1;
        y=y1;
        S=S1;
        cost=Cost()+S;
    }
    int Cost(){//计算预估代价
        int i=abs(x-End1);
        int j=abs(y-End2);
        int k=i+j;
        return k;
    }
}

走迷宫所需要函数

int end(load a)//判断是否已经走到终点
{
    if(a.x==End1&&a.y==End2)
       return 1;
    else
        return 0;
}
int move(int x){
    if(x>=0&&x<=R)//判断到达的这个点是否可以移动
        return 1;
    else
        return -1;
}
priority_queue<load> fit;//使用优先队列存放符合条件的点。
int Fload(){
//将起始点放入到这列表中
    load des(End1, End2, 0);
    load start(S1, S2, 1);
    fit.push(start);//将点压入到队列中
    Loaded[S1][S2] = 1;//标记
    while(!fit.empty())   
    {
    //取出一位
        load T = fit.top();
        fit.pop();
        if (end(T))  return T.S;
        Loadend[T.x][T.y] = 1;//标记走过的路
        Loaded[T.x][T.y] = 1; 
        if ((move(T.x - 1)!=-1)&&(Load[T.x-1][T.y]==0))      //上移
        {
            load up(T.x - 1,T.y, T.S + 1);
            if (end(up)) return T.S+1;
            if (Loaded[up.x][up.y] == 0){ 
                fit.push(up);
            } 
         }
        if ((move(T.x +1)!=-1)&&(Load[T.x+1][T.y]==0))      //上移
        {
            load down(T.x + 1,T.y, T.S + 1);
            if (end(down)) return T.S+1;
            if (Loaded[down.x][down.y] == 0){ 
                fit.push(down);
            } 
         }
        if ((move(T.y - 1)!=-1)&&(Load[T.x][T.y-1]==0))      //左移
        {
            load left(T.x ,T.y-1, T.S + 1);
            if (end(left)) return T.S+1;
            if (Loaded[left.x][left.y] == 0){ 
                fit.push(left);
            } 
         }
        if ((move(T.y+1)!=-1)&&(Load[T.x][T.y+1]==0))      //右移
        {
            load right(T.x ,T.y+1, T.S + 1);
            if (end(right)) return T.S+1;
            if (Loaded[right.x][right.y] == 0){ 
                fit.push(right);
            } 
         }
    }
    return -1;
}

测试

int main(){
    cin>>R;
    C=R;
    for(int i=0;i<R;i++){
        for (int j = 0; j < R; j++){
            char a;
            cin>>a;
            if(a=='0')
                Load[i][j]=0;
            else if(a=='1')
                Load[i][j]=1;
            else if(a=='s'){
                S1=i,S2=j;
                Load[i][j]=0;
            }
            else if(matrii[i][j]=='e'){
                End1=i,End2=j;
                Load[i][j]=0;
            }
        }
    }
    int n=Fload();
    Load[End1][End2]=1;
    cout<<"需要"<<n-1<<"步"<<endl;//起点的第一步不算
    for(int i=0;i<R;i++){
        for(int j=0;j<R;j++){
            cout<<Loadend[i][j]<<' ';
        }
         cout<<endl;
    }
    return 0;
}

一个6*6的迷宫。

image.png 最后的图是所有扩展过的点,并不是最优路线。只能得出最短需要几步。