关于数独的规则,在这里就不介绍了,蓝天十分钟内教你玩转数独对数独的规则和基本方法进行了解释。

这里主要介绍采用程序的方法来解数独问题。

数独问题最基本的方法是排除法和唯余法。

解数独问题,基本方法都是从数独满足的三个条件而得来的:

1、每行数字填入1-9,且不能重复 
2、每列数字填入1-9,且不能重复 
3、每宫数字填入1-9,且不能重复(宫就是黑粗线框出的3×3单元格的区域)

所谓排除法,就是如果i行j列的位置的数字为d,那么i行、i列以及该位置所在的宫的其它位置不能有d的存在,否则就违背了数独的条件。

而唯余法则是用自己本身所在的行、列和宫的其它数字来得到自己可以放置的数字。

不过,应该注意的是,这些基本的方法只能解一些相对比较简单的题目。

排除法是用自己去排除其它格可以放置的数字,因此,就必须保存每个格被排除的数字。依次遍历81个格,如果某个格初始时已经填入了数字,就要用它来排除它所在行、列、宫可以放置的数字,行和列是很好判断的,关键的问题是宫,可以通过某个格获得该格所在的宫,然后依次遍历宫内的各个元素,这是通过in_square()来完成的。如果最后某个格被经过排除后只能放一个数字,那么,该位置就放置该数字。

唯余法是用别人来得到自己可以放置的数字,因此,必须保存每个格可以放置的数字。依次遍历81个格,如果某个宫初始时没有填入数字,就要用它所在的行、列、宫的其它数字来得到它可以放置的数字。其实代码跟上面的类似。

下面解释一下各数据结构和函数的功能:

char sudoku[MAXN][MAXN];    整个数独棋盘,这里为了简单起见,使用二维数组
int flag[MAXN][MAXN];              因为数独棋盘中,有些数字是固定的,保存一个二维数组用于标识这些数字。

当flag[i][j] = 0,说明sudoku[i][j]需要填入数字,如果flag[i][j] = 1,说明 sudoku[i][j]不需要填入数字。
char *exclude[89];                      包含89个指针的数组,每个指针保存某个格的排除的相关信息。

sudoku[i][j]对应的排除信息保存在exclude[i * 10 + j]中,它是一个包含9个元素的字符数组。exclude[i * 10 + j][k]保存的是sudoku[i][j]中是否可以放置k + 1,如果exclude[i * 10 + j][k] = 0表示可以放置k + 1,否则表示不能放置k + 1。

int empty_digit;                           记录未填入数字的个数,可以用于结束程序的条件。

以下就是数据结构的定义:

#define  MAXN    9
#define TRI 3char sudoku[MAXN][MAXN];
int flag[MAXN][MAXN];
char *left_digits[89];
char *exclude[89];
int empty_digit;

void set_sudoku()                       将文件中的未完成的数独读取到sudoku二维数组中,主要是为了方便起见,不用每次都输入数独数据。数独在文件中保存的格式是是类似.6.593...9.1...5...3.4...9.1.8.2...44..3.9..12...1.6.9.8...6.2...4...8.7...785.1.这样的,为了简单,81个格的信息都放在一行,"."表示需要填入数字。该函数的实现涉及到了文件的基本操作,打开文件,然后依次读取81个元素,然后关闭文件。

void set_sudoku()
{FILE *fp;if((fp = fopen("sudoku", "r")) == NULL) {printf("open file error!\n");exit(-1);}char ch = 0;int i = 0, j = 0;for(i = 0; i < MAXN; ++i) {for(j = 0; j < MAXN; ++j) {if((ch = fgetc(fp)) != EOF) {if(ch == '.') {sudoku[i][j] = '.';flag[i][j] = 0;}else {sudoku[i][j] = ch - '0';flag[i][j] = 1;--empty_digit;}}}}if(fclose(fp) == EOF) {printf("close file error!\n");exit(-1);}
}

void print_sudoku()         打印整个数独棋盘,用两层循环就可以实现,要注意的是,在程序中将1-9当作字符还是整数来处理,这里将1-9当作整数来处理,因此,它们虽然保存在char型数组中,在输出的时候,如果是".",就要当作字符来输出,否则,就要作为整数来输出。

void print_sudoku()
{int i = 0, j = 0;for(i = 0; i < MAXN; ++i) {for(j = 0; j < MAXN; ++j) {if(sudoku[i][j] == '.')printf("%c  ", sudoku[i][j]);elseprintf("%d  ", sudoku[i][j]);}printf("\n");}printf("\n");
}

void get_next(int ln, int col, int *next_ln, int *next_col)    获得(ln, col)的下一个位置,而且它在宫内是依次从左到右,从上到下,宫内最后一个元素的一个元素就是下一个宫的第一个元素,因此,利用该函数可以遍历九个宫。

