Solving Sudoku Puzzles
Solving Sudoku Puzzles
CSE2003
DSA PROJECT REPORT
(J Component )
SUBMITTED BY:
B. Tech
in
SCOPE SCHOOL
2. INTRODUCTION
WHAT IS SUDOKU PUZZLE?
A Sudoku puzzle is defined as a logic-based, number-placement puzzle. The objective is to
fill a 9×9 grid with digits in such a way that each column, each row, and each of the nine 3×3
grids that make up the larger 9×9 grid contains all of the digits from 1 to 9. Each Sudoku
puzzle begins with some cells filled in. The player uses these seed numbers as a launching
point toward finding the unique solution.
While solving Sudoku puzzles can be significant challenge, the rules for traditional solution
finding are quite straight forward:
1. Each row, column, and nonet can contain each number (typically 1 to 9) exactly once.
2. The sum of all numbers in any nonet, row, or column must match the small number printed
in its corner. For traditional Sudoku puzzles featuring the numbers 1 to 9,
this sum is equal to 45.
Conceptually, the partial candidates are represented as the nodes of a tree structure, the
potential search tree. Each partial candidate is the parent of the candidates that differ from
it by a single extension step; the leaves of the tree are the partial candidates that cannot be
extended any further. The backtracking algorithm traverses this search tree recursively,
from the root down, in depth-first order. At each node c, the algorithm checks whether c
can be completed to a valid solution. If it cannot, the whole sub-tree rooted at c is
skipped. Otherwise, the algorithm (1) checks whether c itself is a valid solution, and if so
reports it to the user; and (2) recursively enumerates all sub-trees of c. The two tests and
the children of each node are defined by usergiven procedures. Therefore, the actual
search tree that is traversed by the algorithm is only a part of the potential tree. The total
cost of the algorithm is the number of nodes of the actual tree times the cost of obtaining
and processing each node. This fact should be considered when choosing the potential
search tree and implementing the pruning test. Backtracking is an algorithm for capturing
some or all solutions to given computational issues, especially for constraint satisfaction
issues. The algorithm can only be used for problems which can accept the concept of a
“partial candidate solution” and allows a quick test to see if the candidate solution can be
a complete solution. Backtracking is considered an important technique to solve
constraint satisfaction issues and puzzles. It is also considered a great technique for
parsing and also forms the basis of many logic programming languages. The term
recursive backtracking comes from the way in which the problem tree is explored. The
algorithm tries a value, then optimistically recurs on the next cell and checks if the
solution (as built up so far) is valid. If, at some later point in execution, it is determined
that what was built so far will not solve the problem, the algorithm backs up the stack to a
state in which there could be another valid solution. Due to the way that we pass the
puzzle between calls, each recursive call uses minimal memory. We store all necessary
state on the stack, allowing us to almost completely ignore any sort of bookkeeping.
CONSTRAINT PROPAGATION
CONSTRAINT PROPAGATION
Two important strategies that we can use to make progress towards filling in all the squares:
(1)If a square has only one possible value, then eliminate that value from the square's peers.
(2) If a unit has only one possible place for a value, then put the value there.
As an example of strategy (1) if we assign 7 to A1, yielding {'A1': '7', 'A2':'123456789',
...},we see that A1 has only one value, and thus the 7 can be removed from its peer A2 (and
all other peers), giving us {'A1': '7', 'A2': '12345689', ...}. As an example of strategy (2), if it
turns out that none of A3 through A9 has a 3 as a possible value, then the 3 must belong in
A2, and we can update to {'A1': '7', 'A2':'3', ...}. These updates to A2 may in turn cause
further updates to its peers, and the peersof those peers, and so on. This process is called
constraint propagation.
It may seem like a good idea to place a four in the shaded box. It doesn't immediately conflict
with any other locations and after placing it all of the squares still have possible values. Next
look at the square right below the shaded one, it must be a two. Filling in a two there,
however, leaves the lower left square with no possible value. This may not seem like a big
deal, it only takes an extra two assignments to realize that the four was wrong, but what if the
two isn't the next assignment.
The result is an exponential increase in the time needed for the search to realize that four was
a bad choice. The solution is to assign values that only have one possible immediately. After
each value is assigned by the search algorithm, constraint propagation iterates through the
squares assigning value to squares with only one possible value. If a square has no possible
values, the algorithm fails and returns to search which reselects the last value. If multiple
values are assigned by constraint propagation, then they repealed at once upon a fail. In the
example above after the four is assigned, constraint propagation realizes that the space below
the four must be a two. It then notices that the lower left corner has no possible value and
fails, returning to search, which chooses another value for the shaded square.
#include <iostream>
#include "stdio.h"
using namespace std;
// UNASSIGNED is used for empty cells in sudoku grid
#define UNASSIGNED 0
// N is used for size of Sudoku grid. Size will be NxN
#define N 9
// This function finds an entry in grid that is still unassigned
bool FindUnassignedLocation(int grid[N][N], int &row, int &col);
// Checks whether it will be legal to assign num to the given row,col
bool isSafe(int grid[N][N], int row, int col, int num);
/* Takes a partially filled-in grid and attempts to assign values to
all unassigned locations in such a way to meet the requirements
for Sudoku solution (non-duplication across rows, columns, and boxes) */
bool SolveSudoku(int grid[N][N])
{
int row, col;
// If there is no unassigned location, we are done
if (!FindUnassignedLocation(grid, row, col))
return true; // success!
// consider digits 1 to 9
for (int num = 1; num <= 9; num++)
{
// if looks promising
if (isSafe(grid, row, col, num))
{
// make tentative assignment
grid[row][col] = num;
// return, if success, yay!
if (SolveSudoku(grid))
return true;
// failure, unmake & try again
grid[row][col] = UNASSIGNED;
}
}
return false; // this triggers backtracking
}
/* Searches the grid to find an entry that is still unassigned. If
found, the reference parameters row, col will be set the location
that is unassigned, and true is returned. If no unassigned entries
remain, false is returned. */
bool FindUnassignedLocation(int grid[N][N], int &row, int &col)
{
for (row = 0; row < N; row++)
for (col = 0; col < N; col++)
if (grid[row][col] == UNASSIGNED)
return true;
return false;
}
/* Returns a boolean which indicates whether any assigned entry
in the specified row matches the given number. */
bool UsedInRow(int grid[N][N], int row, int num)
{
for (int col = 0; col < N; col++)
if (grid[row][col] == num)
return true;
return false;
}
/* Returns a boolean which indicates whether any assigned entry
in the specified column matches the given number. */
bool UsedInCol(int grid[N][N], int col, int num)
{
for (int row = 0; row < N; row++)
if (grid[row][col] == num)
return true;
return false;
}
/* Returns a boolean which indicates whether any assigned entry
within the specified 3x3 box matches the given number. */
bool UsedInBox(int grid[N][N], int boxStartRow, int boxStartCol, int num)
{
for (int row = 0; row < 3; row++)
for (int col = 0; col < 3; col++)
if (grid[row+boxStartRow][col+boxStartCol] == num)
return true;
return false;
}
/* Returns a boolean which indicates whether it will be legal to assign
num to the given row,col location. */
bool isSafe(int grid[N][N], int row, int col, int num)
{
/* Check if 'num' is not already placed in current row,
current column and current 3x3 box */
return !UsedInRow(grid, row, num) &&
!UsedInCol(grid, col, num) &&
!UsedInBox(grid, row - row%3 , col - col%3, num);
}
/* A utility function to print grid */
void printGrid(int grid[N][N])
{
for (int row = 0; row < N; row++)
{
for (int col = 0; col < N; col++)
printf(" %2d ", grid[row][col]);
printf("\n");
}
}
/* Driver Program to test above functions */
int main()
{
// 0 means unassigned cells
int grid[N][N] = {{6, 5, 0, 8, 7, 3, 0, 9, 4},
{0, 4, 3, 2, 5, 0, 0, 0, 8},
{9, 8, 0, 1, 0, 4, 3, 5, 7},
{1, 2, 5, 4, 0, 0, 0, 0, 9},
{4, 0, 9, 5, 0, 0, 0, 0, 2},
{0, 6, 0, 9, 0, 0, 5, 0, 3},
{5, 7, 8, 3, 0, 1, 0, 2, 6},
{2, 0, 0, 0, 4, 8, 9, 0, 0},
{0, 9, 4, 6, 2, 5, 0, 8, 1}};
if (SolveSudoku(grid) == true)
printGrid(grid);
else
printf("No solution exists");
return 0;
}
4. RESULTS AND DISCUSSION
OUTPUT
ALGORITHM ANALYSIS :
Backtracking
Backtracking is an optimization technique. We start with a possible solution which satisfies
all the required conditions, Then we move to the next level and if that does not produces
satisfactory solution, We return one level back and start with a new option.
Example =Sudoku solver, going through mare, eight queen problem
The backtracking algorithm enumerates a set of partial candidates that, in principle, could be
completed in various ways to give all the possible solutions to the given problem. The
completion is done incrementally, by a sequence of candidate extension steps. Conceptually,
the partial candidates are represented as the nodes of a tree structure, the potential search tree.
Each partial candidate is the parent of the candidates that differ from it by a single extension
step; the leaves of the tree are the partial candidates that cannot be extended any further. The
backtracking algorithm traverses this search tree recursively, from the root down, in depth-
first order. At each node c, the algorithm checks whether c can be completed to a valid
solution. If it cannot, the whole sub-tree rooted at c is skipped (pruned). Otherwise, the
algorithm (1) checks whether c itself is a valid solution, and if so reports it to the user; and (2)
recursively enumerates all sub-trees of c. The two tests and the children of each node are
defined by user-given procedures. Divide and Conquer Divide and Conquer is an algorithmic
paradigm.
A typical Divide and Conquer algorithm solves a problem using following three steps.
1. Divide: Break the given problem into sub problems of same type.
2. Conquer: Recursively solve these sub problems
3. Combine: Appropriately combine the answers
Example:-Binary Search , quick sort , Merge sort
Greedy Method
A greedy algorithm is an algorithmic paradigm that follows the problem solving heuristic of
making the locally optimal choice at each stage with the hope of finding a global optimum. In
many problems, a greedy strategy does not in general produce an optimal solution, but
nonetheless a greedy heuristic may yield locally optimal solutions that approximate a global
optimal solution in a reasonable time.
It is quite easy to come up with a greedy algorithm (or even multiple greedy algorithms) for a
problem.
Analyzing the run time for greedy algorithms will generally be much easier than for other
techniques (like Divide and conquer). For the Divide and conquer technique, it is not clear
whether the technique is fast or slow. This is because at each level of recursion the size of
gets smaller and the number of sub-problems increases.
The difficult part is that for greedy algorithms you have to work much harder to understand
correctness issues. Even with the correct algorithm, it is hard to prove why it is correct.
Proving that a greedy algorithm is correct is more of an art than a science. It involves a lot of
creativity. Dynamic Programming Dynamic programming (usually referred to as DP ) is a
very powerful technique to solve a particular class of problems. It demands very elegant
formulation of the approach and simple thinking and the coding part is very easy.
Some famous Dynamic Programming algorithms are:
Unix diff for comparing two files
Bellman-Ford for shortest path routing in networks
TeX the ancestor of LaTeX
WASP - Winning and Score Predictor
The core idea of Dynamic Programming is to avoid repeated work by remembering partial
results and this concept finds it application in a lot of real life situations.
In programming, Dynamic Programming is a powerful technique that allows one to solve
different types of problems in time O(n2 ) or O(n3 ) for which a naive approach would take
exponential time Branch and Bound programming
Branch and bound (BB, B&B, or BnB) is an algorithm design paradigm for discrete and
combinatorial optimization problems, as well as mathematical optimization . A branchand-
bound algorithm consists of a systematic enumeration of candidate solutions by means of
state space search : the set of candidate solutions is thought of as forming a rooted tree with
the full set at the root. The algorithm explores branches of this tree, which represent subsets
of the solution set. Before enumerating the candidate solutions of a branch, the branch is
checked against upper and lower estimated bounds on the optimal solution, and is discarded
if it cannot produce a better solution than the best one found so far by the algorithm.
The advantage of depth-first Search is that memory requirement is only linear with respect to
the search graph. This is in contrast with breadth-first search which requires more space. The
reason is that the algorithm only needs to store a stack of nodes on the path from the root to
the current node.
The time complexity of a depth-first Search to depth d is O(b^d) since it generates the same
set of nodes as breadth-first search , but simply in a different order. Thus practically
depthfirst search is time-limited rather than space-limited. If depth-first search finds solution
without exploring much in a path then the time and space it takes will be very less
The disadvantage of Depth-First Search is that there is a possibility that it may go down the
left-most path forever. Even a finite graph can generate an infinite tree. One solution to this
problem is to impose a cut off depth on the search. Although the ideal cut off is the solution
depth d and this value is rarely known in advance of actually solving the problem. If the
chosen cut off depth is less than d, the algorithm will fail to find a solution, whereas if the cut
off depth is greater than d, a large price is paid in execution time, and the first solution found
may not be an optimal one.
Depth-First Search is not guaranteed to find the solution. And there is no guarantee to find a
minimal solution, if more than one solution exists.
5. CONCLUSION
In this project we successfully solved Sudoku puzzles using backtracking method and
constraint propagation. This gave us immense knowledge on backtracking and
constraint propagation.
REFERENCES
www.google.com
www.hackernoon.com
Vijayranjan Sir’s notes
Algorithms+Data Structures (By Niklaus Wirth)