题目不重要,我闲着无聊玩数独,自己填的时候呢容易和中等的都比较快,但是困难的时候就比较慢了,主要是去找数字都找得眼花,可能是人老了。自己又是个程序员,当然要让计算机来帮忙了。先前也写过一个程序来解,但是时间久了觉得以前的代码丑,逻辑不够清晰,所以重新写了个。也在网上找过想看看别人怎么写的。基本上都是回溯递归的方式去暴力求解。个人觉得这样没意思,所以还是自己写一个。
思路很简单,自己填数独怎么填的就让计算机怎么填。
确定一个格子里面该填什么值,我这里采用了两种方法,一种直接了当的我称之为确定值法,另一种就是逻辑排除法。
- 确定值法:直接通过数独行、列、单元组里面值是有且仅有1-9进行确定,比如我们在确定一个单元格的时候,通过行确定该单元格值可能是2,3.通过列确定该单元格可能是2,4。那么很显然这个单元格就是2了。
- 逻辑排除法:就是我们通过确定值法尚不能唯一确定单元格的值时,我们就需要用逻辑排除法排除来进行确定值。比如某一个单元格我们通过确定值法只能确定这个单元格的可能值是2,5,7。那么这个时候我们需要结合其同行,同列,同单元组的其他未填值得单元格的可能值来确定其的值。比如该单元格同行的其他单元格的可能值并集是1,5,7,9。那么我们就知道这个单元格的值是2,因为如果这个单元格的值不是2,那么本行将没有2这个值,显然是不对的。
通过两个方法的组合使用(其实准确来说确定值法是未逻辑排除法刷新单元格的可能值的,只是在刷新的过程中可能遇到可能值只剩一个的情况,这个就可以直接填值),代码如下:
Element.java
public class Element {
private int value;
private int x = -1;
private int y = -1;
private int g = -1;
private Set<Integer> possible = new HashSet<>();
public Element(int value,int x,int y){
this.value = value;
this.x = x;
this.y = y;
this.g = (x/3)*3+(y/3);
if(value==0){
for(int i=0;i<9;i++){
possible.add(i+1);
}
}
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
System.out.println(" "+(y+1)+"_"+(x+1)+":"+value);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getG() {
return g;
}
public void setG(int g) {
this.g = g;
}
public Set<Integer> getPossible() {
return possible;
}
public void setPossible(Set<Integer> possible) {
this.possible = possible;
}
}
Board.java
public class Board {
private List<Element> allNodes;
public Board(int[][] init) {
allNodes = new ArrayList<>();
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
allNodes.add(new Element(init[i][j],i,j));
}
}
//清除可能值
}
private List<Element> lineForValue(Element element){
return allNodes.stream().filter(o -> o.getX() == element.getX() && o.getValue()!=0).collect(Collectors.toList());
}
private List<Element> rowForValue(Element element){
return allNodes.stream().filter(o -> o.getY() == element.getY() && o.getValue()!=0).collect(Collectors.toList());
}
private List<Element> groupForValue(Element element){
return allNodes.stream().filter(o -> o.getG() == element.getG() && o.getValue()!=0).collect(Collectors.toList());
}
private List<Element> lineForPossible(Element element){
return allNodes.stream().filter(o -> o.getX() == element.getX() && o.getValue()==0).collect(Collectors.toList());
}
private List<Element> rowForPossible(Element element){
return allNodes.stream().filter(o -> o.getY() == element.getY() && o.getValue()==0).collect(Collectors.toList());
}
private List<Element> groupForPossible(Element element){
return allNodes.stream().filter(o -> o.getG() == element.getG() && o.getValue()==0).collect(Collectors.toList());
}
//行列、单元格需要所有值进行可能值初步筛选
private void cleanPossibleByNeed(){
for(Element element:allNodes){
if(!element.getPossible().isEmpty()){
/************行列单元格确认法***************/
Set<Integer> sv = new HashSet<>();
//行
lineForValue(element).forEach(e-> sv.add(e.getValue()));
//列
rowForValue(element).forEach(e-> sv.add(e.getValue()));
//单元格
groupForValue(element).forEach(e-> sv.add(e.getValue()));
element.getPossible().removeAll(sv);
}
}
}
private void cleanPossibleByExclusive(){
//做排除法之前,先刷一下可能值
cleanPossibleByNeed();
allNodes.stream().filter(element -> !element.getPossible().isEmpty()).forEach(element -> {
Set<Integer> pv = new HashSet<>();
boolean fill = false;
lineForPossible(element).stream().filter(e->e!=element).forEach(e->pv.addAll(e.getPossible()));
int v = exclusive(element.getPossible(),pv);
if(v != 0 ){
element.setValue(v);
element.getPossible().clear();
fill = true;
}
pv.clear();
rowForPossible(element).stream().filter(e->e!=element).forEach(e->pv.addAll(e.getPossible()));
v = exclusive(element.getPossible(),pv);
if(v != 0 ){
element.setValue(v);
element.getPossible().clear();
fill = true;
}
pv.clear();
groupForPossible(element).stream().filter(e->e!=element).forEach(e->pv.addAll(e.getPossible()));
v = exclusive(element.getPossible(),pv);
if(v != 0 ){
element.setValue(v);
element.getPossible().clear();
fill = true;
}
if(fill){
//填了值立马刷一下可能的值
cleanPossibleByNeed();
}
//如果当前的元素的所有可能值在其他可能值里面都有,那么暂无法确认
});
}
/**
* 当前元素和其他元素一起保证数独数字的完整性,但是其他元素的可能值里面不含这个元素的可能值里面的某一个,那这一个肯定就该在这里了
* @param p
* @param e
* @return
*/
private int exclusive(Set<Integer> p,Set<Integer> e){
List<Integer> clone = new ArrayList<>();
p.forEach(pv->clone.add(pv));
clone.removeAll(e);
if(clone.size()==1){
return clone.get(0).intValue();
}else{
return 0;
}
}
/**
*
* @return -1 无解 0无确认值 1有确认值
*/
private int checkAndFill(){
boolean hasFill = false;
for(Element element:allNodes){
//当前元素没有可能值,且没有值,那么说明无解了
if(element.getPossible().isEmpty() && element.getValue() == 0){
return -1;
}
//只有一个可能值,那就是你了
else if(element.getPossible().size() == 1){
element.setValue(element.getPossible().iterator().next());
element.getPossible().clear();
hasFill = true;
}
}
if(hasFill){
return 1;
}else{
return 0;
}
}
private boolean finish(){
return allNodes.stream().filter(element -> element.getValue() == 0).collect(Collectors.toList()).isEmpty();
}
private long emptySize(){
return allNodes.stream().filter(element -> element.getValue() == 0).count();
}
public void solve() throws Exception {
long emptySize = emptySize();
int empTime = 0;
while (!finish()){
//先用基础的方法筛除多余可能值
cleanPossibleByNeed();
//看下能不能填入值
int res = checkAndFill();
if(res<0){
throw new Exception("err");
}
//用排除法再做一次
cleanPossibleByExclusive();
//填充一遍后的空值数
long empty = emptySize();
if(empty<emptySize){
emptySize = empty;
empTime = 0;
System.out.println("剩余空格:"+empty);
}else{
//记录未填值次数
empTime++;
if(empTime>10){
System.err.println("超过10次遍历未填值,异常终止");
return;
}
}
}
print();
}
public void print(){
for(int i=0;i<allNodes.size();i++){
if(i%9 == 0){
System.out.println();
}
System.out.print(allNodes.get(i).getValue()+" ");
}
}
public static void main(String[] args) {
int[][] sd = {
{0,0,0, 0,9,0 ,2,0,0},
{0,9,0, 0,8,0 ,0,4,0},
{5,0,2, 0,0,0 ,3,0,9},
{3,1,0, 8,0,0 ,0,5,0},
{0,0,0, 3,0,4 ,0,2,0},
{8,2,0, 6,5,7 ,0,0,0},
{0,7,0, 0,0,0 ,0,6,0},
{0,0,0, 0,0,0 ,4,1,2},
{1,4,0, 0,3,0 ,0,0,8},
};
Board b = new Board(sd);
try {
b.solve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
1_1:4
6_4:9
5_5:1
7_5:8
8_6:3
1_7:2
9_7:3
8_9:9
剩余空格:43
5_4:2
5_7:4
7_7:5
4_3:4
6_3:1
7_6:9
9_6:1
4_7:1
4_8:9
5_8:7
7_9:7
剩余空格:32
5_3:6
7_4:6
3_6:4
6_7:8
1_8:6
3_1:1
7_2:1
8_3:7
9_4:4
1_5:9
9_5:7
3_7:9
3_8:8
6_9:6
剩余空格:18
8_1:8
1_2:7
2_3:8
3_4:7
6_8:5
3_9:5
4_1:7
9_1:5
3_2:3
4_2:5
6_2:2
9_2:6
2_5:5
3_5:6
2_8:3
4_9:2
剩余空格:2
2_1:6
6_1:3
剩余空格:0
4 6 1 7 9 3 2 8 5
7 9 3 5 8 2 1 4 6
5 8 2 4 6 1 3 7 9
3 1 7 8 2 9 6 5 4
9 5 6 3 1 4 8 2 7
8 2 4 6 5 7 9 3 1
2 7 9 1 4 8 5 6 3
6 3 8 9 7 5 4 1 2
1 4 5 2 3 6 7 9 8
为什么程序设置要10次遍历都没确定值,就异常终止,其实一次遍历没有确定值就可以终止了,只是我难得去改了而已。如果出现这种情况,说明这个数独可能有多个解,靠确定值法无法求出,此时只需要让没有填值的地方选择取一个可能值进行求解即可,这样求出来的解是解空间的一个解。稍微改进下就可以用来求所有解了。