A*寻路算法之解决路径多拐点问题

2023-05-16

1.问题描述

最近公司正在开发的游戏涉及到了寻路算法,然后我从网上找了一份A*算法代码,整理了一下写了一个A*算法基础实现。然而,在真正实用时A*寻路时,却发现了几个问题:

  1. 基础实现版的A*寻路算法在大地图的搜索上,耗时较长;

  2. 使用最小堆实现的OpenList来优化A*算法后,发现最后得到的路径往往是S型的;

然后策划看到效果后,提出了两点要求:1)寻路的路径中,拐点必须最少;2)存在多条路径时,必须优先走最快碰到拐点的路径。

稍微解释一下上面的两个需求:假如出发点和目的点是"日"字型时,可经过上中下三条路径到达目的地。上下两条路径的拐点是1个,而中间路径的拐点是2个,淘汰中间路径。另外,上路走一步碰到拐点,而下路走两步碰到拐点,那么优先选择上路。

1

2. A*算法基本实现及优化

假设看这篇文章的朋友,都是已经了解A*算法的。如果还不了解A*算法,可以先百度一下,网上介绍A*算法的文章很多。

从网上找了一个A*算法的JAVA版实现,经过整理后实现如下:

public class AStar {

	/** 地图 */
	private GameMap map;
	private ArrayList<Node> openList = new ArrayList<>();
	private ArrayList<Node> closeList = new ArrayList<>();

	public AStar(GameMap map) {
		this.map = map;
	}
	
	public Node findPath(Node startNode, Node endNode) {
		// 把起点加入 open list
		openList.add(startNode);
		
		while (openList.size() > 0) {
			// 遍历 open list ,查找 F值最小的节点,把它作为当前要处理的节点
			Node currNode = findMinFNodeInOpenList();
			// 从open list中移除
			openList.remove(currNode);
			// 把这个节点移到 close list
			closeList.add(currNode);
			
			// 查找最小FCost的节点
			ArrayList<Node> neighborNodes = findNeighborNodes(currNode);
			for (Node nextNode : neighborNodes) {
				int gCost = calcNodeCost(currNode, nextNode) + currNode.gCost;
				if (exists(openList, nextNode)) {
					// 如果新的路径fCost更小,更新nextNode节点
					if (gCost < nextNode.gCost) {
						nextNode.parent = currNode;
						nextNode.gCost = gCost;
						nextNode.fCost = nextNode.gCost + nextNode.hCost;
					}
				} else {
					// 计算nextNode节点的fCost
					nextNode.parent = currNode;
					nextNode.gCost = gCost;
					nextNode.hCost = calcNodeCost(nextNode, endNode);
					nextNode.fCost = nextNode.gCost + nextNode.hCost;
					openList.add(nextNode);
				}
			}
			
			Node node = find(openList, endNode);
			if (node != null) {
				return node;
			}
		}

		return find(openList, endNode);
	}
	
	public Node findMinFNodeInOpenList() {
		Node tempNode = openList.get(0);
		for (Node node : openList) {
			if (node.fCost < tempNode.fCost) {
				tempNode = node;
			}
		}
		return tempNode;
	}

	public ArrayList<Node> findNeighborNodes(Node currentNode) {
		ArrayList<Node> arrayList = new ArrayList<Node>();
		addNode(arrayList, currentNode.x, currentNode.y - 1);
		addNode(arrayList, currentNode.x, currentNode.y + 1);
		addNode(arrayList, currentNode.x - 1, currentNode.y);
		addNode(arrayList, currentNode.x + 1, currentNode.y);
		return arrayList;
	}

	private void addNode(ArrayList<Node> arrayList, int x, int y) {
		if (map.canReach(x, y) && !exists(closeList, x, y)) {
			arrayList.add(new Node(x, y));
		}
	}
	
	private int calcNodeCost(Node node1, Node node2) {
		return abs(node2.x - node1.x) + abs(node2.y - node1.y);
	}
	
	public int abs(final int x) {
		final int i = x >>> 31;
		return (x ^ (~i + 1)) + i;
	}

	public static Node find(List<Node> nodes, Node point) {
		for (Node n : nodes)
			if ((n.x == point.x) && (n.y == point.y)) {
				return n;
			}
		return null;
	}
	
