这个循环在回溯时实现了什么功能?(数独求解器)

huangapple go评论76阅读模式
英文:

What does this loop achieve when backtracking? (sudoku solver)

问题

我已经编写了一个生成有效随机数独棋盘的函数。然而,我对为什么需要i循环感到困惑。

如果我删除这个循环,我的程序将无法正确运行。一旦它遇到一个无效的位置(回溯),它就会完全结束。

在大多数实现中,会检查i是否可以放置在空白的方格中。然而,我正在使用随机数,并且没有使用这个i变量。

那么为什么我需要这个循环呢?当回溯时,我的程序是否会返回到迭代中?

go playground演示

// 创建一个有效的随机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?

go playground demo

// 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 &lt;= 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 (
	&quot;fmt&quot;
	&quot;math/rand&quot;
	&quot;time&quot;
)

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 &lt;= 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 &lt;= 9; i++ {
		// board.PrintBoard()
		// fmt.Printf(&quot;Checking: row:%d,col:%d,val:%d \n&quot;, freeRow, freeCol, i)
		if board.ValidPos(i, freeRow, freeCol) {
			// fmt.Printf(&quot;Valid! row:%d, col:%d, val:%d \n&quot;, 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 &amp;&amp; isValidInCol &amp;&amp; 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 &lt; len(board.BoardArray); row++ {
		for col := 0; col &lt; 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 &lt; 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 &lt; 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 &lt; rowStart+3; row++ {
		for col := colStart; col &lt; 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 &lt; len(board.BoardArray); row++{
    fmt.Printf(&quot;row:%d \t&quot;, row)
    for col := 0; col &lt; len(board.BoardArray[row]); col++{
      fmt.Printf(&quot;%d|&quot;, board.BoardArray[row][col])
    }
    fmt.Println()
  }
}

main.go

package main

import (
	&quot;board&quot;
)

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 &lt;= 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.

huangapple
  • 本文由 发表于 2022年2月25日 00:22:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/71255140.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定