編程解決數獨問題,網上查了很多資料,大多數的C語言程序雖然可以運行出結果,但是往往定義了很多全局數組變量來存儲待解決數獨中每一個空的狀態信息(如:通過掃描整個數獨表,記錄待填的空的位置;記錄每個待填空所在行和列已經有哪些值,接下來可以填哪些值等信息),這導致代碼中全局變量很多,全局變量在整個程序中到處調用,在沒有足夠註釋的情況下整個程序可讀性不高,代碼結構不是很清楚。
本文下面給出的C程序中沒有使用多少臨時變量,當然此程序沒有定義那麼多全局變量存儲數獨解決過程的狀態信息,可能導致運行時間稍微長一些,不過在我的環境下進行的幾次測試,運行出結果的時間基本都在1秒以內(偶爾也會有7、8秒纔出結果……)。這就是一個 “浪費空間換時間,浪費時間換空間” 的問題了。
下面的C語言程序是參考着此處(https://www.cnblogs.com/wang-lianjie/p/shudujiafa.html)java程序中的算法所寫的。程序中給出了較詳細的註釋,並且使用純C語言,沒有用C++的函數庫。
理解以下程序需要的主要基礎知識:
1.C語言:數組、循環、判斷、函數、指針操作等。
2.算法方面: 窮舉思想、遞歸、基本的試探與回溯等。
補充說明:
1.代碼沒有進行充分的測試。
輸入的數獨數據本身是無解的,或對於一個數獨可能存在一題多解的情況,程序會有什麼反應我沒測試過。
3.數獨數據是寫死在程序裏的,沒有寫從控制檯手動輸入或是從文件中讀取數獨數據的功能。
2.該段代碼具體的算法描還沒寫,哪天有空了再完善吧。。。
#include <stdio.h>
#include <stdlib.h>
#define N 3 //小九宮格
#define M 9 //大九宮格
/*******************************************定義全局變量***************************************************/
int x[M][M]={
{0,2,0,0,0,9,0,1,0},
{5,0,6,0,0,0,3,0,9},
{0,8,0,5,0,2,0,6,0},
{0,0,5,0,7,0,1,0,0},
{0,0,0,2,0,8,0,0,0},
{0,0,4,0,1,0,8,0,0},
{0,5,0,8,0,7,0,3,0},
{7,0,2,3,0,0,4,0,5},
{0,4,0,0,0,0,0,7,0},
};
/* 結果:
4 2 3 7 6 9 5 1 8
5 7 6 4 8 1 3 2 9
9 8 1 5 3 2 7 6 4
8 3 5 6 7 4 1 9 2
1 9 7 2 5 8 6 4 3
2 6 4 9 1 3 8 5 7
6 5 9 8 4 7 2 3 1
7 1 2 3 9 6 4 8 5
3 4 8 1 2 5 9 7 6
*/
/*---------------------------------------------------------------------------------------------------------*/
//int x[M][M]={
// {0,6,0,5,9,3,0,0,0},
// {9,0,1,0,0,0,5,0,0},
// {0,3,0,4,0,0,0,9,0},
// {1,0,8,0,2,0,0,0,4},
// {4,0,0,3,0,9,0,0,1},
// {2,0,0,0,1,0,6,0,9},
// {0,8,0,0,0,6,0,2,0},
// {0,0,4,0,0,0,8,0,7},
// {0,0,0,7,8,5,0,1,0},
//};
/* 結果:
7 6 2 5 9 3 1 4 8
9 4 1 2 7 8 5 3 6
8 3 5 4 6 1 7 9 2
1 9 8 6 2 7 3 5 4
4 7 6 3 5 9 2 8 1
2 5 3 8 1 4 6 7 9
3 8 7 1 4 6 9 2 5
5 1 4 9 3 2 8 6 7
6 2 9 7 8 5 4 1 3
*/
/******************************************* 函數聲明 ***************************************************/
//解決問題最主要的遞歸函數,遞歸訪問大的9*9矩陣中的每一個小格,對待填空格(二維數組x中值爲零的格子)進行填值操作。
void Solve(int r, int c);
//以下3個函數爲Solve運行過程中調用的判斷函數,具體在下面有註釋。
int checkedbox(int r);
int checked(int r, int c, int num);
int* getData(int r, int c,int *data);
//輸出最終計算結果的函數
void show(void);
/******************************************* 函數定義 ***************************************************/
void Solve(int r, int c)
{
int i;
//(一)
//如果填的行號增加到了9,表示已經填完了整個數獨空格(x二維數組行列的下標範圍均爲 0-8),打印結果即可
if (r == M) {
show();
return; //此處的return返回表示已經填完了整個數獨空格,接下來會逐步返回到主函數
}
//(二)
//在填寫第3、6行開頭數字的時,檢查上邊的三行的方塊裏是不是有重複的數字
if (c == 0 && (r == N || r == N*2)) {
if (checkedbox(r) == 0) { // checkedbox()返回0表示小九宮格中出現了重複數字,說明之前的填寫有誤,則需要返回之前的填寫試探中。
return; //這裏的return會返回到第四個if中的某次循環中
}
}
//(三)
//填到某行最後一個元素時,要轉到下一行開頭去
if (c == M) {
Solve(r+1, 0);
return;
}
//(四)
//當前的座標是0的時候纔開始循環填寫,否則跳過去填寫下一個數字
if (x[r][c] == 0) {
for (i = 1; i <= M; i++) { //對於每一個待填寫的空格,循環1-9進行試探
if (checked(r,c,i) == 1) {
x[r][c]=i;
Solve(r, c+1);
x[r][c]=0;
}
}
}
/* 如果上面的四個情況都不是。則正常地填該行的下一個元素,即行號不動,列號往前走一個。即Solve(r, c+1);
上面的四個情況是指:
1.既沒有填寫或處理完整個數獨表
2.也不是剛填寫或處理完了3個並列的小九宮格
3.也沒有填寫或處理到某行最後一個元素
4.此處座標也不是零,即無需填寫或處理
*/
else{
Solve(r, c+1);
}
}
/**
* 得到指定小九宮格中的九個數據,存入data中並返回data指針
* @param r 行號,可能取值爲3,6
* @param c 列號,可能取值爲0,3,6
* @return 判斷結果,0表示小九宮格中有重複數字。1表示小九宮格中無重複數字。
*/
int checkedbox(int r) {
int i,j,c;
int *data = malloc(sizeof(int)*M);
for (c = 0; c < M; c+=N) { //循環三次,分別是c=0,3,6。對應循環大矩陣中9個3*3的小九宮格
data = getData(r,c,data); //得到小九宮格中的九個數據,存入全局變量數組data中
for(i = 0; i < M-1 ; i++) {
for(j = i+1 ; j < M ; j++) {
if(data[i] == data[j]) { //循環成立表示有重複數字
return 0; //有重複數字,返回0。
}
}
}
}
free(data); //函數執行結束,返回判斷結果以前,釋放指針malloc的空間。
return 1; //該函數能運行至此還沒有結束返回,表示並列的三個小九宮格中,填入的均是1-9,沒有重複數字,故返回1。
}
/**
* 得到指定小九宮格中的九個數據,存入data中並返回data指針
* @param r 行號,可能取值爲3,6
* @param c 列號,可能取值爲0,3,6
* @return data指針(數組)
*/
int* getData(int r, int c,int *data){
int i,j,k = 0;
for (i = r-N; i < r ; i++) {
for(j = c; j < c+N ; j++){
data[k++] = x[i][j];
}
}
return data;
}
/**
* 判斷某個待填位置所在的行和列是否有某個數字
* @param r 待填位置的行號
* @param c 待填位置的列號
* @param num 需要比較的待填數字
* @return 返回0:有重複數字,x[r][c]位置不能填num
*/
int checked(int r, int c, int num) {
int j = 0,flag = 1;
for (j = 0; j < M; j++) {
if (x[j][c]==num || x[r][j]==num) {
flag = 0;
}
}
return flag;
}
//輸出運算結果
void show(void) {
int i,j;
for (i = 0; i < M; i++) {
for (j = 0; j < M; j++) {
printf("%d ",x[i][j]);
}
printf("\n");
}
printf("\n");
}
int main(void)
{
Solve(0,0);
return 0;
}