环境:Visual Studio 2017 + .Net Framework 4.5

应用场景:在画板上查找起始点和目标点之间的最短最直路径,最后画出连接两个点之间的折线。

算法简介:A*算法是一种性能较高的最优路径搜索算法,由Stanford Research Institute(now SRI International)的Peter Hart,Nils Nilsson和Bertram Raphael于1968年发表。A*算法可看做是对Dijkstra算法的扩展和优化,其性能一般情况下比Dijkstra算法快得多。在本文的应用场景中,(根据测试)通常比Dijkstra算法快三倍以上,甚至可能比Dijkstra算法快十几倍甚至几十倍。

A*算法的应用范围也比较广泛,如机器人行走路径规划,游戏中的NPC移动计算等。

更详细的算法说明请参考维基百科A* search algorithm

实现思想:

1,通过Locator把起始点坐标和目标点坐标对齐到步长(step,默认为20,)的整数倍。这样,起始点和目标点就成了原来的起始点目标点的近似点。

2,把包含起始点和目标点的障碍物(如图中所示,为矩形框)排除掉,不然折线遇到障碍物无法通过。

下图中的矩形框的虚边为避障区域,为了防止折线和障碍物碰撞。

3,把起始点添加到待遍历点的集合中(本文中为SortedList<Vertex>)。

4,从待遍历点的集合中取出第一个点(当前的最优点),遍历其东、南、西、北四个方向的相邻节点。东西两个方向和当前点的Y坐标相同,南北两个方向和当前点的X坐标相同。

相邻点距当前点的距离为step参数设定的步长。step越大,搜索速度越快,然而,可能导致折线无法通过间距较小的障碍物。

如果某个方向的相邻点不存在,则创建新的相邻点(如果相邻点不在障碍物内部的话);同时,设置新创建点的四个相邻点(也许新创建点的相邻点已经被创建了)。

把新创建的相邻点的Visited属性设置为false(当前实现中,Visited属性默认为false),然后对新创建点的所有相邻点排序,取最优点,设置为新创建点的前一个点(调用SetPrev方法)。

再把新创建的点添加到待遍历点的集合中(本文中为SortedList<Vertex>)。

当遍历完当前点的四个方向后,把当前点的Visited属性设置为true,并从带遍历点的集合中移除。

说明:当前算法的实现中仅考虑总距离(从起始点到当前点的距离加上猜测距离,起始点距离为0)、猜测距离(从当前点到目标点的距离,为从当前点到目标点的折线长度)和拐点个数(X或Y坐标变化时拐点个数加1)。

5,递归第4步。要么找到和目标点坐标相同的点(即,找到了目标点),要么待遍历点的集合为空(即,无法找到通往目标点的路径)。

6,当找到通往目标点的路径之后,通过Straightener(调直器)调直路径,减少拐点。

7,处理起始点和目标点。用原来的起始点和目标点替换坐标对齐到step整数倍的起始点和目标点,并调直其相邻拐点的X坐标或Y坐标。

8,返回最终路径。

如下两个图所示

第一张图为A*算法查找出来的最优路径(不一定是最短路径,依赖于算法的实现)

第二张图为调直后的最直路径(拐点最少)

代码

由于工程太大,仅上传必要的代码文件。

  1 using System;
