Sudoku is a puzzle game played on a 9x9 grid. The grid is divided into nine 3x3 sub-grids. The goal is to fill the grid with digits from 1 to 9, such that: each digit (1-9) appears exactly once in every row, column, and 3x3 sub-grid.
Given a partially filled Sudoku grid, fill the empty cells such that the resultant fully-filled grid is a valid Sudoku solution.
Figure 1 shows a partially filled input grid and a corresponding fully-filled grid presenting a valid solution.
The input to the program is a partially filled grid: initial state. Each cell in the initial state contains either a digit from 1 to 9 or a '.' representing an empty cell.
The output could be one of two things:
Our first goal is to write a recursive exhaustive search algorithm to find a solution to a partially filled Sudoku grid.
Following is a brief description of the recursive algorithm. It has been deliberately kept at a high level, leaving it to you to work out the details.
The code below shows various function prototypes to be implemented. A brief comment against each prototype describes what the function is supposed to do. The main program shows how the functions are to be used. You may want to test your program on more inputs.
Currently, we could be spending a lot of time searching in subtrees that will not ultimately lead to a solution (goal state).
Heuristics can be used to prioritize subtrees (or paths) that are more likely to lead to a solution based on common-sense rules or educated guesses.
The input of a heuristic function h(s) is a potential next state (or child state) s that can be reached directly from the current state by making a single move. The output of the function is an estimate of the cost. This estimate helps choosing the child node to explore next.
The Minimum Remaining Values (MRV) heuristic selects the cell (to fill) that has the fewest legal options available (least number of valid placements). In other words, the most constrained cell. The intuition here is that since a few options are left for this cell one of them is likely to be part of a goal state.
Write another version of the solve_sudoku
method called solve_sudoku_MRV
. Also, write functions
count_MRV
and h_MRV
. The prototypes of these functions are shown below with brief explanations in
comments:
Use the solve_sudoku_MRV
function in the main and compare the number of recusrive calls it makes
with those made in the basic backtracking code. Is there a reduction in the number of calls?
While the MRV heuristic looks to fill the most constrained cells first, the LCV looks for the least constraining digit.
It works by choosing the value (digit) which, when placed in a selected cell, leaves the most options open for other cells.
Intuitively, this heuristic reduces the likelihood of creating further constraints down the line, helping to avoid dead-ends and excessive backtracking.
Following snippet shows prototypes of functions you need to add (feel free to add more utility
functions if required). As before, compare the number of recursive calls made by
solve_soduku_LCV
per puzzle with those made by the basic backtracking and MRV
methods.