我的程序是:
var maze = MazeFactory.FromPattern(Patterns.Maze3, Patterns.WallSymbol);
var a = maze.GetElementAt(0, 1);
var b = maze.GetElementAt(12, 0);
var printer = new PathPrinter(ConsoleColor.Yellow, ConsoleColor.Gray, ConsoleColor.Black);
Console.WriteLine("Maze");
printer.Print(maze, Path.Empty, Console.CursorLeft, Console.CursorTop);
var ab = new PathFinder(maze, a, b).GetPaths();
var abPath = ab.OrderBy(p => p.Elements.Count()).First();
Console.WriteLine("A -> B");
printer.Print(maze, abPath, Console.CursorLeft, Console.CursorTop);
var ba = new PathFinder(maze, b, a).GetPaths();
var baPath = ba.OrderBy(p => p.Elements.Count()).First();
Console.WriteLine("B -> A");
printer.Print(maze, baPath, Console.CursorLeft, Console.CursorTop);
输出为:
我希望PathFinder.GetPaths()
返回Path
的集合,以便我可以选择最短的集合,但是该集合仅包含一个元素。
欢迎任何答案。
那是为什么?
为什么“ a到b”的路径与“ b到a”的路径不同?
为什么“ a to b”路径那么长?
如何使两条路径最短且相同?
注意:我不是在寻找一种新的解决方案,只是为了解决这个问题。
详细信息:
我有一个MazeElement
班:
class MazeElement
{
public bool IsPass { get; }
public int X { get; }
public int Y { get; }
public MazeElement(int x, int y, bool isPass) => (X, Y, IsPass) = (x, y, isPass);
}
MazeElement
实例可以形成Maze
:
class Maze
{
public IReadOnlyList<IReadOnlyList<MazeElement>> Elements { get; }
public int MaxY { get; }
public int MaxX { get; }
public Maze(MazeElement[][] elements) =>
(Elements, MaxY, MaxX) =
(elements, elements.Length - 1, elements[0].Length - 1);
public MazeElement GetElementAt(int x, int y) =>
0 <= x && x <= MaxX && 0 <= y && y <= MaxY ?
Elements[y][x] :
null;
}
和一个Path
:
class Path
{
public IEnumerable<MazeElement> Elements { get; }
public bool ReachedDestination { get; }
public static Path Empty => new Path(new MazeElement[0], false);
private Path(IEnumerable<MazeElement> elements, bool reachedDestination) => (Elements, ReachedDestination) = (elements, reachedDestination);
public Path Continued(MazeElement next, bool reachedDestination) => new Path(new List<MazeElement>(Elements) { next }, reachedDestination);
public static Path FromStart(MazeElement start) => new Path(new[] { start }, false);
}
我有一个MazeFactory
,可以根据一组Maze
来创建string
:
static class MazeFactory
{
public static Maze FromPattern(IEnumerable<string> pattern, char wallSymbol) =>
new Maze(pattern.Select((line, y) => line.Select((c, x) => new MazeElement(x, y, c != wallSymbol)).ToArray()).ToArray());
}
以及一些这样的集合:
static class Patterns
{
public static readonly char WallSymbol = '#';
public static readonly IReadOnlyList<string> Maze1 = new[]
{
"#########################",
" ###",
"##################### # #",
"# # #",
"# ##################### #",
"# # #",
"# ##################### #",
"# #",
"####################### #"
};
public static readonly IReadOnlyList<string> Maze2 = new[]
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" "
};
public static readonly IReadOnlyList<string> Maze3 = new[]
{
"############ ############",
" ###",
"######## ############ # #",
"# # #",
"# ########## ######## # #",
"# # #",
"# ###### ############ # #",
"# #",
"####################### #"
};
}
要显示结果,我有一个PathPrinter
:
class PathPrinter
{
private static readonly string mazeSymbol = "█";
private static readonly string pathSymbol = "*";
private readonly ConsoleColor wallColor;
private readonly ConsoleColor passColor;
private readonly ConsoleColor pathColor;
public PathPrinter(ConsoleColor wallColor, ConsoleColor passColor, ConsoleColor pathColor) =>
(this.wallColor, this.passColor, this.pathColor) = (wallColor, passColor, pathColor);
public void Print(Maze maze, Path path, int x = -1, int y = -1)
{
x = x == -1 ? Console.CursorLeft : x;
y = y == -1 ? Console.CursorTop : y;
foreach (var line in maze.Elements)
foreach (var e in line)
{
Console.SetCursorPosition(e.X + x, e.Y + y);
Console.ForegroundColor = e.IsPass ? passColor : wallColor;
Console.Write(mazeSymbol);
}
Console.ForegroundColor = pathColor;
foreach (var e in path.Elements)
{
Console.SetCursorPosition(e.X + x, e.Y + y);
Console.BackgroundColor = e.IsPass ? passColor : wallColor;
Console.Write(pathSymbol);
}
Console.SetCursorPosition(0, maze.MaxY + y + 1);
Console.ResetColor();
}
}
最后,我有一个PathFinder
,可以在Maze
中找到一条路径:
class PathFinder
{
private readonly Maze maze;
private readonly MazeElement start;
private readonly MazeElement end;
private readonly Dictionary<MazeElement, bool> elementIsChecked;
public PathFinder(Maze maze, MazeElement start, MazeElement end) =>
(this.maze, this.start, this.end, elementIsChecked) =
(maze, start, end, maze.Elements.SelectMany(i => i).ToDictionary(i => i, i => false));
public Path[] GetPaths() => FindPath(Path.FromStart(start)).ToArray();
private IEnumerable<Path> FindPath(Path path) =>
GetContinuations(path).Where(next => next != null)
.SelectMany(next => next.ReachedDestination ? new[] { next } : FindPath(next));
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.Select(i => GetContinuedPath(path, i));
}
private Path GetContinuedPath(Path path, MazeElement e)
{
if (e == null || elementIsChecked[e])
return null;
elementIsChecked[e] = true;
if (e.IsPass)
return path.Continued(e, e == end);
return null;
}
}
已解决:
就像@symbiont一样,我找出了为什么总是得到包含单个元素的集合的原因。但是我认为还可以。所以我改变了
public Path[] GetPath() => FindPath(Path.FromStart(start)).ToArray();
到
public Path GetPath() => FindPath(Path.FromStart(start)).SingleOrDefault() ?? Path.Empty;
然后,我也专注于发现的方向优先级问题。我想出了通过更改来强制算法选择更短的方法
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.Select(i => GetContinuedPath(path, i));
}
到
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.OrderBy(i => Math.Sqrt(Math.Pow(end.X - i.X, 2) + Math.Pow(end.Y - i.Y, 2)))
.Select(i => GetContinuedPath(path, i));
}
所以我将程序更改为
var maze = MazeFactory.FromPattern(Patterns.Maze3, Patterns.WallSymbol);
var a = maze.GetElementAt(0, 1);
var b = maze.GetElementAt(12, 0);
var printer = new PathPrinter(ConsoleColor.Yellow, ConsoleColor.Gray, ConsoleColor.Black);
Console.WriteLine("Maze");
printer.Print(maze, Path.Empty);
Console.WriteLine("A -> B");
printer.Print(maze, new PathFinder(maze, a, b).GetPath());
Console.WriteLine("B -> A");
printer.Print(maze, new PathFinder(maze, b, a).GetPath());
并获得了预期的输出:
答案 0 :(得分:3)
由于我有自己的答案,因此决定将其正确发布。
@symbiont的答案仍然是公认的答案。
就像@symbiont一样,我找出了为什么总是得到包含单个元素的集合的原因。但是我认为还可以。所以我改变了
public Path[] GetPath() => FindPath(Path.FromStart(start)).ToArray();
到
public Path GetPath() => FindPath(Path.FromStart(start)).SingleOrDefault() ?? Path.Empty;
然后,我也专注于发现的方向优先级问题。我想出了通过更改来强制算法选择更短的方法
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.Select(i => GetContinuedPath(path, i));
}
到
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.OrderBy(i => Math.Sqrt(Math.Pow(end.X - i.X, 2) + Math.Pow(end.Y - i.Y, 2)))
.Select(i => GetContinuedPath(path, i));
}
所以我将程序更改为
var maze = MazeFactory.FromPattern(Patterns.Maze3, Patterns.WallSymbol);
var a = maze.GetElementAt(0, 1);
var b = maze.GetElementAt(12, 0);
var printer = new PathPrinter(ConsoleColor.Yellow, ConsoleColor.Gray, ConsoleColor.Black);
Console.WriteLine("Maze");
printer.Print(maze, Path.Empty);
Console.WriteLine("A -> B");
printer.Print(maze, new PathFinder(maze, a, b).GetPath());
Console.WriteLine("B -> A");
printer.Print(maze, new PathFinder(maze, b, a).GetPath());
并获得了预期的输出:
答案 1 :(得分:2)
您正在函数FindPath中使用深度优先搜索,并且还将您遇到的所有点都标记为elementIsChecked,这是您第一次遇到它们时。标记后,您将不再关注这些点。
但是,由于您使用的是深度优先搜索,因此在完成长路径时,最终可能会遇到一个点。然后,将其标记为elementIsChecked之后,其他路径将不再使用该点。长路径最终会阻塞随后的短路径。
之所以从A-> B开始走很长的路,是因为您在PathFinder.GetContinuations函数中优先考虑了这些方向:上,下,左,右。在(8,1),它选择向下,因为right的优先级较低。 (使用条件断点可以节省很多麻烦)
因此,您实际上无法再次访问的唯一点就是路径上已经存在的点。一个简单的解决方法是摆脱PathFinder.elementIsChecked并在PathFinder.GetContinuedPath中替换:
if (e == null || elementIsChecked[e])
使用
if (e == null || path.Elements.Contains(e))
然后,每个路径继续,直到其自身阻塞为止。效率不高,但我想这很容易解决。
您可能应该使用“广度优先搜索”,并以相同的速率向所有可能的方向扩展,例如池塘中的涟漪,并在到达目的地后停止扩展