这是我解决八个女王拼图的快速代码

时间:2014-12-14 15:17:36

标签: swift recursion puzzle n-queens

八皇后谜题是在8×8棋盘上放置八个国际象棋皇后的问题,这样就没有两个皇后相互威胁。因此,解决方案要求没有两个皇后共享相同的行,列或对角线。八个皇后拼图是在n×n棋盘上放置n个皇后的更一般的n-queens问题的一个例子,其中除了n = 2或n = 3之外,所有自然数n都存在解。

但是,有没有人可以帮助我在递归方法中无限循环地解决这个问题?

ps:你可以复制/粘贴到游乐场并尝试一下,谢谢!

class ChessBoard {

    var limit: Int
    var queens = [Queen]()

    init(limit: Int) {
        self.limit = limit
    }

    // Check if (i,j) is a safe position for queen
    func isSafeForQueen(atRow row: Int, col: Int) -> Bool {

        for q in queens {
            // not in same row
            if q.row == row { return false }
            // not in same column
            if q.col == col { return false }
            // not in same diagol line
            if abs(q.row-row) == abs(q.col-col) { return false }
        }

        return true
    }

    // recursive method
    func dropQueen(atRow r: Int, c: Int) {

        // running into last row
        if r == limit {
            if queens.count < 8 {
                queens.removeLast()
                let q = queens.last!
                dropQueen(atRow: q.row, c: q.col+1)
            }
            output() // if success, log out the positions
            return
        }
        // running into last column of current row
        if c == limit {
            queens.removeLast()
            let q = queens.last!
            // if no position for queen at current row, then back to last row
            dropQueen(atRow: r-1, c: q.col+1)
        }
        // if this postion is safe for queen, then drop the queen and try next row; if not, try next spot
        if isSafeForQueen(atRow: r, col: c) {
            let q = Queen(row: r, col: c)
            queens.append(q)
            dropQueen(atRow: r+1, c: c)
        } else {
            dropQueen(atRow: r, c: c+1)
        }
    }

    func play() {
        dropQueen(atRow: 0, c: 0) // game will start at(0,0)
    }

    func output() -> String {
        var s = ""
        for q in queens {
            s += "(\(q.row),\(q.col))"
        }
        return s
    }
}

struct Queen {
    var row: Int
    var col: Int
}

// Tesing:
let b = ChessBoard(limit: 8)
//b.play()

2 个答案:

答案 0 :(得分:1)

看起来你在理解递归的概念时遇到了麻烦。递归并不意味着所有都成为递归调用。在函数dropQueen中,您正在使用递归调用,就像它们是gotos一样。

特别是这一个:

dropQueen(atRow: r-1, c: q.col+1)

这显然是一种做回溯的尝试。下定决心;要么回溯还是使用递归!在递归中,回到之前的决策是return。如有必要,返回一个布尔值,让调用者知道递归调用是否找到了解决方案(return true)或不是(return false)。如果您希望程序找到所有可能的解决方案,而不仅仅是第一个解决方案,则不需要这样做。

引起我注意的另一个电话:

dropQueen(atRow: r, c: c+1)

这里的递归调用过度,令人困惑;使用循环扫描所有可能的列。顺便说一句,通过进行此更改,您将发现整个第二个函数参数变得多余。

这让我们只留下了一个拒绝的电话;这绰绰有余。

dropQueen(atRow: r+1, c: c)

正如vacawama所指出的,第二个参数c: c似乎是错误的。当你前进一行(r+1)时,你为什么要排除你放在棋盘上的最后一位女王的的所有内容?

如果你想做递归,那么考虑你的递归函数应该做什么。通常,您希望它在已经由 r 皇后占用的板上放置(8- r )更多的皇后。您正在逐行工作,因此在您的方法 r 中只是当前行号。考虑如何根据行 r +1的解决方案来表达行 r 的问题解决方案。对最后一行做一个特殊的例外;一旦 r 等于限制,解决方案就是微不足道的(需要放置零皇后);你完成了。

哦,请重命名函数dropQueen;这个名字清楚表明你正在用错误的心态来处理这个问题。到现在为止,你应该能够找到更合适的东西。


修改 您已经付出了相当大的努力来完成这项工作,因此我将与您分享我的解决方案。它使用您的原始代码(如您的问题所示),但函数dropQueen完全重写(并且一个参数更少)。注意它是多么简单和简单;这是典型的递归。

func dropQueen(atRow r: Int) {
    if queens.count < limit {
        for col in 0...limit-1 {
            if isSafeForQueen(atRow: r, col: col) {
                let q = Queen(row: r, col: col)
                queens.append(q)
                dropQueen(atRow: r+1)
                if queens.count == limit {
                    return
                }
                queens.removeLast()
            }
        }
    }
}

作为奖励,如果您将return替换为println(output()),则该程序将打印所有92个解决方案。您可以在以下位置查看此操作: http://swiftstub.com/923601919/

答案 1 :(得分:0)

我改变了代码:

首先,我为特定目的制作了dropQueen()和takeQueen():

func dropQueen(atRow r: Int, col: Int) {
    let q = Queen(row: r, col: col)
    queens.append(q)
}

func takeLastQueen() -> (Int, Int) {
    let q = queens.last
    if q != nil {
        queens.removeLast()
    }
    // return (-1,0) means it's time to stop the game
    return q != nil ? (q!.row, q!.col) : (-1,0)
}

然后,使用for循环来迭代同一行的每一列,并且只使用递归进行回溯(当然,我重命名了一个名为makeStep()的函数,并且当row == limit时首先添加“Done”检查)

func makeStep(atRow r: Int, fromCol: Int) {

    if r == limit {
        // When find a resolution, keep the result
        // But it won't stop
        output()
    } else if r < 0 {
        // Time to stop game
        // but why just 27 resolutions ?
        return
    }

    var isQueenAtCurrentRow = false

    for c in fromCol..<limit {
        if isSafeForQueen(atRow: r, col: c) {
            dropQueen(atRow: r, col: c)
            isQueenAtCurrentRow = true
            break
        }
    }

    // Only use recursion for backtrack
    if isQueenAtCurrentRow {
        // if queen is set, go to next row
        makeStep(atRow: r+1, fromCol: 0)
    } else {
        // if failed, go back to last row and try next spot
        let (r,c) = backTrackForNext()
        makeStep(atRow: r, fromCol: c)
    }
}

backTrackForNext()函数用于返回最后一行并找到最后一个女王的下一个位置。如果下一个位置没有棋盘,那么再回去

func backTrackForNext() -> (Int, Int) {
    // find last row
    var (r,c) = takeLastQueen()
    c++ // find next spot
    if c == limit {
        // if hit end of column, go back again
        backTrackForNext()
    }
    return (r,c)
}

一旦找到结果,它就不会停止,直到我在操场上得到27个结果。我还没弄清楚为什么是27岁,但是你们这些人帮助我很棒。