【问题标题】:Algorithmic solution of card puzzle卡牌谜题算法解法
【发布时间】:2014-03-09 21:59:30
【问题描述】:

Given 是一款包含九张方形卡片的益智游戏。
每张卡片的顶部、右侧、底部和左侧各有 4 张图片。
卡片上的每张图片都描绘了动物(鳄鱼)的前部或后部。每张图片有 5 种颜色中的一种。

目标:将九张卡片排列成 3x3 的网格,使所有“内部”(完整)鳄鱼与相邻卡片正确组合,即具有前端和后端以及匹配的颜色。

为了直观地理解问题,这里是拼图的图片:

我手动找到了描述的解决方案。
尽管这个拼图乍一看很简单,但由于您可以以 4 种不同的方式旋转每个拼图,因此有非常多的组合。

现在的问题是,我想要一个算法生成所有可能的 3x3 布局,以便检查所有可能的解决方案(如果还有其他解决方案)。最好在 Processing/Java 中。

目前的想法:
我的方法是用一个由 4 个整数组成的数组来表示 9 个片段中的每一个,代表一个片段的 4 个旋转状态。然后生成这 9 个片段的所有可能排列,从一个片段数组中选择 4 个旋转状态中的 1 个。然后,函数isValidSolution() 可以检查解决方案是否违反约束(颜色匹配和前后匹配)。

关于如何实现这一点的任何想法?