2 using System.Collections.Generic;
3 using System.Drawing;
4
5 namespace Pathfinding
6 {
7 /// <summary>
8 /// A*算法
9 /// </summary>
10 public class AStarAlgorithm
11 {
12 private Vertex m_goal;
13 private Locator m_locator;
14 private SortedList<Vertex> m_openSet;
15 private Orientation m_orientation;
16 private Vertex m_start;
17 /// <summary>
18 /// 查找最优路径
19 /// </summary>
20 /// <param name="canvas">画布</param>
21 /// <param name="obstacles">障碍物</param>
22 /// <param name="step">步长</param>
23 /// <param name="voDistance">避障距离</param>
24 /// <param name="initOrient">第一层查找的方向</param>
25 /// <param name="start">起始点</param>
26 /// <param name="goal">目标点</param>
27 /// <returns></returns>
28 public Point[] Find(Rectangle canvas, List<Rectangle> obstacles, int step, int voDistance, Orientation initOrient, Point start, Point goal)
29 {
30 if (start == goal)
31 return null;
32
33 if (start.GetDistanceTo(goal) < step)
34 return this.ProcessShortPath(start, goal);
35
36 this.Init(canvas, obstacles, step, voDistance, initOrient, start, goal);
37 this.AddIntoOpenSet(this.m_start);
38
39 Vertex optimal = null;
40 while (this.m_openSet.Count > 0)
41 {
42 optimal = this.GetOptimalVertex();
43
44 if (this.IsGoal(optimal))
45 {
46 this.WalkTarget();
47 var path = Straightener.Straighten(this.m_locator, this.m_goal.Lines);
48 this.ProcessEndpoint(start, 0, path);
49 this.ProcessEndpoint(goal, path.Length - 1, path);
50
51 return path;
52 }
53
54 this.Walk(optimal);
55 }
56
57 return null;
58 }
59
60 /// <summary>
61 /// 添加待遍历的点
62 /// </summary>
63 /// <param name="vertex"></param>
64 private void AddIntoOpenSet(Vertex vertex)
65 {
66 if (!vertex.IsVisited)
67 this.m_openSet.Add(vertex);
68 }
69
70 /// <summary>
71 /// 获取最优点
72 /// </summary>
73 /// <returns></returns>
74 private Vertex GetOptimalVertex()
75 {
76 var cheapest = this.m_openSet.TakeFirst();
77 cheapest.IsCurrent = true;
78
79 return cheapest;
80 }
81
82 /// <summary>
83 /// 估算到目标点的距离
84 /// </summary>
85 /// <param name="vertex"></param>
86 /// <returns></returns>
87 private int GuessDistanceToGoal(Vertex vertex)
88 {
89 return Math.Abs(vertex.X - this.m_goal.X) + Math.Abs(vertex.Y - this.m_goal.Y);
90 }
91
92 /// <summary>
93 /// 初始化数据
94 /// </summary>
95 /// <param name="canvas"></param>
96 /// <param name="obstacles"></param>
97 /// <param name="step"></param>
98 /// <param name="voDistance"></param>
99 /// <param name="initOrient"></param>
100 /// <param name="start"></param>
101 /// <param name="goal"></param>
102 private void Init(Rectangle canvas, List<Rectangle> obstacles, int step, int voDistance, Orientation initOrient, Point start, Point goal)
103 {
104 this.m_locator = new Locator(canvas, obstacles, step, voDistance);
105
106 this.m_locator.AlignPoint(ref start);
107 this.m_locator.AlignPoint(ref goal);
108 this.m_locator.ExcludeObstacles(start);
109 this.m_locator.ExcludeObstacles(goal);
110
111 this.m_start = new Vertex()
112 {
113 Location = start
114 };
115 this.m_goal = new Vertex()
116 {
117 Location = goal
118 };
119 this.m_openSet = new SortedList<Vertex>();
120 this.m_start.GuessDistance = this.GuessDistanceToGoal(this.m_start);
121 this.m_orientation = initOrient;
122 }
123
124 /// <summary>
125 /// 是否是目标点
126 /// </summary>
127 /// <param name="vertex"></param>
128 /// <returns></returns>
129 private bool IsGoal(Vertex vertex)
130 {
131 if (vertex.Location == this.m_goal.Location)
132 {
133 this.m_goal = vertex;
134 return true;
135 }
136
137 return false;
138 }
139
140 /// <summary>
141 /// 处理端点(起始点或目标点)
142 /// </summary>
143 /// <param name="endpoint"></param>
144 /// <param name="idx"></param>
145 /// <param name="path"></param>
146 private void ProcessEndpoint(Point endpoint, int idx, Point[] path)
147 {
148 Point approximatePoint = path[idx];
149 if (0 == idx)
150 {
151 path[0] = endpoint;
152 idx += 1;
153 }
154 else
155 {
156 path[idx] = endpoint;
157 idx -= 1;
158 }
159
160 if (approximatePoint.X == path[idx].X)
161 path[idx].X = endpoint.X;
162 else
163 path[idx].Y = endpoint.Y;
164 }
165
166 /// <summary>
167 /// 处理短路径
168 /// </summary>
169 /// <param name="start"></param>
170 /// <param name="goal"></param>
171 /// <returns></returns>
172 private Point[] ProcessShortPath(Point start, Point goal)
173 {
174 var dx = Math.Abs(goal.X - start.X);
175 var dy = Math.Abs(goal.Y - start.Y);
176 if (dx >= dy)
177 return new Point[] { start, new Point(goal.X, start.Y), goal };
178 else
179 return new Point[] { start, new Point(start.X, goal.Y), goal };
180 }
181
182 /// <summary>
183 /// 设置前一个点
184 /// </summary>
185 /// <param name="vertex"></param>
186 private void SetPrev(Vertex vertex)
187 {
188 var neighbors = vertex.Neighbors;
189 neighbors.Sort();
190 vertex.SetPrev(neighbors[0]);
191 vertex.GuessDistance = this.GuessDistanceToGoal(vertex);
192 }
193
194
195 #region Traverse Neighbors
196
197 /// <summary>
198 /// 创建东边的相邻点
199 /// </summary>
200 /// <param name="vertex"></param>
201 private void CreateEastNeighbor(Vertex vertex)
202 {
203 var location = new Point(vertex.X + this.m_locator.Step, vertex.Y);
204 if (this.m_locator.AlignPoint(ref location)
205 && Orientation.East == vertex.Location.GetOrientation(location))
206 {
207 var neighbor = new Vertex()
208 {
209 Location = location
210 };
211
212 // ◐
213 // |
214 // ◐---●---○
215 // |
216 // ◐
217 vertex.EastNeighbor = neighbor;
218 // ◐---◐
219 // | |
220 // ◐---●---○
221 // |
222 // ◐
223 neighbor.NorthNeighbor = vertex.NorthNeighbor?.EastNeighbor;
224 // ◐
225 // |
226 // ◐---●---○
227 // | |
228 // ◐---◐
229 neighbor.SouthNeighbor = vertex.SouthNeighbor?.EastNeighbor;
230
231 this.SetPrev(neighbor);
232 this.AddIntoOpenSet(neighbor);
233 }
234 else
235 vertex.CouldWalkEast = false;
236 }
237
238 /// <summary>
239 /// 创建北边的相邻点
240 /// </summary>
241 /// <param name="vertex"></param>
242 private void CreateNorthNeighbor(Vertex vertex)
243 {
244 var location = new Point(vertex.X, vertex.Y - this.m_locator.Step);
245 if (this.m_locator.AlignPoint(ref location)
246 && Orientation.North == vertex.Location.GetOrientation(location))
247 {
248 var neighbor = new Vertex()
249 {
250 Location = location
251 };
252
253 // ○
254 // |
255 // ◐---●---◐
256 // |
257 // ◐
258 vertex.NorthNeighbor = neighbor;
259 // ○---◐
260 // | |
261 // ◐---●---◐
262 // |
263 // ◐
264 neighbor.EastNeighbor = vertex.EastNeighbor?.NorthNeighbor;
265 // ◐---○
266 // | |
267 // ◐---●---◐
268 // |
269 // ◐
270 neighbor.WestNeighbor = vertex.WestNeighbor?.NorthNeighbor;
271
272 this.SetPrev(neighbor);
273 this.AddIntoOpenSet(neighbor);
274 }
275 else
276 vertex.CouldWalkNorth = false;
277 }
278
279 /// <summary>
280 /// 创建南边的相邻点
281 /// </summary>
282 /// <param name="vertex"></param>
283 private void CreateSouthNeighbor(Vertex vertex)
284 {
285 var location = new Point(vertex.X, vertex.Y + this.m_locator.Step);
286 if (this.m_locator.AlignPoint(ref location)
287 && Orientation.South == vertex.Location.GetOrientation(location))
288 {
289 var neighbor = new Vertex()
290 {
291 Location = location
292 };
293
294 // ◐
295 // |
296 // ◐---●---◐
297 // |
298 // ○
299 vertex.SouthNeighbor = neighbor;
300 // ◐
301 // |
302 // ◐---●---◐
303 // | |
304 // ○---◐
305 neighbor.EastNeighbor = vertex.EastNeighbor?.SouthNeighbor;
306 // ◐
307 // |
308 // ◐---●---◐
309 // | |
310 // ◐---○
311 neighbor.WestNeighbor = vertex.WestNeighbor?.SouthNeighbor;
312
313 this.SetPrev(neighbor);
314 this.AddIntoOpenSet(neighbor);
315 }
316 else
317 vertex.CouldWalkSouth = false;
318 }
319
320 /// <summary>
321 /// 创建西边的相邻点
322 /// </summary>
323 /// <param name="vertex"></param>
324 private void CreateWestNeighbor(Vertex vertex)
325 {
326 var location = new Point(vertex.X - this.m_locator.Step, vertex.Y);
327 if (this.m_locator.AlignPoint(ref location)
328 && Orientation.West == vertex.Location.GetOrientation(location))
329 {
330 var neighbor = new Vertex()
331 {
332 Location = location
333 };
334
335 // ◐
336 // |
337 // ○---●---◐
338 // |
339 // ◐
340 vertex.WestNeighbor = neighbor;
341 // ◐
342 // |
343 // ○---●---◐
344 // | |
345 // ◐---◐
346 neighbor.SouthNeighbor = vertex.SouthNeighbor?.WestNeighbor;
347 // ◐---◐
348 // | |
349 // ○---●---◐
350 // |
351 // ◐
352 neighbor.NorthNeighbor = vertex.NorthNeighbor?.WestNeighbor;
353
354 this.SetPrev(neighbor);
355 this.AddIntoOpenSet(neighbor);
356 }
357 else
358 vertex.CouldWalkWest = false;
359 }
360
361 /// <summary>
362 /// <para>遍历四个方位的相邻点:东、南、西、北</para>
363 /// <para>●(实心圆)表示访问过的点</para>
364 /// <para>◐(半实心圆)表示可能访问过的点</para>
365 /// <para>○(空心圆)表示未访问过的点</para>
366 /// </summary>
367 /// <param name="vertex"></param>
368 private void Walk(Vertex vertex)
369 {
370 // ◐
371 // |
372 // ◐---●---◐
373 // |
374 // ◐
375
376 var count = 0;
377 while (count++ < 4)
378 {
379 switch (this.m_orientation)
380 {
381 case Orientation.East:
382 this.WalkEast(vertex);
383 this.m_orientation = Orientation.South;
384 break;
385 case Orientation.South:
386 this.WalkSouth(vertex);
387 this.m_orientation = Orientation.West;
388 break;
389 case Orientation.West:
390 this.WalkWest(vertex);
391 this.m_orientation = Orientation.North;
392 break;
393 case Orientation.North:
394 this.WalkNorth(vertex);
395 this.m_orientation = Orientation.East;
396 break;
397 default:
398 this.m_orientation = Orientation.East;
399 break;
400 }
401 }
402
403 vertex.IsVisited = true;
404 vertex.IsCurrent = false;
405 }
406
407 /// <summary>
408 /// 遍历东边的相邻点
409 /// </summary>
410 /// <param name="vertex"></param>
411 private void WalkEast(Vertex vertex)
412 {
413 if (vertex.CouldWalkEast && vertex.EastNeighbor is null)
414 this.CreateEastNeighbor(vertex);
415 }
416
417 /// <summary>
418 /// 遍历北边的相邻点
419 /// </summary>
420 /// <param name="vertex"></param>
421 private void WalkNorth(Vertex vertex)
422 {
423 if (vertex.CouldWalkNorth && vertex.NorthNeighbor is null)
424 this.CreateNorthNeighbor(vertex);
425 }
426
427 /// <summary>
428 /// 遍历南边的相邻点
429 /// </summary>
430 /// <param name="vertex"></param>
431 private void WalkSouth(Vertex vertex)
432 {
433 if (vertex.CouldWalkSouth && vertex.SouthNeighbor is null)
434 this.CreateSouthNeighbor(vertex);
435 }
436
437 /// <summary>
438 /// 遍历目标点
439 /// </summary>
440 private void WalkTarget()
441 {
442 // 遍历目标点及其相邻点
443 this.Walk(this.m_goal);
444
445 if (null != this.m_goal.EastNeighbor)
446 this.Walk(this.m_goal.EastNeighbor);
447 if (null != this.m_goal.SouthNeighbor)
448 this.Walk(this.m_goal.SouthNeighbor);
449 if (null != this.m_goal.WestNeighbor)
450 this.Walk(this.m_goal.WestNeighbor);
451 if (null != this.m_goal.NorthNeighbor)
452 this.Walk(this.m_goal.NorthNeighbor);
453
454 this.SetPrev(this.m_goal);
455 }
456
457 /// <summary>
458 /// 遍历西边的相邻点
459 /// </summary>
460 /// <param name="vertex"></param>
461 private void WalkWest(Vertex vertex)
462 {
463 if (vertex.CouldWalkWest && vertex.WestNeighbor is null)
464 this.CreateWestNeighbor(vertex);
465 }
466
467 #endregion Traverse Neighbors
468 }
469 }

