我有一个用prolog编写的NxN数独求解器。下面的代码适用于4 * 4求解。 (我有一些域名信息)但9x9的速度很慢。我已经尝试了很多方法来改进创建行功能,它已经在考虑行中的每个值都应该是唯一的(并且必须包含在其域中),但它们不起作用。 如果没有任何图书馆,我怎么能改进呢?
all_distinct(X) :-
sort(X, Sorted),
length(X, OriginalLength),
length(Sorted, SortedLength),
OriginalLength == SortedLength.
good_by_coulmns(Solution) :- length(Solution, Length),
forall((between(1, Length, X), get_column(Solution, X, Y)),
all_distinct(Y)).
get_area(Solution, X, Y, Z) :- length(Solution, Length),
SQRootF is sqrt(Length),
SQRoot is round(SQRootF),
MinCol is SQRoot * (X-1) + 1,
MinRow is SQRoot * (Y-1) + 1,
matrix_block(MinRow, MinCol, SQRoot, SQRoot, Solution, A), flatten2(A,Z).
good_by_areas(Solution) :- length(Solution, Length),
SQRootF is sqrt(Length), SQRoot is round(SQRootF),
forall((between(1, SQRoot, X), between(1, SQRoot, Y), get_area(Solution, X, Y, Z)),
all_distinct(Z)).
createRow(Solution, Domain, Row) :- maplist(member, Row, Domain),
all_distinct(Row),
good_by_coulmns(Solution).%, write(Solution), nl.
tryToSolve(Domains, Solution) :- length(Solution, L), length(Domains, L), !,
maplist(createRow(Solution), Domains, Solution),
good_by_coulmns(Solution),
good_by_areas(Solution).
如果需要,我可以提供缺少的规则,但这样做很好,所以我们可以将它们用作构建块。
答案 0 :(得分:1)
问题更多是关于A.I然后编程。
嗯,你需要为你的数独做一些约束才能让它更快地运作。有一个约束,但它有很多工作,所以我会用数学形式解释和写它,这取决于你如何实现你的代码,你可以轻松采用它:
想法:基本的想法是,当你获得一些数独游戏时,你可以立即填补几个地方,这样许多分支机构的程序将不会深入,然后失败,然后回溯,而不是如果它在某个更高的节点上失败,那就更好了。请看下面的图片,如果你放置这个约束,那么它可以阻止6个地方的prolog方式,并且只允许它在3个地方,因为每行有9个元素。在许多情况下,它不会探索我标记为蓝色的节点。没有约束它会一直下降到最后,你会看到它探索了多少额外的分支。
怎么样?我们利用了数独的属性:
规则:每一行,每一列和每个网格都有唯一的元素。
算法:当你得到一些(数独表)时,你必须检查前三行(0,1,2)是否有任何两行相同的元素。例如,对于第一行中的每个元素,如果没有,则必须检查该元素是否在第二行或第三行,然后转到第二行并尝试查找第二行和第三行中的元素。这有两种可能性:
你没有匹配:如果前三行没有任何相同的元素,那么转到下一行(3,4,5)并找到那里然后行(6) ,7,8)。
注意:你必须只检查那些形成网格(3x3矩阵)的线。例如,行(0,1,2)在一起然后(3,4,5)是一起然后(6,7,8)在一起。
您有匹配:假设您在行(0,2)中找到匹配项。由于每一行都可以有一个唯一的元素,我们知道这个元素应该在第1行,但是我们怎么知道结果行是1,我们需要一个公式。见下:
数学形式:您可以逐行获得结果。我们在第0行和第2行有一个匹配。这三行是正确的,所以我们得到它们的行号之和(0 + 1 + 2 = 3)。因为我们使用变量,你匹配(i = 0和j = 2),你的总和= 3
和sum- i - j = 1 so the new element should be in row 1
。此公式始终有效:认为您在行号(i = 4 and j = 5)
处匹配,然后是这三个行号的总和
是4 + 5 + 6 = 15
并减去15 - 4 -5 = 5.
但是有其他方法,但我们使用变量,所以我们需要知道在哪一行我们必须放置我们在其他两行中匹配的元素
确定网格一旦确定了新元素所在的行,那么下一步就是检查它将位于哪个网格中,因为每行都是三个网格的一部分(3x3矩阵) )。例如,行0在第一个网格中有元素(0,1,2),然后在第二个网格中有(3,4,5),然后在第三个网格中有(6,7,8)。
总共有(9)个子网格,从(0到8)开始。
子网格和行之间的关系:我们只有关于行的信息,所以我们需要找到行和网格数之间的关系。它是一个NxN矩阵,因此网格的高度等于网格的宽度(3x3),这意味着每三行一起形成3个网格,因为行的长度为9,网格的长度为3,所以我们得到3个高度3和长度为3的网格。例如:行(0,1,2)形成从0,1,2开始的三个网格,然后从另外3个网格形成行(4,5,6):gridnumber(4,5, 6)。
我们可以通过这样做得到他们的网格数:(假设在第0行我们在第4列有一个第2行和第2列的匹配然后我们应该得到网格0和网格1(respt),因为第一个网格是网格0(列:0 1 2)。
我们使用此公式获取一维列表中的网格编号。
(mod(x,9)/3) + 3*(x/27)
我们现在知道,网格1和2不能有这个元素,因为在每个网格中我们需要一个从1到9的唯一元素。所以它应该是网格0但是如何得到结果网格,我们可以通过取三个行数的总和(因为行数等于网格数)得到那个= 3并减去其他两个网格的网格数(0 + 1)然后我们得到网格2。 / p>
我们还没有完成,我们需要将这个东西转换为一个索引,因为我们主要使用一维列表。所以这个公式给出了确切的位置:
公式: x =(newRow)* 9 +(newGrid)* 3 和 x 是我们的位置。您可以通过插入值来验证此公式。现在我们可以获得精确的三个插槽,可以放置我们的元素。此时你要么列出[x] = list [i]或list [x + 1] = list [i]或list [x + 2] = list [i]。