递归树搜索

5
考虑到一个5x5的网格,需要记录每个起始方格通过每个后续方格的所有可能的骑士移动组合。
我将其设想为类似于二叉树的结构,但由于棋盘上的每个方块都可能有超过2个的下一步潜在移动,所以我认为这种方法行不通。我研究了A* /路径规划算法,但据我所见,它们都需要一个结束节点来运行,而我不知道结束节点在每次运行时都会发生变化。
到目前为止,我的伪代码如下:
For each square on the board
    Check if this key has potential moves
        If Potential moves
            <Some way of selecting a next move (this could be the square we just originated from too!)>
            Register this move into a collection we can check against for subsequent                             moves
            Recursively call function with the square we just landed on 
        Else Continue
End

任何建议/指导都将不胜感激,因为我非常迷茫!

你可能想要研究一下链表数据结构。你是否与你的教授核实过你的伪代码是否正确呢?由于我以前从未做过这样的项目,那么什么情况下算是一个有效的移动呢?只有连接到骑士所在方格的网格上的方格是正确的吗? - Security Hound
你被要求列出所有旅游路线,还是计算到达每个方格的方式数量,或者其他什么?如果你被要求列出所有旅游路线,那么你列出它们的顺序是否重要? - Gareth McCaughan
3个回答

3

好的,这里将会有大量可能的移动序列,正如评论所提到的。

非递归版本:您需要一个位置列表的列表(称为位置列表),这将是您的最终答案,我将称其为路线列表。

为每个起始位置创建一个列表,并将它们全部放入路线列表中。

While the routes list has a position list that's less than the required length
{
    Get a position list that's too short.
    Remove it from the routes list
    Create new position lists that are a copy of the list we just removed + a possible next position from the last position in the list.
    Add those lists to the routes list.
}

编辑:递归版本:

using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static int GridSize = 5;

        static void Main(string[] args)
        {
            List<List<Point>> Positions = (from X in Enumerable.Range(0, GridSize)
                                           from Y in Enumerable.Range(0, GridSize)
                                           select new List<Point>() { new Point(X, Y) }).ToList();

            var PossibleRoutes = WalkGraph(Positions, 5);
        }

        static List<List<Point>> WalkGraph(List<List<Point>> RoutesList, int DesiredLength)
        {
            List<List<Point>> Result = new List<List<Point>>();

            foreach (var Route in RoutesList)
            {
                if (Route.Count < DesiredLength)
                {
                    // Extend the route (produces a list of routes) and recurse
                    Result.AddRange(WalkGraph(ExtendRoute(Route), DesiredLength));
                }
                else
                {
                    Result.Add(Route);
                }
            }

            return Result;
        }

        static List<List<Point>> ExtendRoute(List<Point> Route)
        {
            List<List<Point>> NextMoveRoutes = new List<List<Point>>();

            // Itterate through each possible move
            foreach (var NextMove in PossibleMoves(Route.Last()))
            {
                // Create a copy of the route, and add this possible move to it.
                List<Point> NextMoveRoot = new List<Point>(Route);
                NextMoveRoot.Add(NextMove);
                NextMoveRoutes.Add(NextMoveRoot);
            }

            return NextMoveRoutes;
        }

        static List<Point> PossibleMoves(Point P)
        {
            // TODO Generate a list of possible places to move to

            List<Point> Result = new List<Point>();

            Result.Add(new Point(P.X + 1, P.Y + 2));
            Result.Add(new Point(P.X - 1, P.Y + 2));
            Result.Add(new Point(P.X + 1, P.Y - 2));
            Result.Add(new Point(P.X - 1, P.Y - 2));

            Result.Add(new Point(P.X + 2, P.Y + 1));
            Result.Add(new Point(P.X - 2, P.Y + 1));
            Result.Add(new Point(P.X + 2, P.Y - 1));
            Result.Add(new Point(P.X - 2, P.Y - 1));

            Result.RemoveAll(PossibleMove => PossibleMove.X < 0 || PossibleMove.X > GridSize ||
                                             PossibleMove.Y < 0 || PossibleMove.Y > GridSize);

            return Result;
        }
    }
}

