人工智能学习2(A*算法):如何使用A*算法解决实际题目(图中有障碍物情况下的最优路径:寻路算法抽象)(C++版本)

前言

A*算法的核心思想在于启发式搜索,解决问题的关键在于找到一种方式,能够使得遍历往更接近最终目标的方向上进行,这需要我们能够找到它的代价函数来辅助我们进行遍历。

图中有障碍物情况下的最优路径

问题描述

给定一个 n 行 m 列的矩阵 graph , 其中如果 graph[i][j] = 1 表示这里不能通过, 如果 graph[i][j] = 0 表示这里可以通过,从矩阵左上角开始遍历,你每次可以往上下左右这四个方向移动,请你返回到右下角的最短路径的节点顺序,返回其中一种即可,如果不能到达,返回空;

输入描述

1 <= m,n <= 10000;
graph[i][j] = 0 或 graph[i][j] = 1;

给出的代码

class Solution{
public:
	vector<int> FindMinPath(vector<vector<int>> &graph){
		//在这里书写你的代码
	}
}

注:暂时未找到这道题目在哪有,所以没有测试,实现不保证完全通过所有测试用例,仅仅提供思路

思路与分析

图的遍历我们使用 open 表来记录未遍历的节点, 使用 close 表记录已经遍历的节点。

如果使用 A*算法 来求解这一道问题,我们需要明确它的实际代价(Actual Cost) g(i, j)启发式代价(Heuristic Cost) h(i, j)

这里它的实际代价 g(i, j) 为从 (0,0) 开始到节点 (i, j)实际路径长度;启发式代价 h(i, j) 为节点 (i,j) 到 节点 (n-1, m-1)曼哈顿距离

曼哈顿距离是指两个点之间沿着坐标轴方向的总距离,也叫城市街区距离,因为它类似于城市中按街道和大道的网格形式行驶的距离。

定义:在二维空间中,两个点 ( P(x_1, y_1) ) 和 ( Q(x_2, y_2) ) 之间的曼哈顿距离为:
[ d ( P , Q ) = ∣ x 2 − x 1 ∣ + ∣ y 2 − y 1 ∣ ] [ d(P, Q) = |x_2 - x_1| + |y_2 - y_1| ] [d(P,Q)=x2x1+y2y1]
这种度量忽略了直接的直线距离,而是计算沿着坐标轴的距离。

代价函数:
f ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n) f(n)=g(n)+h(n)

首先对于一个正在访问的节点,它可以访问的下一个节点为 U (上面的节点), D (下面的节点),L (左面的节点),R (右面的节点),如果这个节点不是障碍物且这个节点合法,那么将这个节点加入到 open 表里。

每次选择 open 表中 f 最小的节点进行访问,这代表着我们有更大概率更快地找到目标节点。

当 open 表里为空或者找到了最后的节点,跳出循环。

最后通过 close 表来得到最后要求的路径。

代码实现

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <unordered_map>
#include <algorithm>

using namespace std;

class OpenNode{
public:
	int indexX;   //当前的节点号X
	int indexY;   //当前的节点号Y
	int parX;     //前驱节点X
	int parY;     //前驱节点Y
	int g;       //当前代价
	int h;       //未来代价
	
    OpenNode(int indexX, int indexY, int parX, int parY, int g, int h):indexX(indexX),indexY(indexY),parX(parX),parY(parY),g(g), h(h){};
	OpenNode(){}
	
    bool operator > (const OpenNode& node) const{
		int a = this->g + this->h;
		int b = node.g + node.h;
		return a==b?(this->h > node.h):a>b;
	}
	bool operator < (const OpenNode& node) const{
		int a = this->g + this->h;
		int b = node.g + node.h;
		return a==b?(this->h < node.h):a<b;
	}
	
	friend std::ostream& operator<<(ostream &os, const OpenNode& node);
};

std::ostream& operator<<(ostream &os, const OpenNode& node){
	os<<node.indexX<<" "<<node.indexY<<" "<<node.parX<<" "<<node.parY<<" "<<node.g<<" "<<node.h;
	return os;
}

class Solution{
public:
	vector<vector<int>> FindMinPath(vector<vector<int>> &graph){
		if(graph.size()==0 && graph[0].size()==0) return {};
		vector<vector<int>> ans;                                             //存储最终的路径
		priority_queue<OpenNode, vector<OpenNode>, std::greater<OpenNode>> openTable;                //open表 存储未遍历的节点
		unordered_map<int, OpenNode> closeTable;                             //close表 存储已经遍历的节点, key = (indexX<<16) + indexY;
		vector<pair<int,int>> direction = {{-1,0},{0,1},{1,0},{0,-1}};       //存储遍历的方向
		
		int n = graph.size(), m = graph[0].size();
		
		OpenNode start(0,0,-1,-1,0,n+m-1);                                   //左上角的节点
		openTable.push(start);
		
		while(!openTable.empty()){
			OpenNode curNode = openTable.top();                              //取出总代价最小的节点
			openTable.pop();
			if(closeTable.count((curNode.indexX<<16)+ curNode.indexY)) continue;
			closeTable[((curNode.indexX)<<16) + curNode.indexY] = curNode;   //将当前节点加入到Close表中
			if(curNode.indexX == n-1 && curNode.indexY == m-1) break;        //找到了最后的节点
			for(const auto &dir: direction){
				int i=curNode.indexX + dir.first;
				int j=curNode.indexY + dir.second;
				if(i<0 || j<0 || i>=n || j>=m) continue;
				if(graph[i][j] == 1) continue;                                //如果遇到了障碍物,不扩展该节点
				if(closeTable.count((i<<16) + j)==0){
					OpenNode nextNode(i,j,curNode.indexX,curNode.indexY,curNode.g+1,m+n-i-j-1);
					openTable.push(nextNode);
				} 
			}
		}
		
		//根据close表反向得到路径
		if(closeTable.count(((n-1)<<16) + m-1)==0) return {};                  //未遍历到最后一个节点
		OpenNode curNode = closeTable[((n-1)<<16)+ m-1];
		while(curNode.indexX!=0 || curNode.indexY!=0){
			vector<int> vec(2);
			vec[0] = curNode.indexX;
			vec[1] = curNode.indexY;
			ans.emplace_back(vec);
			curNode = closeTable[(curNode.parX<<16) + curNode.parY];
		}
		vector<int> startPos(2);
		ans.emplace_back(startPos);
		reverse(ans.begin(), ans.end());
		return ans;
	}
};

int main() {
	vector<vector<int>> graph = {
		{0,0,0,0,0,0},
		{0,0,1,0,1,1},
		{0,0,1,0,1,0},
		{0,1,0,0,0,0},
		{0,0,0,0,0,0}
	};
	int rs = 10;
	Solution solution;
	vector<vector<int>> path = solution.FindMinPath(graph);
	for(auto node:path){
		cout<<"("<<node[0]<<","<<node[1]<<")"<<"->";
	}
	cout<<"End Search"<<endl;
}

得到结果

在这里插入图片描述

其他题目链接

洛谷 P1379 八数码难题

备注

该文仅提供一种写法思路,未证明其正确性,如有错误和疏漏,敬请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值