网站首页 > 知识剖析 正文
A*(A star)算法是一种广泛应用于路径规划的启发式搜索算法,兼具Dijkstra算法的优点与启发式估计的策略。在C#中,我们可以结合GDI+进行可视化以更好地理解算法的运作机制。
A* 算法的逻辑
A*算法通过维护一个开放列表和一个封闭列表来寻找路径:
- 初始化:将起始点加入开放列表。
- 循环过程: 从开放列表中选取估价函数(f = g + h)最小的节点作为当前节点。 若该节点是目标节点,则路径找到,算法结束。 否则,将该节点从开放列表移至封闭列表。 对当前节点的每个邻居(可通行节点): 如果邻居在封闭列表中,忽略。 如果不在开放列表中,加入开放列表,设置当前节点为其父节点,并计算g(从起始点到该节点的实际代价)和h(从该节点到终点的估计代价)。 如果在开放列表中,检查新的路径是否更优。如果更优,更新该邻居的父节点和g值。
C# 中的 A* 实现
以下是一个简单的A*算法的C#实现示例。我们假设网格地图中,每个节点的代价为1(无障碍),h函数使用曼哈顿距离。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppAStar
{
//Node类 - 节点定义
public class Node
{
public Point Position { get; set; }
public int G { get; set; }
public int H { get; set; }
public int F => G + H;
public Node Parent { get; set; }
public NodeState State { get; set; }
public Node(Point position)
{
Position = position;
State = NodeState.Unvisited;
}
}
//NodeState枚举 - 节点状态
public enum NodeState
{
Unvisited,
OpenList,
ClosedList,
Path,
Wall,
Start,
End
}
public class AStarPathfinding
{
private const int GridSize = 20;
private readonly Node[,] grid;
private List<Node> openList;
private HashSet<Node> closedList;
private Node startNode;
private Node endNode;
private bool pathFound;
private readonly Random random = new Random();
public event Action<Node> OnNodeStateChanged;
public event Action OnPathFound;
public AStarPathfinding()
{
grid = new Node[GridSize, GridSize];
InitializeGrid();
}
private void InitializeGrid()
{
// Create nodes
for (int x = 0; x < GridSize; x++)
{
for (int y = 0; y < GridSize; y++)
{
grid[x, y] = new Node(new Point(x, y));
// Randomly place walls (20% chance)
if (random.NextDouble() < 0.2)
{
grid[x, y].State = NodeState.Wall;
}
}
}
// Set start and end points
startNode = grid[0, 0];
endNode = grid[GridSize - 1, GridSize - 1];
startNode.State = NodeState.Start;
endNode.State = NodeState.End;
}
public async void StartPathfinding()
{
openList = new List<Node> { startNode };
closedList = new HashSet<Node>();
pathFound = false;
while (openList.Count > 0 && !pathFound)
{
Node current = openList.OrderBy(n => n.F).First();
if (current == endNode)
{
pathFound = true;
ReconstructPath();
OnPathFound?.Invoke();
return;
}
openList.Remove(current);
closedList.Add(current);
if (current != startNode)
{
current.State = NodeState.ClosedList;
OnNodeStateChanged?.Invoke(current);
}
foreach (Node neighbor in GetNeighbors(current))
{
if (closedList.Contains(neighbor) || neighbor.State == NodeState.Wall)
continue;
int tentativeG = current.G + 1;
if (!openList.Contains(neighbor))
{
neighbor.Parent = current;
neighbor.G = tentativeG;
neighbor.H = CalculateHeuristic(neighbor, endNode);
openList.Add(neighbor);
if (neighbor != endNode)
{
neighbor.State = NodeState.OpenList;
OnNodeStateChanged?.Invoke(neighbor);
}
}
else if (tentativeG < neighbor.G)
{
neighbor.Parent = current;
neighbor.G = tentativeG;
}
}
await System.Threading.Tasks.Task.Delay(50); // Animation delay
}
}
private List<Node> GetNeighbors(Node node)
{
List<Node> neighbors = new List<Node>();
Point[] directions = new Point[]
{
new Point(0, 1), // right
new Point(1, 0), // down
new Point(0, -1), // left
new Point(-1, 0), // up
};
foreach (Point dir in directions)
{
int newX = node.Position.X + dir.X;
int newY = node.Position.Y + dir.Y;
if (newX >= 0 && newX < GridSize && newY >= 0 && newY < GridSize)
{
neighbors.Add(grid[newX, newY]);
}
}
return neighbors;
}
private int CalculateHeuristic(Node a, Node b)
{
return Math.Abs(a.Position.X - b.Position.X) + Math.Abs(a.Position.Y - b.Position.Y);
}
private void ReconstructPath()
{
Node current = endNode;
while (current != startNode)
{
if (current != endNode)
{
current.State = NodeState.Path;
OnNodeStateChanged?.Invoke(current);
}
current = current.Parent;
}
}
}
}
窗体
using Timer = System.Windows.Forms.Timer;
namespace AppAStar
{
public partial class Form1 : Form
{
private const int GridSize = 20;
private const int CellSize = 30;
private readonly Node[,] grid;
private List<Node> openList;
private HashSet<Node> closedList;
private Node startNode;
private Node endNode;
private readonly Random random = new Random();
private bool isRunning = false;
private readonly Dictionary<NodeState, Color> nodeColors = new Dictionary<NodeState, Color>
{
{ NodeState.Unvisited, Color.White }, // 未访问节点为白色
{ NodeState.OpenList, Color.LightGreen }, // 开放列表节点为浅绿色
{ NodeState.ClosedList, Color.LightGray }, // 关闭列表节点为浅灰色
{ NodeState.Path, Color.Yellow }, // 最终路径为黄色
{ NodeState.Wall, Color.Black }, // 障碍物为黑色
{ NodeState.Start, Color.Green }, // 起点为绿色
{ NodeState.End, Color.Red } // 终点为红色
};
public Form1()
{
InitializeComponent();
ClientSize = new Size(CellSize * GridSize, CellSize * GridSize + 40);
grid = new Node[GridSize, GridSize];
InitializeGrid();
Button startButton = new Button
{
Text = "开始搜索",
Location = new Point(10, ClientSize.Height - 35),
Width = 120
};
startButton.Click += btnStart_Click;
Controls.Add(startButton);
SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer, true);
}
private void InitializeGrid()
{
// 创建节点
for (int x = 0; x < GridSize; x++)
{
for (int y = 0; y < GridSize; y++)
{
grid[x, y] = new Node(new Point(x, y));
if (random.NextDouble() < 0.2)
{
grid[x, y].State = NodeState.Wall;
}
}
}
// 设置开始与结束节点
startNode = grid[0, 0];
endNode = grid[GridSize - 1, GridSize - 1];
startNode.State = NodeState.Start;
endNode.State = NodeState.End;
grid[0, 0].State = NodeState.Start;
grid[GridSize - 1, GridSize - 1].State = NodeState.End;
}
private async void btnStart_Click(object sender, EventArgs e)
{
if (isRunning) return;
isRunning = true;
openList = new List<Node> { startNode }; // 初始化开放列表,将起点加入
closedList = new HashSet<Node>(); // 初始化关闭列表
while (openList.Count > 0)
{
// 从开放列表中选择F值最小的节点
Node current = openList.OrderBy(n => n.F).First();
// 判断是否到达终点
if (current == endNode)
{
await ReconstructPath(); // 重建路径
MessageBox.Show("找到路径!");
isRunning = false;
return;
}
// 将当前节点从开放列表移到关闭列表
openList.Remove(current);
closedList.Add(current);
// 更新节点状态和显示
if (current != startNode)
{
current.State = NodeState.ClosedList;
Invalidate();
await System.Threading.Tasks.Task.Delay(50); // 动画延迟
}
// 处理相邻节点
foreach (Node neighbor in GetNeighbors(current))
{
// 跳过已关闭或是墙壁的节点
if (closedList.Contains(neighbor) || neighbor.State == NodeState.Wall)
continue;
// 计算从起点经过当前节点到相邻节点的代价
int tentativeG = current.G + 1;
// 如果是新节点或找到更好的路径
if (!openList.Contains(neighbor))
{
neighbor.Parent = current; // 设置父节点
neighbor.G = tentativeG; // 设置G值
neighbor.H = CalculateHeuristic(neighbor, endNode); // 计算H值
openList.Add(neighbor); // 加入开放列表
// 更新显示
if (neighbor != endNode)
{
neighbor.State = NodeState.OpenList;
Invalidate();
await System.Threading.Tasks.Task.Delay(50);
}
}
else if (tentativeG < neighbor.G) // 找到更短的路径
{
neighbor.Parent = current;
neighbor.G = tentativeG;
}
}
}
}
private List<Node> GetNeighbors(Node node)
{
List<Node> neighbors = new List<Node>();
// 定义四个方向:右、下、左、上
Point[] directions = new Point[]
{
new Point(0, 1), // 右
new Point(1, 0), // 下
new Point(0, -1), // 左
new Point(-1, 0), // 上
};
// 检查每个方向的相邻节点
foreach (Point dir in directions)
{
int newX = node.Position.X + dir.X;
int newY = node.Position.Y + dir.Y;
// 确保节点在网格范围内
if (newX >= 0 && newX < GridSize && newY >= 0 && newY < GridSize)
{
neighbors.Add(grid[newX, newY]);
}
}
return neighbors;
}
private int CalculateHeuristic(Node a, Node b)
{
// 使用曼哈顿距离作为启发式函数
return Math.Abs(a.Position.X - b.Position.X) + Math.Abs(a.Position.Y - b.Position.Y);
}
private async Task ReconstructPath()
{
Node current = endNode;
// 从终点往回追踪到起点
while (current != startNode)
{
if (current != endNode)
{
current.State = NodeState.Path; // 标记路径
Invalidate();
await System.Threading.Tasks.Task.Delay(50);
}
current = current.Parent; // 沿着父节点指针往回走
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
for (int x = 0; x < GridSize; x++)
{
for (int y = 0; y < GridSize; y++)
{
Node node = grid[x, y];
Rectangle rect = new Rectangle(x * CellSize, y * CellSize, CellSize, CellSize);
g.FillRectangle(new SolidBrush(nodeColors[node.State]), rect);
g.DrawRectangle(Pens.Gray, rect);
}
}
}
}
}
观察算法执行过程:
- 浅绿色:当前正在考虑的节点(开放列表)
- 灰色:已经访问过的节点(关闭列表)
- 黄色:最终找到的路径
代码说明
- 数据结构:使用List<Node>来表示开放列表,HashSet<Node>表示封闭列表。
- 算法过程:以起点为初始节点,不断扩展节点直至找到终点或开放列表为空。
- GUI 部分:使用Windows Forms和GDI+进行简单的图形显示。红色方块表示找到的路径。
A算法由于其高效的计算特性,已被广泛应用于游戏AI、机器人导航及其他需要路径规划的领域。希望通过以上C#示例能帮助您理解和应用A算法。
猜你喜欢
- 2025-09-18 50 道高频 JavaScript 面试题,从基础到进阶 (附答案)
- 2025-09-18 Origami动效制作-入门必看(附3个练习案例)
- 2025-09-18 Android 流畅度检测原理简析_手机流畅度检测
- 2025-09-18 SwiftUI入门五:让视图和过渡动起来
- 2025-09-18 抖音品质建设 - iOS启动优化《原理篇》
- 2025-09-18 手机性能好不好 GPU玄学曲线告诉你
- 2025-09-18 使用ImageMagick自动化图片处理_image图像处理软件
- 2025-09-18 CollectionView 添加/删除动画_recyclerview添加动画
- 2025-09-18 CSS 中实现动画效果的方法_css实现动画有哪些方式
- 2025-09-18 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)