【问题标题】:How to solve black & white knights problem in 3×3 grid如何解决 3×3 网格中的黑白骑士问题
【发布时间】:2019-12-04 07:38:55
【问题描述】:

这是对人工智能知情和非知情搜索算法的测试。

我们有一个 3×3 的网格,其中 B 表示黑骑士,W 表示国际象棋的白骑士。

+---+---+---+ +---+---+---+ | W | | W | |乙| |乙| +---+---+---+ +---+---+---+ | | | | ----> | | | | +---+---+---+ +---+---+---+ |乙| |乙| | W | | W | +---+---+---+ +---+---+---+

B 和 W 可以像骑士棋子一样移动“L”。

将黑色放入当前白色方块并将白色放入当前黑色方块的最佳搜索算法是什么?

  1. 一颗星星
  2. BFS
  3. DFS
  4. 爬山

我真的很困惑,我不知道正确的选择。

【问题讨论】:

  • 回溯算法。
  • 请注意,无论您选择哪种算法,您都必须注意不要多次通过同一个节点,即通过同一个位置。传统上,国际象棋程序在分析棋子数量较少的位置时使用哈希表
  • 在我的理解中,爬山方法提供了一个局部最小值,而不是一个绝对最小值。它不应该在这里回答问题。
  • Damien tnx,这是一个测试,在 A* 和 BFS 中你更喜欢什么?
  • 很难回答。由于位置的数量相当少(756),您可以肯定一个简单的 DFS 在删除重复位置的情况下不会使用太多内存并且会相当快。 A* 算法可能更快,可能(或可能不)更难实现......在理想的世界中,您将实现两者并进行比较:)

标签: algorithm artificial-intelligence depth-first-search breadth-first-search a-star


【解决方案1】:

A* 应该是解决这个问题的合适算法。作为目标启发式,你可以使用 no。假设棋盘是空的,到达目标目的地所需的移动次数,这总是

【讨论】:

  • 一开始我也对A*的使用很有信心。然后,在尝试手动解决它之后,我发现马变得疯狂,并且在这种情况下很难找到一个好的启发式。您可以在我的帖子中查看国际象棋解决方案。
【解决方案2】:

一个重要的方面是可能的位置数量相当少:

420 = Binomial(8,2) * Binomial(6, 2)

结果是无论选择哪种算法,你都必须注意不要多次通过同一个节点,即通过同一个位置。传统上,国际象棋程序在分析棋子数量较少的位置时使用哈希表。在这里,考虑到少数可能的位置,可以选择完美散列,即没有串通。

关于算法:

  • 爬山方法提供了一个局部最小值,而不是一个绝对最小值。它不应该在这里回答问题
  • DFS(例如,通过回溯实现)在这里似乎不是最好的,因为您冒着深入错误路线的风险
  • A* 算法通常非常有效。但是,它依赖于对给定节点的良好启发式估计。在这里,在手动寻找解决方案之后,很明显骑士必须在很短的表面上转很长时间(疯马)。在这种情况下很难找到好的启发式方法
  • BFS 通常的缺点是它会生成大量内存,因此会花费大量时间访问这些节点。在这里,由于可能的节点(位置)数量有限并且由于散列,情况并非如此。此外,使用 BFS,一旦我们得到一个解决方案,我们就确定它是最短的一个

因此,我在 C++ 中实现了 BFS。结果是即时提供的。

下面给出16个半步:

+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| W |   | W |  |   |   | W |  |   |   | W |  |   |   |   |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
|   |   |   |  |   |   |   |  |   |   | B |  | W |   | B |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| B |   | B |  | B | W | B |  |   | W | B |  |   | W | B |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  

+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| B |   |   |  | B |   | W |  | B | B | W |  | B | B | W |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| W |   |   |  | W |   |   |  | W |   |   |  |   |   |   |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
|   | W | B |  |   |   | B |  |   |   |   |  |   |   | W |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  

+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| B |   | W |  | B | W | W |  | B | W | W |  | B | W |   |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
|   |   |   |  |   |   |   |  |   |   | B |  | W |   | B |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| B |   | W |  | B |   |   |  |   |   |   |  |   |   |   |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  

+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
|   | W |   |  |   | W |   |  |   | W | B |  |   |   | B |  | B |   | B |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
| W |   | B |  |   |   | B |  |   |   | B |  |   |   | B |  |   |   |   |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  
|   | B |   |  |   | B | W |  |   |   | W |  | W |   | W |  | W |   | W |
+---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  +---+---+---+  

还有节目:

#include    <iostream>
#include    <array>
#include    <vector>
#include    <tuple>
#include    <algorithm>

int depl[9][2] = {
    {5,7}, {6,8}, {3,7}, {2,8}, {-1,-1},
    {0,6}, {1,5}, {0,2}, {1,3}};

