【问题标题】:Store Grid N*N into an Adjacency Graph? Position and Neighbors将网格 N*N 存储到邻接图中?位置和邻居
【发布时间】:2021-11-15 09:23:17
【问题描述】:

再次更新这篇文章。

这次是为了让事情更清楚。 我正在尝试解析大小为 9x9Grid,但这个大小会随着时间的推移而改变,它不是固定的。这是一款名为Quoridor 的棋盘游戏。我可以使用的是Board 课程。这为我提供了以下horizontal bool[,]vertical bool[,],我可以遍历每个并打印出x, y 位置。但这些会有所不同,具体取决于它是水平方向,还是垂直方向和位置

玩家可以仅移动一步向北、向南、向西或向东移动。另一个玩家(人类)可以放置一堵水平或垂直覆盖两个街区的墙(障碍物)。我的自动播放器必须从棋盘上构建一个节点图,并根据棋盘上的变化和它自己的位置刷新图表。例如,如果玩家不能从当前位置向左走,那么连接两个节点之间的一条边将被删除,除非是由障碍物引起的。然后 BFS 将再次针对 Graph 运行并返回该(自动)玩家使用并执行其移动的新位置 (x, y)。

9x9 网格上的每个块,将代表Graph 中的一个 Node。这意味着Graph 中的顶点或节点数 List 将是 9x9=81。每个Nodes 都持有size 4 中的list2D array,用于代表NorthSouthWest 和East,可以是 bool 类型。

现在,我提供了我编写的Graph 类的示例代码以及Node 类。我希望这里的最新信息能说明问题。我已经实现了 BFS 算法。但这部分是我无法正确理解的。为了一些想法,我观看了这个视频:https://www.youtube.com/watch?v=KiCBXu4P-2Y



代码

class Graph<T>
{        
    private int _V;
    private List<T>[] _Nodes;

    public Graph(int v) 
    {
        _V = v;
        _Nodes = new List<T>[v];
        for (int i = 0; i < _Nodes.Length; i++)
            _Nodes [i] = new List<T>();
    }

    public IEnumerable<T> Nodes(int v) { return (IEnumerable<T>)_Nodes[v];}
    public int V { get => _V; }
    public bool EdgeExists(){}
    public void AddEdge(Node n, T u, T w){}
    public void RemoveEdge(){}
}

和,

class Node<T>
{        
    private int _E;    
    private List<T>[] _Adj;

    public Node(int e) 
    {
        _E = e;
        _Adj = new List<T>[e];
        for (int i = 0; i < _Adj.Length; i++)
            _Adj [e] = new List<T>();
    }

    public IEnumerable<T> Adj(int e) { return (IEnumerable<T>)_Adj[e];}
    public int E { get => _E; }

    public void AddEdge(Node n, T u, T w){}
    public void RemoveEdge(T value){}
}

我阅读了以下 SO 主题:

【问题讨论】:

  • 您真正想做的是什么?没有任何背景信息,很难判断您是否走在正确的轨道上。
  • 你取第一个 0,0,然后存储它最近的邻居。
  • @JonasH,请参阅我的编辑。感谢您的提问。
  • @JonasH,你认为这是正确的方向吗?我正在尝试为 Quoridor 游戏构建一个包含位置和邻居的 9x9 网格图。
  • _我不明白,如何将此信息存储在节点和邻居为 4 的图中。_ 你不明白。您将节点存储在图中(可以是一个简单的列表)。每个节点都存储有关其邻居的信息。

标签: c# matrix graph


【解决方案1】:

这是构建与二维数组具有相同布局的邻接矩阵的一种方法:

internal record Graph
{
    public List<Node> Nodes { get; set; }

    public Graph(int numberOfTiles)
    {
        var matrixSize = (int)Math.Sqrt(numberOfTiles);
        var rows = matrixSize;
        var columns = matrixSize;
        var nodes = new Node[rows, columns];
        for (int row = 0; row < rows; row++)
        {
            for (int column = 0; column < columns; column++)
            {
                nodes[row, column] = new Node(row, column);
            }
        }

        Nodes = new List<Node>(rows * columns);
        foreach (var node in nodes)
        {
            var row = node.Row;
            var column = node.Column;
            if (row > 0) node.West = nodes[row - 1, column];
            if (column > 0) node.North = nodes[row, column - 1];
            if (row < rows - 1) node.East = nodes[row + 1, column];
            if (column < columns - 1) node.South = nodes[row, column + 1];
            Nodes.Add(node);
        }

    }
}

internal record Node
{
    public int Row { get; }
    public int Column { get; }
    public Node[] Neighbors { get; } = new Node[4];
    public Node North
    {
        get => Neighbors[0];
        set => Neighbors[0] = value;
    }
    public Node East
    {
        get => Neighbors[1];
        set => Neighbors[1] = value;
    }
    public Node South
    {
        get => Neighbors[2];
        set => Neighbors[2] = value;
    }
    public Node West
    {
        get => Neighbors[3];
        set => Neighbors[3] = value;
    }
    public Node(int row, int column)
    {
        Row = row;
        Column = column;
    }
}

这适用于 BFS。边/边界由节点的邻居为空来定义。要创建边,只需将适当的邻居设置为空。例如要创建一条水平边,将西节点的东邻居设置为空,将东节点的西邻居设置为空。

【讨论】:

  • @JohnSmith 为什么你的节点类是通用的?
  • @JohnSmith 是的,这正是我的代码所做的。
  • @JohnSmith 关于泛型,类型参数T 在正常使用中会是什么?似乎 T 是另一个节点,实际上不会按照您编写的方式编译
  • @JohnSmith 既然你在这个问题上悬赏,你能在悬赏到期之前选择一个答案吗?浪费这种声誉是没有意义的。如果您最终选择了我的答案,我将很乐意在聊天中提供进一步帮助
  • @John Smith 我的算法是 O(N),你没有比这更好的了。我的代码中的每个节点都有 4 条边。是什么让你说我的算法效率低下?