【问题讨论】:

    标签: algorithm permutation puzzle


    【解决方案1】:

    可以找到所有解决方案,尽量不要探索搜索树的所有不成功路径。下面的 C++ 代码没有经过高度优化,几乎可以在我的计算机上立即找到 2 个解决方案(结果证明是相同的唯一解决方案,因为有一个重复的图块,正确答案?)。

    这里避免探索所有可能性的技巧是在我们仍在放置瓷砖时调用函数isValidSolution()(该函数处理空瓷砖)。另外,为了加快这个过程,我按照给定的顺序放置瓷砖,从中间开始,然后是左、右、上和下的十字,然后是左上角、右上角、下角-左和右下。可能其他组合可以更快地执行。

    当然可以优化这一点,因为这个谜题中的特殊模式分布(带有字母的模式只接受一个可能的匹配项),但这超出了我的回答范围。

    #include<iostream>
    
    // possible pattern pairs (head, body)
    #define PINK    1
    #define YELLOW  2
    #define BLUE    3
    #define GREEN   4
    #define LACOSTE 5
    
    typedef int8_t pattern_t; // a pattern is a possible color, positive for head, and negative for body
    typedef struct {
        pattern_t p[4]; // four patterns per piece: top, right, bottom, left
    } piece_t;
    
    unsigned long long int solutionsCounter = 0;
    
    piece_t emptyPiece = {.p = {0,  0,  0, 0} };
    
    piece_t board[3][3] = {
        { emptyPiece, emptyPiece, emptyPiece},
        { emptyPiece, emptyPiece, emptyPiece},
        { emptyPiece, emptyPiece, emptyPiece},
        };
    
    inline bool isEmpty(const piece_t& piece) {
        bool result = (piece.p[0] == 0);
        return result;
    }
    
    // check current solution
    bool isValidSolution() {
        int i, j;
        for (i = 0; i < 2; i++) {
            for (j = 0; j < 3; j++) {
                if (!isEmpty(board[i][j]) && !isEmpty(board[i+1][j]) && (board[i][j].p[1] != -board[i+1][j].p[3])) {
                    return false;
                }
            }
        }
        for (i = 0; i < 3; i++) {
            for (j = 0; j < 2; j++) {
                if (!isEmpty(board[i][j]) && !isEmpty(board[i][j+1]) && (board[i][j].p[2] != -board[i][j+1].p[0])) {
                    return false;
                }
            }
        }
        return true;
    }
    
    // rotate piece
    void rotatePiece(piece_t& piece) {
        pattern_t paux = piece.p[0];
        piece.p[0] = piece.p[1];
        piece.p[1] = piece.p[2];
        piece.p[2] = piece.p[3];
        piece.p[3] = paux;
    }
    
    void printSolution() {
        printf("Solution:\n");
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                printf("\t  %2i  ", (int) board[j][i].p[0]);
            }
            printf("\n");
            for (int j = 0; j < 3; j++) {
                printf("\t%2i  %2i", (int) board[j][i].p[3], (int) board[j][i].p[1]);
            }
            printf("\n");
            for (int j = 0; j < 3; j++) {
                printf("\t  %2i  ", (int) board[j][i].p[2]);
            }
            printf("\n");
        }
        printf("\n");
    }
    
    bool usedPiece[9] = { false, false, false, false, false, false, false, false, false };
    int colocationOrder[9] = { 4, 3, 5, 1, 7, 0, 2, 6, 8 };
    
    void putNextPiece(piece_t pieces[9], int pieceNumber) {
    
        if (pieceNumber == 9) {
            if (isValidSolution()) {
                solutionsCounter++;
                printSolution();
            }
        } else {
            int nextPosition = colocationOrder[pieceNumber];
            int maxRotations = (pieceNumber == 0) ? 1 : 4; // avoids rotation symmetries.
            for (int pieceIndex = 0; pieceIndex < 9; pieceIndex++) {
                if (!usedPiece[pieceIndex]) {
                    usedPiece[pieceIndex] = true;
                    for (int rotationIndex = 0; rotationIndex < maxRotations; rotationIndex++) {
                        ((piece_t*) board)[nextPosition] = pieces[pieceIndex];
                        if (isValidSolution()) {
                            putNextPiece(pieces, pieceNumber + 1);
                        }
                        rotatePiece(pieces[pieceIndex]);
                    }
                    usedPiece[pieceIndex] = false;
                    ((piece_t*) board)[nextPosition] = emptyPiece;
                }
            }
        }
    }
    
    int main() {
    
        // register all the pieces (already solved, scramble!)
        piece_t pieces[9] = {
            {.p = { -YELLOW,    -BLUE,     +GREEN,  +PINK} },
            {.p = { -YELLOW,    -GREEN,    +PINK,   +BLUE} },
            {.p = { -BLUE,      -YELLOW,   +PINK,   +GREEN }},
            {.p = { -GREEN,     -BLUE,     +PINK,   +YELLOW }},
            {.p = { -PINK,      -LACOSTE,  +GREEN,  +BLUE }},
            {.p = { -PINK,      -BLUE,     +GREEN,  +LACOSTE }},
            {.p = { -PINK,      -BLUE,     +PINK,   +YELLOW }},
            {.p = { -GREEN,     -YELLOW,   +GREEN,  +BLUE }},
            {.p = { -GREEN,     -BLUE,     +PINK,   +YELLOW }}
        };
    
        putNextPiece(pieces, 0);
    
        printf("found %llu solutions\n", solutionsCounter);
    
        return 0;
    }
    

    【讨论】:

    • 可以使用数组int availabilityPerPiece[] 而不是bool usedPiece[] 来处理重复的图块,并且只能找到一种解决方案。
    • 谢谢!立即计算解决方案。我真的认为需要相当长的时间才能完成所有 950 亿个组合。而且我没有意识到重复的瓷砖......很好发现!
    • 您好,您能告诉我如何进一步优化此解决方案吗?
    【解决方案2】:

    只有 9 块,因此每个潜在的解决方案都可以用一个小结构来表示(比如 3x3 的块数组,每个块都有它的旋转),所以对这些块的准确描述并不是太重要。

    尝试所有可能的排列是浪费的(在这里滥用 LaTeX,将 9 块放在网格上可以 9 美元!$ 订单完成,因为每个可以有 4 个不同的方向,总共需要 9 美元!\ cdot 4^9 = 95126814720 \大约 10^{11}$,有点太多了,无法全部检查)。你会用手做的是放置一块,比如在左上角,然后尝试通过将匹配的部分安装到其余部分来完成正方形。因此,您永远不会考虑第一个和第二个部分不匹配的任何组合,从而大大减少了搜索。这种想法称为回溯。为此,您需要描述部分解决方案(3x3 网格,已填充的部分和空白位置,以及尚未使用的部分;填充网格的特定顺序),一种前进的方式(放置下一块如果合适,则跳过那个,如果不合适)并向后(找不到任何合适,撤消上一步并尝试下一种可能性)。

    显然,您必须设计一种方法来确定是否存在潜在的匹配项(鉴于已填充的邻居,请在指定位置尝试一块棋子的所有方向)。对于这样一个小问题,这可能不是性能关键,但如果你尝试解决,比如 100x100,情况就不同了......

    【讨论】:

    • 正确的可能性数是:9!*4^9 = 95126814720
    • 并且省略旋转对称(相当于避免旋转第一个瓦片),可能性的数量减少到9!*4^8 = 23781703680
    猜你喜欢
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-01
    • 2011-12-09
    • 1970-01-01
    • 2011-11-03
    相关资源
    最近更新 更多