数独(九宫格)的高效算法
2015-12-19 21:06
183 查看
比较容易想到的是用回溯法,从第一个格子开始到最后一格,每个格子由1到9进行尝试,看能否填下去,不能就回头。思路简单,可是执行时间太长了。有没有更加高效的搜索算法了?当然有。
我们仅需要改变搜索的顺序,或者说改变填数的方法。
先将数字1分别填入1到9区域中,然后再将数字2填入1到9区域,重复该操作,直到所有空位都被填满为止。
为什么这样就快了?因为先将一个数字成功填入9个区域的制约性,远远大于按顺序地从第一个填到最后一个格。假设第一行全是空的,在第一行的第一格填了数字1,然后假如在第二行填不下数字1,证明第一行的数字1填错了,这时通过回溯纠正它,最坏需要9!次。
但如果是一个个区域地填入数字1,当第二个1填不下时,马上就能回溯到上一个1去纠正它的位置了。
具体C语言源程序如下:
#include<stdio.h>
#include<string.h>
int quyu[4][4]={{0,0,0,0},
{0,1,2,3},
{0,4,5,6},
{0,7,8,9}};
int a[10][10],hang[10][10],lie[10][10],grid[10][10],sum;
int p[10][2]={{0,0},{1,1},{1,4},{1,7},{4,1},{4,4},{4,7},{7,1},{7,4},{7,7}};
//p[i][0]和p[i][1]数组记录第i个区域的左上角的横纵坐标
void init()
{
int i,j,k;
memset(hang,0,sizeof(hang));
memset(lie,0,sizeof(lie));
memset(grid,0,sizeof(grid));
sum=0;
for(i=1;i<=9;i++)
for(j=1;j<=9;j++)
{
scanf("%d",&k);
a[i][j]=k;
if(k)
{
hang[i][k]=1; //第i行填入k了
lie[j][k]=1; //第j列填入k了
grid[quyu[(i-1)/3+1][(j-1)/3+1]][k]=1; // (i , j)所在区域填入k了
sum++;
}
}
}
void outt()
{
int i,j;
for(i=1;i<=9;i++)
{
for(j=1;j<=8;j++) printf("%d ",a[i][j]);
printf("%d\n",a[i][9]);
}
}
void solve(int k1,int k2) //将数字k1填入区域k2
{
int x,y,i,j;
if(sum==81) {outt();return;} //数独全填好了就输出
if(grid[k2][k1]) //区域k2中已有数字k1了
{
if(k2<9) solve(k1,k2+1); //填下一个区域
else solve(k1+1,1); //填下一个数字
}
x=p[k2][0]; y=p[k2][1];
for(i=x;i<=x+2;i++)
for(j=y;j<=y+2;j++)
if(a[i][j]==0 && hang[i][k1]==0 && lie[j][k1]==0) //第(i,j)格为空,且可填入k1
{
a[i][j]=k1;
sum++;
hang[i][k1] = lie[j][k1] = 1;
if(k2<9) solve(k1,k2+1); //填下一个区域
else solve(k1+1,1); //填下一个数字
a[i][j]=0;
sum--;
hang[i][k1] = lie[j][k1] = 0;
}
}
int main()
{
init();
solve(1,1); //将数字1从区域1开始填起
return 0;
}
我们仅需要改变搜索的顺序,或者说改变填数的方法。
先将数字1分别填入1到9区域中,然后再将数字2填入1到9区域,重复该操作,直到所有空位都被填满为止。
为什么这样就快了?因为先将一个数字成功填入9个区域的制约性,远远大于按顺序地从第一个填到最后一个格。假设第一行全是空的,在第一行的第一格填了数字1,然后假如在第二行填不下数字1,证明第一行的数字1填错了,这时通过回溯纠正它,最坏需要9!次。
但如果是一个个区域地填入数字1,当第二个1填不下时,马上就能回溯到上一个1去纠正它的位置了。
具体C语言源程序如下:
#include<stdio.h>
#include<string.h>
int quyu[4][4]={{0,0,0,0},
{0,1,2,3},
{0,4,5,6},
{0,7,8,9}};
int a[10][10],hang[10][10],lie[10][10],grid[10][10],sum;
int p[10][2]={{0,0},{1,1},{1,4},{1,7},{4,1},{4,4},{4,7},{7,1},{7,4},{7,7}};
//p[i][0]和p[i][1]数组记录第i个区域的左上角的横纵坐标
void init()
{
int i,j,k;
memset(hang,0,sizeof(hang));
memset(lie,0,sizeof(lie));
memset(grid,0,sizeof(grid));
sum=0;
for(i=1;i<=9;i++)
for(j=1;j<=9;j++)
{
scanf("%d",&k);
a[i][j]=k;
if(k)
{
hang[i][k]=1; //第i行填入k了
lie[j][k]=1; //第j列填入k了
grid[quyu[(i-1)/3+1][(j-1)/3+1]][k]=1; // (i , j)所在区域填入k了
sum++;
}
}
}
void outt()
{
int i,j;
for(i=1;i<=9;i++)
{
for(j=1;j<=8;j++) printf("%d ",a[i][j]);
printf("%d\n",a[i][9]);
}
}
void solve(int k1,int k2) //将数字k1填入区域k2
{
int x,y,i,j;
if(sum==81) {outt();return;} //数独全填好了就输出
if(grid[k2][k1]) //区域k2中已有数字k1了
{
if(k2<9) solve(k1,k2+1); //填下一个区域
else solve(k1+1,1); //填下一个数字
}
x=p[k2][0]; y=p[k2][1];
for(i=x;i<=x+2;i++)
for(j=y;j<=y+2;j++)
if(a[i][j]==0 && hang[i][k1]==0 && lie[j][k1]==0) //第(i,j)格为空,且可填入k1
{
a[i][j]=k1;
sum++;
hang[i][k1] = lie[j][k1] = 1;
if(k2<9) solve(k1,k2+1); //填下一个区域
else solve(k1+1,1); //填下一个数字
a[i][j]=0;
sum--;
hang[i][k1] = lie[j][k1] = 0;
}
}
int main()
{
init();
solve(1,1); //将数字1从区域1开始填起
return 0;
}
相关文章推荐
- wine下玩魔兽的设置
- 如何将办公文件转换成图片(一) -- 搭建Windows环境下OpenOffice+mupdf+vs2013的开发环境
- JsplitPane
- 安装mysql过程中卡在starting server解决办法
- js从数组中删除指定值(不是指定位置)的元素
- C语言回顾 学习进程
- 生产应用常见坑
- zoj 3640 Help Me Escape 期望DP 简单题 适合记忆化搜索
- 多媒体_音乐播放器
- Project Euler-Integer right triangles
- 主要代码
- Linux下Hadoop集群的搭建(1)—重新编译Hadoop
- 文件
- React事件处理函数的bind复用和name复用
- sprint最后冲刺-out to out
- hdoj 诡异的楼梯 1180 (bfs&&奇偶判断) 好题
- Java基础:死锁示例
- 通过浏览器链接启动本地 Activity
- 20151219 ocp examA(60-70) examB(1-20)
- 织梦 dedecms导航无法调用外部链接的原因