版权声明:本文为博主徐松亮的原创作品,未经允许不得转载,多谢支持!QQ:5387603
推荐点击此链接:欢迎进入徐松亮博客一站式导航搜索(随时更新)
目录
一,前言
- 昨天,发布了一篇数独解题的文章,但是只有解题没有出题,总是感觉差点意思。
- 网上搜了一些相关资料,大多都是单一方式,流程基本上是填入部分随机数---产生一个解盘---扣掉一些数。这个方案如果只是用于出题也是可行的,但是这么简单的话,我真是懒得总结。
- 本文的矩阵转换算法都为博主亲自编写调试,都是自身转换,而不需要建立同样大小的缓存,尤其是矩阵旋转相关代码,还真花费了我一些时间,很是推荐,比目前网上的要好很多,后续我会把矩阵转换相关的代码标准化后另建立学习文档。
- 偶然在网上发现一篇文章,它讲了多种数独终盘生成办法,主要是用的矩阵变换,还是那句话:虽然我对数独本身这个游戏没什么兴趣,但是对它涉及到的计算机数学问题,还是颇有兴趣。
- 做到这,我想到一个问题,接数独是不是也可以用这篇文章讲的矩阵变换方法,经过各种行列兑换,生成一个解,原理有点像拧魔方似的,方法理论上应该是可行的,但不过这个方法不能统计出解的数量。
二,开发环境
-
电脑系统
- windows10
-
编译器
- cygwin下的gcc
-
编程语言
- C语言
-
编辑与工程环境
- VSCode
- 不了解VSVode安装配置的请看本人的原创文档:
三,方法
-
方法概述
- 基于一个成型的终盘,经过下列任意变换后,还是一个符合规则的终盘。
-
生成(或随机选定)种子矩阵终盘
- 如果能真正的随机生成一个终盘,那就不用下面的变换了,所以我们在此用选定的现有矩阵做种子。
- 真正做可以多做几个,然后用随机数来选。
-
两数交换法
- 就是随机选取两个数,可以是1和9,也可以是3和5,全局交换后,仍是有效终盘。
- 下图举例,我们已2和8来做交换,如图所示:
-
非跨界行交换
- 以宫为界,即1-3行为一组,4-6行为一组,7-9行为一组,组内可执行行交换,组间不能。符合此规则执行行交换后,终盘仍为符合规则的终盘。
- 下图举例,同色为界,我们执行1与3行的数据交换。
-
非跨界列交换
- 以宫为界,即1-3列为一组,4-6列为一组,7-9列为一组,组内可执行行交换,组间不能。符合此规则执列列交换后,终盘仍为符合规则的终盘。
- 下图举例,我们执列1与3列的数据交换。
-
块行交换
- 以宫为界,即1-3行为一组,4-6行为一组,7-9行为一组,组与组交换在此我起名称作块行交换。符合此规则交换后,终盘仍为符合规则的终盘。
- 下图举例,我们组1与组2的数据交换。
-
块列交换
- 以宫为界,即1-3列为一组,4-6列为一组,7-9列为一组,组与组交换在此我起名称作块列交换。符合此规则交换后,终盘仍为符合规则的终盘。
- 下图举例,我们组1与组2的数据交换。
-
矩阵旋转
- 矩阵旋转,就是相当于你转着圈的去看数独题,你从四个方向看,这个题都是成立的。
- 下图举例,如图。
-
矩阵行镜像
- 矩阵行镜像,就好比你看完报纸正面,反过来看报纸背面一样。符合此规则交换后,终盘仍为符合规则的终盘。
- 下图举例,如图
-
矩阵列镜像
- 矩阵列镜像,原理同行镜像其实是一样的。符合此规则交换后,终盘仍为符合规则的终盘。
- 下图举例,如图
-
矩阵对角线镜像
- 矩阵对角线镜像,原理相当于数学当中的矩阵转置。
- 下图举例,如图
四,源码
/**
******************************************************************************
* @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_SudoGenerateBuf[XSLGAMESUDO_SUDOKU_LEVEL][XSLGAMESUDO_SUDOKU_LEVEL] = {
8, 1, 2, 7, 5, 3, 6, 4, 9,
9, 4, 3, 6, 8, 2, 1, 7, 5,
6, 7, 5, 4, 9, 1, 2, 8, 3,
1, 5, 4, 2, 3, 7, 8, 9, 6,
3, 6, 9, 8, 4, 5, 7, 2, 1,
2, 8, 7, 1, 6, 9, 5, 3, 4,
5, 2, 1, 9, 7, 4, 3, 6, 8,
4, 3, 8, 5, 2, 6, 9, 1, 7,
7, 9, 6, 3, 1, 8, 4, 5, 2};
//
XSLGAMESUDO_S_SUDO XslGameSudo_s_Sudo;
/* Extern variables ----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief 格式化打印
* @note 打印指定行列的数组
* @param *pXslGameSudoS --- 数据指针
* mode --- 0-打印问题 1-打印答案 2-只打印数独不打印字符串
* @return null
*/
static void XslGameSudo_FormatPrint(XSLGAMESUDO_S_SUDO *pXslGameSudoS, uint8_t mode)
{
uint8_t i, j, k;
uint8_t *pbuf;
// 打印问题
if ((mode == 0) || (mode == 2))
{
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->pAnswersCount == 0)
{
printf("Sudo Processor : No Solution!\n");
return;
}
//
pbuf = pXslGameSudoS->pOutBuf;
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");
}
}
}
if (pXslGameSudoS->pAnswersCount != NULL)
{
printf("Sudo Answers Count: %d\n", *(pXslGameSudoS->pAnswersCount));
}
}
}
uint8_t XslGameSudo_Generate(XSLGAMESUDO_S_SUDO *pXslGameSudoS)
{
uint8_t num = 1;
// 生成种子数独
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
// 两数交换法
{
uint8_t commutation_data_1, commutation_data_2;
uint8_t i;
uint8_t *pbuf = pXslGameSudoS->cells;
// 设定交换数
commutation_data_1 = 2;
commutation_data_2 = 8;
// 执行数据交换
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
if (pbuf[i] == commutation_data_1)
{
pbuf[i] = commutation_data_2;
}
else if (pbuf[i] == commutation_data_2)
{
pbuf[i] = commutation_data_1;
}
else
{
;
}
}
}
printf("Point Exchange method sudoku(point:2,8):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 非跨界行交换
{
uint8_t commutation_row_1, commutation_row_2;
uint8_t i, temp;
// 设定交换行(0-8有效)
commutation_row_1 = 0;
commutation_row_2 = 2;
// 执行交换行
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
temp = pXslGameSudoS->cells[commutation_row_1][i];
pXslGameSudoS->cells[commutation_row_1][i] = pXslGameSudoS->cells[commutation_row_2][i];
pXslGameSudoS->cells[commutation_row_2][i] = temp;
}
}
printf("Row Exchange method sudoku(row:1,3):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 非跨界列交换
{
uint8_t commutation_col_1, commutation_col_2;
uint8_t i, temp;
// 设定交换行(0-8有效)
commutation_col_1 = 0;
commutation_col_2 = 2;
// 执行交换行
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
temp = pXslGameSudoS->cells[i][commutation_col_1];
pXslGameSudoS->cells[i][commutation_col_1] = pXslGameSudoS->cells[i][commutation_col_2];
pXslGameSudoS->cells[i][commutation_col_2] = temp;
}
}
printf("Col Exchange method sudoku(col:1,3):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 块行交换
{
uint8_t commutation_blockrow_1, commutation_blockrow_2;
uint8_t i, j, temp;
// 设定交换行(0-2有效)
commutation_blockrow_1 = 0;
commutation_blockrow_2 = 1;
// 执行交换行
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
for (j = 0; j < 3; j++)
{
temp = pXslGameSudoS->cells[commutation_blockrow_1 * 3 + j][i];
pXslGameSudoS->cells[commutation_blockrow_1 * 3 + j][i] = pXslGameSudoS->cells[commutation_blockrow_2 * 3 + j][i];
pXslGameSudoS->cells[commutation_blockrow_2 * 3 + j][i] = temp;
}
}
}
printf("Block Row Exchange method sudoku(Block Row:1,2):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 块列交换
{
uint8_t commutation_blockrow_1, commutation_blockrow_2;
uint8_t i, j, temp;
// 设定交换行(0-2有效)
commutation_blockrow_1 = 0;
commutation_blockrow_2 = 1;
// 执行交换行
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL; i++)
{
for (j = 0; j < 3; j++)
{
temp = pXslGameSudoS->cells[i][commutation_blockrow_1 * 3 + j];
pXslGameSudoS->cells[i][commutation_blockrow_1 * 3 + j] = pXslGameSudoS->cells[i][commutation_blockrow_2 * 3 + j];
pXslGameSudoS->cells[i][commutation_blockrow_2 * 3 + j] = temp;
}
}
}
printf("Block Col Exchange method sudoku(Block Col:1,2):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 矩阵旋转(下面是方形矩阵的自旋转程序(不用额外缓存))
{
uint8_t i, j, k, n, x;
uint8_t temp1, temp2;
//设置参数
n = XSLGAMESUDO_SUDOKU_LEVEL;
x = n / 2;
// 一圈一圈处理
k = n - 1;
for (i = 0; i < x; i++)
{
for (j = 0; j < k; j++)
{
//右列
temp1 = pXslGameSudoS->cells[i + j][n - 1 - i];
pXslGameSudoS->cells[i + j][n - 1 - i] = pXslGameSudoS->cells[i][i + j];
//下行
temp2 = pXslGameSudoS->cells[n - 1 - i][n - 1 - i - j];
pXslGameSudoS->cells[n - 1 - i][n - 1 - i - j] = temp1;
//左列
temp1 = pXslGameSudoS->cells[n - 1 - i - j][i];
pXslGameSudoS->cells[n - 1 - i - j][i] = temp2;
//左上
pXslGameSudoS->cells[i][i + j] = temp1;
}
k -= 2;
}
}
printf("Rotate method sudoku(90 degrees):\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 矩阵行镜像
{
uint8_t i, j;
uint8_t temp;
//
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL / 2; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
temp = pXslGameSudoS->cells[j][XSLGAMESUDO_SUDOKU_LEVEL - 1 - i];
pXslGameSudoS->cells[j][XSLGAMESUDO_SUDOKU_LEVEL - 1 - i] = pXslGameSudoS->cells[j][i];
pXslGameSudoS->cells[j][i] = temp;
}
}
}
printf("X Mirror sudoku:\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 矩阵列镜像
{
uint8_t i, j;
uint8_t temp;
//
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL / 2; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL; j++)
{
temp = pXslGameSudoS->cells[XSLGAMESUDO_SUDOKU_LEVEL - 1 - i][j];
pXslGameSudoS->cells[XSLGAMESUDO_SUDOKU_LEVEL - 1 - i][j] = pXslGameSudoS->cells[i][j];
pXslGameSudoS->cells[i][j] = temp;
}
}
}
printf("Y Mirror sudoku:\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
memcpy((uint8_t *)(pXslGameSudoS->cells), (uint8_t *)&XslGameSudo_SudoGenerateBuf[0][0], XSLGAMESUDO_SUDOKU_LEVEL * XSLGAMESUDO_SUDOKU_LEVEL);
printf("%d,----------Seeds sudoku:\n", num++);
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
// 矩阵对角线镜像
{
uint8_t i, j;
uint8_t temp;
for (i = 0; i < XSLGAMESUDO_SUDOKU_LEVEL - 1; i++)
{
for (j = 0; j < XSLGAMESUDO_SUDOKU_LEVEL - 1 - i; j++)
{
temp = pXslGameSudoS->cells[i + j + 1][i];
pXslGameSudoS->cells[i + j + 1][i] = pXslGameSudoS->cells[i][i + j + 1];
pXslGameSudoS->cells[i][i + j + 1] = temp;
}
}
}
printf("Transpose sudoku:\n");
XslGameSudo_FormatPrint(pXslGameSudoS, 2);
//----------
}
/**
* @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 Generate(Normal) \n");
printf("--------------------------------------------------\n");
XslGameSudo_Generate(&XslGameSudo_s_Sudo);
while(1);
printf("--------------------------------------------------\n");
printf(" XSL Sudo Processor(Normal) \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 = 5;
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();
}
}
五,运行结果演示
-
运行结果---两数交换法
-
运行结果---非跨界行交换
-
运行结果---非跨界列交换
-
运行结果---块行交换
-
运行结果---块列交换
-
运行结果---矩阵旋转
-
运行结果---矩阵行镜像
-
运行结果---矩阵列镜像
-
运行结果---矩阵对角线镜像
六,引用资料致谢
- 感谢如下连接给的思路: