日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

回溯法解数独游戏

發布時間:2023/12/14 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 回溯法解数独游戏 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

采用回溯法最經典的例子是解決8皇后和迷宮的問題。不習慣走別人的路,所以下面介紹下用回溯法解數獨游戲。寫這個算法的起因是之前在玩數獨游戲時,遇到了難解的專家模式,就想著寫程序來暴力破解,是不是很無賴,啊哦……

數獨介紹

數獨(すうどく,Sūdoku),是源自18世紀瑞士發明,流傳到美國,再由日本發揚光大的一種數學游戲。是一種運用紙、筆進行演算的邏輯游戲。玩家需要根據9×9盤面上的已知數字,推理出所有剩余空格的數字,并滿足每一行、每一列、每一個粗線宮內的數字均含1-9,不重復。

數獨盤面是個九宮,每一宮又分為九個小格。在這八十一格中給出一定的已知數字和解題條件,利用邏輯和推理,在其他的空格上填入1-9的數字。使1-9每個數字在每一行、每一列和每一宮中都只出現一次,所以又稱“九宮格”。下圖是一個完整的數獨例子。


下圖是iPad上一個數獨游戲專家模式下的截圖。81個格子,只給了17個數字,確實有點難度哈。

將上圖轉化成程序能夠識別的輸入,{1,1,9}表示第一行第一列的格子數字是9。

{1,1,9},{1,2,1},{1,8,4},{3,4,5},{3,6,3},{4,1,5},{4,3,6},{4,6,8},{4,7,3},{6,5,1},{6,8,2},{6,9,4},{7,1,8},{7,3,5},{8,3,3},{9,5,4},{9,9,1}

運行程序,得出如下解:

9 1 7 | 6 8 2 | 5 4 3
3 5 2 | 1 9 4 | 7 6 8
6 8 4 | 5 7 3 | 1 9 2
~~~~~~~~~~~~
5 9 6 | 4 2 8 | 3 1 7
4 2 1 | 7 3 6 | 9 8 5
7 3 8 | 9 1 5 | 6 2 4
~~~~~~~~~~~~
8 7 5 | 2 6 1 | 4 3 9
1 4 3 | 8 5 9 | 2 7 6
2 6 9 | 3 4 7 | 8 5 1

仔細觀察不難發現,每一行、每一列和每一個九宮格里都是數字1~9不重復。這就構成了一個數獨的解。也證明,算法通過測試。

算法在解數獨題時,在依次決定每個格子的數字時會根據之前已經填入的數字判斷當前格子可能填入的數字,然后選擇其中一個再去跳到下一個格子。當后面出現無解的情況(一個格子沒有可填入的數字),就依次回退到上一個格子,選取下一個可能填入的數字,再依次執行下去。直到填入了最后一個格子,才算完成了數獨的一個解。

回溯法思想

在包含問題的所有解的解空間樹中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。當探索到某一結點時,要先判斷該結點是否包含問題的解,如果包含,就從該結點出發繼續探索下去,如果該結點不包含問題的解,則逐層向其祖先結點回溯。(其實回溯法就是對隱式圖的深度優先搜索算法)。 若用回溯法求問題的所有解時,要回溯到根,且根結點的所有可行的子樹都要已被搜索遍才結束。 而若使用回溯法求任一個解時,只要搜索到問題的一個解就可以結束。

核心算法

