这个循环回溯时实现了什么? (数独求解器)
我编写了一个生成有效随机数独板的函数。尽管如此,我对为什么需要 i 循环感到困惑。
如果我删除这个循环,我的程序将不再正确运行。一旦到达无效位置(回溯),它就会一起结束。
在大多数实现中,会检查i
以查看它是否可以位于空闲方块处。虽然,我使用的是随机数,而不是使用这个 i
var。
那么为什么我需要这个循环呢?我的程序在回溯时是否会返回到迭代?
// 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.去
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()
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
让我们输出每次尝试(使用 此应用 - 请注意,为了节省空间,我已经进行了多次尝试每行):
正如您将注意到的那样,每次尝试都会尝试不同的数字(伪随机)。如果棋盘有效,则算法会尝试将另一个数字添加到下一个空闲插槽中。需要注意的重要一点是,当棋盘无效 (
6|7|3|3
) 时,会尝试使用不同的值 (6|7|3|5
) >) - 如果没有循环,第二次尝试就不会发生。我认为简单的对代码进行更改将更容易理解为什么循环会产生区别(实际上与您的代码相同 - 它只是删除了一些不必要的工作):
循环很重要,因为它控制递归调用
board.GenerateBoard()
的频率。每次进行递归调用时,代码都会尝试将另一个伪随机数添加到下一个可用槽中,尝试的次数越多,找到解决方案的可能性就越大。Lets output each attempt made (using this app - note that to save space I have put multiple attempts on each line):
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):
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.