徐松亮算法教学-基于C语言的数独(九宫格)求解(含多解和解数统计)

版权声明:本文为博主徐松亮的原创作品,未经允许不得转载,多谢支持!QQ:5387603

推荐点击此链接:欢迎进入徐松亮博客一站式导航搜索(随时更新)


目录

一,前言

二,开发环境

电脑系统

编译器

编程语言

编辑与工程环境

三,编程思路与流程

流程

设计数独数据结构

编写测试代码框架

编写核心代码---接口函数

编写核心代码---寻找下一个空单元

编写核心代码---利用递归回溯寻找空单元并填入合规数据

四,完整源码(免费精简版)

五,免费精简版源码结果演示

六,正常版源码下载链接与演示

源码下载链接

演示


一,前言

  • 数独,说实话我玩过,且并不是很喜欢玩,觉得无聊也太浪费时间,当然玩的水平也不咋样。
  • 但是我为什么又写这篇文章又编写代码的去做呢?
    • 因为它是一个很好的适合计算机处理的数学算法的问题。
    • 数独规则很简单
    • 解题需要计算机编程的递归
    • 出题需要矩阵变换
  • 数独的最大终盘数量
    • 目前普遍认为是6670903752021072936960,也就是所如果完全随机,一个人一辈子玩数独很难遇到万全一样的题,这个数量级大大出乎了我的意料。
    • 刨去对称,换行等等等规则后,也有5472730538个终盘。
    • 本人一开始想用穷举给算出来,看到这个数后就服了,即使用最快的计算机就算把计算机算坏了也算不完,而且统计这么大的数,还要做大数计算程序,目前的64位计算机肯定是不行。
  • 本文着重说明通过C语言来解析数独问题,至于数独的随机出题问题由下一篇文章说明
  • 本文只针对标准的9宫格数独,不讨论其他变种数独,也不讨论数独的难度等级等其他问题。
  • 本文为了简化代码,题目直接在源码中,结果直接终端打印,没有做文件数据的输入输出等操作。
  • 本文有免费代码下载(复制即可),可以计算并打印出第一个解。
  • 本文另有收费代码,可以计算并打印出指定数量的解,并且统计题目有效解的总数量。
    • 代码只建议用来学习,并没有做太多的容错处理。
    • 这是我个人第一个收费的代码,且只是象征性收取5元哦,对我来说,只是跑跑流程,玩一玩,看看这玩意能不能赚到钱,哈哈,毕竟打字编程改良验证每个环节也消耗了我的一些精力。所以太认真较真讲究值与不值的朋友就别买了。

二,开发环境

三,编程思路与流程

  • 流程

    • 本着自顶向下的程序设计理念,从使用者的角度来设计代码。
  • 设计数独数据结构

    • 满足条件:
      • 灵活载入题目
      • 用户可以自由设定求解的数量(打印出来解的数量)
      • 可以统计指定题目的所有解的数量(不打印具体解的内容,只统计解的个数)
    • 程序实现如下,按输入,输入输出,输出三类,给出如下程序结构并标注。
  • 编写测试代码框架

    • 满足条件:
      • 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>&copy; 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元
    • 获取获取下载链接和下载码
    • 操作半天,实属扯犊子,太麻烦,说不上什么时候就全开源了,但是让我现在就开源,白折腾一通还心有不甘,这篇就这么地吧,下回还是全开源的好!
       
  • 演示

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐松亮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值