/*** 填充數字。實質上是深度遍歷一棵具有9分支的樹,直至遍歷到它的第81層。一定要通過一些判斷剪斷其無用分支,不然該算法的時間復雜度相當可怕*/ void set_item(Item* items, int position) {if (position==ROW*COLUMN) {printf("第%d個\n",++count);//填充完最后一個元素,輸出結果for (int i = 0; i < COLUMN * ROW; i++) {printf("%3d", items[i].numbers[0]);if ((i + 1) % COLUMN == 0)printf("\n");}printf("\n");return ;}if(items[position].choose!=0){//解數獨題時,當前格子已經有填入的數字,就跳到下一個格子。//如果窮舉出9x9數獨所有的解,這個if判斷不執行set_item(items, position + 1);return;}int num =0;//保存當前格子可輸入的數字while (( num= getUseableNum(items, position)) != 0) {//while循環依次找出當前格子,所有可能插入的數字,再交給下面的if函數判斷。找不到就結束while循環,回到上一個格子,找到了就遞歸進入下一個格子。//不為0,可輸入//判斷可以填入numif (is_can_set(items, num, position) == 1) {//設置下一個格子set_item(items, position + 1);}}//找不到可輸入的數字,清空當前格子填充記錄,回到上一個格子//若為第一個格子,找不到填充的數字就結束if (position != 0&&items[position].numbers[0]!=0)goback(items, position);}

測試算法代碼段

//定義好17個已經確認的格子 Point points[17]={{1,1,9},{1,2,1},{1,8,4},{3,4,5},{3,6,3},{4,1,5},{4,3,6},{4,6,8},{4,7,3},{6,5,1},{6,8,2},{6,9,4},{7,1,8},{7,3,5},{8,3,3},{9,5,4},{9,9,1}}; //初始化數獨矩陣 init_matrix(items, COLUMN * ROW); //將17個格子填入數獨矩陣中 init_point(items, points, 17); //從第一個格子開始遍歷 set_item(items, 0);

附上用到的自定義的函數,用來決定當前格子可填入的數字的判斷。

/*** 獲取可用的數字*/ int getUseableNum(Item* items, int position) {for (int i = 1; i < SCALE * SCALE + 1; i++) {if (items[position].used[i] == 0 && items[position].numbers[i] == 0) {//數字i(1~9)未被使用過且在它所屬的行、列、九宮格里均未出現過就表示可以填入數字i//有可用的數字,直接返回return i;}}//沒有可用的數字,返回0return 0; } /*** 判斷一個數字在指定的格子中是否可以插入,返回0表示不能插入,尋找下一個,*/ int is_can_set(Item* items, int num, int position) {//找到影響的行find_row(row_matrix, position);for (int i = 1; i < SCALE * SCALE + 1; i++) {if(items[row_matrix[i]].numbers[0]==num&&(items[row_matrix[i]].used[num]*items[row_matrix[i]].numbers[num])!=0){items[position].used[num]=1;return 0;}}//找到影響的列find_column(column_matrix, position);for (int i = 1; i < SCALE * SCALE + 1; i++) {if(items[column_matrix[i]].numbers[0]==num&&(items[column_matrix[i]].used[num]*items[column_matrix[i]].numbers[num])!=0){items[position].used[num]=1;return 0;}}//找到影響的塊find_block(block_matrix, position);for (int i = 1; i < SCALE * SCALE + 1; i++) {if(items[block_matrix[i]].numbers[0]==num&&(items[block_matrix[i]].used[num]*items[block_matrix[i]].numbers[num])!=0){items[position].used[num]=1;return 0;}}if (items[position].numbers[0] != 0) {// items[position].used[items[position].numbers[0]]=0;//重置影響到的行changeItemNum(items, row_matrix, items[position].numbers[0],SCALE * SCALE + 1, -1);//重置影響到的列changeItemNum(items, column_matrix, items[position].numbers[0],SCALE * SCALE + 1, -1);//重置影響到的塊changeItemNum(items, block_matrix, items[position].numbers[0],SCALE * SCALE + 1, -1);}items[position].numbers[0] = num;//設置影響到的行changeItemNum(items, row_matrix, num, SCALE * SCALE + 1, 1);//設置影響到的列changeItemNum(items, column_matrix, num, SCALE * SCALE + 1, 1);//設置影響到的塊changeItemNum(items, block_matrix, num, SCALE * SCALE + 1, 1);items[position].used[num] = 1;//能填入return 1; }/*** 清空當前格子輸入的信息*/ void goback(Item* items, int position) {//刪除當前數字產生的影響int currentNum = items[position].numbers[0];//重置影響到的行find_row(row_matrix, position);changeItemNum(items, row_matrix, currentNum, SCALE * SCALE + 1, -1);//重置影響到的列find_column(column_matrix, position);changeItemNum(items, column_matrix, currentNum, SCALE * SCALE + 1, -1);//重置影響到的塊find_block(block_matrix, position);changeItemNum(items, block_matrix, currentNum, SCALE * SCALE + 1, -1);items[position].numbers[0]=0;//清空這個格子里面,使用過的數字記錄for (int i = 1; i < SCALE * SCALE + 1; i++) {items[position].used[i] = 0;}} /***修改item中的數字,items表示整個數獨。matrix表示需要修改的下標,length表示需要修改個數。flag +1表示設置,-1 表示重置*/ void changeItemNum(Item* items, int* matrix, int num, int length, int flag) {for (int i = 1; i < length; i++) {//需要修改的位置int position = matrix[i];items[position].numbers[num] += flag;} }

小結

這個算法,不僅可以用來解數獨題,還可以用來遍歷數獨所有的終盤,由于9x9的數獨,終盤數量巨大,共6,670,903,752,021,072,936,960(約為6.67×10^21)種組合,程序一直運行不完結果。如果對這個值沒有概念,請試想將所有的終盤存在txt文本中,只存儲數字將占用6.07x10^9 TB存儲空間,相信沒有哪臺電腦能夠做到。換句話說,我電腦CPU的主頻是2.4GHz,意思是1秒鐘執行2.4x10^12條指令,假設解出一種終盤需要一條指令(事實上遠大于1條),消耗的時間是88年,本寶寶等不了那么久。慶幸的是4x4 的數獨只有288個終盤,程序還是能夠很快的完美輸出所有解。為什么兩者差別這么大,因為窮舉法數獨的算法時間復雜度是T= n^(n^2).
n = 4時,T = 4294967296。n = 9時,T= 1.97Ex10^77。呵呵噠……

總結

以上是生活随笔為你收集整理的回溯法解数独游戏的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。