环境: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. SpringBeanUtils的部分方法类

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/12060553.html SpringBeanUtils的部分方法类: import java. ...

  2. python——虚拟环境管理大合集

    个人常用:pipenv 安装 pip3 install pipenv 创建虚拟环境 # 默认安装在~/.local/virtualenv下 mkdir project cd project pipen ...

  3. Solution -「CF 156D」Clues

    \(\mathcal{Description}\)   link.   给一个 \(n\) 个点 \(m\) 条边的无向图 \(G\).设图上有 \(k\) 个连通块,求出添加 \(k-1\) 条边使 ...

  4. 在ABP VNext框架中处理和用户相关的多对多的关系

    前面介绍了一些ABP VNext架构上的内容,随着内容的细化,我们会发现ABP VNext框架中的Entity Framework处理表之间的引用关系还是比较麻烦的,一不小心就容易出错了,本篇随笔介绍 ...

  5. shell脚本部署redis以及redis主从复制和redis-cluster集群

    # 关于脚本: # 使用root用户执行此脚本,提前关闭selinux: # 执行脚本之前,hostsIP内的IP修改成自己的机器IP: # hostsIp内的IP数量如果有增加或者减少,for循环的 ...

  6. linux 运维工程师如何降低工作难度

    文章目录 1.Linux "优化" 2.git "优化" 3.mysql "优化" 4.kubernetes "优化" ...

  7. 阿里云服务器ECS挂载数据盘—linux系统

    参考阿里云官网帮助文档:https://help.aliyun.com/document_detail/25426.html 里面有些步骤说的不是很清楚,初学者可能操作时会遇到问题.通过这篇文档进行进 ...

  8. 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台

    前言   大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用.   XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...

  9. 深入理解Java类加载机制,再也不用死记硬背了

    谈谈"会"的三个层次 在<说透分布式事务>中,我举例里说明了会与会的差别.对一门语言的学习,这里谈谈我理解的"会"的三个层次: 第一层:了解这门语言 ...

  10. Sqlmap数据库注入攻击

    实验目的 利用sqlmap命令破解出access数据中的admin的密码bfpns 实验原理 SQLMap是一个先进的自动化SQL注入工具,其主要功能是扫描.发现并利用给定的URL的SQL注入漏洞.目 ...