编辑:以下是使用IEnumerable版本,以消除初始计算时间,并大幅减少内存占用。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static int GridSize = 5;

        static void Main(string[] args)
        {
            IEnumerable<IEnumerable<Point>> Positions = from X in Enumerable.Range(0, GridSize)
                                                        from Y in Enumerable.Range(0, GridSize)
                                                        select new List<Point>() { new Point(X, Y) } as IEnumerable<Point>;

            var PossibleRoutes = WalkGraph(Positions, 100);

            foreach (var Route in PossibleRoutes)
            {
                Console.WriteLine(Route.Select(P => P.ToString()).Aggregate((curr, next) => curr + " " + next));
                //Thread.Sleep(500); // Uncomment this to slow things down so you can read them!
            }

            Console.ReadKey();
        }

        static IEnumerable<IEnumerable<Point>> WalkGraph(IEnumerable<IEnumerable<Point>> RoutesList, int DesiredLength)
        {
            foreach (var Route in RoutesList)
            {
                if (Route.Count() < DesiredLength)
                {
                    // Extend the route (produces a list of routes) and recurse
                    foreach (var ExtendedRoute in WalkGraph(ExtendRoute(Route), DesiredLength))
                        yield return ExtendedRoute;
                }
                else
                {
                    //Result.Add(Route);
                    yield return Route;
                }
            }
        }

        static IEnumerable<IEnumerable<Point>> ExtendRoute(IEnumerable<Point> Route)
        {
            // Itterate through each possible move
            foreach (var NextMove in PossibleMoves(Route.Last()))
            {
                // Create a copy of the route, and add this possible move to it.
                List<Point> NextMoveRoute = new List<Point>(Route);
                NextMoveRoute.Add(NextMove);
                yield return NextMoveRoute;
            }
        }

        static IEnumerable<Point> PossibleMoves(Point P)
        {
            List<Point> Result = new List<Point>();

            Result.Add(new Point(P.X + 1, P.Y + 2));
            Result.Add(new Point(P.X - 1, P.Y + 2));
            Result.Add(new Point(P.X + 1, P.Y - 2));
            Result.Add(new Point(P.X - 1, P.Y - 2));

            Result.Add(new Point(P.X + 2, P.Y + 1));
            Result.Add(new Point(P.X - 2, P.Y + 1));
            Result.Add(new Point(P.X + 2, P.Y - 1));
            Result.Add(new Point(P.X - 2, P.Y - 1));

            Result.RemoveAll(PossibleMove => PossibleMove.X < 0 || PossibleMove.X > GridSize ||
                                             PossibleMove.Y < 0 || PossibleMove.Y > GridSize);

            return Result as IEnumerable<Point>;
        }
    }
}

在我看到你编辑过的帖子之前,已经有评论了,让我看一下,然后再重新评论。 - Gary
非常感谢,这种迭代的方式似乎确实可行(尽管一旦达到一定数量的序列,内存使用量就会很大,但出于显而易见的原因 - 有大量递归进行)。 - Gary
@Gary,我已经编辑了我的答案,包括使用IEnumerable的版本。根据您的用途,它可能会消除内存使用问题。 - George Duckett

1

虽然深度优先搜索可以工作,但我认为你可以用广度优先搜索做得更好(更快),因为你实际上不需要生成移动,你只需要计算移动次数。

如果你可以创建一个矩阵,其中包含当你进行第(n-1)次移动时可能分支的计数数量,那么你可以使用它来计算第n次移动的可能分支计数。

在迭代0中,该矩阵只是一个由1组成的矩阵,因为你还没有移动棋子:

    table0

   1 1 1 1
   1 1 1 1
   1 1 1 1
   1 1 1 1

我们将上面的表格命名为table0,因为这是第0步。要从table0得到table1,您需要执行table1[r1c1] = table0[r2c3] + table0[r3c2],因为只有在r2c3和r3c2中的骑士才能到达r1c1。另一个例子,要找到table1[r2c2] = table0[r1c4] + table0[r3c4] + table0[r4c3] + table0[r4c1],因为只有在r1c4、r3c4、r4c3、r4c1中的骑士才能到达r2c2。以此类推。