AStarAlgorithm

using System.Collections.Generic;
using System.Drawing;
using System.Linq; namespace Pathfinding
{
/// <summary>
/// 定位器(用于避障,查找相邻点或者对齐坐标等)
/// </summary>
public class Locator
{
/// <summary>
/// 画布
/// </summary>
private readonly Rectangle m_canvas; /// <summary>
/// 障碍物
/// </summary>
private readonly List<Rectangle> m_obstacles; /// <summary>
/// 步长
/// </summary>
private readonly int m_step; /// <summary>
/// 避障距离
/// </summary>
private readonly int m_voDistance; public Locator(Rectangle canvas, List<Rectangle> obstacles, int step = 20, int voDistance = 10)
{
this.m_canvas = canvas;
if (step < 20)
step = 20;
this.m_step = (step >= 20) ? step : 20;
this.m_voDistance = (voDistance >= 10) ? voDistance : 10;
this.m_obstacles = this.BuildObstacles(obstacles);
} /// <summary>
/// 画板
/// </summary>
public Rectangle Canvas => this.m_canvas; /// <summary>
/// 步长
/// </summary>
public int Step => this.m_step; /// <summary>
/// 避障距离
/// </summary>
public int VODistance => this.m_voDistance; /// <summary>
/// 对齐坐标(把“点”的坐标值对齐到Step的整数倍)
/// </summary>
/// <param name="point"></param>
public Point AlignPoint(Point point)
{
return new Point(this.AlignCoord(point.X, 1), this.AlignCoord(point.Y, 1));
} /// <summary>
/// <para>对齐坐标(把“点”的坐标值对齐到Step的整数倍,同时校验“点”是否在画布内或是否和障碍物冲突)</para>
/// <para>如果对齐前或对齐后的“点”坐标不在画布内或者和障碍物冲突,则返回false;否则,返回true。</para>
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public bool AlignPoint(ref Point point)
{
if (!this.m_canvas.Contains(point))
return false; point = this.AlignPoint(point); if (!this.m_canvas.Contains(point))
return false; return !this.InObstacle(point);
} /// <summary>
/// 排除包含“点”的障碍物
/// </summary>
/// <param name="point"></param>
public void ExcludeObstacles(Point point)
{
this.m_obstacles.RemoveAll(o => o.Contains(point));
} /// <summary>
/// 判断点是否在障碍物内
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public bool InObstacle(Point point)
{
return this.m_obstacles.Exists(obst => obst.Contains(point));
} /// <summary>
/// <para>判断水平线段或垂直线段(ab)是否和障碍物相交</para>
/// <para>a、b的顺序和结果无关</para>
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public bool IntersectWithObstacle(Point a, Point b)
{
if (a.X == b.X)
return this.IntersectVerticallyWithObstacle(a, b);
else // a.Y == b.Y
return this.IntersectHorizontallyWithObstacle(a, b);
} /// <summary>
/// 判断水平线段(ab,其中a.X ≤ b.X)是否和障碍物相交
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public bool IntersectHorizontallyWithObstacle(Point a, Point b)
{
if (a.X > b.X)
{
var t = a;
a = b;
b = t;
} return this.m_obstacles.Exists(obst =>
(obst.Top <= a.Y && a.Y <= obst.Bottom && ((a.X <= obst.Left && obst.Left <= b.X) || (a.X <= obst.Right && obst.Right <= b.X)))
|| obst.Contains(a)
|| obst.Contains(b));
} /// <summary>
/// 判断垂直线段(ab,其中a.Y ≤ b.Y)是否和障碍物相交
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public bool IntersectVerticallyWithObstacle(Point a, Point b)
{
if (a.Y > b.Y)
{
var t = a;
a = b;
b = t;
} return this.m_obstacles.Exists(obst =>
(obst.Left <= a.X && a.X <= obst.Right && ((a.Y <= obst.Top && obst.Top <= b.Y) || (a.Y <= obst.Bottom && obst.Bottom <= b.Y)))
|| obst.Contains(a)
|| obst.Contains(b));
} /// <summary>
/// 对齐坐标的值
/// </summary>
/// <param name="val"></param>
/// <param name="direction"></param>
/// <returns></returns>
private int AlignCoord(int val, int direction)
{
int md = val % this.m_step;
if (0 == md)
return val;
else if (md <= this.m_step / 2)
return val - md;
else
return val - md + (direction * this.m_step);
} /// <summary>
/// 构造障碍物(用于调试)
/// </summary>
/// <param name="obstacles"></param>
/// <returns></returns>
private List<Rectangle> BuildDebugObstacles(List<Rectangle> obstacles)
{
if (obstacles is null || obstacles.Count <= 0)
return new List<Rectangle>();
else
return obstacles.Select(o => new Rectangle(o.X - this.m_voDistance,
o.Y - this.m_voDistance,
o.Width + this.m_voDistance * 2,
o.Height + m_voDistance * 2)).ToList();
} /// <summary>
/// 构造障碍物
/// </summary>
/// <param name="obstacles"></param>
/// <returns></returns>
private List<Rectangle> BuildObstacles(List<Rectangle> obstacles)
{
if (obstacles is null || obstacles.Count <= 0)
return new List<Rectangle>();
else
return obstacles.Select(o => new Rectangle(o.X - this.m_voDistance + 1,
o.Y - this.m_voDistance + 1,
o.Width + this.m_voDistance * 2 - 2,
o.Height + m_voDistance * 2 - 2)).ToList();
}
}
}

