Futoshiki求解
Futoshiki求解
Futoshiki是对于一个n的方阵,需要满足如下条件:
·每一行和每一列的元素都不能重复,即每一行和每一列1到n,n个数字都出现,且只出现一次。
·同一行或同一列中相邻两个元素需要满足预先设定的一些关系,比如大于、小于等等。
例如,有以下样例:
这是一个5的方阵,方阵中元素为0的元素表示该元素为空,即还没有放置1-n中的某个数字。初始方阵中有些元素已经被放置了,这些元素不能被修改。同一行的元素被|字符间隔,同时也有<和>关系符,来规定相邻两个元素的关系。同样,同一列的元素被-字符间隔,同时^和v字符用来规定相邻两个元素间的关系。
对应于上面的初始方阵,有以下一个结果,符合Futoshiki规定:
这是其中之一的结果,对于一个初始方阵来说,可能有很多种结果。在后面的程序中,我们将说明如何求解第一个结果和如何求解所有的结果。
初始方阵存储在文件中,诸如一下格式:
第一行是数值n,表示为n方阵。后面是2*n-1行,记录整个n方阵的数据。
我们要做的是,首先读取数据文件,并将其保存。然后我们需要判断该初始方阵是否是未完成的,所谓未完成就是其中有的元素值为0,即表示该元素位置还没有被放置数字。如果存在为0的元素,那么该方阵是未完成的。如果初始方阵是完成的,那么还分为两种情况,第一种是该方阵是满足其中的约束条件的,比如大于、小于等关系,或者同一行或同一列不存在重复元素,那么该初始方阵不需要进行求解,即便求解也没关系,求解的话得到的也是初始方阵。第二种情况是该方阵不满足约束条件,因为已经是完成的了,不用进行进一步求解,这是一种无效的初始方阵,如果进行求解,那么同样无法得到结果。
如果初始方阵是未完成的,也同样分为两种情况,第一种情况是该方阵是无效的,那么即便求解也是无效的。第二种情况是该方阵是有效的,那么可以对其进行进一步求解,求解的结果还分为两种情况,第一种是得到有效的结果,第二种情况是没有得到有效的结果。
一般情况下,我们处理的情况是,初始方阵是未完成的,有效的,求解后得到有效的结果。
下面给出初始方阵的几种情况处理方式:
初始方阵是完成的
是有效的
是无效的
初始方阵是未完成的
是无效的
是有效的——进行求解
求解后得到有效的结果(这是我们常见的情况)
求解后得到无效的结果
下面,我们根据程序进行逐步讲解。最后我们将附上我们的程序,在程序中有相当可观的注释,可供查阅参考。
1.数据结构的设计
首先是数据结构的设计,用于存储方阵。我们对于方阵中的每个元素设定了以下几个属性:
·自身的值,0表示为空,其范围为1-n
·与左边元素的关系
·与上边元素的关系
·用于检测其所在行是否存在重复元素
·用于检测器所在列是否存在重复元素
另外,为了求解初始方阵对应的所有结果,我们还定义了一个存储所有结果的链表。
2.读取数据,并保存
我们根据给定的文件名,从文件中读取初始方阵。在读取的过程中,检测该方阵是否已经完成还是未完成,该初始方阵是有效的还是无效的。
在读取中,如果碰到一个元素为0,则将其设置为未完成的。如果有元素小于0或者大于n,则将其设置为无效的。如果同一行或同一列中存在重复元素,那么也将其设置为无效的。另外,我们还要检测同一行和同一列中相邻两个元素之间约束关系,如果没有满足特定的约束关系,那么我们要将初始方阵设置为无效的。
3.求解
我们首先给出求解一个结果的过程。整个求解过程相当于是对树的深度优先搜索。树的根节点是初始方阵,树中的每个节点是方阵的某个状态,一直深度搜索下去,如果搜索到叶子节点,并且符合约束条件,那么该叶子节点就是初始方阵的求解结果。
我们这里的求解过程是求解初始方阵对应的第一个结果。整个深度优先搜索是一个递归的过程,这个过程类似于《约束条件下的优化问题》、《图的建立、广度优先遍历和深度优先遍历》和《多叉树的设计、建立、层次优先遍历和深度优先遍历》中有关深度优先搜索问题。
下面,我们讲解一下具体的求解过程。
首先深度递归求解函数的参数step表示递归求解的步数,其记录递归求解结束的条件,即step>=n*n,step的初值为0。
如果当前元素,或者说是树中第step深度的节点状态不为0且满足约束条件,则进行下一步的求解。
如果当前元素为0,则对当前元素进行从1-n的逐个测试,在测试的过程中,还要检测每一行和每一列是否存在重复数,如果不存在重复数,且符合约束条件,则进行递归调用,如果调用成功,则返回1。这里保证了只求解第一个有效结果即终止,对于后面的结果不在求解。如果递归调用后没有得到正确结果,则需要将当前元素还原(退栈),继续检测下一个1-n的数。
4.求解所有的结果
求解所有的结果和求解第一个结果最大的不同在于对当前元素进行递归调用的时候,不管递归调用得到的结果是否有效还是无效,都不返回,而是继续检测下一个1-n的数。这样最终传进来的初始方阵没有得到改变,因为对于状态树的深度优先搜索,最终还是回溯到树的根节点上。
以上是我们对Futoshiki问题的相关求解。
对于递归函数参数和变量的说明
在递归函数中,如果数据需要共用的话,则进行引用传递,需要进栈退栈,不同的递归调用间是相互关联的。
如果数据不需要共用的话,则进行值传递,不需要倒退,值传递是多份拷贝,不同递归调用的状态是相互独立的。
相关程序实现细节请阅读代码和注释。
// Futoshiki
#include <stdio.h>
#include <stdlib.h> typedef struct
{
int val; // 值
char leftRel; // 与左边元素的关系
char upRel; // 与上边元素的关系
char rowFlag; // 用于检测该元素所在的行中是否存在重复数,重复数作为该元素的列坐标
char colFlag; // 用于检测该元素所在的列中是否存在重复数,重复数作为该元素的行坐标
} cell_t; typedef struct result
{
cell_t** scheme;
struct result* next;
} Result; // 检测关系
int check_relations(cell_t** scheme, int row, int col)
{
int val = scheme[row][col].val;
char sep = ; if (col > && scheme[row][col - ].val != )
{
sep = scheme[row][col].leftRel; // 获取左边的关系符
if (sep == '<' && scheme[row][col - ].val > val) // 不用检测=的情况,因为每行数据不存在重复数
{
return ;
}
if (sep == '>' && scheme[row][col - ].val < val)
{
return ;
}
} if (row > && scheme[row - ][col].val != )
{
sep = scheme[row][col].upRel; // 获取上面的关系符
if (sep == '^' && scheme[row - ][col].val > val)
{
return ;
}
if (sep == 'v' && scheme[row - ][col].val < val)
{
return ;
}
} return ;
} // 读取数据
// complete表示正确方案下是否完成
// valid表示初始方案是否是有效的
cell_t** load_futoshiki(char* name, int* dim, int* complete, int* valid)
{
cell_t** scheme = NULL;
int n = , i = , j = , val = ;
char sep[];
FILE* fp = NULL; fp = fopen(name, "r"); fscanf(fp, "%d", &n);
scheme = (cell_t**)malloc(n * sizeof (cell_t*));
if (scheme == NULL)
{
fprintf(stderr, "Memory error!\n");
exit();
return NULL;
} for (i = ; i < n; ++i)
{
scheme[i] = (cell_t*)malloc(n * sizeof (cell_t));
if (scheme[i] == NULL)
{
fprintf(stderr, "Memory error!\n");
exit();
return NULL;
} for (j = ; j < n; ++j)
{
scheme[i][j].val = ;
scheme[i][j].leftRel = '|';
scheme[i][j].upRel = '-';
scheme[i][j].rowFlag = ;
scheme[i][j].colFlag = ;
}
} *dim = n;
*complete = ;
*valid = ; for (i = ; i < n; ++i)
{
if (i > )
{
for (j = ; j < n; ++j)
{
fscanf(fp, "%s", sep);
scheme[i][j].upRel = sep[];
if (j < n - )
{
fscanf(fp, "%*s"); // 将中间的间隔符-忽略
}
}
} for (j = ; j < n; ++j)
{
if (j > )
{
fscanf(fp, "%s", sep);
scheme[i][j].leftRel = sep[];
}
fscanf(fp, "%d", &val);
scheme[i][j].val = val; // 三种情况分支
if (val == )
{
*complete = ;
}
else if (val < || val > n)
{
*valid = ;
}
else // val在1到n之间
{
if (scheme[i][val - ].rowFlag++) // 用于检测同一行中是否存在重复数,若存在,则该方案失效
{
*valid = ;
} if (scheme[val - ][j].colFlag++) // 用于检测同一列中是否存在重复数,若存在,则该方案失效
{
*valid = ;
} if (check_relations(scheme, i, j) == )
{
*valid = ;
}
}
}
} fclose(fp); return scheme;
} void print_futoshiki(cell_t** scheme, int n)
{
int i = , j = ; for (i = ; i < n; ++i) //
{
if (i > ) // 如果不是第一行
{
for (j = ; j < n; ++j) // 打印行上面的间隔符
{
printf("%c", scheme[i][j].upRel);
if (j < n - )
{
printf(" - "); // 间隔符间的间隔符
}
}
printf("\n");
} for (j = ; j < n; ++j)
{
if (j > )
{
printf(" %c ", scheme[i][j].leftRel); // 打印数值左边的间隔符
}
printf("%d", scheme[i][j].val); // 打印具体数值
}
printf("\n");
}
printf("\n");
} // 求解结果
// 该函数只得到一个结果,并保存到scheme中
// 该结果是所有结果中的第一个结果
int solve_futoshiki(cell_t** scheme, int n, int step)
{
int i = , j = , k = , rowUsed = , colUsed = ; i = step / n;
j = step % n; if (step >= n * n) // 终止
{
// 如果step=n*n,此时说明将n*n个元素都已检测完,并且均合法,所以得到可行的方案
// 因为这里只是得到第一个可行的结果,所以对应于for循环的实现
return ;
} // 如果step的步骤小于n*n
// 检测当前元素
if (scheme[i][j].val) // 如果当前值不为0
{
if (check_relations(scheme, i, j)) // 如果检测成功
{
return solve_futoshiki(scheme, n, step + ); // 递归调用,继续检测下一个元素
}
else
{
// 检测不成功,返回失败
return ;
}
} // 如果当前值为0
for (k = ; k <= n; ++k)
{
scheme[i][j].val = k;
rowUsed = scheme[i][k - ].rowFlag++;
colUsed = scheme[k - ][j].colFlag++; if (rowUsed == && colUsed == && check_relations(scheme, i, j) == ) // 如果行和列都不存在重复数,并且关系合法
{
// 这里如果检测到第一个成功的则返回
// 如果失败了,则不能返回,倒退,继续检测下一个数
// 如果想得到所有的正确结果,则不需要返回,直接递归调用
// 三种处理方式 if (solve_futoshiki(scheme, n, step + )) // 继续检测下一个,如果合法返回1
{
// 如果检测下一个元素合法,则直接返回,所以,如果一直合法下去,那么后面的将k退出没有被执行
return ;
}
// 上面的solve_futoshiki检测成功,则直接返回1
// 结果是只得到第一个可行的结果
// 后面可行的结果被忽略
// 如果我们想得到所有的可行结果,可以在这里进行修改
// 请看求解函数2:solve_futoshiki_2 // 将其直接返回,如果solve_futoshiki为真,则和上面的等价
// 如果为假,则返回0,导致不再检测下一个k,不应该这样做
// return solve_futoshiki(scheme, n, step + 1);
} // 将当前k退出,继续检测下一个k
scheme[i][j].val = ;
--scheme[i][k - ].rowFlag;
--scheme[k - ][j].colFlag;
} // 如果将k都检测完,此时k=n+1,还没有找到合适的,说明检测失败,返回0
// 比如前面设置5,后面必须还要有5,但是因为不能重复,所以最终导致k越界,没有找到合法的,这时返回失败
// 不再对后面的节点进行检测
return ;
} cell_t** copyscheme(cell_t** src, int n)
{
cell_t** ret = NULL;
int i = , j = ; ret = (cell_t**)malloc(n * sizeof (cell_t*));
if (ret == NULL)
{
fprintf(stderr, "Memory error!\n");
exit();
return NULL;
} for (i = ; i < n; ++i)
{
ret[i] = (cell_t*)malloc(n * sizeof (cell_t));
if (ret[i] == NULL)
{
fprintf(stderr, "Memory error!\n");
exit();
return NULL;
} // 在这里执行失败
//for (j = 0; j < n; ++i)
//{
// ret[i][j] = src[i][j];
//}
} for (i = ; i < n; ++i)
{
for (j = ; j < n; ++j)
{
ret[i][j] = src[i][j];
}
} return ret;
} // 释放结果
void destroyresult(cell_t** src, int n)
{
int i = ; for (i = ; i < n; ++i)
{
free(src[i]);
}
free(src);
} // 求解所有的结果
// results是有头结点的指针
int solve_futoshiki_2(cell_t** scheme, int n, int step, Result* results, int* total)
{
int i = , j = , k = , rowUsed = , colUsed = ;
Result* tmp = NULL; i = step / n;
j = step % n; if (step >= n * n)
{
++*total; // 将结果记录
tmp = (Result*)malloc(sizeof (Result));
if (tmp == NULL)
{
fprintf(stderr, "Memory error!\n");
exit();
return ;
} // 拷贝结果
tmp->next = results->next;
tmp->scheme = copyscheme(scheme, n);
results->next = tmp; return ;
} if (scheme[i][j].val != )
{
if (check_relations(scheme, i, j))
{
return solve_futoshiki_2(scheme, n, step + , results, total);
}
else
{
return ;
}
} for (k = ; k <= n; ++k)
{
scheme[i][j].val = k;
rowUsed = scheme[i][k - ].rowFlag++;
colUsed = scheme[k - ][j].colFlag++; if (rowUsed == && colUsed == && check_relations(scheme, i, j) == )
{
// 这里不直接返回solve_futoshiki_2
// 也不根据solve_futoshiki_2的值返回结果
// 而是直接调用solve_futoshiki_2 // 共有三种处理方式 // 即便成功也不返回,失败也不返回,该循环不负责返回
solve_futoshiki_2(scheme, n, step + , results, total); // 成功就返回
//if (solve_futoshiki_2(scheme, n, step + 1, results, total))
//{
// return 1;
//} // 不管成功与否都返回
//return solve_futoshiki_2(scheme, n, step + 1, results, total);
} scheme[i][j].val = ; // 原来是0,现在也要还原,如果不还原,回溯不到之前的状态
--scheme[i][k - ].rowFlag;
--scheme[k - ][j].colFlag;
} // 如果k>n则检测失败
return ;
} int main(int argc, char* argv[])
{
int i = , n = , valid = , valid2 = , complete = ;
cell_t** scheme = NULL, **backup = NULL;
Result* results = NULL, *res1 = NULL, *res2 = NULL;
int total = ; if (argc != )
{
fprintf(stderr, "Error in passing arguments!\n");
exit();
} scheme = load_futoshiki(argv[], &n, &complete, &valid); backup = copyscheme(scheme, n); if (complete)
{
printf("Complete scheme.\n");
if (valid)
{
printf("Valid solution.\n");
}
else
{
printf("Invalid solution.\n");
}
}
else
{
printf("Incomplete scheme.\n");
if (valid)
{
valid2 = solve_futoshiki(scheme, n, ); // 求解完结果后,只得到了一个结果,也就是状态数中的第一个合法的叶子节点
// 后面的合法结果没有继续检测,scheme保存的是第一个合法结果
// 可以对其改进,将所有的结果都得到
} //printf("\n%d\n", valid); if (valid2)
{
printf("Solution:\n");
print_futoshiki(scheme, n);
}
else
{
printf("A solution does not exist.\n");
}
} // 将futoshiki消毁释放
for (i = ; i < n; ++i)
{
free(scheme[i]);
}
free(scheme); // =========================================
// 获取所有结果 if (complete)
{
// 如果是完整的,如果是有效的,则输入1
if (valid)
{
printf("%d\n", );
}
else // 如果是无效的,则输出0
{
printf("%d\n", );
}
}
else // 如果不是完整的
{
// 这时结果可能是有效的,也可能是无效的,如果无效的,则直接返回0
if (!valid)
{
printf("%d\n", );
}
else // 如果是有效的,则计算结果个数
{
scheme = copyscheme(backup, n); // 对结果分配头结点
results = (Result*)malloc(sizeof (Result));
results->next = NULL;
results->scheme = NULL; //print_futoshiki(scheme, n); solve_futoshiki_2(scheme, n, , results, &total); // 执行solve_futoshiki_2后,scheme还是初始状态
// 因为最后回溯到了状态树的根节点
//print_futoshiki(scheme, n); // 将结果输出
//res1 = results->next;
//while(res1 != NULL)
//{
// print_futoshiki(res1->scheme, n);
// res1 = res1->next;
//} printf("\n%d\n", total); // 消毁释放
destroyresult(scheme, n);
destroyresult(backup, n); res1 = results->next;
while (res1 != NULL)
{
res2 = res1->next;
destroyresult(res1->scheme, n);
res1 =res2;
}
free(results);
}
} return EXIT_SUCCESS;
}
20120202.in
data.in
data2.in
Futoshiki求解的更多相关文章
- 0-1背包问题蛮力法求解(c++版本)
// 0.1背包求解.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #define ...
- 4. SVM分类器求解(2)
最优间隔分类器(optimal margin classifier) 重新回到SVM的优化问题: 我们将约束条件改写为: 从KKT条件得知只有函数间隔是1(离超平面最近的点)的线性约束式前面的系数,也 ...
- 3. SVM分类器求解(1)——Lagrange duality
先抛开上面的二次规划问题,先来看看存在等式约束的极值问题求法,比如下面的最优化问题: 目标函数是f(w),下面是等式约束.通常解法是引入拉格朗日算子,这里使用来表示算子,得到拉格朗日公式为 是等式约束 ...
- 求解第N个素数
任务 求解第 10,0000.100,0000.1000,0000 ... 个素数(要求精确解). 想法 Sieve of Eratosthenes 学习初等数论的时候曾经学过埃拉托斯特尼筛法(Sie ...
- 跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1 例如:如下的矩阵 就包含了这样一个集合(第1.4.5行) 如何利用给定的矩阵求出相应的行的集合 ...
- [LeetCode] Sudoku Solver 求解数独
Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated by th ...
- R语言 批量规划求解
昨天读到一个项目,是关于优化求解的. 约束条件如下: 公司里有很多客户,客户之所以不继续用我们的产品了,是因为他账户余额是负的,所以,为了重新赢回这些客户,公司决定发放优惠券cover掉客户账户的负余 ...
- MATLAB求解代数方程、微分方程的一些常用指令
MATLAB版本:R2015b 1.求解符号矩阵的行列式.逆.特征值.特征向量 A = sym('[a11, a12; a21, a22]');deltaA = det(A)invA = inv(A) ...
- RNN求解过程推导与实现
RNN求解过程推导与实现 RNN LSTM BPTT matlab code opencv code BPTT,Back Propagation Through Time. 首先来看看怎么处理RNN. ...
随机推荐
- 萌萌的websocket原理解析
转载自:http://www.zhihu.com/question/20215561 一.WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持 ...
- 使用spring手动获取Bean的时候,不能强转回它自己。
这个问题好像有点长,描述一下: 就是通过类名的方式获取Bean后,得到一个Object对象,但是这个Object不能再强转回Bean了.抛出的异常时类型转换异常. java.lang.ClassCa ...
- Find the largest K numbers from array (找出数组中最大的K个值)
Recently i was doing some study on algorithms. A classic problem is to find the K largest(smallest) ...
- 深入了解jQuery之整体架构
本文是在阅读了Aaron艾伦的jQuery源码解析(地址:http://www.imooc.com/learn/172)后的个人体会以及笔记.在这里感谢艾伦老师深入浅出的讲解!! 先来看看如何生成一个 ...
- Weka使用常见问题
1.中文乱码 办法:找到RunWeka.ini文件,将Cp1252替换成数据对应的字符集编码,比如utf-8,cp936(简体中文),cp950(繁体中文).
- js原生方法传参的细节(面试必问)
废话不说,直接上题. slice(),接收两个参数,第一个为开始index(从0开始),第二个为结束的index(也是从0开始,但是不包括index本身,只到index-1).返回值是截取的数组,原数 ...
- 协同开发中SVN的使用建议
协同开发中SVN的使用建议 1. 注意个人账户密码安全 各员工需牢记各自的账户和密码,不得向他人透漏,严禁使用他人账户进行SVN各项操作(主要考虑每个SVN账号的使用者的权限范围问题).如有忘记,请 ...
- 大毕设-MATLAB-滤波器的实现
在工程实际中遇到的信号经常伴有噪声,为了消除或减弱噪声,提取有用信号,必须进行滤波,能实现滤波功能的系统称为滤波器.严格地讲,滤波器可以定义为对已知的激励提供规定响应的系统,响应的要求可以在时域或频域 ...
- RPC 框架通信原理
RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据: ...
- IELTS Writing Tips