void get_next(int ln, int col, int *next_ln, int *next_col)
{if(!((ln + 1) % TRI) && !((col + 1) % TRI)) {if(ln == 8 && col == 8) {*next_ln = ln + 1;*next_col = col + 1;}else if(ln != 8 && col == 8) {*next_ln = ln + 1;*next_col = 0;}else {*next_ln = ln - 2;*next_col = col + 1;}}else if(((ln + 1) % TRI) && !((col + 1) % TRI)) {*next_ln = ln + 1;*next_col = col - 2;}else {*next_ln = ln;*next_col = col + 1;}
}

int in_square(int ln, int col, int digit)           判断digit是否在(ln, col)所在的宫内,首先利用计算机的四则运算来获得(ln, col)所在的宫的第一个元素,然后调用9次get_next()来遍历这个宫,如果digit存在,则digit在该宫中,否则不在。

int in_square(int ln, int col, int digit)
{int s_ln = (ln / TRI) * TRI, s_col = (col / TRI) * TRI;int cnt = 9;int next_ln = 0, next_col = 0;while(cnt--) {if(sudoku[s_ln][s_col] == digit)return 1;get_next(s_ln, s_col, &next_ln, &next_col);s_ln = next_ln;s_col = next_col;}return 0;
}

int is_valid(int ln, int col, char digit)        判断digit是否在(ln, col)所在的行和列,其实两个循环可以合并成一个,不过为了直观,这里并没有将它们合并。

int is_valid(int ln, int col, char digit)
{int i = 0, j = 0;for(i = 0; i < MAXN; ++i) {if(i != ln && sudoku[i][col] == digit) {return 0;}}for(j = 0; j < MAXN; ++j) {if(j != col && sudoku[ln][j] == digit) {return 0;}}return 1;
}

void set_exclude(int ln, int col)      为sudoku[ln][col]设置排除集,设置排除集是用别的元素来排除它,依旧是分为三个部分。

void set_all_exclude()                    为数独中所有元素设置排除集,两层循环,不做过多解释。

void set_exclude(int ln, int col)
{if(flag[ln][col] == 0)return;int i = 0, j = 0;for(i = 0; i < MAXN; ++i) {if(i != ln && flag[i][col] == 0) {exclude[i * 10 + col][sudoku[ln][col] - 1] = 1;}}for(j = 0; j < MAXN; ++j) {if(j != col && flag[ln][j] == 0) {exclude[ln * 10 + j][sudoku[ln][col] - 1] = 1;}}int s_ln = (ln / TRI) * TRI, s_col = (col / TRI) * TRI;int next_ln = 0, next_col = 0;int cnt = 9;while(cnt--) {if(flag[s_ln][s_col] == 0) {exclude[s_ln * 10 + s_col][sudoku[ln][col] - 1] = 1;}get_next(s_ln, s_col, &next_ln, &next_col);s_ln = next_ln;s_col = next_col;}
}

void set_left(int ln, int col)    得到(ln, col)可以放置的数字,最后,如果只有一个可以放入的数字,就放入这个数字。

void set_all_left()                   为所有元素调用set_left()。

void set_left(int ln, int col)
{if(flag[ln][col] == 1)return;memset(left_digits[ln * 10 + col], 0, MAXN);char digit = 0;for(digit = 1; digit < 10; ++digit) {if(!in_square(ln, col, digit) && is_valid(ln, col, digit)) {left_digits[ln * 10 + col][strlen(left_digits[ln * 10 + col])] = digit + '0';}}if(strlen(left_digits[ln * 10 + col]) == 1) {sudoku[ln][col] = left_digits[ln * 10 + col][0] - '0';flag[ln][col] = 1;--empty_digit;}
}

然后就可以利用set_all_exclude()和set_all_left()得到元素的排除集和余集(自己取的名字,呵呵)。

下面来看看,两种方式的结果:

对上面的数独执行set_all_exclude(),得到:

上面就是部分成员的排除集,当有8个元素时,剩下那个就是需要填入的值,执行10次排除法,就能够得到上述数独的解。

执行set_all_left(),得到:

上面是部分成员的余集,当只有一个元素时,就可以填入那个元素,如上图的[4][4]、[5][3]、[6][4]、[6][5]、[6][8]都可以直接填入了。执行5次唯余法,就可以得到上述数独的解。

下面给出上述数独的解:

数独问题是个很有意思的问题,而且数独还分为不同的难度级别,像上面这两种方法只能解决一些简单的数独问题,对于难的问题就不行了。当然,个人水平有限,从数据结构到代码可能写的不是很好,望各位批评指正!