	public static boolean exists(List<Node> nodes, Node node) {
		for (Node n : nodes) {
			if ((n.x == node.x) && (n.y == node.y)) {
				return true;
			}
		}
		return false;
	}

	public static boolean exists(List<Node> nodes, int x, int y) {
		for (Node n : nodes) {
			if ((n.x == x) && (n.y == y)) {
				return true;
			}
		}
		return false;
	}
}

上面的代码实现了A*算法的逻辑,但是在200*200的地图上测试时,从(0,0)点走到(199,199)寻路一次通常要几秒到十几秒。

那么,我们先分析一下代码的性能为什么那么低。主要有两点:

1)  findMinFNodeInOpenList() 方法需要遍历 open list ,查找 F值最小的节点,该方法的时间复杂度是O(n)。虽然该方法时间复杂度是线性的,但是每次检查一个节点时,都会执行一次该方法。

2) 添加新节点或者更新下一个节点时,都会调用一次exists()方法判断节点是否在openList或者closeList中。该方法同样是线性的,但是添加新节点或者更新下一个节点时,总会调用一次或多次,因此时间复杂度是m*O(n)。

针对上面两个痛点进行优化:首先,在一个队列中获取获取最大/最小的元素,我们通常首先想到的就是最大/小堆,因此,利用优先队列PriorityQueue实现openList,那么每次从openList中查找 最小F值的节点时,时间复杂度将降为O(lg n)。其次,分析上下文发现,closeList仅在添加节点到openList时做去重判断,而没有其他作用,那么可以通过数组标记或者Map存储遍历信息,使时间复杂度达到O(1)。以下是优化后的代码:

public class AStarOptimization {
	
	/** 地图 */
	private GameMap map;
	
	private PriorityQueue<Node> newOpenList = new PriorityQueue<>(new Comparator<Node>() {

		@Override
		public int compare(Node o1, Node o2) {
			return o1.fCost - o2.fCost;
		}
		
	});
	
	private Set<String> openSet = new HashSet<>();
	private Set<String> closeSet = new HashSet<>();
	
	public AStarOptimization(GameMap map) {
		this.map = map;
	}
	
	public List<Node> findPath() {
		return findPath(map.getStartNode(), map.getEndNode());
	}
	
	public List<Node> findPath(Node startNode, Node endNode) {
		newOpenList.add(startNode);

		Node currNode = null;
		while ((currNode = newOpenList.poll()) != null) {
			removeKey(openSet, currNode.x, currNode.y);
			addKey(closeSet, currNode.x, currNode.y);

			ArrayList<Node> neighborNodes = findNeighborNodes(currNode);
			for (Node nextNode : neighborNodes) {
				int gCost = calcNodeCost(currNode, nextNode) + currNode.gCost;
				if (contains(openSet, nextNode.x, nextNode.y)) {
					if (gCost < nextNode.gCost) {
						nextNode.parent = currNode;
						nextNode.gCost = gCost;
						nextNode.fCost = nextNode.gCost + nextNode.hCost;
					}
				} else {
					nextNode.parent = currNode;
					nextNode.gCost = gCost;
					nextNode.hCost = calcNodeCost(nextNode, endNode);
					nextNode.fCost = nextNode.gCost + nextNode.hCost;
					newOpenList.add(nextNode);
					
					addKey(openSet, nextNode.x, nextNode.y);
				}
			}
			
			if (contains(openSet, endNode.x, endNode.y)) {
				Node node = findOpenList(newOpenList, endNode);
				return getPathList(node);
			}
		}

		Node node = findOpenList(newOpenList, endNode);
		return getPathList(node);
	}
	
	public ArrayList<Node> findNeighborNodes(Node currentNode) {
		ArrayList<Node> arrayList = new ArrayList<Node>();
		addNode(arrayList, currentNode.x, currentNode.y - 1);
		addNode(arrayList, currentNode.x, currentNode.y + 1);
		addNode(arrayList, currentNode.x - 1, currentNode.y);
		addNode(arrayList, currentNode.x + 1, currentNode.y);
		return arrayList;
	}

	private void addNode(ArrayList<Node> arrayList, int x, int y) {
		if (map.canReach(x, y) && !contains(closeSet, x, y)) {
			arrayList.add(new Node(x, y));
		}
	}