struct position {
    std::array<int, 2> B;
    std::array<int, 2> W;
    int hash;
    position *up = nullptr;
    position (int w0, int w1, int b0, int b1) {
        if (w0 > w1) std::swap (w0, w1);
        if (b0 > b1) std::swap (b0, b1);
        B[0] = b0;
        B[1] = b1;
        W[0] = w0;
        W[1] = w1;
        cal_hash();
    }
    position() {};
    void cal_hash () {
        hash = 1000*B[0] + 100*B[1] + 10*W[0] + W[1];
    }
    std::vector<position> gene_mov (int white_black) {
        std::vector<position> res;
        if (!white_black) {     // White
            for (int i = 0; i < 2; ++i) {
                for (int j = 0; j < 2; ++j) {
                    int pos = depl[W[i]][j];
                    bool test = (pos == W[1-i]) || (pos == B[0]) || (pos == B[1]);
                    if (!test) {
                        res.push_back (position(pos, W[1-i], B[0], B[1]));
                    }
                }
            }
        } else {                // Black
            for (int i = 0; i < 2; ++i) {
                for (int j = 0; j < 2; ++j) {
                    int pos = depl[B[i]][j];
                    bool test = (pos == B[1-i]) || (pos == W[0]) || (pos == W[1]);
                    if (!test) {
                        res.push_back (position(W[0], W[1], pos, B[1-i]));
                    }
                }
            }           
        }
        return res;
    }
    void print (int shift = 0) {
        char displ[3][3] = {{' ',' ',' '},
                            {' ',' ',' '},
                            {' ',' ',' '}};
        displ[1][1] = ' ';
        displ[2 - W[0]/3][W[0]%3] = 'W';
        displ[2 - W[1]/3][W[1]%3] = 'W';
        displ[2 - B[0]/3][B[0]%3] = 'B';
        displ[2 - B[1]/3][B[1]%3] = 'B';
        Wshift(shift);
        std::cout << "+---+---+---+\n";
        for (int i = 0; i < 3; ++i) {
            Wshift(shift);
            for (int j = 0; j < 3; j++) {
                std::cout << "| " << displ[i][j] << " ";
            }
            std::cout << "|\n";
            Wshift(shift);
            std::cout << "+---+---+---+\n";
        }
        std::cout << "\n";
    }
    void Wshift (int shift) {
        for (int i = 0; i < shift; ++i)
            std::cout << " ";
    }
};

std::tuple<bool, int, std::vector<position>> find_moves (position &first, position &end) {
    std::vector<position> moves;
    std::array<bool, 10000> pos_found = {false};
    std::vector<std::vector<position>> dfs;
    using pvector = std::vector<position>;
    dfs.push_back(pvector(1, first));

    int iter = 1;
    int white_black = 0;
    while (true) {
        int n = dfs[iter-1].size();
        if (n == 0) return std::make_tuple(false, 0, moves);
        dfs.push_back(pvector(0));
        for (int i = 0; i < n; ++i) {
            auto candidates = dfs[iter-1][i].gene_mov(white_black);
            for (auto &c: candidates) {
                if (pos_found[c.hash]) continue;
                c.up = &dfs[iter-1][i];
                if (c.hash == end.hash) {   // Last position attained: get the previous moves
                        moves.resize(iter+1);
                        moves[iter] = c;
                        for (int i = iter - 1; i >= 0; i--) {
                            moves[i] = *(moves[i+1].up);
                        }
                        return std::make_tuple(true, iter, moves);
                }
                pos_found[c.hash] = true;
                dfs[iter].push_back (c);
            }
        }
        iter++;
        white_black = 1 - white_black;
    }

    return std::make_tuple(false, -1, moves);
}

int main () {
    position first(6, 8, 0, 2);
    position end(0, 2, 6, 8);

    bool success;
    int n_iter;
    std::vector<position> moves;
    std::tie (success, n_iter, moves) = find_moves (first, end);
    std::cout << "success = " << success << "\n";
    std::cout << "n_iter = " << n_iter << "\n";

    for (auto &m: moves) {
        m.print();
        std::cout << "\n";
    }
    return 0;
}

【讨论】:

  • Damien,Tnx,你的回答太棒了
  • 请注意,我在代码中做了一个小的更正。通过确保w0 &lt; w1b0 &lt; b1,我提高了散列的效率。结果不受影响,可能效率有点影响
  • Damien,请您再解释一下 A* 中的“疯马”吗?这部分没看懂
  • 看看解决方案。马似乎在一个小区域内以一种不稳定的方式转动。在这种情况下,似乎很难找到一个好的启发式方法来猜测先验最有趣的路线是什么。 A* 算法基于这种启发式的使用。 A* 的另一个问题:当找到解决方案时,我们不能确定它是最短的。
猜你喜欢
  • 2022-01-23
  • 2013-11-25
  • 1970-01-01
  • 2017-07-30
  • 1970-01-01
  • 1970-01-01
  • 2020-10-28
  • 2017-04-11
  • 2010-09-30
相关资源
最近更新 更多