它是 .NET Framework 4.7.2 中的应用程序,用 C# 编写。它通过节点网格并通过 BFS 算法找到从节点 A 到 B 的最短路径。在算法的每次迭代中,在确认当前节点不是目标节点后,它通过将相邻节点放入队列来继续检查相邻节点,然后进入下一次迭代,在那里它使下一个节点出列以进行检查。当它想将相邻节点添加到队列中时,它首先检查该节点是否已被访问(每个节点中都有一个字段 isVisited : bool ),如果没有,则将其入队。 队列为先进先出。
所以这个问题是一个相当奇怪的问题。我让程序在调试模式下运行一段时间,给它足够的时间卡住(通常非常快)并暂停它以查看 BFS 算法在哪里结束。我发现它跟踪的节点(路径)的轨迹是数百个,当 2 个输入节点非常接近时,相隔 2 个空格。绕过网格中的障碍物最多只能占 30 条轨迹长度,在最极端的情况下可能是 100 条,但绝不会超过数百条。所以很明显出了问题。我检查了根节点,我发现它的一个邻居甚至没有被访问过,它由于某种原因没有排队......
然后当根是导致整个事情卡住的节点时,我在搜索的开始处设置了一个断点。如果我只是从那里继续,搜索将立即找到节点(相对而言)。如果我只是在每次搜索调用之前放置一个断点,然后让它继续下一次搜索调用以获取一对新节点,这也有效......如果我这样做,它永远不会卡住并且非常快,但是如果我让它自己运行它会在几次搜索中卡住。 所以我的问题,最后,是...到底是什么?这到底是什么原因造成的?
代码如下:
public class Grid {
public GraphNode[,] grid;
public Grid(int resolution){
grid = new GraphNode[resolution,resolution];
foreach (GraphNode g in grid) {
SetNeighbours(g);
}
}
public void SetNeighbours(GraphNode node)
{
int x = node.x;
int y = node.y;
if (y != 0)
grid[x, y].N = grid[x, y - 1];
else
grid[x, y].N = null;
if (x != _resolution - 1)
grid[x, y].E = grid[x + 1, y];
else
grid[x, y].E = null;
if (y != _resolution - 1)
grid[x, y].S = grid[x, y + 1];
else
grid[x, y].S = null;
if (x != 0)
grid[x, y].W = grid[x - 1, y];
else
grid[x, y].W = null;
}
}
public class GraphNode {
public long id;
public int x, y;
public GraphNode N, E, S, W;
public bool isVisited;
public List<bool> isPath = new List<bool>();
}
public class GraphPathSegment
{
public GraphNode start;
public GraphNode end;
public int run;
}
public class BFS
{
private Queue _searchQueue;
private GraphNode _root;
private Grid grid;
private long id;
public BFS(GraphNode rootNode, Grid context)
{
_searchQueue = new Queue();
_visitedNodes = new List<Tuple<long, long>>();
_root = rootNode;
grid = context;
}
public List<GraphPathSegment> Search(long id, int run)
{
this.id = id;
GraphNode _node = grid.grid[_root.x, _root.y]; // starting node
_node.isVisited = true;
_visitedNodes.Add(new Tuple<long, long>(_node.x, _node.y));
List<GraphNode> _trace = new List<GraphNode>(); // path trace
_searchQueue.Enqueue(new Tuple<GraphNode, List<GraphNode>>(_node, _trace));
bool wrongNode;
while (_searchQueue.Count != 0)
{
Tuple<GraphNode, List<GraphNode>> _current = (Tuple<GraphNode, List<GraphNode>>)_searchQueue.Dequeue();
_node = _current.Item1;
_trace = _current.Item2;
wrongNode = false;
if (_node.isNode) // is the node an entity?
{
if (_node.Data.ID == id) // is it the target node?
{
_trace.Add(_node);
CleanUp(run, _trace);
List<GraphPathSegment> retSegments = new List<GraphPathSegment>();
for(int i = 1; i < _trace.Count; i++)
{
retSegments.Add(new GraphPathSegment(_trace[i - 1], _trace[i], run));
}
return retSegments;
}
if(!_root.Equals(_node)) // is not root
wrongNode = true;
}
if(!wrongNode) //node is blank or path
{
List<GraphNode> newTrace = new List<GraphNode>(_trace);
newTrace.Add(_node);
//is there a node above | is not visited | is not path in current run
if (_node.N != null && !_node.N.isVisited && !_node.N.isPath[run])
{
_node.N.isVisited = true;
_searchQueue.Enqueue(new Tuple<GraphNode, List<GraphNode>>(_node.N, newTrace)); // check next
}
//is there a node right | is not visited | is not path in current run
if (_node.E != null && !_node.E.isVisited && !_node.E.isPath[run])
{
_node.E.isVisited = true;
_searchQueue.Enqueue(new Tuple<GraphNode, List<GraphNode>>(_node.E, newTrace));
}
//is there a node below | is not visited | is not path in current run
if (_node.S != null && !_node.S.isVisited && !_node.S.isPath[run])
{
_node.S.isVisited = true;
_searchQueue.Enqueue(new Tuple<GraphNode, List<GraphNode>>(_node.S, newTrace));
}
//is there a node left | is not visited | is not path in current run
if (_node.W != null && !_node.W.isVisited && !_node.W.isPath[run])
{
_node.W.isVisited = true;
_searchQueue.Enqueue(new Tuple<GraphNode, List<GraphNode>>(_node.W, newTrace));
}
}
}
CleanUp(run); // this sets all the nodes in the grid to unvisited
return null;
}
}
private void CleanUp(int run = 0, List<GraphNode> trace = null)
{
foreach(GraphNode n in grid.grid)
{
n.isVisited = false;
}
if (trace == null) trace = new List<GraphNode>();
for (int i = 1; i < trace.Count - 1; i++)
{
grid.grid[trace[i].x, trace[i].y].isPath[run] = true;
}
}
并且可以在初始化图后从main调用调用方法:
.
.
.
BFS bfs = new BFS(grid.grid[N1.x, N1.y], grid);
//param1 sets the root node, param2 gives context to the algorithm
List<GraphPathSegment> pathSegments = bfs.Search(N2.Data.ID, run);
// run is used when there can be no more nonoverlapping paths in the current run => go to next run, not relevant for this, just so there's no confusion
.
.
.
重要
此版本的应用程序在使用大型列表和网格的 GraphNode 对象矩阵的任何地方都使用哈希表。该应用还有另一个版本,它使用列表和节点列表矩阵,虽然速度慢得惊人。