徐鬆亮演算法教學-基於C語言的數獨(九宮格)求解(含多解和解數統計)
阿新 • • 發佈:2019-02-19
目錄
一,前言
- 數獨,說實話我玩過,且並不是很喜歡玩,覺得無聊也太浪費時間,當然玩的水平也不咋樣。
- 但是我為什麼又寫這篇文章又編寫程式碼的去做呢?
- 因為它是一個很好的適合計算機處理的數學演算法的問題。
- 數獨規則很簡單
- 解題需要計算機程式設計的遞迴
- 出題需要矩陣變換
- 數獨的最大終盤數量
- 目前普遍認為是6670903752021072936960,也就是所如果完全隨機,一個人一輩子玩數獨很難遇到萬全一樣的題,這個數量級大大出乎了我的意料。
- 刨去對稱,換行等等等規則後,也有5472730538個終盤。
- 本人一開始想用窮舉給算出來,看到這個數後就服了,即使用最快的計算機就算把計算機算壞了也算不完,而且統計這麼大的數,還要做大數計算程式,目前的64位計算機肯定是不行。
- 本文著重說明通過C語言來解析數獨問題,至於數獨的隨機出題問題由下一篇文章說明
- 本文只針對標準的9宮格數獨,不討論其他變種數獨,也不討論數獨的難度等級等其他問題。
- 本文為了簡化程式碼,題目直接在原始碼中,結果直接終端列印,沒有做檔案資料的輸入輸出等操作。
- 本文有免費程式碼下載(複製即可),可以計算並打印出第一個解。
- 本文另有收費程式碼,可以計算並打印出指定數量的解,並且統計題目有效解的總數量。
- 程式碼只建議用來學習,並沒有做太多的容錯處理。
- 這是我個人第一個收費的程式碼,且只是象徵性收取5元哦,對我來說,只是跑跑流程,玩一玩,看看這玩意能不能賺到錢,哈哈,畢竟打字程式設計改良驗證每個環節也消耗了我的一些精力。所以太認真較真講究值與不值的朋友就別買了。
- 程式碼只建議用來學習,並沒有做太多的容錯處理。
二,開發環境
-
電腦系統
- windows10
-
編譯器
- cygwin下的gcc
-
程式語言
- C語言
三,程式設計思路與流程
-
流程
- 本著自頂向下的程式設計理念,從使用者的角度來設計程式碼。
-
設計數獨資料結構
- 滿足條件:
- 靈活載入題目
- 使用者可以自由設定求解的數量(打印出來解的數量)
- 可以統計指定題目的所有解的數量(不列印具體解的內容,只統計解的個數)
- 程式實現如下,按輸入,輸入輸出,輸出三類,給出如下程式結構並標註。
- 滿足條件:
-
編寫測試程式碼框架
- 滿足條件:
- 1,自由載入數獨題目
- 2,配置解析引數
- 解一個解就行?
- 耗時最小
- 解指定數量的解,並列印?
- 耗時依照題目和設定數量而定
- 解指定數量的解,並且要統計出所有解的數量?
- 耗時最長,因為得出指定數量的解之後程式依然不能停止,需要持續計算,直到窮舉所有資料為止,只有這樣才會統計出所有的解的數量。
- 解一個解就行?
- 程式實現如下:
- 滿足條件:
-
編寫核心程式碼---介面函式
- 滿足條件:
- 只有一個結構體指標引數,計算的結果也列印到這個結構體裡,這樣使用者只要弄明白這個結構體即可。
- 裡面包含兩層函式
- 1,初始化
- 2,執行計算
- 程式實現如下:
- 滿足條件:
-
編寫核心程式碼---尋找下一個空單元
- 滿足條件
- 每個單元有效的數字為1-9,而初始的0就可以用來表達是空單元。
- 為了加快計算速度,我們每次不是從頭找,而是從引數指定行開始搜尋。
- 程式實現如下:
- 滿足條件
-
編寫核心程式碼---利用遞歸回溯尋找空單元並填入合規資料
- 程式架構
-
函式(結構體指標,行,列) { 引數 迴圈 { 1,編寫數獨規則 1-9都嘗試完了,仍不合規,則回溯 填充違規,則繼續嘗試下個數 2,尋找下一個空單元 找不到下一個空單元,說明每個單元都已經填入合規數,解成立。 3,利用遞迴嘗試向下一單元填入合規數 } }
-
-
程式實現:
-
數獨規則核心程式碼
-
- 程式架構
四,完整原始碼(免費精簡版)
/**
******************************************************************************
* @file xsl_game_sudo.c
* @author 徐鬆亮 許紅寧([email protected])
* @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元
- 獲取獲取下載連結和下載碼
- 操作半天,實屬扯犢子,太麻煩,說不上什麼時候就全開源了,但是讓我現在就開源,白折騰一通還心有不甘,這篇就這麼地吧,下回還是全開源的好!
-
演示