版权声明:本文为博主徐松亮的原创作品,未经允许不得转载,多谢支持!QQ:5387603
推荐点击此链接:欢迎进入徐松亮博客一站式导航搜索(随时更新)
目录
一,前言
- 数独,说实话我玩过,且并不是很喜欢玩,觉得无聊也太浪费时间,当然玩的水平也不咋样。
- 但是我为什么又写这篇文章又编写代码的去做呢?
- 因为它是一个很好的适合计算机处理的数学算法的问题。
- 数独规则很简单
- 解题需要计算机编程的递归
- 出题需要矩阵变换
- 数独的最大终盘数量
- 目前普遍认为是6670903752021072936960,也就是所如果完全随机,一个人一辈子玩数独很难遇到万全一样的题,这个数量级大大出乎了我的意料。
- 刨去对称,换行等等等规则后,也有5472730538个终盘。
- 本人一开始想用穷举给算出来,看到这个数后就服了,即使用最快的计算机就算把计算机算坏了也算不完,而且统计这么大的数,还要做大数计算程序,目前的64位计算机肯定是不行。
- 本文着重说明通过C语言来解析数独问题,至于数独的随机出题问题由下一篇文章说明
- 本文只针对标准的9宫格数独,不讨论其他变种数独,也不讨论数独的难度等级等其他问题。
- 本文为了简化代码,题目直接在源码中,结果直接终端打印,没有做文件数据的输入输出等操作。
- 本文有免费代码下载(复制即可),可以计算并打印出第一个解。
- 本文另有收费代码,可以计算并打印出指定数量的解,并且统计题目有效解的总数量。
- 代码只建议用来学习,并没有做太多的容错处理。
- 这是我个人第一个收费的代码,且只是象征性收取5元哦,对我来说,只是跑跑流程,玩一玩,看看这玩意能不能赚到钱,哈哈,毕竟打字编程改良验证每个环节也消耗了我的一些精力。所以太认真较真讲究值与不值的朋友就别买了。
二,开发环境
-
电脑系统
- windows10
-
编译器
- cygwin下的gcc
-
编程语言
- C语言
-
编辑与工程环境
- VSCode
- 不了解VSVode安装配置的请看本人的原创文档:
三,编程思路与流程
-
流程
- 本着自顶向下的程序设计理念,从使用者的角度来设计代码。
-
设计数独数据结构
- 满足条件:
- 灵活载入题目
- 用户可以自由设定求解的数量(打印出来解的数量)
- 可以统计指定题目的所有解的数量(不打印具体解的内容,只统计解的个数)
- 程序实现如下,按输入,输入输出,输出三类,给出如下程序结构并标注。
- 满足条件:
-
编写测试代码框架
- 满足条件:
- 1,自由载入数独题目
- 2,配置解析参数
- 解一个解就行?
- 耗时最小
- 解指定数量的解,并打印?
- 耗时依照题目和设定数量而定
- 解指定数量的解,并且要统计出所有解的数量?
- 耗时最长,因为得出指定数量的解之后程序依然不能停止,需要持续计算,直到穷举所有数据为止,只有这样才会统计出所有的解的数量。
- 解一个解就行?
- 程序实现如下:
- 满足条件:
-
编写核心代码---接口函数
- 满足条件:
- 只有一个结构体指针参数,计算的结果也打印到这个结构体里,这样用户只要弄明白这个结构体即可。
- 里面包含两层函数
- 1,初始化
- 2,执行计算
- 程序实现如下:
- 满足条件:
-
编写核心代码---寻找下一个空单元
- 满足条件
- 每个单元有效的数字为1-9,而初始的0就可以用来表达是空单元。
- 为了加快计算速度,我们每次不是从头找,而是从参数指定行开始搜索。
- 程序实现如下:
- 满足条件
-
编写核心代码---利用递归回溯寻找空单元并填入合规数据
- 程序架构
-
函数(结构体指针,行,列) { 参数 循环 { 1,编写数独规则 1-9都尝试完了,仍不合规,则回溯 填充违规,则继续尝试下个数 2,寻找下一个空单元 找不到下一个空单元,说明每个单元都已经填入合规数,解成立。 3,利用递归尝试向下一单元填入合规数 } }
-
-
程序实现:
-
数独规则核心代码
-
-
- 程序架构
四,完整源码(免费精简版)
/**
******************************************************************************
* @file xsl_game_sudo.c
* @author 徐松亮 许红宁(5387603@qq.com)
* @version V1.0.0
* @date 2018/11/01
******************************************************************************
* @attention
* 待解决
* 1,数独出题
* GNU General Public License (GPL)
*
* <h2><center>© COPYRIGHT 2017 XSLXHN</center></h2>
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include <stdint.h>
// 用于计算耗时统计
#include <time.h>
/* Private define ------------------------------------------------------------*/
#define XSLGAMESUDO_SUDOKU_LEVEL 9
#define OK 0
#define ERR 1
/* Private typedef -----------------------------------------------------------*/
typedef struct
{
// 输入---计算前用户设定
uint8_t cells[XSLGAMESUDO_SUDOKU_LEVEL][XSLGAMESUDO_SUDOKU_LEVEL];
//-----最大输出答案数量(1,为了防止pOutBuf数据溢出;2,为了计算适可而止;)
uint32_t MaxOutAnswersCount;
// 输入输出
//-----输出缓存(NULL---无解输出)
uint8_t *pOutBuf;
//-----解最大数量(NULL---不求此值则非全局搜索)
uint32_t *pAnswersCount;
// 输出---计算后的统计结果
uint32_t OutAnswersCount;
} XSLGAMESUDO_S_SUDO;
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static const uint8_t XslGameSudo_SudoBuf[XSLGAMESUDO_SUDOKU_LEVEL][XSLGAMESUDO_SUDOKU_LEVEL] = {
8, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3, 6, 0, 0, 0, 0, 0,
0, 7, 0, 0, 9, 0, 2, 0, 0,
0, 5, 0, 0, 0, 7, 0, 0, 0,
0, 0, 0, 0, 4, 5, 7, 0, 0,
0, 0, 0, 1, 0, 0, 0, 3, 0,
0, 0, 1, 0, 0, 0, 0, 6, 8,
0, 0, 8, 5, 0, 0, 0, 1, 0,
0, 9, 0, 0, 0, 0, 4, 0, 0
};
//
XSLGAMESUDO_S_SUDO XslGameSudo_s_Sudo;
/* Extern variables ----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief 格式化打印
* @note 打印指定行列的数组
* @param *pXslGameSudoS --- 数据指针
* mode --- 0-打印问题 1-打印答案
* @return null
*/
static void XslGameSudo_FormatPrint(XSLGAMESUDO_S_SUDO *pXslGameSudoS, uint8_t mode)
{
uint8_t i, j, k;
uint8_t *pbuf;
// 打印问题
if (mode == 0)
{
printf("Sudo Questions:\n");
pbuf = (uint8_t *)pXslGameSudoS->cells;
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
printf("%2d", pbuf[i * XSLGAMESUDO_SUDOKU_LEVEL + j]);
if (j == (XSLGAMESUDO_SUDOKU_LEVEL - 1))
printf("\n");
}
}
}
// 打印答案
else if (mode == 1)
{
if (pXslGameSudoS->OutAnswersCount == 0)
{
printf("Sudo Processor : No Solution!\n");
return;
}
//
pbuf = pXslGameSudoS->cells;
for (k = 0; k < pXslGameSudoS->OutAnswersCount; k++)
{
printf("Sudo Answers(%d):\n", k + 1);
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
printf("%2d", pbuf[(k * XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL) + (i * XSLGAMESUDO_SUDOKU_LEVEL + j)]);
if (j == (XSLGAMESUDO_SUDOKU_LEVEL - 1))
printf("\n");
}
}
}
}
}
/**
* @brief 寻找下一个未填充的单元
* @note 寻找下一个未填充的单元
* @param buf --- 输入输出 --- 数据
* startrow --- 输入 --- 查找起始行,此函数用于优化计算速度
* *row ---
* *col ---
* @return 0-没有空单元 1-有空单元
*/
static uint8_t XslGameSudo_findNextEmpty(XSLGAMESUDO_S_SUDO *pXslGameSudoS, int startrow, uint8_t *row, uint8_t *col)
{
uint8_t i, j;
for (i = startrow; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
if (pXslGameSudoS->cells[i][j] == 0)
{
*row = i;
*col = j;
return OK;
}
}
}
return ERR;
}
/**
* @brief 寻找下一个未填充的单元
* @note 寻找下一个未填充的单元
* @param arr
* row
* col
* @return 0-没有符合规则的算法 1-已经填入符合规则的数据
*/
static int XslGameSudo_Cal(XSLGAMESUDO_S_SUDO *pXslGameSudoS, uint8_t row, uint8_t col)
{
uint8_t i = 0, j = 0, n = 0;
uint8_t next_row = 0, next_col = 0;
while (1)
{
//-----------------------1,向空单元填数
next_num:
// 填充失败判断-->尝试填充1-9都失败
++n;
if (n >= (XSLGAMESUDO_SUDOKU_LEVEL + 1))
{
break;
}
// 填充违规判断--1-->判断行重复
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
if (pXslGameSudoS->cells[row][j] == n)
{
goto next_num;
}
}
// 填充违规判断--2-->判断列重复
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
if (pXslGameSudoS->cells[i][col] == n)
{
goto next_num;
}
}
// 填充违规判断--3-->判断所在小九宫格重复
uint8_t x = (row / 3) * 3;
uint8_t y = (col / 3) * 3;
for (i = x; i < x + 3; i++)
{
for (j = y; j < y + 3; j++)
{
if (pXslGameSudoS->cells[i][j] == n)
{
goto next_num;
}
}
}
//填充确认-->可以填充
pXslGameSudoS->cells[row][col] = n;
//-----------------------2,寻找下一个空单元
//如果9宫格已填满
if (ERR == XslGameSudo_findNextEmpty(pXslGameSudoS, row, &next_row, &next_col))
{
pXslGameSudoS->OutAnswersCount=1;
return OK;
}
//-----------------------3,向下一个空单元填数
if (ERR == XslGameSudo_Cal(pXslGameSudoS, next_row, next_col))
{
pXslGameSudoS->cells[row][col] = 0;
continue;
}
else
{
return OK;
}
}
// 失败
return ERR;
}
/**
* @brief 数独解析
* @note 数独解析,计算的结果都包含在参数结构体里,所以并没有返回值。
* @param *pXslGameSudoS --- 数独结构体指针
* @return null
*/
void XslGameSudo_Processor(XSLGAMESUDO_S_SUDO *pXslGameSudoS)
{
uint8_t row, col;
//初始化
XslGameSudo_findNextEmpty(pXslGameSudoS, 0, &row, &col);
pXslGameSudoS->OutAnswersCount = 0;
if (pXslGameSudoS->pAnswersCount != NULL)
{
*(pXslGameSudoS->pAnswersCount) = 0;
}
//计算
XslGameSudo_Cal(pXslGameSudoS, row, col);
}
/**
* @brief main函数
* @note 主函数入口
* @param null
* @return null
*/
uint8_t MemBuf[1024]; //81个字节存储一组解,1024可以存储大于10个解
uint32_t SudoCount;
void main(int argc, char **argv)
{
uint8_t res;
uint32_t time1, time2;
printf("--------------------------------------------------\n");
printf(" XSL Sudo Processor(simply) \n");
printf("--------------------------------------------------\n");
// 数据载入
memcpy((uint8_t *)(XslGameSudo_s_Sudo.cells), (uint8_t *)&XslGameSudo_SudoBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
// 设置配置
//---最多解析10个解
XslGameSudo_s_Sudo.MaxOutAnswersCount = 0;
XslGameSudo_s_Sudo.pOutBuf = MemBuf;
XslGameSudo_s_Sudo.pAnswersCount = &SudoCount;
// 打印原始数独
XslGameSudo_FormatPrint(&XslGameSudo_s_Sudo, 0);
// 启动数独测试
time1 = GetTickCount();
XslGameSudo_Processor(&XslGameSudo_s_Sudo);
time2 = GetTickCount();
// 打印结果
XslGameSudo_FormatPrint(&XslGameSudo_s_Sudo, 1);
// 打印耗时
printf("Time(ms):%ld\n", time2 - time1);
// 定住页面,否则程序结束直接闪退,延时10秒自动退出
time1 = time2 = 0;
time1 = GetTickCount();
while (((time2 - time1) < 100000) || (time2 == 0))
{
time2 = GetTickCount();
}
}
五,免费精简版源码结果演示
六,正常版源码下载链接与演示
-
收费源码下载
- 本来想用Demo大师,不过没弄明白,还是用最笨的办法吧。
- QQ添加联络本博主:5387603
- 支付宝/微信付费5元
- 获取获取下载链接和下载码
- 操作半天,实属扯犊子,太麻烦,说不上什么时候就全开源了,但是让我现在就开源,白折腾一通还心有不甘,这篇就这么地吧,下回还是全开源的好!
-
演示