回溯问题:N皇后问题 (C, Python)

回溯问题:N皇后问题

  • 思路
  • Python 基础解法
  • C 基础解法
  • Python 简化方法

N皇后问题是个经典问题,在 N*N 的国际象棋棋盘内放下N个皇后有几种解法,使其不能互相攻击,属于典型的回溯问题。国际象棋中的皇后可以横、竖、斜走,很强大。
(好久没下了)

思路

2x2 和 3x3 棋盘肯定不行,斜线都会碰到;从 4*4 棋盘开始有解,并且增速很快。
为了方便解释,直接用二维数组表示。1表示放置的位置,空和0表示没放,x表示检查过不行(也是0)。代码中 i 表示行,j 表示列。
算法核心两个函数即可:place 用于检查目标位置 [i, j] 是否能放,即同一行、同一列、斜线(包括四个方向)有没有1,直接用粗暴方法就好。queen 用于递归循环和回溯,从 [0, 0] 开始放皇后,然后找第1行哪个位置能放,如果第1行能放再找第2行哪里能放,当 i == 边长(注意数从0开始)就是找到了解法。每一行找到能放的地方后递归进入下一行,递归完后进行回溯,寻找本行能否有另一个可放置位置。
流程演示:
在 [0, 0] 放下
回溯问题:N皇后问题 (C, Python)_第1张图片
找第1行哪里能放,找到 [1, 2];找第2行哪里能放,没有位置回退
回溯问题:N皇后问题 (C, Python)_第2张图片
第1行放 [1, 2] 不行了,回溯后标记为0;在 [1, 3] 也可以放下,然后找第2行找到 [2, 1] 可以放,但是第3行没地方放,回退
回溯问题:N皇后问题 (C, Python)_第3张图片
4x4 棋盘放在 [0, 0] 不行,就在 [0, 1] 放第一个,慢慢检查,找到的第一个解
回溯问题:N皇后问题 (C, Python)_第4张图片
其实难点不在检查在回溯不丢失可能解,当问题数据大时因为递归复杂度很高。我试了下我的 MBP 15 (Mid 2014) 跑8/9个卡一下就出来,但是10个就要时间了。

Python 基础解法

# coding=utf-8

"""
Python “没有数组”只有广义表
二维“数组”格式 [[], [], []]
for 两变量循环时要用 zip 把俩循环范围包装起来
"""

def place(i, j, board):
    lent = len(board)
    # 行,直接检查每个 list 有几个1
    if board[i].count(1) != 0:
        return False
    # 列,每个 list 第j号元素是不是1
    for n in range(lent):
        if board[n][j] == 1:
            return False
    # 左上
    for n1, n2 in zip(range(i-1, -1, -1), range(j-1, -1, -1)):
        if board[n1][n2] == 1:
            return False
    # 右下
    for n1, n2 in zip(range(i+1, lent), range(j+1, lent)):
        if board[n1][n2] == 1:
            return False
    # 右上
    for n1, n2 in zip(range(i-1, -1, -1), range(j+1, lent)):
        if board[n1][n2] == 1:
            return False
    # 左下
    for n1, n2 in zip(range(i+1, lent), range(j-1, -1, -1)):
        if board[n1][n2] == 1:
            return False
    return True


def queen(i, board):
    if i == len(board):
        print(board)
        return
    # 找每一行第几列能放
    for j in range(len(board)):
        if place(i, j, board):
            board[i][j] = 1
            # 进入递归找下一行可放位置
            queen(i+1, board)
            # 递归完后回溯
            board[i][j] = 0


n = int(input("皇后个数?"))
board = []
# 初始化二维数组
for n1 in range(n):
    board.append([])
    for n2 in range(n):
        board[n1].append(0)
queen(0, board)

效果如下,懒得做输出美工…回溯问题:N皇后问题 (C, Python)_第5张图片

C 基础解法

#include 
#include 
#include 

void printBoard(bool* board, short a);
bool check(bool* board, short i, short j, short a);
void queen(short i, bool* board, short a);

int main(int argc, const char * argv[])
{
    int a;
    printf("皇后个数?");
    scanf("%d", &a);
    bool board[a][a];
    for (short i1 = 0; i1 < a; i1++)
        for (short i2 = 0; i2 < a; i2++)
            board[i1][i2] = 0;
    queen(0, (bool*) board, a);
    return 0;
}

void queen(short i, bool* board, short a)
{
    if (i == a)
    {
        printBoard(board, a);
        return;
    }
    for (short j = 0; j < a; j++)
    {
        if (check(board, i, j, a))
        {
            *(board+i*a+j) = 1;
            queen(i+1, board, a);
            *(board+i*a+j) = 0;
        }
            
    }
}

void printBoard(bool* board, short a)
{
    for (uint8_t i1 = 0; i1 < a; i1++)
    {
        for (short i2 = 0; i2 < a; i2++)
            printf("%d", *(board+i1*a+i2));
        puts("");
    }
    puts("");
}

bool check(bool* board, short i, short j, short a)
{
    // 行
    for (short n = 0; n < a; n++)
        if (*(board+i*a+n) == 1)
            return false;
    // 列
    for (short n = 0; n < a; n++)
        if (*(board+n*a+j) == 1)
            return false;
    // 左上
    for (short n1 = i-1, n2 = j-1; n1 >= 0 && n2 >= 0; n1--, n2--)
        if (*(board+a*n1+n2) == 1)
            return false;
    // 左下
    for (short n1 = i-1, n2 = j+1; n1 >= 0 && n2 < a; n1--, n2++)
        if (*(board+a*n1+n2) == 1)
            return false;
    // 右下
    for (short n1 = i+1, n2 = j+1; n1 < a && n2 < a; n1++, n2++)
        if (*(board+a*n1+n2) == 1)
            return false;
    // 右上
    for (short n1 = i+1, n2 = j-1; n1 < a && n2 >= 0; n1++, n2--)
        if (*(board+a*n1+n2) == 1)
            return false;
    return true;
}

和 Python 的一样,就是语言变了,处理二维数组比较难受。稍微改了一下输出,变成密恐福音。C++ 和 C 写法类似,就是有高级些功能比如用 vector 和 cout,推荐 C++ 不要混用 C 写法。
回溯问题:N皇后问题 (C, Python)_第6张图片

Python 简化方法

因为每一行只有一个位置能放,可以只用一维数组存储,数组第n个元素的值代表第n行哪一列能放。
回溯问题:N皇后问题 (C, Python)_第7张图片
检查函数也可以简化,稍微需要一些数学逻辑。设被检查位置相当于二维数组 [row, col],同列的情况即一维数组存在与 col 相同的值,对角线冲突情况则是行之差和列之差绝对值相等。
比如下面:真实数组里是 [?, 1, 2, 0],行之差为1,列之差为1,冲突。
回溯问题:N皇后问题 (C, Python)_第8张图片
再看一种例子:真实数组里是 [3, 2, ?, ?],行之差为 -1,列之差为 1,也是冲突的。
回溯问题:N皇后问题 (C, Python)_第9张图片
C/C++ 的就懒得写了。

def check(board, row, col):
    for i in range(row):
        if board[i]-col == 0 or abs(board[i]-col) == abs(i-row):
            return False
    return True


def queen(board, row):
    if row >= len(board):
        print(board)
    for col in range(len(board)):
        if check(board, row, col):
            board[row] = col
            queen(board, row+1)


n = int(input("皇后数量?"))
board = [0 for i in range(n)]
queen(board, 0)

你可能感兴趣的:(算法:回溯问题)