【问题标题】:Here is my swift code for solving eight queens puzzle这是我解决八个皇后谜题的快速代码
【发布时间】:2026-01-17 17:40:01
【问题描述】:

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

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

ps:您可以复制/粘贴到 Playground 并尝试一下,谢谢!

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()

【问题讨论】:

    标签: swift recursion puzzle n-queens


    【解决方案1】:

    您似乎无法理解递归的概念。递归并不意味着 一切 成为递归调用。在函数 dropQueen 中,您使用递归调用,就好像它们是 goto 一样。

    尤其是这个:

    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/

    【讨论】:

    • 非常感谢。在我看到你的答案之前,我真的没有太多使用递归的经验。显然,为了方便起见,我过度使用了递归,并将所有繁重的工作都交给了计算机并达到了性能。所以我改变了代码的核心部分并且它可以工作。我会将我的代码作为新评论。只是出于好奇,如果我像旧方式一样写,它会运行得更快吗? (例如,在 C 语言中,人们使用 array[i]=j 表示 Queen(i,j) 而不是将数据存储在结构或类中?)这需要很多秒。
    • 很高兴听到你让它工作,但它比原始代码更复杂的事实令人不安。递归应该让事情变得更简单;你让自己变得困难!请看一下我的代码(见我上面的编辑);整个算法在一个简单、紧凑的递归函数中。就像我之前说的:no 递归算法中的回溯!至于“数组”方法,是的, 稍微高效一些,因为您没有重复创建和处置对象(struct Queen 的实例)的开销。
    • 向大师学习!当我开始编码时,我只是遵循我的直觉逻辑:解决问题的方法可能是 step1,step2 ......这似乎定义了我的编码风格。每当出现问题时,我都会尝试修复它,这意味着添加更多代码。大多数时候,让程序运行成为优先事项,而不是让代码灵活和可重用。每次我认为引用“代码行数越少越好”或“简单性..”时,我完全同意,我不知道如何从这里到达那里。也许很少的代码意味着更多的思考。
    【解决方案2】:

    我更改的代码:

    首先,我将 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时首先添加了“完成”检查)

    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 岁,但是你们在帮助我方面很棒。

    【讨论】:

      最近更新 更多