趣题.C-8皇后问题

描述

在8x8的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

思路

枚举8个皇后的位置:将皇后甲放到第1行的第1列,在棋盘上标记出皇后甲不能攻击到的位置,接着将皇后乙尝试着放到皇后甲不能攻击到的每一个位置,并在每一次尝试中,标记出皇后乙不能攻击到在每一个位置,然后放皇后丙……直到8个皇后都放完或没有皇后可放时输出结果。

我们先输出每一个解法的ASCII棋盘,接着输出一共有多少种解法。

代码

定义棋盘类型

棋盘的每一个格子有三种情况:

  • 放了皇后
  • 没放皇后但可以放
  • 没放皇后且不能放

我们用char来表示每个格子。因为以下两个原因:

  1. 省内存
  2. 输出ASCII棋盘时可以少做些转换
enum {
    QUEEN     = 'X', // 放了皇后
    AVAILABLE = '_', // 没放皇后但可以放
    UNSAFE    = '.'  // 没放皇后且不能放
};
typedef char Chessboard[8][8];

main()

我们先定义一个辅助函数:

void backtrack(unsigned nextQueenNo, Chessboard board);

它做以下事情:

  • 在一个已经填好\(nextQueenNo-1\)个皇后,且已经标注好哪里不能放皇后的棋盘board上,放置从第nextQueenNo个到第8个的皇后,输出所有解法,然后把解法数量放到一个全局变量total里面。

main()可以这样定义:

int main(int argc, char **argv) {
    Chessboard board;
    memset(board, AVAILABLE, sizeof(Chessboard));
    
    backtrack(1, board)
    printf("Total: %d\n", total);

    exit(0);
}

它做以下事情:

  1. 初始化一个空棋盘
  2. 输出在这个棋盘上放置第1个到第8个皇后有多少种方法

注意:ASCII棋盘的输出不在main()中进行,而是在backtrack()中进行。

backtrack()

backtrack函数做以下事情:

total = 0
backtrack(nextQueenNo, board)
    if (board已经填好8个皇后了)
        输出
        ++total
    else
        total = 0
        for i in board的所有没放但可放皇后的格子
            newBoard = board
            在newBoard的i位置放置皇后
            给newBoard中,由于在i位置放皇后而导致变成UNSAFE的格子加标签

我们先实现一些复杂的部分,再实现整个函数:

  • 输出
    我们给board稍微美化下,加上|标记列,加上\n标记行。

    int r, c;
    for (r = 0; r < 8; ++r) {
        for (c = 0; c < 8; ++c) {
            printf("%s%c%s",
                   (c == 0) ? "|" : "",
                   board[r][c],
                   (c == 7) ? "|\n" : "|");
        }
    }
    printf("\n");
  • 给newBoard中,由于在i位置放皇后而导致变成UNSAFE的格子加标签
    有哪些格子会变成UNSAFE呢?有三种:
  1. 与皇后同行
  2. 与皇后同列
  3. 与皇后同一对角线
    因为前面两种都很好实现,所以我们只实现第三种。
    我们先引入一个真命题:
    若点\((x,y)\)与点\((x',y')\)处于同一对角线,则:\[|x'-x|=|y'-y|\]
    有了这个命题就好办了。

    for (i = 1; i < 8; ++i) {
        CHECK_AND_MARK(r + i, c + i);
        CHECK_AND_MARK(r + i, c - i);
        CHECK_AND_MARK(r - i, c + i);
        CHECK_AND_MARK(r - i, c - i);
    }

    其中,CHECK_AND_MARK宏表示先检查\((r,c)\)是否在board内,若是则在此处标上UNSAFE。

完整实现:

void backtrack(unsigned nextQueenNo, Chessboard board) {
    if (nextQueenNo == 9) {
        int r, c;
        for (r = 0; r < 8; ++r) {
            for (c = 0; c < 8; ++c) {
                printf("%s%c%s",
                       (c == 0) ? "|" : "",
                       board[r][c],
                       (c == 7) ? "|\n" : "|");
            }
        }
        printf("\n");        
        
        ++total;
    } else {
        int r, c;

        for (r = nextQueenNo - 1; r < 8; ++r) {
            for (c = 0; c < 8; ++c) {
                if (board[r][c] != AVAILABLE) continue;
                
                Chessboard newBoard;
                memcpy(newBoard, board, sizeof(Chessboard));
                newBoard[r][c] = QUEEN;

                int rr, cc, i;
                for (cc = 0; cc < 8; ++cc) { // 行
                    MARK(r, cc);
                }
                for (rr = 0; rr < 8; ++rr) { // 列
                    MARK(rr, c);
                }
                for (i = 1; i < 8; ++i) { // 对角线
                    CHECK_AND_MARK(r + i, c + i);
                    CHECK_AND_MARK(r + i, c - i);
                    CHECK_AND_MARK(r - i, c + i);
                    CHECK_AND_MARK(r - i, c - i);
                }

                backtrack(nextQueenNo + 1, newBoard);
            }
        }
    }
}

其中MARK,CHECK_AND_MARK两个宏的实现:

#define MARK(r, c) do { \
 if (newBoard[r][c] == AVAILABLE) { \
 newBoard[r][c] = UNSAFE; \
 } \
} while (0)

#define CHECK_AND_MARK(r, c) do { \
 if (0 <= r && r < 8 && 0 <= c && c < 8 && \
 newBoard[r][c] == AVAILABLE) { \
 newBoard[r][c] = UNSAFE; \
 } \
} while (0)

完整代码及输出

在线查看:http://codepad.org/ujLqa4Mj

或者点这里下载文件:http://files.cnblogs.com/files/jt2001/8queens.7z

文件: G:\8queens.7z
大小: 1680 字节
修改时间: 2016年2月 4日, 12:07:10
MD5: 6747715D1B3F92ECA95B71496BF6A5F2
SHA1: A455D3BDB92F1D2F0E9B2A3B3B5AD4A739D411AC
CRC32: 01AB441C

你可能感兴趣的:(趣题.C-8皇后问题)