数独求解(回溯)

/**
* Sudoku.cpp
* @author arhaiyun
* Date : 2013-10-13
*
**/

#include "stdafx.h"
#include <iostream> 
#include <fstream>
#include <algorithm> 
using namespace std; 
int map[9][9]; 

bool isPlace(int index)
{
	int row = index / 9;
	int col = index % 9;
	//[1].检查每一行是否有相同数字
	for(int i = 0; i < 9; i++)
	{
		if(map[row][i] == map[row][col] && i != col)
		{
			return false;
		}
	}
	
	//[2].检查每一列是否有相同数字
	for(int i = 0; i < 9; i++)
	{
		if(map[i][col] == map[row][col] && i != row)
		{
			return false;
		}
	}
	
	//[3].检查每个小格中是否有相同的数字
	int _row = row / 3 * 3;
	int _col = col / 3 * 3;
	
	for(int i = _row; i < _row + 3; i++)
	{
		for(int j = _col; j < _col + 3; j++)
		{
			if(map[i][j] == map[row][col] && (i != row || j != col))
			{
				return false;
			}
		}
	}
	
	return true;
}

void back_trace(int index)
{
	if(index == 81)
	{
		fstream out("out.txt",ios::out);
		for(int i = 0; i < 9; i++)
		{
			for(int j = 0; j < 9; j++)
			{
				out << map[i][j] <<" ";
			}
			out << endl;
		}
		return ;
	}
	
	int row = index / 9;
	int col = index % 9;
	
	if(map[row][col] == 0) 
	{
		for(int i = 1; i <= 9; i++)
		{
			map[row][col] = i;
			if(isPlace(index))
			{
				back_trace(index + 1);
			}
		}
		map[row][col] = 0;
	}
	else
	{
		back_trace(index + 1);
	}
}

int main()
{
	fstream in("in.txt", ios::in);
	
	for(int i = 0; i < 9; i++)
	{
		for(int j = 0; j < 9; j++)
		{
			in >> map[i][j];
		}
	}
	
	back_trace(0);
	
	system("pause");
	return 0;
}






/*
input:
5 3 0 0 7 0 0 0 0 
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1 
7 0 0 0 2 0 0 0 6 
0 6 0 0 0 0 2 8 0 
0 0 0 4 1 9 0 0 5 
0 0 0 0 8 0 0 7 9
output:
5 3 4 6 7 8 9 1 2 
6 7 2 1 9 5 3 4 8 
1 9 8 3 4 2 5 6 7 
8 5 9 7 6 1 4 2 3 
4 2 6 8 5 3 7 9 1 
7 1 3 9 2 4 8 5 6 
9 6 1 5 3 7 2 8 4 
2 8 7 4 1 9 6 3 5 
3 4 5 2 8 6 1 7 9 
*/


类的形式实现

#include <assert.h>
#include <iostream>
using namespace std;

// 求解数独程序

typedef int GridData[9][9]; // 9x9的网格数据

// 网格类
// 每个网格由3x3个块,每个块有3x3个0~9的数字
// 记录网格数据和9个块中已经有多少被填写了
class Grid
{
	GridData _data;
	int _filledBlockCount;// 已经填写完成的块的数量
public:
	// 构建函数,复制网格数据,并将块完成计数置0
	Grid(GridData data) : _filledBlockCount(0)
	{
		memcpy(_data, data, sizeof(int)*81);
	}
	
	// 构建函数,复制另一网格中的网格数据和块完成计数
	Grid(const Grid& other) : _filledBlockCount(other._filledBlockCount) 
	{
		memcpy(_data, other._data, sizeof(int)*81);
	}
	
	// 取得网格中x, y处的数字
	int get(int x, int y) const 
	{
		int res = _data[y][x];
		return _data[y][x];
	}
  
	// 设置网格中x, y处的数字
	void set(int x, int y, int digit)
	{
		_data[y][x] = digit;
	}
	
	// 取得完成块计数
	int getFilledBlockCount() 
	{
		return _filledBlockCount;
	}
	
	// 增加完成块计数
	void incrFilledBlockCount() 
	{
		_filledBlockCount ++;
	}
	
	// 减少完成块计数
	void decrFilledBlockCount() 
	{
		_filledBlockCount --;
	}
};

// 数独类
class Sudoku
{
	// 格子类
	// 记录一个格子的位置
	struct Cell
	{
		int x;
		int y;
	};
	
	// 判断一个x, y处的格子是否可以填入数字digit
	static bool canFill(const Grid& grid, int x, int y, int digit) 
	{
		// 判断横竖两行是否已经存在数字digit
		// 如果存在,就不能填充
		for (int i=0; i<9; i++) 
		{
			if(grid.get(x, i) == digit || grid.get(i, y) == digit)
			return false;
		}
		return true;
	}
  
	// 块类
	// 一个块包括3x3个数字
	// 填充完成后,一个块应该包括1-9这9个数字
	class Block
	{
		int _x, _y;            // 块在网格中的位置
		Grid& _grid;           // 网格的引用,从中取得块中的数字
		Cell _blankCells[9];   // 在块中的空白格子
		int  _unfillDigits[9]; // 在块中还未填入的数字
		int  _unfillCount;     // 未填入的数字的个数
	public:
		Block(int x, int y, Grid& grid) : _x(x), _y(y), _grid(grid)  
		{
			Cell* pCell = _blankCells;
			// 先将所有的数字都视为没有填入的数字
			for(int i = 0; i < 9; i++)
			{
				_unfillDigits[i] = i+1;
			}
			
			// 遍历3x3的块
			for(int y=0; y<3; y++) 
			{
				for(int x=0; x<3; x++) 
				{
					int d = grid.get(_x + x, _y + y); // 从网格中取得块中x, y处的数字
					if (d == 0) 
					{                     // 如果数字为0,表示没有填充
						pCell->x = _x + x;            // 记录在空白格子列表中
						pCell->y = _y + y;
						pCell++;
					}
					else 
					{                           // 否则是已经填入的数字
						_unfillDigits[d-1] = 0;      // 将该数字从未填入数字中清楚
					}
				}
			}
		
			// 把已清楚的未填入数字挤掉,并统计未填入数字数量
			_unfillCount = 0;
			extractUnfill();
		}
    