Locator

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq; namespace Pathfinding
{
/// <summary>
/// 路径调制器
/// </summary>
public class Straightener
{
/// <summary>
/// 满足调直的前提条件(路径最少包含4个点)
/// </summary>
private const int MIN_COUNT_POINTS = 4;
/// <summary>
/// 定位器
/// </summary>
private Locator m_locator;
/// <summary>
/// 原始路径
/// </summary>
private LinkedList<Point> m_originalPath;
/// <summary>
/// 调直后的路径
/// </summary>
private LinkedList<Point> m_straightenedPath; private Straightener()
{
this.Reset();
} /// <summary>
/// 调直路径,减少拐点(参数为空或小于4个点不做任何处理)
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static Point[] Straighten(Locator locator, Point[] path)
{
if (locator is null || path is null || path.Length < MIN_COUNT_POINTS)
return path; var worker = new Straightener();
worker.m_locator = locator;
worker.m_originalPath = new LinkedList<Point>(path);
worker.Straighten(); return worker.m_straightenedPath.ToArray();
} /// <summary>
/// 创建假设的拐点
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private static Point CreateHypotheticalInflection(LinkedListNode<Point> node)
{
if (node.Value.X == node.Next.Value.X)
return new Point(node.Next.Next.Value.X, node.Value.Y);
else
return new Point(node.Value.X, node.Next.Next.Value.Y);
} /// <summary>
/// 计算线段(abcd)上拐点的个数
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="d"></param>
/// <returns></returns>
private static int GetCountInflections(Point a, Point b, Point c, Point d)
{
var inflections = 0;
if (c.X != a.X && c.Y != a.Y)
inflections++;
if (d.X != b.X && d.Y != b.Y)
inflections++; return inflections;
} private static int GetDistance(Point a, Point b)
{
if (a.X == b.X)
return Math.Abs(a.Y - b.Y);
else
return Math.Abs(a.X - b.X);
}
/// <summary>
/// 添加拐点
/// </summary>
/// <param name="inflection"></param>
private void AddInflection(Point inflection)
{
if (null != this.m_straightenedPath.Last
&& this.m_straightenedPath.Last.Value == inflection)
return; var last = this.m_straightenedPath.AddLast(inflection);
if (null != last.Previous?.Previous
&& (last.Value.X == last.Previous.Previous.Value.X
|| last.Value.Y == last.Previous.Previous.Value.Y))
this.m_straightenedPath.Remove(last.Previous);
} private void DoStraighten()
{
this.Reset(); var current = this.m_originalPath.First;
while (null != current.Next?.Next)
{
this.AddInflection(current.Value); this.RemoveRedundantInflections(current);
if (current.Next?.Next is null)
break; var inflection = CreateHypotheticalInflection(current);
if (!this.IntersectWithObstacle(current.Value, inflection, current.Next.Next.Value))
{
var success = false; if (null != current.Previous)
{
var i1 = GetCountInflections(current.Previous.Value, current.Value, current.Next.Value, current.Next.Next.Value);
var i2 = GetCountInflections(current.Previous.Value, current.Value, inflection, current.Next.Next.Value);
if (i2 < i1)
{
current.Next.Value = inflection;
success = true;
}
}
else if (null != current.Next?.Next?.Next)
{
var i3 = GetCountInflections(current.Value, current.Next.Value, current.Next.Next.Value, current.Next.Next.Next.Value);
var i4 = GetCountInflections(current.Value, inflection, current.Next.Next.Value, current.Next.Next.Next.Value);
if (i4 < i3)
{
current.Next.Value = inflection;
success = true;
}
} if (success)
{
this.RemoveRedundantInflections(current.Next);
if (current.Next?.Next is null)
break;
}
} this.RemoveTurnBackInflections(current); current = current.Next;
} this.AddInflection(this.m_originalPath.Last.Previous.Value);
this.AddInflection(this.m_originalPath.Last.Value);
} private int GetDistance()
{
var dist = 0;
var current = this.m_straightenedPath.First;
do
{
if (null != current.Next)
{
dist += GetDistance(current.Value, current.Next.Value);
current = current.Next;
}
else
break; } while (true); return dist;
} /// <summary>
/// 判断线段(abc)是否和障碍物相交
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <returns></returns>
private bool IntersectWithObstacle(Point a, Point b, Point c)
{
return this.m_locator.IntersectWithObstacle(a, b)
|| this.m_locator.IntersectWithObstacle(b, c);
} /// <summary>
/// 删除冗余拐点
/// </summary>
/// <param name="node"></param>
private void RemoveRedundantInflections(LinkedListNode<Point> node)
{
while (true)
{
if (node.Next?.Next is null)
break; if (node.Value.X == node.Next.Next.Value.X
|| node.Value.Y == node.Next.Next.Value.Y)
this.m_originalPath.Remove(node.Next);
else
break;
}
} /// <summary>
/// 删除回转拐点
/// </summary>
/// <param name="node"></param>
private void RemoveTurnBackInflections(LinkedListNode<Point> node)
{
if (node.Next?.Next?.Next is null)
return; var point = node.Value;
var nPoint = node.Next.Value;
var nnPoint = node.Next.Next.Value;
var nnnPoint = node.Next.Next.Next.Value; // ●为已知拐点;○为假设拐点
// 消除如下形式的拐点
// ●
// |
// ○---●
// | |
// ●---●
if (point.X == nPoint.X
&& nPoint.Y == nnPoint.Y
&& nnPoint.X == nnnPoint.X)
{
var dy1 = point.Y - nPoint.Y;
var dy2 = nnnPoint.Y - nnPoint.Y;
var p1 = new Point(nnnPoint.X, point.Y);
if (Math.Abs(dy2) >= Math.Abs(dy1)
&& Math.Abs(dy1) / dy1 == Math.Abs(dy2) / dy2
&& !this.m_locator.IntersectHorizontallyWithObstacle(node.Value, p1))
{
this.m_originalPath.Remove(node.Next);
this.m_originalPath.Remove(node.Next);
this.m_originalPath.AddAfter(node, p1);
}
}
// ●为已知拐点;○为假设拐点
// 消除如下形式的拐点
// ●---○---●
// | |
// ●---●
else if (point.Y == nPoint.Y
&& nPoint.X == nnPoint.X
&& nnPoint.Y == nnnPoint.Y)
{
var dx1 = point.X - nPoint.X;
var dx2 = nnnPoint.X - nnPoint.X;
var p2 = new Point(point.X, nnnPoint.Y);
if (Math.Abs(dx2) >= Math.Abs(dx1)
&& Math.Abs(dx1) / dx1 == Math.Abs(dx2) / dx2
&& !this.m_locator.IntersectVerticallyWithObstacle(node.Value, p2))
{
this.m_originalPath.Remove(node.Next);
this.m_originalPath.Remove(node.Next);
this.m_originalPath.AddAfter(node, p2);
}
}
} private void Reset()
{
this.m_straightenedPath = new LinkedList<Point>();
} private void Straighten()
{
int prevDistance = 0;
int prevInflections = 0;
int distance = 0;
int inflections = 0; while (true)
{
this.DoStraighten();
this.m_originalPath = this.m_straightenedPath; distance = this.GetDistance();
inflections = this.m_originalPath.Count; if (distance == prevDistance
&& inflections == prevInflections)
break; prevDistance = distance;
prevInflections = inflections;
}
}
}
}