	private int calcNodeCost(Node node1, Node node2) {
		return abs(node2.x - node1.x) + abs(node2.y - node1.y);
	}
	
	private Node findOpenList(PriorityQueue<Node> nodes, Node point) {
		for (Node n : nodes) {
			if ((n.x == point.x) && (n.y == point.y)) {
				return n;
			}
		}
		return null;
	}
	
	public List<Node> getPathList(Node parent) {
		List<Node> list = new ArrayList<>();
		while (parent != null) {
			list.add(new Node(parent.x, parent.y));
			parent = parent.parent;
		}
		return list;
	}
	
	public int abs(final int x) {
		final int i = x >>> 31;
        return (x ^ (~i + 1)) + i;
	}
	
	private void addKey(Set<String> set, int x, int y) {
		set.add(getKey(x, y));
	}
	
	private void removeKey(Set<String> set, int x, int y) {
		set.remove(getKey(x, y));
	}
	
	private boolean contains(Set<String> set, int x, int y) {
		return set.contains(getKey(x, y));
	}
	
	private String getKey(int x, int y) {
		StringBuilder sb = new StringBuilder();
		sb.append(x).append('_').append(y);
		return sb.toString();
	}
	
}

优化后的代码在200*200的地图上测试,在障碍物比例为0.3时(200 * 200 * 0.3),从(0,0)点走到(199,199)寻路一次基本在30ms~50ms。

3. 多拐点问题

由于基于最小堆实现的优先队列是不稳定,在多个具有相同F值的节点中选择最小节点的顺序是无法保证的。因此,以上优化后的代码走出来的路径通常都是S型的,也就是存在拐点过多的问题。

是否使用基础版本的A*算法是否可以消除多拐点的问题呢?答案是否定的,基础版本的A*算法是按照一定的方向顺序添加节点,在相同F值时,获取节点也是按照相同方向顺序获取节点,这在空旷的地形中是不会出现S型路径。但是,当路径上有障碍物时,按照顺序添加节点然后按照顺序取节点的方法,就会出现走S型路径的问题。

例如,如图所示情形,当从A点出发移动到B, A*算法按照上下左右的方向顺序添加节点,A从绿色路径移动到B;相反,如果从B移动到A点,A*算法会向下走到A点,但是沿途有障碍物,于是就形成了S型路径。

2 

那么,基于最小堆实现的openList,如何杜绝路径多拐点呢?路径是算法执行过程中动态生成,生成或有路径去选择一条是不可能。最好的办法是,根据路径的特点调整路径上拐点的F值。

A*算法是利用启发函数:F(n) = G(n) + H(n),来确定每个点的F值,采用贪心策略选择F值最小的点作为下一个待更新节点,直到找到终点。其中G(n)表示从起始点走到该点的代价,H(n)估算从该点走到终点的代价。通常G(n)采用走到该点所用的步数来表示,而H(n)使用该点到终点的距离来表示。

从启发函数来看,A*算法对于路径的特点根本没有做任何要求,只要是最小F值的节点都可以加入路径当中。因此,我们可以在启发函数中加入对节点的路径特征的评判,使算法选择的最终结果符合我们的预期。基于此目的,修改启发函数:

F(n) = G(n) + H(n) + E(n)

其中E(n)表示加入该节点后,对路径的评分进行的微调。话不多说,先上代码:

	public List<Node> findPath(Node startNode, Node endNode) {
		newOpenList.add(startNode);

		Node currNode = null;
		while ((currNode = newOpenList.poll()) != null) {
			removeKey(openSet, currNode.x, currNode.y);
			addKey(closeSet, currNode.x, currNode.y);

			ArrayList<Node> neighborNodes = findNeighborNodes(currNode);
			for (Node nextNode : neighborNodes) {
				// G + H + E
				int gCost = 10 * calcNodeCost(currNode, nextNode) + currNode.gCost 
						+ calcNodeExtraCost(currNode, nextNode, endNode);
				if (contains(openSet, nextNode.x, nextNode.y)) {
					if (gCost < nextNode.gCost) {
						nextNode.parent = currNode;
						nextNode.gCost = gCost;
						nextNode.fCost = nextNode.gCost + nextNode.hCost;
					}
				} else {
					nextNode.parent = currNode;
					nextNode.gCost = gCost;
					nextNode.hCost = 10 * calcNodeCost(nextNode, endNode);
					nextNode.fCost = nextNode.gCost + nextNode.hCost;
					newOpenList.add(nextNode);
					
					addKey(openSet, nextNode.x, nextNode.y);
				}
			}
			
			if (contains(openSet, endNode.x, endNode.y)) {
				Node node = findOpenList(newOpenList, endNode);
				return getPathList(node);
			}
		}

		Node node = findOpenList(newOpenList, endNode);
		return getPathList(node);
	}
	
	private int calcNodeExtraCost(Node currNode, Node nextNode, Node endNode) {
		// 第一个点或直线点
		if (currNode.parent == null || nextNode.x == currNode.parent.x 
				|| nextNode.y == currNode.parent.y) {
			return 0;
		}
		
		// 拐向终点的点
		if (nextNode.x == endNode.x || nextNode.y == endNode.y) {
			return 1;
		}
		
		// 普通拐点
		return 2;
	}

