利用递归思想解决N-皇后问题(个人理解)

N-皇后问题是可用回溯法解决的经典案例。而回溯法本质上基于递归思想,对所有可行的方案分支进行深度优先的探索,而对于验证失败的分支回溯到其他可能分支,或全部验证失败后pass。

本文将N-皇后问题进行拓展,衍生为 M × N M\times N M×N棋盘布局下的布局问题,各棋子间不能同行、同列或对角线。当 M = N M=N M=N时,即为严格意义上的N-皇后问题。

回溯法,或者更广义意义上的递归法的关键在于明确如下问题:
(1)函数的定位/意义;
(2)边界条件;
(3)出口条件;
(4)子问题间的迭代关系。

下面从两个不同的方向来解决N-皇后问题。

解法1:前向分解

这里所谓的前向,指的是迭代关系为 f ( n ) = g ( f ( n − 1 ) ) f(n)=g(f(n-1)) f(n)=g(f(n1))。在N-皇后问题中即指:子问题的分解方向是沿着棋盘规模不断变小的方向。

值得注意的是:N-皇后( N × N N\times N N×N)的子问题并不应当理解为N-1-皇后( ( N − 1 ) × ( N − 1 ) (N-1)\times (N-1) (N1)×(N1)),而应当理解为棋盘布局为 ( N − 1 ) × N (N-1)\times N (N1)×N的子问题。

在前向方法中,我们可以把函数定义为:从空棋盘开始到指定行的所有可能布局路径,其出口条件为:当棋局只有1行时的所有布局方式,而迭代关系为在子问题布局(即更小规模的函数)路径下,在下一行添加一个新点的所有可能路径。

基于上述理解,我们可以快速给出递归函数:

def conflict(point, state):
    """
    point: 新point缩放的列
    state: (), 之前各行放的棋子元组序列
    新point所在位置:(len(state), point)
    """
    for row, previous in enumerate(state):
        if abs(point - previous)  in (0, len(state) - row):
            return True
    return False


def queen(row, columns):
    """
    row: 当前行
    columns: 所有列
    """
    if row == 1:
        for c in range(columns):
            yield (c, )

    else:
        for c in range(columns):
            for sequence in queen(row-1, columns):
                if not conflict(c, sequence):
                    yield sequence + (c, )

解法2:后向分解

与上文前向的定义相反,这里的后向指的是迭代关系为 f ( n − 1 ) = g ( f ( n ) ) f(n-1)=g(f(n)) f(n1)=g(f(n))。在N-皇后问题中即指:子问题的分解方向是沿着棋盘规模不断变大的方向。

这里的规模变大或许让人费解,问题既然没有解决,那棋盘不断变大不是更难解决吗?

因此,这里需要明确,所谓的规模变大只具有实际问题背景下的意义,而我们在思考迭代关系时,应当捕捉的是随着子问题分解方向,不断变小的变量。所以,无论前向分解,还是后向分解,其本质思想还是一致的。以路线长度的计算为例,在前向分解中,我们从终点出发,不断将问题分解为靠近起点的子问题,我们关注的核心变量应当是起点距当前点的距离;而在后向分解中,我们将问题分级为距终点的子问题,那我们关注的核心变量应当是当前点距终点的距离。

利用递归思想解决N-皇后问题(个人理解)_第1张图片

所以按照后向分解的思路,我们可以把函数定义为:从给定棋盘状态下,距布满全局所有可能布局路径,其出口条件为:当棋局只剩最后1行时的所有不与前序布局冲突的落子方式,而迭代关系为在子问题布局(即对应更大棋盘规模的函数)路径下,在该行添加一个新点的所有可能路径。

基于上述理解,我们引入一个当前棋局的状态变量state,可以写出如下的迭代过程:

def conflict(point, state):
    """
    point: 新point缩放的列
    state: (), 之前各行放的棋子元组序列
    新point所在位置:(len(state), point)
    """
    for row, previous in enumerate(state):
        if abs(point - previous)  in (0, len(state) - row):
            return True
    return False

def queen(max_rows, columns,  state=()):
    """
    row: 当前行
    columns: 所有列
    """
    assert len(state) <= max_rows
    if len(state) == max_rows - 1:
        for c in range(columns):
            if not conflict(c, state):
                yield (c, )

    else:
        for c in range(columns):
            if not conflict (c, state):
                for sequence in queen(max_rows, columns,  state=state+(c, )):
                    yield (c, ) + sequence

上述的queen函数中在出口函数和迭代函数部分的判断部分有重复,为了阅读上的顺畅,这里不做简化,读者可自行进行简化。

你可能感兴趣的:(数据结构与算法)