Straightener

using System;
using System.Collections;
using System.Collections.Generic; namespace Pathfinding
{
/// <summary>
/// <para>有序链表</para>
/// <para>此类不是线程安全的</para>
/// </summary>
/// <typeparam name="T"></typeparam>
public class SortedList<T> : IEnumerable<T> where T : IComparable<T>
{
private int m_count = 0;
private Node m_first;
private Node m_last; public SortedList()
{
// do nothing
} public SortedList(IEnumerable<T> collection)
{
this.AddRange(collection);
} /// <summary>
/// 链表中的元素个数
/// </summary>
public int Count => this.m_count; /// <summary>
/// 第一个元素
/// </summary>
public T First
{
get
{
if (null != this.m_first)
return this.m_first.Value;
else
return default(T);
}
} public bool IsEmpty => this.m_count <= 0; /// <summary>
/// 最后一个元素
/// </summary>
public T Last
{
get
{
if (null != this.m_last)
return this.m_last.Value;
else
return default(T);
}
} public void Add(T value)
{
var node = new Node(value);
if (this.IsEmpty)
{
this.m_first = node;
this.m_last = node;
}
else if (value.CompareTo(this.m_first.Value) < 0)
{
node.Next = this.m_first;
this.m_first.Prev = node;
this.m_first = node;
}
else if (this.m_last.Value.CompareTo(value) <= 0)
{
node.Prev = this.m_last;
this.m_last.Next = node;
this.m_last = node;
}
else
{
Node current = this.m_first;
do
{
if (value.CompareTo(current.Value) < 0)
break; current = current.Next; } while (current != null); var prev = current.Prev;
prev.Next = node;
node.Prev = prev; node.Next = current;
current.Prev = node;
} this.m_count++;
} public void AddRange(IEnumerable<T> collection)
{
if (collection is null)
return; foreach (var item in collection)
this.Add(item);
} /// <summary>
/// 清除所有元素
/// </summary>
public void Clear()
{
this.m_first = null;
this.m_last = null;
this.m_count = 0;
} /// <summary>
/// 判断链表是否包含指定的元素
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public bool Contains(T value)
{
if (this.IsEmpty)
return false; var current = this.m_first;
while (null != current)
{
if (value.CompareTo(current.Value) == 0)
return true; current = current.Next;
} return false;
} public int IndexOf(T value)
{
if (this.IsEmpty)
return -1; var current = this.m_first;
var idx = 0;
while (null != current)
{
if (value.CompareTo(current.Value) == 0)
return idx; idx++;
current = current.Next;
} return -1;
} /// <summary>
/// 获取IEnumerator<T>
/// </summary>
/// <returns></returns>
public IEnumerator<T> GetEnumerator()
{
return new Enumerator(this);
} IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
} /// <summary>
/// 删除指定的元素
/// </summary>
/// <param name="value"></param>
public void Remove(T value)
{
if (this.IsEmpty)
return; var current = this.m_first;
while (null != current)
{
if (value.CompareTo(current.Value) == 0)
break; current = current.Next;
} if (null != current)
{
var prev = current.Prev;
var next = current.Next; if (null != prev && null != next)
{
prev.Next = next;
next.Prev = prev;
}
else if (null != prev)
this.SetLast(prev);
else
this.SetFirst(next); this.m_count--;
}
} /// <summary>
/// 返回并删除第一个元素
/// </summary>
/// <returns></returns>
public T TakeFirst()
{
if (this.IsEmpty)
return default(T); var value = this.m_first.Value; this.SetFirst(this.m_first.Next);
this.m_count--; return value;
} /// <summary>
/// 返回并删除最后一个元素
/// </summary>
/// <returns></returns>
public T TakeLast()
{
if (this.IsEmpty)
return default(T); var value = this.m_last.Value; this.SetLast(this.m_last.Prev);
this.m_count--; return value;
} public T[] ToArray()
{
if (this.IsEmpty)
return null; var a = new T[this.m_count];
var current = this.m_first;
var idx = 0;
while (null != current)
{
a[idx++] = current.Value;
current = current.Next;
} return a;
} public List<T> ToList()
{
if (this.IsEmpty)
return null; var l = new List<T>(this.m_count);
var current = this.m_first;
while (null != current)
{
l.Add(current.Value);
} return l;
} private void SetFirst(Node first)
{
this.m_first = first;
if (this.m_first is null)
this.m_last = null;
else
this.m_first.Prev = null;
} private void SetLast(Node last)
{
this.m_last = last;
if (this.m_last is null)
this.m_first = null;
else
this.m_last.Next = null;
} /// <summary>
/// 枚举器
/// </summary>
private class Enumerator : IEnumerator<T>
{
private readonly SortedList<T> m_list;
private readonly Node m_prevFirst = new Node(default(T));
private Node m_current; public Enumerator(SortedList<T> list)
{
this.m_list = list;
this.Reset();
} public T Current
{
get
{
if (null != this.m_current)
return this.m_current.Value;
else
return default(T);
}
} object IEnumerator.Current
{
get
{
if (null != this.m_current)
return this.m_current.Value;
else
return default(T);
}
} public void Dispose()
{
// do nothing
} public bool MoveNext()
{
if (object.ReferenceEquals(this.m_current, this.m_prevFirst))
this.m_current = this.m_list.m_first;
else
this.m_current = this.m_current?.Next; return null != this.m_current;
} public void Reset()
{
this.m_current = this.m_prevFirst;
}
} /// <summary>
/// 链表节点
/// </summary>
private class Node
{
public Node(T data)
{
this.Value = data;
} public Node Next { get; set; } public Node Prev { get; set; } public T Value { get; } public override string ToString()
{
if (null != Value)
return Value.ToString();
return null;
}
}
}
}