代码的终点是calcNodeExtraCost()方法,方法中判断如果nextNode和之前的节点是保持直线的,那么E值为0,否则如果是一个拐点的话,E值将大于0,并且这个E值会和G值存在一起,作为新的G值。

A*路径多拐点的问题暂时写到这里了,之后再补上后面的内容。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

A*寻路算法之解决路径多拐点问题 的相关文章

  • open 函数的 flag 参数和错误代码

    一 flag 参数 定义头文件 xff1a lt bits fcntl linux h gt 必选参数说明 xff1a define O ACCMODE 0003 xff1a 读写文件操作时取出 flag 的低两位 define O RDO
  • 系统全局变量 errno 是如何获得 errno.h 中的值的呢?

    很多时候我们在使用 errno 的时候都知道它代表的是 errno h 中的错误值 xff0c 可是为什么它就是代表那些值的呢 xff1f 系统在哪里给它赋值了呢 xff1f 故事就要从源头开始 1 errno 全局变量是在哪里定义的 xf
  • const pointer

    int a b const int p 61 a 与int const p 61 a 是一样的 表示p可以指向a xff0c 也可以改变指向b xff0c 但是不能通过指针p来修改a的值 p 61 b p 61 4 int const q
  • I_O —基础概念(参照 Ubuntu 16.04 版本)

    一 文件的概念 定义 xff1a 所谓文件是指一组相关数据的有序集合 xff0c 这个数据集有一个名称 xff0c 叫文件名 如源程序文件 xff0c 目标文件 xff0c 可执行文件 xff0c 头文件等文件通常是在驻留在外部介质上的 x
  • 网络通信2—UDP 模型程序编写步骤(参照 Ubuntu 16.04 版本)

    UDP 模型程序编写步骤 一 UDP基础模型 服务器流程 step 1 xff1a 创建 socket 套接字接口并判断 sockfd 61 socket AF INET SOCK DGRAM 0 if sockfd 61 61 1 per
  • switch 中 break 和 continue 的区别

    1 break 用来退出 switch xff0c continue 本身是不能用在 switch 里的 xff0c 他必须结合循环来用 xff0c 表示跳过本次循环 2 switch 的 case 语句最后如果没有加 break cont
  • 立即数

    一 概念 xff1a 通常把在 立即寻址方式 指令中给出的数称为立即数 二 判断步骤 xff1a 把数据转换成二进制 xff0c 从低到高写成 4 个一组 xff0c 最高位不够一组的补 0 xff1b 数 1 的个数 xff0c 如果大于
  • 位、字节、char、int(32位系统) 之间的关系

    一 概念 xff1a 位 xff08 bit xff09 xff1a 计算机中最小的数据单位 每一位的状态只能是0或1 字节 xff08 byte xff09 xff1a 存储空间的基本计量单位 xff0c 8 个二进制位构成1个字节 1
  • C语言中的那些宏

    DATE 进行预处理的日期 xff08 Mmm dd yyyy 形式的字符串文字 xff09 FILE 代表当前源代码文件名的字符串文字 LINE 代表当前源代码中的行号的整数常量 TIME 源文件编译时间 xff0c 格式微 hh xff
  • 任务栈简单入门

    最近又把两本进阶书看了一遍 xff0c 但总感觉好记性不如烂笔头 xff0c 所以还是决定通过博客记录一下 xff0c 我们将分两篇来全面深入地记录Activity 启动模式与任务栈的内容 android任务栈简单了解 1 android任
  • VS2010里函数枚举

    一 cout函数 说明 xff1a 调用该函数必须申明头文件 include lt iostream gt 同时声明后面必须使用 using namespace std 正确书写为 xff1a include lt iostream gt
  • I_O—标准 I_O 实验

    一 测试标准 I O 一次可以同时打开多少个文件 1 实验思路 xff1a 利用循环同时打开文件 xff0c 直到不能打开 2 代码如下 xff1a 二 fgetc 和 fputc 实现拷贝文件并输出文件行数 1 实验思路 xff1a 打开
  • Source Insight 配色方案

    Source Insight 对于程序员来说应该不陌生 xff0c 当然一个个性化的编程界面也会让自己赏析悦目 xff0c 下面就将个人的界面设置分享一下 xff1a 一 背景色设置 1 选择 Options Preferences 2 选
  • Linux 网络——交换机不能用两根网线相连

    同一个局域网所有的交换机之间可以用网线串联起来 xff0c 但绝对不能使任意 gt 61 2个交换机形成环路 xff0c 否则局域网内将形成广播风暴 xff0c 所用局域网内的用户都将不能上网 例如局域网内的交换机可以使用如下相连 xff1
  • GDB 知识点——基础操作

    Linux C 中的 GDB 调试使用 xff1a 1 GDB 的主要功能 xff1a 1 启动被调试程序 2 让被调试的程序在指定的位置停住 3 当程序被停住时 xff0c 可以检查程序状态 xff08 如变量的值 xff09 2 检查
  • 员工管理系统(C 语言)——项目说明

    项目名称 xff1a 员工管理系统 项目目的 xff1a 1 实现简单的公司对员工信息的管理 2 通过项目锻炼实现逻辑转换为代码的能力 3 利用函数封装实现项目过程中的逻辑过程以及需求功能的实现 4 学会数据库的操作以及网络通信 5 强化代
  • 员工管理系统(C 语言)——客户端解析

    源码下载地址 xff1a https download csdn net download wenfei11471 10477504 客户端功能 xff1a 1 运行时先测试是否能连通服务器 xff08 不畅通如下图所示 xff09 xff
  • 员工管理系统(C 语言)——服务器解析

    源码下载地址 xff1a https download csdn net download wenfei11471 10477504 服务器功能 xff1a 1 运行时主界面 xff08 服务器启动后 xff0c 只有管理员下线 xff0c
  • 排序——选择排序、冒泡排序和快速排序比较

    一 选择排序思路 xff1a 1 以 int 类型为例 2 拿第一个数与后面数相比较 xff0c 如果比后面的数大则交换 3 拿第二个数与后面的数比较 xff0c 如果比后面的数大则交换 4 直到比较到倒数第二个数 xff0c 最后一个数不
  • C 语言中 const 与指针的结合使用

    请区分一下几种指针的区别 1 const int p 2 int const p 3 int const p 4 const int const p 5 const int const p 解析 xff1a 1 const int p 中