	// 求解一个块
    bool solve() 
	{
		return solve(_grid, _blankCells, _unfillDigits, _unfillCount);
    }
	private:
    // 求解网格grid中的一个块
    // blank是当前的未填入格子
    // unfills是未填入数
    // num是未填入数的数量,也是未填入格子数量
    bool solve(Grid& grid, Cell* blank, int* unfills, int num) {
      if(num == 0) {                             // 如果没有未填入格子,表示已经填入所有数字,即填完一个块
        grid.incrFilledBlockCount();           // 将网格的完成块计数加一
        if (grid.getFilledBlockCount() == 9)   // 如果填入的块的数量已经达到9块
          return true;                       // 问题已经解决,返回真
        Sudoku sdk(grid);                      // 如果还有没有填完的块,把当前的网格视为一个新的数独
        if(sdk.solve()) {                      // 对这个新的数独进行求解,如果求解成功
          sdk.copyGridTo(grid);              // 这也是前一个数独的解,将解复制到原来的数独中
          return true;                       // 成功,返回真
        }
        else {                                 // 否则
          grid.decrFilledBlockCount();       // 废弃掉上次的填完数字的块,完成块计数减一
          return false;
        }
      } 
      // 如果还有未填入数字,开始填入工作

      // 取得空白格子的位置
      int x = blank->x;
      int y = blank->y;
      // 依次尝试填入未填数字
      for (int i=0; i<num; i++) 
	  {
		int digit = unfills[i];
		if (Sudoku::canFill(grid, x, y, digit)) 
		{ // 如果未填数字能够填入这个位置
			grid.set(x, y, digit);                // 填入
			int restUnfills[9];
			getOtherDigits(restUnfills, unfills, num, i);   // 将其它数字放入一个新的未填入列表restUnfills
			if (solve(grid, blank+1, restUnfills, num-1)) 
			{ // 从下一个空白格子开始,将剩下的未填入数据视为一个新的块进行求解
				return true;                                // 如果能够解出来,则求出了整个问题的解
			}
			else 
			{
				grid.set(x, y, 0);                          // 无法解出,说明填入这个数字是不可以的,清除这个数字,开始尝试下一个数字
			}
        }
      }
      return false; // 如果所有的数字顺序都不正确,则说明前面的块填写不正确,返回到前一个块进行求解
    }
    // 将all中除excepIdx位置外的数字放到rest中
    void getOtherDigits(int* rest, int* all, int num, int exceptIdx) { 
      for (int i=0; i<num; i++) {
        if(i < exceptIdx)       // 如果在exceptIdx前面的数字,直接放过去
          rest[i] = all[i];
        else if(i > exceptIdx)
          rest[i-1] = all[i]; // 后面的数字向前移一位
      }
    }
    // 挤掉_unfillDigits中的已填入数(对应位置的数字被清0)
    void extractUnfill() 
	{
		int * p, * q;
		p = q = _unfillDigits; // p, q指针指向_unfillDigits数组
		while (q < _unfillDigits+9) 
		{ // q遍历数组
			if(*q != 0) 
			{             // q指向的数组如果不为0
				*p = *q;              // 复制到p指向的位置
				p++;                  // p只在*q不为0的情况下移动
				_unfillCount++;       // 对未填入数计数
			}
			q++;
		}
    }
  };
  
	Grid _grid;
public:
  Sudoku(GridData initData) : _grid(initData) 
  {
  
  }
  Sudoku(const Grid& grid) :  _grid(grid) 
  {
  
  }
  
  // 将网格数据复制到网格grid中
  void copyGridTo(Grid& grid) 
  {
	grid = _grid;
  }
  
  // 求解
  bool solve() 
  {
	// 获得已完成的块的数量
	int filledCount = _grid.getFilledBlockCount();
	
	// 计算第一个有未填入数字的块的位置
	int bx = filledCount % 3;
	int by = filledCount / 3;
	
	// 从这个块开始进行求解
	Block block(bx * 3, by * 3, _grid);
		return block.solve();
  }
  // 打印块的数据
  void printGrid() 
  {
    printGrid(_grid);
  }
private:
  void printGrid(const Grid& grid) 
  {
	for (int y=0; y<9; y++) 
	{
		for(int x=0; x<9; x++) 
		{
			cout << grid.get(x, y) << " ";
			if(x % 3 == 2) cout << " ";
		}
        cout << endl;
		if(y % 3 == 2) cout << endl;
    }
  }
};

int main()
{
  GridData initData = {
    5,3,0,  0,7,0,  0,0,0,
    6,0,0,  1,9,5,  0,0,0,
    0,9,8,  0,0,0,  0,6,0,

    8,0,0,  0,6,0,  0,0,3,
    4,0,0,  8,0,3,  0,0,1,
    7,0,0,  0,2,0,  0,0,6,

    0,6,0,  0,0,0,  2,8,0,
    0,0,0,  4,1,9,  0,0,5,
    0,0,0,  0,8,0,  0,7,9
  };
  Sudoku sdk(initData);
  if(sdk.solve()) 
  {
    sdk.printGrid();
  }
  else 
  {
    cout << "该数独无解" << endl;
  }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值