SortedList

namespace Pathfinding
{
/// <summary>
/// 方向
/// </summary>
public enum Orientation
{
/// <summary>
/// 无方向
/// </summary>
None = 0,
/// <summary>
/// 东
/// </summary>
East = 0x1,
/// <summary>
/// 南
/// </summary>
South = 0x10,
/// <summary>
/// 西
/// </summary>
West = 0x100,
/// <summary>
/// 北
/// </summary>
North = 0x1000,
/// <summary>
/// 东西
/// </summary>
EastWest = East | West,
/// <summary>
/// 南北
/// </summary>
NorthSouth = South | North,
/// <summary>
/// 东南
/// </summary>
SouthEast = East | South,
/// <summary>
/// 西南
/// </summary>
SouthWest = South | West,
/// <summary>
/// 西北
/// </summary>
NorthWest = West | North,
/// <summary>
/// 东北
/// </summary>
NorthEast = North | East,
}
}

Orientation

namespace Pathfinding
{
public static class OrientationExtension
{
/// <summary>
/// 是否为东西方向
/// </summary>
/// <param name="orient"></param>
/// <returns></returns>
public static bool IsEastWest(this Orientation orient)
{
return orient == Orientation.East
|| orient == Orientation.West
|| orient == Orientation.EastWest;
} /// <summary>
/// 是否为南北方向
/// </summary>
/// <param name="orient"></param>
/// <returns></returns>
public static bool IsNorthSouth(this Orientation orient)
{
return orient == Orientation.South
|| orient == Orientation.North
|| orient == Orientation.NorthSouth;
} /// <summary>
/// <para>把方向转换为EastWest或NorthSouth</para>
/// <para>如果方向不是东西方向或南北方向,则返回None</para>
/// </summary>
/// <param name="orient"></param>
/// <returns></returns>
public static Orientation ConvertToEWOrNS(this Orientation orient)
{
if (orient.IsEastWest())
return Orientation.EastWest;
else if (orient.IsNorthSouth())
return Orientation.NorthSouth;
else
return Orientation.None;
}
}
}