随机推荐

  • Ubuntu16.04上安装百度网盘后打不开

    现在百度网盘推出了Linux版本 xff0c 也有Ubuntu下安装的deb文件 xff0c 但是我在Ubuntu上安装后却打不开 xff0c 报错 baidunetdisk crashed with SIGABRT in gnu cxx
  • C/C++的“文件包含”处理时头文件被重复包含的问题探究及解决方法(用最简单的例子进行说明)

    这篇博文是博文https blog csdn net wenhao ir article details 125668051的配套博文 头文件被重复包含是下面这样的现象 xff1a A文件里包含了C文件 xff0c B文件里也包含了C文件
  • BIN,BCD,ASCII码分别对应的Hex(16进制)数

    BIN BCD ASCII码分别对应的Hex xff08 16进制 xff09 数 以十进制的 56 为例 BIN 码 对应二进制数为 0011 1000对应Hex数据为 0x38BIN码就是二进制数 xff1b 压缩BCD 码 对应二进制
  • .LDS 文件详解

    最近在研究uboot xff0c 红色部分为我加上的注解 转载地址 xff1a http blog chinaunix net space php uid 61 23373524 amp do 61 blog amp cuid 61 232
  • 13 select的优化一

    1 上个例子中 xff0c select通过for循环轮询client套接字 xff0c 轮询的范围比较大 xff0c 有优化的地方 2 优化代码 xff1a 通过数组存储client的套接字 xff0c 达到少轮询的效果 xff0c 可以
  • 二.手写迷你版Tomcat-minicat2.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0需求 xff1a 封装Request和Response对象 xff0c 返回html静态资源文件 封装Request对象 想要封
  • 三.手写迷你版Tomcat-minicat3.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0封装Request和Response对象 xff0c 返回html静态资源文件 minicat 3 0需求 xff1a 请求se
  • python爬取全国五级行政区

    以前爬过国家统计局的四级行政区 xff08 http www stats gov cn tjsj tjbz tjyqhdmhcxhfdm 2017 xff09 xff0c 但是对于五级数据效果不是很好 偶然间发现这个网站 xff1a htt
  • ElasticSearch使用elasticsearchTemplate聚合查询

    这两天正好做个需求 xff0c 需要用到聚合查询 前几篇文章只是简单的提到过 xff0c 并没有真正的运用到实际产出中 xff0c 本篇结合实际代码 xff0c 专项学习ES的聚合查询 1 业务背景 有一张地址索引表 xff1a hisAd
  • Java字节码

    Java最黑科技的玩法就是字节码编程 xff0c 也就是动态修改或是动态生成 Java 字节码 使用字节码可以玩出很多高级的玩法 xff0c 最高级的还是在 Java 程序运行时进行字节码修改和代码注入 听起来是不是一些很黑客 xff0c
  • TCP/IP (一) accept建立连接

    七层网络协议 三次握手 四次分手 xff0c 这些大家都比较熟知 xff0c 这里主要是带着一些问题来思考整个TCP IP流程 1 三次握手的具体流程是怎么样的 xff1f 2 socket编程中int listen int fd int
  • http 的认证模式

    周海汉 2006 7 11 ablozhou 64 gmail com SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Base模式和摘要模式 xff08 Digest sche
  • Java Agent

    在 Java 字节码 一文中有提到 xff0c 使用 Java Agent 操控字节码 xff0c 本文将讨论 Java Agent xff0c 这是普通 Java 开发人员的真正的黑魔法 Java Agent 能够通过执行字节码的直接修改
  • 通过gitlab远程统计git代码量

    git的代码量大多数都是根据命令行统计 xff0c 或者根据第三方插件统计 但是都不满足我的需求 xff0c 因为我们代码都由gitlab管理 xff0c 于是想到了通过gitlab暴露出来的接口获取数据 第一步 xff0c 生成私钥 登录
  • Qt第二十二章:将控件放到另一个控件的后面或前面

    话不多说 xff1a 看图
  • 缓存行填充与@sun.misc.Contended注解

    1 缓存模型 CPU和主内存之间有好几层缓存 xff0c 因为与cpu的速度相比 xff0c 访问主内存的速度是非常慢的 如果频繁对同一个数据做运算 xff0c 每次都从内存中加载 xff0c 运算完之后再写回到主内存中 xff0c 将会严
  • ThreadLocal那点事

    目录 1 ThreadLocal原理 2 ThreadLocal内存泄漏 3 ThreadLocal最佳实践 4 FastThreadLocal原理 5 FastThreadLocal最佳实践 6 ThreadLocal与FastThrea
  • 关于雪花算法的设计与思考

    2017年的时候项目组在开发一款大区游戏 xff0c 由于之前demo阶段的玩家id都是单服生成的 xff0c 只能保证单进程中的唯一 xff0c 而无法保证在分布式服务器端的唯一性 随着项目的开发进展 xff0c 需要设计能保证在分布式的
  • java反射之Method的invoke方法实现

    在框架中经常会会用到method invoke 方法 xff0c 用来执行某个的对象的目标方法 以前写代码用到反射时 xff0c 总是获取先获取Method xff0c 然后传入对应的Class实例对象执行方法 然而前段时间研究invoke
  • A*寻路算法之解决路径多拐点问题

    1 问题描述 最近公司正在开发的游戏涉及到了寻路算法 xff0c 然后我从网上找了一份A 算法代码 xff0c 整理了一下写了一个A 算法基础实现 然而 xff0c 在真正实用时A 寻路时 xff0c 却发现了几个问题 xff1a 基础实现