編程之美有一道關於深度搜索和回溯應用的題目——構造數獨:
數獨的棋盤是由九九八十一個小方格組成的。玩家在每個小格子中,分別天上1至9的任意一個數字,讓整個棋盤每一行,每一列,以及每一個3*3的小矩陣中的數字都不重複。
作者給兩種解法:
解法一:
下面的GenerateValidMatrix()函數用經典的深度優先搜索來生成一個可行解。從(0,0)開始,對沒有處理過的格子,調用GetValidValueList(coCurrent)來獲得當前格子可能的取值選擇,並從中取一個爲當前格子的取值,接着搜索下一個格子。在搜索uocheng中,若出現某個格子沒有可行的值,則回溯,修改前一個格子的取值,直到所有的格子都找到可行的取值爲止,這是可行解。
本人下面的代碼實現參考了http://blog.csdn.net/linyunzju/article/details/7673959博客,但我發現該博主的代碼實現有一處代碼順序的執行錯誤,那就運行其構造數獨的結果來看,發現最後一個格子竟然被賦值爲0,還有就是他的代碼實現只能固定的構造一個數獨,一般是前面的格子從小到大賦值,而不是隨機的去構造數獨,這樣不太符合構造不同的數獨,本人對此代碼做糾正和改進,有不恰之處,請多多包涵。
代碼實現:
#ifndef COOR_H
#define COOR_H
//存儲格子的行和列座標
class Coor
{
public:
Coor(int xvalue=0,int yvalue=0)
{
x=xvalue;
y=yvalue;
}
int x;
int y;
};
#endif
#ifndef CELL_H
#define CELL_H
#include<stdlib.h>
//代表每個格子的詳細信息
class Cell
{
public:
bool validList[10];//存儲格子賦值情況信息
int validValue;//格子賦值
Cell()
{
memset(validList,true,10*sizeof(validList[0]));
validValue=0;
}
//判斷是否還存在有效值
bool isNoValidValue()
{
for(int i=1;i<=10;i++)
{
if(validList[i]==true) return false;
}
return true;
}
};
#endif // CELL_H
#include <iostream>
#include <cstdlib>
#include<Coor.h>
#include<Cell.h>
#define CLEAR(a) memset((a), 0, sizeof(a))
#define SIZE 9
Cell cell[SIZE+1][SIZE+1];//存儲每個格子詳細信息,索引代表格子的行和列座標
int value[SIZE+1];//索引代表對應格子的賦值,而該數組值代索引的數字值對當前格子的有效性
using namespace std;
int pickNextVailVale(int x,int y)
{
CLEAR(value);
//遍歷該已填的數字
for(int i=1;i<y;i++)
{
value[cell[x][i].validValue]=1;
}
//遍歷該列已填的數字
for(int i=1;i<x;i++)
{
value[cell[i][y].validValue]=1;
}
//求出是屬於哪一個九空格的第一個格子的行和列座標
int ninegrid_x=(x-1)/3*3+1;
int ninegrid_y=(y-1)/3*3+1;
// 遍歷該九空格已填的數字
for(int i=ninegrid_x;i<ninegrid_x+3;i++)
for(int j=ninegrid_y;j<ninegrid_y+3;j++)
{
value[cell[i][j].validValue]=1;
}
//隨機生成數字
int randomValue=(int)rand()%9+1;
while(true)
{
//存儲已被用過的數字
cell[x][y].validList[randomValue]=false;
//如果該數字沒被用過,則返回該數字
if(value[randomValue]==0)
{
return randomValue;
}
//如果所有數字都被用過,則回溯前一個格子
if(cell[x][y].isNoValidValue())
{
return -1;
}
//尋找下一個數字
randomValue++;
randomValue=randomValue%9+1;
}
}
//尋找下一個格子
void next(Coor &curCoor)
{
curCoor.y++;
if(curCoor.y>9)
{
curCoor.y=1;
curCoor.x++;
}
}
//回溯前一個格子
void pre(Coor &curCoor)
{
curCoor.y--;
if(curCoor.y<1)
{
curCoor.y=9;
curCoor.x--;
}
}
int main()
{
Coor coCurrent(1,1);
while(true)
{
//找不到滿足的情況
if(coCurrent.x==0&&coCurrent.y==0)
break;
//附一個有效的數字
cell[coCurrent.x][coCurrent.y].validValue=pickNextVailVale(coCurrent.x,coCurrent.y);
//噹噹前格子沒有找到滿足的數字時,回溯前一個格子
if(cell[coCurrent.x][coCurrent.y].validValue==-1)
{
//恢復初始化狀態
cell[coCurrent.x][coCurrent.y].validValue=0;
memset(cell[coCurrent.x][coCurrent.y].validList,true,10*sizeof(cell[coCurrent.x][coCurrent.y].validList[0]));
pre(coCurrent);
}
else
{
// 滿足成功結果
if (coCurrent.y==SIZE && coCurrent.x==SIZE)
{
for (int i=1; i<=SIZE; i++)
{
for (int j=1; j<=SIZE; j++)
cout << cell[i][j].validValue << " ";
cout << endl;
}
break;
}
// 進一步搜索
next(coCurrent);
}
}
return 0;
}
解法二:
假設把整個棋盤分成9個九空格,按順序分別命名爲B1,B2,B3….B9.先從位於中心的九宮格隨機用1到9填滿該九宮格後,通過置換行的辦法把B4和B6矩陣填好,然後對中央小矩陣的每一列做同樣的變換,把B2和B8都解決了,再分別用B4和B6通過同樣的一些系列矩陣變換生成B1和B7以及B2和B8,代碼實現可以參考http://www.cnblogs.com/xulu/archive/2012/05/09/2491346.html。