OrientationExtension

using System;
using System.Drawing; namespace Pathfinding
{
public static class PointExtension
{
/// <summary>
/// <para>获取第二个点相对于第一个点的方位</para>
/// <para>此方法只判断是否为正南,正北,正东或正西四个方向。</para>
/// <para>如果两个点坐标一样,则返回Orientation.None。</para>
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns>East、South、West、North</returns>
public static Orientation GetOrientation(this Point from, Point to)
{
if (from.X == to.X)
{
if (to.Y > from.Y)
return Orientation.South;
else if (to.Y < from.Y)
return Orientation.North;
}
else if (from.Y == to.Y)
{
if (to.X > from.X)
return Orientation.East;
else if (to.X < from.X)
return Orientation.West;
} return Orientation.None;
} /// <summary>
/// <para>判断两点之间的相对位置:东西方向或南北方向</para>
/// <para>如果两个点坐标一样或不是东西或南北方向,则返回Orientation.None。</para>
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns>EastWest或NorthSouth</returns>
public static Orientation GetOrientationEWOrNS(this Point from, Point to)
{
if (from.X == to.X && to.Y != from.Y)
{
return Orientation.NorthSouth;
}
else if (from.Y == to.Y && to.X != from.X)
{
return Orientation.EastWest;
} return Orientation.None;
} /// <summary>
/// 两点的位置是否为东西方向:Y坐标相等,且X坐标不相等
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static bool IsEastWest(this Point from, Point to)
{
return from.Y == to.Y && to.X != from.X;
} /// <summary>
/// 两点的位置是否为南北方向:X坐标相等,且Y坐标不相等
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static bool IsNorthSouth(this Point from, Point to)
{
return from.X == to.X && to.Y != from.Y;
} /// <summary>
/// 计算两点之间的距离(仅计算东西和南北方向的距离)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int GetAlignedDistanceTo(this Point a, Point b)
{
if (a.IsEastWest(b))
return Math.Abs(a.X - b.X);
else
return Math.Abs(a.Y - b.Y);
} /// <summary>
/// 计算两点之间的距离
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static double GetDistanceTo(this Point from, Point to)
{
return Math.Sqrt(Math.Pow(from.X - to.X, 2.0d) + Math.Pow(from.Y - to.Y, 2.0d));
} /// <summary>
/// 判断两个点是否在东西方向或南北方向的同一条直线上
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static bool InStraightLine(this Point a, Point b)
{
return a.X == b.X || a.Y == b.Y;
}
}
}

