Kakuro数独问题.docx
《Kakuro数独问题.docx》由会员分享,可在线阅读,更多相关《Kakuro数独问题.docx(17页珍藏版)》请在冰豆网上搜索。
Kakuro数独问题
Kakuro数独问题
091122文彬多
091148唐龙
092616路驰
090915吕灵卓
摘要
数独的历史可以追溯到1783年,瑞士数学家欧拉发明了一种当时称作“拉丁方块”(LatinSquare)的游戏,这个游戏是一个n×n的数字方阵,每一行和每一列都是由不重复的n个数字或者字母组成的。
后被称为Sudoku数独。
当大家还在钻研Sudoku数独,究竟填写1至9这几个数字的窍门时,另一个相类似的游戏于最近迅速火爆,这就是Kakuro。
Kakuro在英美等地人气急升,它的好玩之处在于既有Sudoku的逻辑推理,还多了加数运算。
本文主要通过建立数学求解模型解决Kakuro数独的解法,以及相关问题。
主要包括以下几个问题:
1.建立数学模型,编写MatLab程序求解Kakuro数独;
2.算法复杂性讨论;
3.为Kakuro数独问题划分级别,给出一种划分方式;
4.产生不同级别且有唯一解的Kakuro数独。
问题背景
Kakuro数独游戏规则:
●在空格中填入数字1-9,数字0不能出现;
●带斜线的方格,斜线上方的数字等于该方格右面对应的一组水平空格里的数字之和;斜线下方的数字,等于该方格下面对应一组垂直空格里的数字之和;
●同一数字在每组水平(垂直)空格里只能出现一次。
问题分析和模型建立
1、建立数学模型,编写MatLab程序求解Kakuro数独
考虑到Kakuro数独对横向和纵向的加和都有要求,且交叉点为同一个数,
所以我们考虑到将要求简化,如果纵向没有任何要求,那么就简单多了。
简化后就变成了如下两个问题:
●在空格中填入数字1-9,数字0不能出现;
●每个格里的数字等于该方格右面对应的一组水平空格里的数字之和;
●同一数字在每组水平空格里只能出现一次,垂直空格中的数字无要求
3
4
21
5
6
29
26
13
15
11
10
17
15
7
同样道理,如果横向没有要求,竖向要求加和,则得到如下问题:
●在空格中填入数字1-9,数字0不能出现;
●每个格里的数字等于该方格下面对应的一组垂直空格里的数字之和;
●同一数字在每组垂直空格里只能出现一次,水平空格中的数字无要求
21
6
22
7
3
21
38
6
16
23
3
3
13
按照以上简化,只要两个问题的解是相同的即对应的空白格中的数字完全相同,则就是Kakuro数独的解。
那么下面的问题就是如何解上面的两种数独:
由于垂直(水平)方向上没有数字不相等的要求,那么就可以单独拿出一行(一列)求解即可。
例如:
第三行
3
4
3=1+2;2+1;
4=1+3;3+1;
将不需要填写的格子和有已知条件数字的格子表示成Inf,则该行可以表示成4种行向量:
(InfInf12Inf13InfInfInf);
(InfInf21Inf13InfInfInf);
(InfInf12Inf31InfInfInf);
(InfInf21Inf31InfInfInf)。
按此方法去解其它行(列),再合并成矩阵
其中空格表示需要填数字的格子。
根据此法可得到两个矩阵a,b(一个是根据行算出的,另一个是根据列算出的),如果两个矩阵
则该矩阵就是Kakuro数独的解。
但是矩阵a和矩阵b都有很多种可能,下面的问题就是根据任意一个Kakuro数独问题得出所有的矩阵a和所有的矩阵b,然后进行比较。
要得到所有的矩阵a和矩阵b,由于矩阵a中垂直方向上的数字无要求,矩阵b中水平方向上的数字无要求,所以只要求出所有的行可能的结果和所有的列可能的结果然后进行循环组合即可。
因此我们编了一个程序函数z=paixu(a),(附录),该函数的作用是输入任何一行或列的任何初始条件,均能得到一行或列的所有可能解。
然后将所有可能解组成矩阵,进行比较(即穷举法),得出矩阵。
2、算法复杂性讨论
经过计算发现,该方法只是理论上可能,实际运行时需要很长时间,原因是每一行都有很多种可能,假设为n,组成矩阵a就有
种可能,同样道理,矩阵b也有
种可能,再将矩阵a和矩阵b进行比较就要进行
次比较,所以计算时间非常长,大大超出了我们可以接受的范围。
简化问题,缩短时间:
记矩阵a的第i行为
,矩阵b的第j列为
,编写一个程序,对矩阵a中的
行的所有可能的行向量进行循环,如果矩阵a中的第i行
中的第j个数,在矩阵b中的所有可能的第j列
中没有这个数,则将这个行向量删除。
同样道理,相反的对矩阵b中的
列的所有可能的列向量进行循环,如果矩阵b中的第j列
中的第i个数,在矩阵a中的所有可能的第i行
中没有这个数,则将这个列向量删除。
这样一来,循环次数明显减少,但是仍然没减少到能够计算的数量级。
于是,类似于“八皇后”问题的原理,我们又编写了另一个递归求解函数:
该函数的大体思路如下:
1、对矩阵a中第n(从1开始)行的第m(从1开始)种可能,判断矩阵b中第i(
)列的第n个元素是否与矩阵a中第n行的第i个元素相等,若相等,保留这一种可能,并进行步骤2。
若i不能取遍1到10,则令
,重复进行步骤1。
若
,输出结果。
2、令
,进行步骤1,此时列的可能性为上次步骤1中所保留的可能性。
例如:
如果矩阵a的第一行的一种可能解为(InfInf12Inf13InfInfInf),则对矩阵b中的第一列的每种可能解进行循环,如果该解的第一个数为Inf,则保留此解,反之去掉此解,在下次循环中对它不进行循环。
依次原理对矩阵b的其他列也做同样筛选。
3、以此类推,再对矩阵b的第一列,矩阵a的第二行,矩阵b的第二列
分别作此操作,每次所需循环的次数都在减少,直到最后,矩阵a和b只有一种可能(唯一解),或多种可能(多解),或没有可能(无解)。
经过这样的简化,再次运行例题,时间大约为2分钟左右。
和我们的第一个程序相比(大约要运行一万亿亿年),有了跨越性的进步,时间缩短为原来的
。
3、为Kakuro数独问题划分级别,给出一种划分方式和实例
因为此题的解题原理基本与手工解题的原理相同,所以问题的级别可以按照此题的解题原理定出,也就是矩阵a和矩阵b的可能情况越多,筛选时间越长,问题越复杂
根据解题原理划分方案具体可以大致分为以下几步
1、调用函数z=paixu(a),分别计算出每行和每列的所有可能的解的数量;
2、将每行的所有可能的解的数量相乘,再将每列的所有可能的解的数量相乘;
3、将行和列的所有的可能值再相乘,得到的值为n,由于n值过大,所以取对数k=log(n),将k命名为难度系数。
按此方法,对任何题目即可判定其难度系数。
4、产生不同级别且有唯一解的Kakuro数独
Kakuro数独是一种非常灵活的数学游戏,在8*10的kakuro问题中,共有三种类型的方格:
阴影格(不添加数字的),空白格(供解题者添加数字的),斜线格(给定的条件格)。
我们总结出了这些格子的大致分布规律如下:
1、阴影格子主要分布于左边和上边,但不能占满这两边,否则就变成了一个7*9的kakuro数独;
2、由于每个空白格都位于行列的交叉点上,且行的和等于左侧斜线格中的数字,列的和等于上方斜线格中的数字,所以每组空白格的左方和上方都必为斜线格。
换言之,每个阴影格的右方和下方都为斜线格;
3、每个斜线格在一个方向上所约束的空白格的数目至少为2;
4、先画出边缘的阴影格和斜线格,然后在中部添加少数阴影格和斜线格,原则为在满足上述原则情况下,尽量使连续的空白格数目趋于多样化,即不能存在太多二连空白格,也不能存在太多八九个空白格连在一起的情况,这个做法的目的是使题目难度适中,不会过于简单或者困难。
5、格子画完后,用电脑产生随机数添加到各个斜线格子内。
随机数的范围为空格数对应的和的范围,比如若有三个空格,那和的范围就是1+2+3=6与7+8+9=24之间。
然后用已经做好的程序验证此数独是否有解以及解是否唯一。
验证到有唯一解的数独,即可输出,作为一道Kakuro题目。
点评
该解法最初使用的是穷举法,后来发现穷举法并不适用于所有题目,因为要考虑到时间长短的问题,所以最后采取递归回溯的方法解决时间过长的问题。
下次遇到循环的问题是要考虑多种方法,尽量减少运算时间。
总结
这四周来,我们小组付出了很大的努力,只因为最初的想法没有考虑到程序的计算时间问题,导致最终的程序虽然编写出来,但是不实用,经过努力的简化,我们已经把运算时间从不可能变成了可能。
虽然跟其他组相比还是有些慢,不过我们都付出了极大的辛苦。
附录
附录一:
主函数:
function[out,k]=kakuro4(a,b)
%a为横向约束条件,b为纵向约束条件
ifnargin<2
a=[infinfinfinfinfinfinfinfinfinf;
inf300400infinfinf;
21000000500;
60029000000;
inf26000013000;
15000110000inf;
10000017000inf;
infinf1500inf700inf;];
b=[infinf216inf227infinfinf;
inf3002100inf386;
inf0000001600;
inf0023000000;
inf300003000;
inf000130000inf;
inf0000inf000inf;
infinfinf00infinf00inf];
end
n=length(a(1,:
));
m=length(b(:
1));
fort=1:
n
c{t}=paixu(b(:
t));%列出所有列的可能性
cl=cl*length(c{t}(:
1));
end
fort=1:
m
d{t}=paixu(a(t,:
));%列出所有行的可能性
dl=dl*length(d{t}(:
1));
end
k=log(cl*dl);
d=shaixuan(d,c,n,m);c=shaixuan(c,d,m,n);初步筛选
k=log(length(d)*length(c),10);
f=xunhuan(c,d);%递归求解
iflength(f)==1%以下为输出结果
fprintf('NOSolution!
!
!
')
elsefori=1:
(length(f)-1)
out(:
:
i)=f{i+1};
end
end
shaixuan函数的作用是初步筛选,减少循环次数
functiony=shaixuan(d,c,n,m)
forh=1:
m
fori1=1:
length(d{h}(:
1));
ajj=d{h}(i1,:
);
g=1;
fori=1:
n
ifbijiao(ajj(i),c{i})==1
else
g=0;
break
end
end
ifg==0
d{h}(i1,:
)=zeros(1,n);
end
end
j=1;z=[];
fori1=1:
length(d{h}(:
1));
ifd{h}(i1,:
)~=zeros(1,n);
z(j,:
)=d{h}(i1,:
);
j=j+1;
end
end
y{h}=z;
end
xunhuan为递归求解函数
functionf=xunhuan(d,c,h,f)
ifnargin<3
h=1;
f={[]};
end
n=length(c(1,:
));
m=length(d(1,:
));
e=c;
w=length(d{h}(:
1));
fori1=1:
length(d{h}(:
1));
ajj=d{h}(i1,:
);
g=1;
c=e;
fori=1:
n
ifisempty(c{i})~=1
ifbijiao(ajj(i),c{i})==1
else
g=0;
break
end
else
g=0;
break
end
end
ifg==1
forl=1:
n
w=length(c{l}(:
1));
fori2=1:
w
ifc{l}(i2,h)~=ajj(l);
c{l}(i2,1)=0;
end
end
j=1;
z=[];
fori3=1:
w
ifc{l}(i3,1)~=0
z(j,:
)=c{l}(i3,:
);
j=j+1;
end
end
c{l}=z;
end
ifh==m
t=1;
fori=1:
n
ifisempty(c{i})==1;
t=0;
end
end
ift==1
tt=length(f);
fori=1:
n
f{tt+1}(i,:
)=c{i};
end
end
end
ifhf=xunhuan(d,c,h+1,f);
else
end
else
end
end
end
paixu函数的作用是给定数独某一行的初值,输出该行的所有可能性组成的矩阵
functionz=paixu(a)
n=length(a);y=0;
fori=1:
n
ifa(i)~=inf&&a(i)~=0;
flag=1;
p=1;j=i+1;
whileflag==1
ifa(j)==0
p=p+1;
j=j+1;
ifj>n
p=p-1;
break
end
else
p=p-1;
flag=0;
end
end
d=bianxu(plus1(a(i),p));
y=houjie(y,d);
end
end
z=zeros(1,n);
fori=1:
n
ifa(i)==inf||a(i)~=0
z(1,i)=inf;
end
end
m=length(y(:
1));
fori=1:
m
z(i,:
)=z(1,:
);
end
j=1;
fori=1:
length(z(1,:
))
ifz(1,i)==0
z(:
i)=y(:
j);
j=j+1;
end
end
bijiao函数为判断所选行的第n个元素是否满足第n列的可能性
functiony=bijiao(a,b)
p=length(b(1,:
));
q=length(b(:
1));
y=0;
fori=1:
p
forj=1:
q
ifa==b(j,i)
y=1;
break
end
end
end
end
plus1该函数的作用是输入几个数的和值,以及个数,得出所有可能解
functiona=plus1(b,c)
d=[1:
9];
e=zeros(9,1);
f=1;
fori=0:
1
e
(1)=i;
fori=0:
1
e
(2)=i;
fori=0:
1
e(3)=i;
fori=0:
1
e(4)=i;
fori=0:
1
e(5)=i;
fori=0:
1
e(6)=i;
fori=0:
1
e(7)=i;
fori=0:
1
e(8)=i;
fori=0:
1
e(9)=i;
ifsum(e)==c&&d*e==b
g=d.*(e)';
a(f,:
)=g(g~=0);
f=f+1;
end
end;end;end;end;end;end;end;end;end
bianxu函数的作用是改变向量的顺序,输出所有的排序可能
functiona=bianxu(b)
m=length(b(:
1));
n=length(b(1,:
));
p=length(perms(b(1,:
)));
fori=1:
m
a(((i-1)*p+1:
(i*p)),:
)=perms(b(i,:
));
end
houjie该函数的作用是将两个向量衔接
functionc=houjie(a,b)
ifsum(a)==0
c=b;
else
m=length(a(:
1));
s=length(b(:
1));
fori=1:
m
forj=1:
s
c((i-1)*s+j,:
)=[a(i,:
)b(j,:
)];
end
end
end
生成数独的函数:
function[b,c]=tianshu(a)
ifnargin<1
a=[infinfinfinfinfinfinfinfinfinf;
infinf00inf00infinfinf;
inf000000inf00;
inf00inf000000;
infinf0000inf000;
inf000inf0000inf;
inf0000inf000inf;
infinfinf00infinf00inf];
end
m=length(a(1,:
));n=length(a(:
1));
forj=1:
m-1
fori=1:
n
ifa(i,j)==inf&&a(i,j+1)==0
flag=1;
p=1;j1=j+1;
whileflag==1
ifa(i,j1)==0
p=p+1;
j1=j1+1;
ifj1>n
p=p-1;
break
end
else
p=p-1;
flag=0;
end
end
b(i,j)=floor((sum(9:
-1:
10-p)-sum(1:
p))*rand
(1)+sum(1:
p)+1);
elseifa(i,j)==inf&&a(i,j+1)==inf
b(i,j)=inf;
end
end
end
end
b(:
m)=a(:
m);b
forj=1:
m
fori=1:
n-1
ifa(i,j)==inf&&a(i+1,j)==0
flag=1;
p=1;i1=i+1;
whileflag==1
ifa(i1,j)==0
p=p+1;
i1=i1+1;
ifi1>n
p=p-1;
break
end
else
p=p-1;
flag=0;
end
end
c(i,j)=floor((sum(9:
-1:
10-p)-sum(1:
p))*rand
(1)+sum(1:
p)+1);
elseifa(i,j)==inf&&a(i+1,j)==inf
c(i,j)=inf;
end
end
end
end
c(n,:
)=a(n,:
);c
kakuro4(b,c)
该函数可随机生成符合假定条件的数独横向约束条件b和纵向约束条件c,然后代入求解函数中,若产生的数独无解,则程序在运行过程中出错。