【解决方案2】:

如果我们有一个 9x9 方阵,我们可以执行以下操作: 第一步,用所有节点填充图形节点列表。第二步,做一个循环来处理邻居。如果我们说图形中间的节点,那么 currentNode.LeftNode = NodeList[currentNodePosition-1] currentNode.RightNode = NodeList[currentNodePosition+1] currentNode.TopNode = NodeList[currentNodePosition+9] currentNode.BottomNode = NodeList[currentNodePosition -9]。正如我之前所说,您应该处理上图中的空案例。我希望你明白这一点

【讨论】:

  • 你很接近,请参阅我的编辑。谢谢。
  • 我正在循环,一个水平和垂直的二维数组。我可以从中提取 (0,0)、(0,1) bool[,]。
  • 你有任何代码可以证明这是有效的,上面有更新信息吗?
  • @JohnSmith 他描述的算法是我的答案中代码的作用
【解决方案3】:

根据您所说的,我理解您的问题如下: 如何处理边缘上的节点,如 (x=0,y=0)、(x=9,y=5) 或 (x=9.y=9) ..... 你应该处理8个案例

对于左上角的情况,节点只有 2 个邻居,因此将顶部和左侧邻居设置为 Null

【讨论】:

  • 您好 ProWily,欢迎您!我试图弄清楚,Node 在 9x9 网格中有一个正方形,这意味着该图将包含 81 个正方形。每个节点有 4 条边来表示 (W, E, N, S) = West, East, North, South。而且我不明白,如何将这些信息存储在节点和邻居为 4 的图中。我已经编写了 Graph 和节点的代码并将其发布在这里。
  • ProWily,我现在看了这个链接,看起来很有趣,stackoverflow.com/questions/57582782/… 你在这里的回答,目前并没有真正解决问题。
  • 如果我们有一个 9x9 方阵,我们可以执行以下操作: 第一步,用所有节点填充图形节点列表。第二步,做一个循环来处理邻居。如果我们说图形中间的节点,那么 currentNode.LeftNode = NodeList[currentNodePosition-1] currentNode.RightNode = NodeList[currentNodePosition+1] currentNode.TopNode = NodeList[currentNodePosition+9] currentNode.BottomNode = NodeList[currentNodePosition -9]。正如我之前所说,您应该处理上图中的空案例。我希望你明白这一点
  • 如果您使用节点填充列表,它将帮助您根据索引计算邻居。我的想法是图表列表将使用 81 个节点对象进行初始化。之后开始计算邻居
  • 所以你创建一个属性 Edge 来连接节点?所以你的意思是节点之间没有直接连接?
【解决方案4】:

要将排列在 2D 网格中的节点(以及相关数据)存储到邻接图中,我更喜欢分三步完成。

1 将节点数据读入二维向量 2 将节点添加到图类 2 扫描向量,将相邻节点之间的链接添加到图类

假设输入是一个文本文件,其中节点数据以空格分隔的正交醇行排列(由 'o' 表示)。对于 3 x 3,它可能看起来像这样

o 1 2 3
o 2 5 2
o 3 2 1

一些 C++ 代码将其读入向量

            std::vector<std::vector<float>> grid;
            int RowCount = 0;
            int ColCount = -1;
            int start = -1;
            int end = -1;
            std::string line;
            while (std::getline(myFile, line))
            {
                std::cout << line << "\n";
                auto token = ParseSpaceDelimited(line);
                if (!token.size())
                    continue;
                switch (token[0][0])
                {
                case 'o':
                {
                    if (ColCount == -1)
                        ColCount = token.size() - 1;
                    else if (token.size() - 1 != ColCount)
                        throw std::runtime_error("Bad column count");
                    std::vector<float> row;
                    for (int k = 1; k < token.size(); k++)
                        row.push_back(atof(token[k].c_str()));
                    grid.push_back(row);
                }
                ... other cases if needed to parse other kinds of input data

现在我们可以将节点添加到图形类中。我有一个方便的方法(正交GridNodeName),它为位于网格上的节点提供人类可读的名称

        cGraph myFinder;
        RowCount = grid.size();

        // add nodes at each grid cell
        for (int row = 0; row < RowCount; row++)
        {
            for (int col = 0; col < ColCount; col++)
            {
                int n = myFinder.findoradd(
                    orthogonalGridNodeName(row, col));
                 // TODO: add data for this node from grid
            }
        }

现在我们应该可以添加节点之间的链接了。这里我假设每个链接的成本为 1。

        // link cells orthogonally
        for (int row = 0; row < RowCount; row++)
            for (int col = 0; col < ColCount; col++)
            {
                int n = row * ColCount + col;

                if (fDirected)
                {
                    if (col > 0)
                    {
                        int left = row * ColCount + col - 1;
                        myFinder.addLink(n, left, 1);
                    }
                }
                if (col < ColCount - 1)
                {
                    int right = row * ColCount + col + 1;
                    myFinder.addLink(n, right, 1);
                }
                if (fDirected)
                {
                    if (row > 0)
                    {
                        int up = (row - 1) * ColCount + col;
                        myFinder.addLink(n, up, 1);
                    }
                }
                if (row < RowCount - 1)
                {
                    int down = (row + 1) * ColCount + col;
                    myFinder.addLink(n, down, 1);
                }
            }

我认为这应该很容易移植到 C#

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-11
    • 2020-08-15
    • 2016-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多