使用这个算法,表格的下几个值如下:

    table1

   2 3 3 2
   3 4 4 3
   3 4 4 3
   2 3 3 2




    table2

   8 10 10  8
  10 10 10 10
  10 10 10 10
   8 10 10  8




    table3

  20 30 30 20
  30 36 36 30
  30 36 36 30
  20 30 30 20




    table4

  72  96  96 72
  96 100 100 96
  96 100 100 96
  72  96  96 72



    table5

  200 292 192 200
  192 336 336 192
  192 336 336 192
  200 192 192 200

所以基本上在这个4x4游戏中,这将是计算下一个网格的公式:

last = table from previous iteration
next = create new table of zeros

// corners
next[r1c1] = last[r2c3] + last[r3c2]
next[r1c4] = last[r2c2] + last[r3c3]
next[r4c1] = last[r2c3] + last[r3c2]
next[r4c4] = last[r3c3] + last[r3c3]

// sides, clockwise
next[r1c2] = last[r3c1] + last[r3c3] + last[r2c4]
next[r1c3] = last[r3c2] + last[r3c4] + last[r2c1]
next[r2c4] = last[r1c2] + last[r3c2] + last[r4c3]
next[r3c4] = last[r2c2] + last[r4c2] + last[r1c3]
next[r4c3] = last[r2c2] + last[r2c4] + last[r3c1]
next[r4c2] = last[r2c1] + last[r2c3] + last[r3c4]
next[r3c1] = last[r2c3] + last[r4c3] + last[r1c2]
next[r2c1] = last[r1c3] + last[r3c3] + last[r4c2]

// middle four
next[r2c2] = last[r1c4] + last[r3c4] + last[r4c1] + last[r4c3]
next[r2c3] = last[r1c1] + last[r3c1] + last[r4c2] + last[r4c4]
next[r3c2] = last[r1c1] + last[r1c3] + last[r2c4] + last[r4c4]
next[r3c3] = last[r2c1] + last[r4c1] + last[r1c2] + last[r1c4]

这将需要常数内存O(1),操作次数取决于移动次数的线性O(n)。注意:您需要检查此算法是否实际有效,我并没有考虑它是否正确计算了计数。


我非常喜欢这种方法。为了使其适用于任何大小,您可以循环遍历表中的每个点,并为该点的每个可能移动增加目标点。但我不确定这是否正是OP想要的,因为这只会告诉您每个位置在所有路线中遇到的次数。我有一种困扰的感觉,这个想法可以被改编来解决问题,但我无法完全理解它。 - George Duckett

0

所以这可以通过深度优先搜索(DFS)完成。我相当确定有更快的方法,因为DFS会生成每条路径,而且有O(2^{count})条路径。这里是一些Python代码,只需从每个起始点DFS每条路径。

def extended_knight_tours(width, height, count):
    start_x, start_y = -1, -1

    def dfs(x, y, moves_left):
        if not (0 <= x < width and 0 <= y < height):
            return 0
        if moves_left == 0:
            if (start_x, start_y) == (x, y):
                return 1
            else:
                return 0

        knight_moves = [(1, 2), (-1, 2), (1, -2), (-1, -2),
                        (2, 1), (2, -1), (-2, 1), (-2, -1)]

        return sum(dfs(x + dx, y + dy, moves_left - 1)
                   for dx, dy in knight_moves)

    ans = 0
    for x in range(width):
        for y in range(height):
            start_x = x
            start_y = y
            ans += dfs(x, y, count)

    return ans

一个简单的时间与空间权衡是可以进行DFS记忆化(记得为每个起始位置清除缓存)来加速。

通过对这个函数的试玩,我注意到每个奇数计数的答案都是零。因此,一个可能更快的算法是找到每个起始位置长度为count/2的路径数量(不一定是路线)。然后使用count/2的值计算以该位置为中点的路径数量,但我会把它留给读者作为练习 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接