英文:
What does this loop achieve when backtracking? (sudoku solver)
问题
我已经编写了一个生成有效随机数独棋盘的函数。然而,我对为什么需要i
循环感到困惑。
如果我删除这个循环,我的程序将无法正确运行。一旦它遇到一个无效的位置(回溯),它就会完全结束。
在大多数实现中,会检查i
是否可以放置在空白的方格中。然而,我正在使用随机数,并且没有使用这个i
变量。
那么为什么我需要这个循环呢?当回溯时,我的程序是否会返回到迭代中?
// 创建一个有效的随机BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
// 获取棋盘上的下一个可用位置
freePos := board.FreePos()
if freePos == nil {
// 没有更多的位置,完成
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
// 生成随机数
randNum := rand.Intn(10-1) + 1
rand.Seed(time.Now().UnixNano())
// 这个i循环是什么意思?
for i := 0; i <= 8; i++ {
// 检查是否可以放置在freePos
if board.ValidPos(randNum, freeRow, freeCol) {
// 有效,放置
board.BoardArray[freeRow][freeCol] = randNum
// 递归
if board.GenerateBoard(freeRow, freeCol + 1) {
return true
} else {
// 回溯,设置回0
board.BoardArray[freeRow][freeCol] = 0
}
}
}
return false
}
board/board.go
package board
import (
"fmt"
"math/rand"
"time"
)
type Board struct {
BoardArray [9][9]int
}
// 创建一个有效的随机BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
// 获取棋盘上的下一个可用位置
freePos := board.FreePos()
if freePos == nil {
// 没有更多的位置,完成
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
// 生成随机数
randNum := rand.Intn(10-1) + 1
rand.Seed(time.Now().UnixNano())
for i := 0; i <= 8; i++ {
if board.ValidPos(randNum, freeRow, freeCol) {
board.BoardArray[freeRow][freeCol] = randNum
if board.GenerateBoard(freeRow, freeCol + 1) {
return true
} else {
board.BoardArray[freeRow][freeCol] = 0
}
}
}
return false
}
// 解决BoardArray(现有棋盘)
func (board *Board) SolveBoard(cellVal int, row int, col int) bool {
// 获取棋盘上的下一个可用位置
freePos := board.FreePos()
if freePos == nil {
// 没有更多的位置,完成
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
for i := 1; i <= 9; i++ {
// board.PrintBoard()
// fmt.Printf("Checking: row:%d,col:%d,val:%d \n", freeRow, freeCol, i)
if board.ValidPos(i, freeRow, freeCol) {
// fmt.Printf("Valid! row:%d, col:%d, val:%d \n", freeRow, freeCol, i)
board.BoardArray[freeRow][freeCol] = i
if board.SolveBoard(i, freeRow, freeCol + 1) {
return true
}
board.BoardArray[freeRow][freeCol] = 0
}
}
return false
}
// 检查所有有效的位置函数
// 如果有效则返回true
func (board *Board) ValidPos(cellVal int, row int, col int) bool {
isValidInRow := board.ValidPosInRow(cellVal, row)
isValidInCol := board.ValidPosInCol(cellVal, col)
isValidInSubGrid := board.ValidPosInSubGrid(cellVal, col, row)
if isValidInRow && isValidInCol && isValidInSubGrid {
return true
}
return false
}
// 检查棋盘上的下一个空闲位置
// 返回自由位置的[row,col]
func (board *Board) FreePos() []int {
for row := 0; row < len(board.BoardArray); row++ {
for col := 0; col < len(board.BoardArray[row]); col++ {
if board.BoardArray[row][col] == 0 {
validPos := []int{row, col}
return validPos
}
}
}
return nil
}
// 检查cellVal是否可以放置在rowN行
// 如果是有效位置则返回true
func (board *Board) ValidPosInRow(cellVal int, row int) bool {
for i := 0; i < len(board.BoardArray[row]); i++ {
currentValue := board.BoardArray[row][i]
if currentValue == cellVal {
return false
}
}
return true
}
// 检查cellVal是否可以放置在colN列
// 如果是有效位置则返回true
func (board *Board) ValidPosInCol(cellVal int, col int) bool {
for i := 0; i < len(board.BoardArray[col]); i++ {
currentValue := board.BoardArray[i][col]
if currentValue == cellVal {
return false
}
}
return true
}
// 检查cellVal是否可以放置在3x3子网格中
// 如果是有效位置则返回true
func (board *Board) ValidPosInSubGrid(cellVal int, colN int, rowN int) bool {
rowStart := (rowN / 3) * 3
colStart := (colN / 3) * 3
// 遍历子网格,
// 检查是否有任何值等于cellVal
for row := rowStart; row < rowStart+3; row++ {
for col := colStart; col < colStart+3; col++ {
subGridCell := board.BoardArray[row][col]
if subGridCell == cellVal {
return false
}
}
}
return true
}
// 辅助方法:
// 美观地打印棋盘
func (board *Board) PrintBoard() {
for row := 0; row < len(board.BoardArray); row++{
fmt.Printf("row:%d \t", row)
for col := 0; col < len(board.BoardArray[row]); col++{
fmt.Printf("%d|", board.BoardArray[row][col])
}
fmt.Println()
}
}
main.go
package main
import (
"board"
)
func main() {
grid := [9][9]int{}
board := board.Board{BoardArray: grid}
board.GenerateBoard(0, 0)
board.PrintBoard()
}
英文:
I have written a function that generates a valid random sudoku board. Although, I am confused on why I need the i
loop.
If I remove this loop my program no longer runs correctly. Once it hits an invalid position (backtracks) it ends all together.
In most implementations, the i
is checked to see if it can be positioned at the free square. Although, I am using random numbers and not using this i
var.
Therefore why do I need this loop? Does my program return back to the iteration when backtracking?
// Creates a valid random BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
// get the next available pos on board
freePos := board.FreePos()
if freePos == nil {
// no more positions, done
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
// generate random number
randNum := rand.Intn(10-1) + 1
rand.Seed(time.Now().UnixNano())
// this i loop is the confusion?
for i := 0; i <= 8; i++ {
// check if we can place at freePos
if board.ValidPos(randNum, freeRow, freeCol) {
// valid, place
board.BoardArray[freeRow][freeCol] = randNum
// recursion
if board.GenerateBoard(freeRow, freeCol + 1) {
return true
} else {
// backtrack, set back to 0
board.BoardArray[freeRow][freeCol] = 0
}
}
}
return false
}
board/board.go
package board
import (
"fmt"
"math/rand"
"time"
)
type Board struct {
BoardArray [9][9]int
}
// Creates a valid random BoardArray
func (board *Board) GenerateBoard(row int, col int) bool {
// get the next available pos on board
freePos := board.FreePos()
if freePos == nil {
// no more positions, done
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
// generate random number
randNum := rand.Intn(10-1) + 1
rand.Seed(time.Now().UnixNano())
for i := 0; i <= 8; i++ {
if board.ValidPos(randNum, freeRow, freeCol) {
board.BoardArray[freeRow][freeCol] = randNum
if board.GenerateBoard(freeRow, freeCol + 1) {
return true
} else {
board.BoardArray[freeRow][freeCol] = 0
}
}
}
return false
}
// Solves BoardArray (existing board)
func (board *Board) SolveBoard(cellVal int, row int, col int) bool {
// get the next available pos on board
freePos := board.FreePos()
if freePos == nil {
// no more positions, done
return true
}
freeRow := freePos[0]
freeCol := freePos[1]
for i := 1; i <= 9; i++ {
// board.PrintBoard()
// fmt.Printf("Checking: row:%d,col:%d,val:%d \n", freeRow, freeCol, i)
if board.ValidPos(i, freeRow, freeCol) {
// fmt.Printf("Valid! row:%d, col:%d, val:%d \n", freeRow, freeCol, i)
board.BoardArray[freeRow][freeCol] = i
if board.SolveBoard(i, freeRow, freeCol + 1) {
return true
}
board.BoardArray[freeRow][freeCol] = 0
}
}
return false
}
// Checks all valid pos funcs
// Returns true if valid
func (board *Board) ValidPos(cellVal int, row int, col int) bool {
isValidInRow := board.ValidPosInRow(cellVal, row)
isValidInCol := board.ValidPosInCol(cellVal, col)
isValidInSubGrid := board.ValidPosInSubGrid(cellVal, col, row)
if isValidInRow && isValidInCol && isValidInSubGrid {
return true
}
return false
}
// Checks next free pos on board
// Returns [row,col] of free pos
func (board *Board) FreePos() []int {
for row := 0; row < len(board.BoardArray); row++ {
for col := 0; col < len(board.BoardArray[row]); col++ {
if board.BoardArray[row][col] == 0 {
validPos := []int{row, col}
return validPos
}
}
}
return nil
}
// Check if cellVal can be placed in the row rowN
// Returns true if valid pos
func (board *Board) ValidPosInRow(cellVal int, row int) bool {
for i := 0; i < len(board.BoardArray[row]); i++ {
currentValue := board.BoardArray[row][i]
if currentValue == cellVal {
return false
}
}
return true
}
// Check if cellVal can be placed in the column colN
// Returns true if valid pos
func (board *Board) ValidPosInCol(cellVal int, col int) bool {
for i := 0; i < len(board.BoardArray[col]); i++ {
currentValue := board.BoardArray[i][col]
if currentValue == cellVal {
return false
}
}
return true
}
// Check if cellVal can be placed in the 3x3 subgrid
// Returns true if valid pos
func (board *Board) ValidPosInSubGrid(cellVal int, colN int, rowN int) bool {
rowStart := (rowN / 3) * 3
colStart := (colN / 3) * 3
// Go through the subgrid,
// check if any values are equal to cellVal
for row := rowStart; row < rowStart+3; row++ {
for col := colStart; col < colStart+3; col++ {
subGridCell := board.BoardArray[row][col]
if subGridCell == cellVal {
return false
}
}
}
return true
}
// Helper Method:
// Prints board nicely
func (board *Board) PrintBoard() {
for row := 0; row < len(board.BoardArray); row++{
fmt.Printf("row:%d \t", row)
for col := 0; col < len(board.BoardArray[row]); col++{
fmt.Printf("%d|", board.BoardArray[row][col])
}
fmt.Println()
}
}
main.go
package main
import (
"board"
)
func main() {
grid := [9][9]int{}
board := board.Board{BoardArray: grid}
board.GenerateBoard(0, 0)
board.PrintBoard()
}
答案1
得分: 1
让我们输出每次尝试的结果(使用这个应用程序 - 注意为了节省空间,我将多个尝试放在同一行上):
Board is valid? true Board is valid? true Board is valid? true
row:0 6|0|0|0|0|0|0|0|0| row:0 6|7|0|0|0|0|0|0|0| row:0 6|7|3|0|0|0|0|0|0|
row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0|
row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0|
row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0|
row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0|
row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0|
row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0|
row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0|
row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0|
Board is valid? false Board is valid? true Board is valid? false
row:0 6|7|3|3|0|0|0|0|0| row:0 6|7|3|5|0|0|0|0|0| row:0 6|7|3|5|7|0|0|0|0|
row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0|
row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0|
row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0|
row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0|
row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0|
row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0|
row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0|
row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0|
正如你注意到的,每次尝试都会使用不同的数字(伪随机数)。如果棋盘有效,则算法尝试将另一个数字添加到下一个空槽中。需要注意的重要部分是,在棋盘无效的情况下(6|7|3|3
),使用不同的值(6|7|3|5
)进行尝试 - 如果没有循环,第二次尝试将不会发生。
我认为对代码进行简单的更改将更容易看出循环的作用(实际上与您的代码相同 - 只是删除了一些不必要的工作):
randNum := rand.Intn(10-1) + 1
if board.ValidPos(randNum, freeRow, freeCol) {
board.BoardArray[freeRow][freeCol] = randNum
for i := 0; i <= 8; i++ {
if board.GenerateBoard() {
return true
}
}
board.BoardArray[freeRow][freeCol] = 0
}
return false
循环很重要,因为它控制了递归调用board.GenerateBoard()
的频率。每次进行递归调用时,代码尝试将另一个伪随机数添加到下一个可用槽中,尝试次数越多,找到解决方案的可能性就越大。
英文:
Lets output each attempt made (using this app - note that to save space I have put multiple attempts on each line):
Board is valid? true Board is valid? true Board is valid? true
row:0 6|0|0|0|0|0|0|0|0| row:0 6|7|0|0|0|0|0|0|0| row:0 6|7|3|0|0|0|0|0|0|
row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0|
row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0|
row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0|
row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0|
row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0|
row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0|
row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0|
row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0|
Board is valid? false Board is valid? true Board is valid? false
row:0 6|7|3|3|0|0|0|0|0| row:0 6|7|3|5|0|0|0|0|0| row:0 6|7|3|5|7|0|0|0|0|
row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0| row:1 0|0|0|0|0|0|0|0|0|
row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0| row:2 0|0|0|0|0|0|0|0|0|
row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0| row:3 0|0|0|0|0|0|0|0|0|
row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0| row:4 0|0|0|0|0|0|0|0|0|
row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0| row:5 0|0|0|0|0|0|0|0|0|
row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0| row:6 0|0|0|0|0|0|0|0|0|
row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0| row:7 0|0|0|0|0|0|0|0|0|
row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0| row:8 0|0|0|0|0|0|0|0|0|
As you will note with each attempt a different number (pseudo random) is being tried. If the board is valid then the algorithm attempts to add another number into the next free slot. The important bit to note is that where the board is invalid (6|7|3|3
) an attempt is made with a different value (6|7|3|5
) - without the loop that second attempt would not happen.
I think that a simple change to your code will make it easier to see why the loop makes a difference (the is effectively the same as your code - it just removes some unnecessary work):
randNum := rand.Intn(10-1) + 1
if board.ValidPos(randNum, freeRow, freeCol) {
board.BoardArray[freeRow][freeCol] = randNum
for i := 0; i <= 8; i++ {
if board.GenerateBoard() {
return true
}
}
board.BoardArray[freeRow][freeCol] = 0
}
return false
The loop is important because it controls how often board.GenerateBoard()
is called recursively. Each time a recursive call is made the code attempts to add another pseudo random number into the next available slot and the more attempts that are made the more likely it is that a solution will be found.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论