PointExtension

路径查找算法应用之A*算法的更多相关文章

  1. 查找最小生成树:克鲁斯克尔算法(Kruskal)算法

    一.算法介绍 Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表.用来解决同样问题的还有Prim算法和Boruvka算法等.三种算法都是贪心算法的应用.和 ...

  2. 【网络流24题】 No.3 最小路径覆盖问题 (网络流|匈牙利算法 ->最大二分匹配)

    [题意] 给定有向图 G=(V,E).设 P 是 G 的一个简单路(顶点不相交) 的集合.如果 V 中每个顶点恰好在 P 的一条路上,则称 P 是 G 的一个路径覆盖. P 中路径可以从 V 的任何一 ...

  3. 查找最小生成树:普里姆算法算法(Prim)算法

    一.算法介绍 普里姆算法(Prim's algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之 ...

  4. 算法:Astar寻路算法改进,双向A*寻路算法

    早前写了一篇关于A*算法的文章:<算法:Astar寻路算法改进> 最近在写个js的UI框架,顺便实现了一个js版本的A*算法,与之前不同的是,该A*算法是个双向A*. 双向A*有什么好处呢 ...

  5. 一步一步写算法(之 A*算法)

    [ 声明:版权全部,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 在前面的博客其中,事实上我们已经讨论过寻路的算法.只是,当时的演示样例图中,可选的路径是唯一的 ...

  6. 一步步学算法(算法分析)---6(Floyd算法)

    Floyd算法 Floyd算法又称为弗洛伊德算法,插点法,是一种用于寻找给定的加权图中顶点间最短路径的算法.该算法名称以创始人之一.1978年图灵奖获得者.斯坦福大学计算机科学系教授罗伯特·弗洛伊德命 ...

  7. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  8. 静态频繁子图挖掘算法用于动态网络——gSpan算法研究

    摘要 随着信息技术的不断发展,人类可以很容易地收集和储存大量的数据,然而,如何在海量的数据中提取对用户有用的信息逐渐地成为巨大挑战.为了应对这种挑战,数据挖掘技术应运而生,成为了最近一段时期数据科学的 ...

  9. 启发式搜索A-Star算法 【寻找 最短路径 算法】【地理几何位置 可利用的情况】

    在处理最短路径问题时,有一种启发式算法是我们应该了解的,由于其有着优秀的探索效率在各自现实项目中多有应用,它就是 A-star 算法,或  A*  算法. 个人观点: A*  算法并不保证找到的路径一 ...

随机推荐

  1. 入门-k8s集群环境搭建(二)

    对于 Kubernetes 初学者,在搭建K8S集群时,推荐在阿里云或腾讯云采购如下配置:(您也可以使用自己的虚拟机.私有云等您最容易获得的 Linux 环境) 至少2台 2核4G 的服务器 Cent ...

  2. DatabaseMetaData

    getColumns public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern ...

  3. LNMP平台的redis对接安装

    LNMP平台的redis对接安装 目录 LNMP平台的redis对接安装 一.安装LNMP的各个组件 二.安装redis服务 三.安装redis扩展 四.修改php配置文件 五.测试连接 一.安装LN ...

  4. Solution -「CF 1372E」Omkar and Last Floor

    \(\mathcal{Description}\)   Link.   给定一个 \(n \times m\) 的矩阵,每行被划分为若干段,你可以钦定每段中恰好一个位置为 \(1\),其余位置为 \( ...

  5. 主机磁盘使用率超过85%导致es索引变为只读模式

    [ type=cluster_block_exception, reason=index [ index_name ] FORBIDDEN/12/index read-only / allow del ...

  6. zabbix主动上报mysql数据库内容

    zabbix_sender命令支持主动上报数据,web服务端添加对应机器和采集器即可. 2015年刚接触zabbix时候,用的上报sqlserver脚本是select数据后插入到临时表,bcp下载到本 ...

  7. Nginx频繁报“500 Internal Server Error”错误

    服务器导致访问量激增,频繁报"500 Internal Server Error"错误.我查了一下nginx的错误日志(apt-get方式安装的nginx的错误日志在/var/lo ...

  8. MongoDB 带访问控制的副本集部署

    当你需要用到一个MongoDB 副本集集群,用于开发测试时, 可以通过下面的步骤简单完成. 版本及环境 MongoDB4.4  Centos6.5  一. 下载安装 MongoDB Server 及 ...

  9. [Golang]Go语言入门笔记

    跟着尚硅谷B站视频记的笔记 入门 go 编译和运行源代码 go build 编译源代码,生成可执行文件 go build -o newName.exe name.go go run 直接编译运行代码 ...

  10. 人工智能进度好慢,呜呜呜 还是先来学python!!!!!!!二十三讲、二十四讲!

    0:十转2 def my_bin(x): a ='' if x : a = my_bin(x//2) return a+str(x%2) else: a+='0b' return a print(my ...