C++使用回溯法生成數獨

數獨,應該不用我說明吧,是一個9*9的矩陣,矩陣裏的每一個數字都是1~9中的一個。在每一行、每一列每個數字只能出現一次,另外,在每一個九宮格里每個數字也只能出現一次。

我曾經在網上看過一種“假”的數獨生成法,生成的數獨其實是有規律的,只要把中間的九宮生成好,那麼周圍的8個九宮就可以按照規律自動生成,但是這樣的話便達不到“隨機”的效果,於是經過苦想我想出了下面的方法:

按行生成數字。如果生成了與該行或該列相同的數字,或在這個九宮裏有重複的數字,則刪除剛剛生成的相同數字並且重新生成該數字。簡而言之,就是先檢查行,再檢查列,再檢查九宮,直到把這81個數字都生成出來。如果某一行出現“無解”的情況,則重新生成上一行,稱之爲“回溯”。

可想而知,這樣的程序效率是不高的。首先的問題就是,要如何生成第一行沒有重複的數字?最原始的辦法就是從1~9依次隨機,如果生成的數與該行某個數相同,則重新生成。但是這樣的話程序效率很低,因爲隨機的次數會很多,比方說假設到了要生成某一行的最後一個數了,那麼這個數成功生成的概率只有1/9,電腦平均要隨機9次纔可以生成。因此我想的方法是把每一行能夠填入的數保存在一個數組avail裏,並且初始化int avail[9]= {1,2,3,4,5,6,7,8,9},一旦成功生成某個數,那麼就在avail裏刪除這個數,例如生成了3,那麼就從avail裏刪掉'3',這樣的話就可以大大的減少隨機的次數。

按照這個思路,我設計了一下代碼:

#include <iostream>

#include <stdlib.h>

#include <time.h>

#include <conio.h>

using namespace std;

int sudoku[9][9],avail[9]={1,2,3,4,5,6,7,8,9};//定義數獨數組和可選數字數組

代碼中的sudoku是用來保存數獨的二維數組,avail是每一行可以填入的數字,可以知道,每當行數向下加一的時候avail就必須要重置一次。

void remove(int t[],int);//聲明移除前置函數

void restore(void);//聲明重置函數

void print(void);//聲明打印數獨函數

void init(void);//聲明初始化函數

int search(int t[],int);//聲明搜索下標函數

int en=9;//可選avail下標

Remove函數是用來刪除t數組(也就是avail數組)裏某個特定的數的,restore函數是用於重置avail數組的,print函數是用於打印數組的,init函數是用於初始化sudoku二維數組。Search函數用於查找avail數組中某個數的位置(下標),以便刪除。如果這個數不存在則返回-1。整數en是可選數字的最大下標,每從avail裏刪除一個數,則都要有en--

下面是函數的定義:其中srand是根據時間來隨機化。

void init() //初始化

{

int x,y;

srand(time(NULL));

for (y=0;y<=8;y++)

{

for (x=0;x<=8;x++)

{

sudoku[y][x]=0;

}

}//初始化數組

}

void remove(int t[],int n) //n爲下標,移除排除下標,項前移

{

int i;

for (i=n;i<=7;i++)

{

t[i]=t[i+1];

}

en--;//可選數字在原有基礎上減

}

int search(int t[],int n) //n爲搜索的數字,返回下標,如果沒有則返回-1

{

int i;

for (i=0;i<=en-1;i++)

if (t[i]==n)

return i;

return -1;

}

void restore() //初始化en,avail

{

int i;

en=9;

for (i=0;i<=8;i++)

avail[i]=i+1;

}

void print(void)

{

int i,j;

for (i=0;i<=8;i++)

{

for (j=0;j<=8;j++)

{

cout << sudoku[i][j] << " "

}

cout << endl;

}

}

接下來就只有main函數了。

我的思路是每生成一個數,馬上判斷它的同行、同列和同九宮裏有沒有與之相同的數,如果有則重新生成該數,沒有則從avail裏刪除這個數。判斷同行和同列比較方便,用簡單的循環就可以搞定,排除九宮則稍微複雜一點,它需要兩個循環。假設有兩個整型變量xy,分別是sudoku數組的列座標和行座標,已知某數的是sudoku[y][x],由於在C++中兩個整型數相除的結果還是整數,那麼sudoku[(y/3)*3][(x/3)*3]就是該數所在的九宮的第一個數,然後進行循環便可以判斷九宮了。如果是無解,則重新生成上一行。Main函數源代碼如下:

int main()

{

int x=0,y=0;

int kill;//已存在的數的小標

init();

cout << "正在生成一個數獨..." << endl;

for (y=0;y<=8;y++)

{

for (x=0;x<=8;x++)

{

/*思路:每將生成一個新數字的時候,重新生成avail數列*/

int i,j;

for (i=0;i<=x;i++)//排除橫行

{

kill=search(avail,sudoku[y][i]);

if (kill!=-1)

remove(avail,kill);

}

for (i=0;i<=y;i++)//排除縱行

{

kill=search(avail,sudoku[i][x]);

if (kill!=-1)

remove(avail,kill);

}

for (i=0;i<=2;i++) //排除九宮

{

for (j=0;j<=2;j++)

{

kill=search(avail,sudoku[(y/3)*3+i][(x/3)*3+j]);

if (kill!=-1)

remove(avail,kill);

}

}

if (en==0 && y>0) //排除無解

{

y-=2;

break;

}

else if (en==0 && y==0)

{

init();

y=-1;

break;

}

sudoku[y][x]=avail[rand()%en];

restore();

}

restore();

}

print();//打印數獨

getch();

return 0;

}

心得:儘管經過優化,這個程序運算速度仍然要靠人品。這是個純計算問題,沒有用到一些高深的知識。沒有用到類,函數與函數之間的耦合性也非常的強。但是這還是需要掌握對數組的操作。該程序在VC6.0下編譯通過。

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章