数独问题之排除法和唯余法相关推荐

  1. 数独基本规则_思维训练|数独入门第五课:唯余解法

    大家好,我们每周一次的数独课又来了. 本期,我们会介绍唯余解法: 唯余解法的原理十分简单,但是在实际的解题中,非常不容易辨认. 由于唯余解非常不容易辨认,所以一般的报章杂志及较大众化的数独网站,通常会 ...

  2. 哈希--直接定值法和除留取余法

    1. 哈希是一种算法,哈希表是用哈希算法构造出来的一种数据结构 2. 哈希算方法的几种方法 直接定值法 这里有一个例题,就是我们想判断某一字符串中,某一个字符出现的个数,我们可以使用哈希的思想,就是可 ...

  3. 多重比较示例:Bonferroni校正法和Benjamini Hochberg法

    全文链接:http://tecdat.cn/?p=21825 假设检验的基本原理是小概率原理,即我们认为小概率事件在一次试验中实际上不可能发生. 相关视频 多重比较的问题 当同一研究问题下进行多次假设 ...

  4. 两种随机数生成方法——反函数法和舍选法

    两种随机数生成方法--反函数法和舍选法 我们知道,对于均匀分布的随机数,我们可以使用随机数生成器类似的方法生成均匀分布的随机数.包括线性同余发生器,FSR发生器等.对于非均匀分布的随机数,对于连续型随 ...

  5. 密码学基础算法(一)基于整数的欧几里得算法和扩展欧几里得算法

    图片来源: 随便谷歌的一个图片 图片地址: https://jason-chen-1992.weebly.com/uploads/1/0/8/5/108557741/euclidean_3_orig. ...

  6. C语言的双向链表头插法和尾插法,指定节点删除

    文章目录 前言 头插法 尾插法 删除节点 测试代码如下 前言 双向链表和单链表的唯一区别就是多个一个指针域而已,该指针域可以访问链表的上一个节点. 关于构造双向链表的过程我们常见的有两种方法,和单链表 ...

  7. (C++)1022 D进制的A+B 除基取余法将10进制数换成给定进制数

    #include<cstdio> //除基取余法 const int M = 30;int main(){long long a,b,c;int D,ans[M+2];scanf(&quo ...

  8. 骆驼命名法,帕斯卡命名法和匈牙利命名法(转)

    一.匈牙利命名法:广泛应用于象Microsoft Windows这样的环境中.       Windows 编程中用到的变量(还包括宏)的命名规则匈牙利命名法,这种命名技术是由一位能干的 Micros ...

  9. 哈希表的实现(取余法)

    哈希表,又叫散列表,它可以提供快速的插入查找操作,对于大规模数据的查找时间空间效率会很高.哈希表构造方式有多种,其中取余法在比赛中最常用. 如果读入很多值非常大的数,让你判断一些数是否出现过.如果用桶 ...

最新文章

  1. PNAS-2018-多年多点5千样本鉴定玉米根际可遗传微生物
  2. python中if not x: 和 if x is not None: 和 if not x is None的使用和区别
  3. Visual Assist X插件
  4. 艾媒:第三方应用商店形成BAT3争霸格局
  5. Node.js package.json
  6. Spring Security学习
  7. Codeforces Beta Round #1 B. Spreadsheets 模拟
  8. 使用Arcpy进行数据批处理-批量裁剪
  9. 如何用Python的标准print函数打印一个彩色的围棋局面?
  10. 解决Xshell6 Xftp 6 强制更新,无法更新使用问题
  11. 2021-06-05按键精灵实现远程获取消息内容
  12. 虎胆熊威2中文版体验
  13. 中文数字阿拉伯数字相互转换(Java版本)
  14. 关于web出现此问题:index:25 Uncaught ReferenceError: delFruit is not defined at HTMLImageElement.onclick
  15. 05如何处理类别特征?
  16. gamma分布python代码_python scipy stats.gamma用法及代码示例
  17. 基于JAVA+SpringBoot+Mybatis+MYSQL的相册管理系统
  18. C语言 IO多路复用——poll函数
  19. fld to xml and xml to fld
  20. C++四种cast的详细介绍

热门文章

  1. 电脑 显示 无可用电源选项 怎么办
  2. .Net程序员学用Oracle系列(9):系统函数(上)
  3. 利用空闲服务器搭建frps服务端-实现穿透代理
  4. 金融机构数字化转型对央企建筑公司数字化转型的启示
  5. v4l2_async_subdev_notifier_register 分析
  6. php数组竖转横,数组纵向转横向怎么实现
  7. 邓俊辉 数据结构 习题4-18 Fermat-Lagrange定理代码实现
  8. linux的xshell怎么保存密码,Xshell保存账号密码方法
  9. 针对复杂问题的知识图谱问答最新进展
  10. python处理excel