數獨這個遊戲很適合鍛鍊大腦思考,因爲規則很簡單,所以很適合我寫代碼拿來破解。因此就有了這篇隨筆了。
首先我想經過本身的思考完成數獨的求解,而後再到網上抄答案。提供一個【在線玩數獨】的網站。php
我想經過本身的思路來求解,雖然網上確定有很是巧妙高效的解法。所以我安裝了HoDoKu
這個軟件,這個軟件會分析當前數獨每一個待填格子可能存在的值,目前我發現Naked Or Hiden Single
這2中是最容易找出來的,找出來了該位置就必填那個數。下圖是一個例子,表示裸露的單個數字,該位置只有一種可能值。通過仔細研究,我得出了2個原則:html
有了上述2個原則,那麼我必須有一種算法計算每個待填單元格可能填入的數據。其實很簡單,只須要遍歷這些代填的位置,而後遍歷當前行列所在宮格,去掉已經肯定的值,剩下的就是待填值。
通過上面的計算也只能將待填位置確認值填好,可是剩下有可能存在多個值且沒法肯定。所以我首先想到的就是暴力破解法,假設代填位置爲其中一個可能值,由此繼續填數字,每次填入數字後再進行一次上面找已肯定單個數,若是沒法繼續,或者獲得某個位置沒有可能填入數據則說明假設出錯,恢復上一次保存的狀態,繼續假設下一個可能值。
下面就貼上個人代碼,其中保存狀態用了棧結構,每次緩存則壓棧,恢復則彈棧:c++
package main import ( "container/list" "fmt" "log" "time" "io/ioutil" "flag" "github.com/jan-bar/golibs" ) const Length = 9 /* 數獨長寬都是9 */ /** * 下面這個結構有點複雜 * num: 當前位置數據,包括初始值,已經填寫的值 * cnt: 標識該位置可能數的個數 * flag: 初始時和num相同,只是在結果打印時區別初始值和計算獲得值顏色 * may: 該數組記錄當前位置可能值,老是從數組頭開始 **/ type MySudokuData struct { num, cnt, flag int /* 點位具體值,可能值的個數,該位置須要填值 */ may [Length]int /* 記錄點位可能的值 */ } /** * 下面結構保存存在多個可能值的位置 * pos: 記錄可能值的座標(其中i表示多少行,j表示多少列) * cnt: 記錄這些座標個數 **/ type MyMayPos struct { pos [Length * Length]struct { i, j int /* 緩存待定位置i,j值 */ } cnt int /* 待定位置個數 */ } /** * 整體的數據結構 * data: 記錄9*9的81個點位數據 * pos: 表示可能值的數據 * dot: 在計算時表示當前假設到哪一個可能點 * may: 在計算時表示dot的點找到哪一個可能值 **/ type MyCacheData struct { data [Length][Length]MySudokuData /* 緩存整個數獨 */ pos MyMayPos /* 緩存當前可能位置 */ dot, may int /* 緩存第幾個可能點,和該點第幾個可能值 */ } var SudokuData MyCacheData /* 獲得數獨數據,和每一個空位可能值,用於計算 */ func init() { fr := flag.String("f", "Sudoku.txt", "input data file!") flag.Parse() byt, err := ioutil.ReadFile(*fr) if err != nil { log.Fatal(err.Error()) } var i, j, cnt, tmp int for _, v := range byt { if tmp = int(v - '0'); tmp >= 0 && tmp <= 9 { /* 只處理文件中數字0~9 */ SudokuData.data[i][j].num = tmp SudokuData.data[i][j].flag = tmp if cnt++; j < 8 { j++ } else { i++ j = 0 } } } if cnt != 81 { /* 不管如何須需要有81個輸入 */ log.Fatal("輸入文件不正確!") } } /** * 主程序入口 * http://aperiodic.net/phil/scala/s-99/ **/ func main() { var ( pos, may, x, y, cnt int CacheData = list.New() /* 緩存數據棧 */ TmpElement *list.Element /* 緩存鏈表元素 */ tStart = time.Now() /* 開始時間 */ ) FlushMayNum() /* 初始刷新一下可能值 */ for false == GameComplete() { /* 若是沒有完成則一直繼續計算 */ for ; pos < SudokuData.pos.cnt; pos++ { /* 遍歷可能點 */ x, y = SudokuData.pos.pos[pos].i, SudokuData.pos.pos[pos].j for ; may < SudokuData.data[x][y].cnt; may++ { /* 遍歷可能點中可能填寫的值 */ SudokuData.dot, SudokuData.may = pos, may CacheData.PushFront(SudokuData) /* 保存當前狀態到棧中 */ SudokuData.data[x][y].num = SudokuData.data[x][y].may[may] /* 數據中填寫可能值 */ cnt++ if FlushMayNum() { /* 進行一次尋找,返回true表示還能繼續找 */ pos, may = 0, 0 goto NextGameLoop /* 數據已經重排,因此要從新遍歷 */ } /* 下面是else部分 */ /* 若是找到了一個沒有可能值的位置,從棧頂取數據,從下一個值開始遍歷 */ if TmpElement = CacheData.Front(); TmpElement == nil { /* 取棧頂元素,計算下一個可能值 */ return /* 棧中沒有數據,無解 */ } SudokuData = TmpElement.Value.(MyCacheData) /* 恢復上次狀態 */ CacheData.Remove(TmpElement) /* 移除棧頂狀態 */ } } /* 下面表示經過上面的計算,把全部可能點的可能值遍歷,仍是沒法獲得結果 */ if TmpElement = CacheData.Front(); TmpElement == nil { /* 取棧頂元素,計算下一個可能值 */ return /* 棧中沒有數據,無解 */ } SudokuData = TmpElement.Value.(MyCacheData) /* 恢復上次狀態 */ CacheData.Remove(TmpElement) /* 移除棧頂狀態 */ pos, may = SudokuData.dot, SudokuData.may+1 /* may從下一個開始 */ NextGameLoop: /* 重排的數據繼續計算 */ } fmt.Println("計算耗時 :", time.Since(tStart)) PrintSudoku() /* 完成後打印數獨 */ fmt.Scanln() /* 避免一閃而逝 */ } /** * x橫座標,向下遞增 * y縱座標,向右遞增 * 若是運行過程當中有空位只有惟一值,那麼填好值,再刷新一次 * 該方法結束後,空位必定存在多個可能值 * 返回false表示有位置無解,返回true表示全部位置都有多個解 **/ func FlushMayNum() bool { var i, j, k, t, x, y, tmpMay, flagBreak, xS, xE, yS, yE int StartLoop: /* 若是結果中有惟一值的位置,則從新計算 */ SudokuData.pos.cnt = 0 /* 待定位置從0計數 */ for i = 0; i < Length; i++ { for j = 0; j < Length; j++ { if 0 == SudokuData.data[i][j].num { /* 空位才須要刷新可能值 */ for k = 0; k < Length; k++ { SudokuData.data[i][j].may[k] = k + 1 /* 爲可能值賦初值 */ } /* 初始i,j位置默承認能存在的數值 */ for k = 0; k < Length; k++ { if t = SudokuData.data[i][k].num; t > 0 { /* 遍歷行 */ SudokuData.data[i][j].may[t-1] = 0 /* 從可能中剔除該數字 */ } if t = SudokuData.data[k][j].num; t > 0 { /* 遍歷列 */ SudokuData.data[i][j].may[t-1] = 0 /* 從可能中剔除該數字 */ } } /* 上面循環剔除行列的值 */ xS = i / 3 * 3 /* 所在宮格x起始 */ xE = xS + 3 /* 所在宮格x結束 */ yS = j / 3 * 3 /* 所在宮格y起始 */ yE = yS + 3 /* 所在宮格y結束 */ for ; xS < xE; xS++ { for k = yS; k < yE; k++ { if t = SudokuData.data[xS][k].num; t > 0 { SudokuData.data[i][j].may[t-1] = 0 /* 從可能中剔除該數字 */ } } } /* 上面雙層循環遍歷所在宮格 */ /* 下面將可用值左移,保證有效值從數組頭開始 */ for k, SudokuData.data[i][j].cnt = 0, 0; k < Length; k++ { if t = SudokuData.data[i][j].may[k]; t > 0 { SudokuData.data[i][j].may[SudokuData.data[i][j].cnt] = t SudokuData.data[i][j].cnt++ /* 將可能的值移動到前面 */ } } if 0 == SudokuData.data[i][j].cnt { return false /* 該位置沒有解 */ } if 1 == SudokuData.data[i][j].cnt { /* 若是當前位置只有一種可能值 */ SudokuData.data[i][j].num = SudokuData.data[i][j].may[0] /* 將該值填入數組中 */ goto StartLoop /* 從新刷新可能值數據 */ } /* 下面用插入排序發將每一個點可能的個數從小到大添加到MayPos中 */ //for k = 0; k < SudokuData.pos.cnt; k++ { // if SudokuData.data[i][j].cnt < SudokuData.data[SudokuData.pos.pos[k].i][SudokuData.pos.pos[k].j].cnt { // break /* 找到位置,由小到達的排序,可讓循環次數減小 */ // } //} //for t = SudokuData.pos.cnt; t > k; t-- { /* 上面找到位置,該位置右邊數據集體右移一位 */ // SudokuData.pos.pos[t].i, SudokuData.pos.pos[t].j = SudokuData.pos.pos[t-1].i, SudokuData.pos.pos[t-1].j //} //SudokuData.pos.pos[k].i, SudokuData.pos.pos[k].j = i, j //SudokuData.pos.cnt++ /* 可能點個數加1 */ SudokuData.pos.pos[SudokuData.pos.cnt].i, SudokuData.pos.pos[SudokuData.pos.cnt].j = i, j SudokuData.pos.cnt++ /* 可能點個數加1 */ } /* end if 0 == SudokuData[i][j].num { */ } /* end j */ } /* end i */ flagBreak = 0 /* 上面獲得一個局面,及可能點必定有多個值,下面找隱藏的只有一個解的位置 */ for i = 0; i < SudokuData.pos.cnt; i++ { /* 遍歷每一個可能點位置 */ x, y = SudokuData.pos.pos[i].i, SudokuData.pos.pos[i].j /* 獲得該點位置 */ for j = 0; j < SudokuData.data[x][y].cnt; j++ { tmpMay = SudokuData.data[x][y].may[j] /* 找這個可能值,看看是否爲隱藏單個 */ for k = 0; k < Length; k++ { if t = SudokuData.data[x][k].num; t == 0 { /* 遍歷行中不肯定格子 */ for ; t < SudokuData.data[x][k].cnt; t++ { if tmpMay == SudokuData.data[x][k].may[t] { goto NextFlagX /* 這個可能值和在當前行不惟一 */ } } } } /* 在行上找相同可能值 */ SudokuData.data[x][y].num = tmpMay /* 這個值在行上可能值中是惟一,填值並從新填值 */ flagBreak = 1 break NextFlagX: for k = 0; k < Length; k++ { if t = SudokuData.data[k][y].num; t == 0 { /* 遍歷列中不肯定格子 */ for ; t < SudokuData.data[k][y].cnt; t++ { if tmpMay == SudokuData.data[k][y].may[t] { goto NextFlagY /* 這個可能值和在當前列不惟一 */ } } } } /* 在列上找相同可能值 */ SudokuData.data[x][y].num = tmpMay /* 這個值在行上可能值中是惟一,填值並從新填值 */ flagBreak = 1 break NextFlagY: xS = x / 3 * 3 /* 所在宮格x起始 */ xE = xS + 3 /* 所在宮格x結束 */ yS = y / 3 * 3 /* 所在宮格y起始 */ yE = yS + 3 /* 所在宮格y結束 */ for ; xS < xE; xS++ { for k = yS; k < yE; k++ { if t = SudokuData.data[xS][k].num; t == 0 { for ; t < SudokuData.data[xS][k].cnt; t++ { if tmpMay == SudokuData.data[xS][k].may[t] { goto NextFlagZ /* 這個可能值和在當前列不惟一 */ } } } } } SudokuData.data[x][y].num = tmpMay /* 這個值在行上可能值中是惟一,填值並從新填值 */ flagBreak = 1 break NextFlagZ: } } if 1 == flagBreak { goto StartLoop } for i = 1; i < SudokuData.pos.cnt; i++ { x, y = SudokuData.pos.pos[i].i, SudokuData.pos.pos[i].j tmpMay = SudokuData.data[x][y].cnt for j = i - 1; j >= 0 && SudokuData.data[SudokuData.pos.pos[j].i][SudokuData.pos.pos[j].j].cnt > tmpMay; j-- { SudokuData.pos.pos[j+1].i = SudokuData.pos.pos[j].i SudokuData.pos.pos[j+1].j = SudokuData.pos.pos[j].j } SudokuData.pos.pos[j+1].i = x SudokuData.pos.pos[j+1].j = y } /* 下面打印可能點個數由少到多的排序 */ //for i = 0; i < SudokuData.pos.cnt; i++ { // fmt.Println(SudokuData.pos.pos[i], SudokuData.data[SudokuData.pos.pos[i].i][SudokuData.pos.pos[i].j]) //} //fmt.Print("\n\n\n") //os.Exit(0) return true } /** * 打印數獨 * 這裏須要win32api * 將計算獲得的數據上不一樣顏色 **/ func PrintSudoku() { var ( i, j, tmp int api = golibs.NewWin32Api() ) fmt.Println(" ---------+---------+---------") for i = 0; i < Length; i++ { fmt.Print("|") for j = 0; j < Length; j++ { if tmp = SudokuData.data[i][j].num; tmp > 0 { if 0 == SudokuData.data[i][j].flag { /* 該位置是計算獲得的,標紅色 */ api.TextBackground(golibs.ForegroundRed | golibs.ForegroundIntensity) } fmt.Printf(" %d ", tmp) /* 下面把前景色重置爲白色 */ api.TextBackground(golibs.ForegroundRed | golibs.ForegroundGreen | golibs.ForegroundBlue) } else { fmt.Print(" . ") } if j == 2 || j == 5 { fmt.Print("|") } } switch i { case 2, 5: fmt.Print("|\n|---------+---------+---------|\n") case 0, 1, 3, 4, 6, 7: fmt.Println("|\n| | | |") } } fmt.Println("|\n ---------+---------+---------") } /** * 判斷當前成功沒 * 若是遊戲完成則返回true * 不然沒有完成則返回false **/ func GameComplete() bool { var i, j int for i = 0; i < Length; i++ { for j = 0; j < Length; j++ { if 0 == SudokuData.data[i][j].num { return false /* 數獨中存在沒有完成的位置,則遊戲還要繼續 */ } } } return true /* 全部位置都完成 */ } /** * http://cn.sudokupuzzle.org/ * https://www.newdoku.com/zh/sudoku.php * 上面是2個在線數獨網站 * 技巧:http://www.conceptispuzzles.com/zh/index.aspx?uri=puzzle/sudoku/techniques * 規則:http://www.conceptispuzzles.com/zh/index.aspx?uri=puzzle/sudoku/rules **/
可經過執行
Sudoku.exe -f Sudoku.txt
來求解文件中的數獨數據。下面就是一道數獨題,複製後保存到Sudoku.txt中。git
0,0,0,0,7,0,0,0,8 0,2,0,8,0,0,0,0,0 8,0,0,0,0,9,5,0,4 0,0,4,0,0,5,0,0,1 0,0,1,0,0,0,0,0,7 0,0,0,6,0,0,0,8,0 1,9,0,0,0,0,4,0,0 0,0,6,0,5,0,0,0,0 5,7,0,0,0,0,3,0,0
下面是結果,白色是題目數字,紅色部分是答案:程序員
上面的方案效率在應對簡單級別的也是很快的,基本毫秒級別。可是比較蛋疼的就是暴力求解存在把全部解遍歷一遍的狀況,那將遍歷很是大,雖然我已經保證每次把肯定的值填入,但仍然無可避免窮舉的事實。測試過一個骨灰級的例子,用時44分鐘。好了上面就把我本身的想法寫成代碼,並能正確獲得結果,只是某些狀況計算效率比較低,並且沒有處理存在多個值的狀況。github
求解數獨最佳方案固然是舞蹈鏈了,優勢就是不會佔用多於空間,緩存和恢復狀態很是快。
http://www.cnblogs.com/grenet/p/3145800.html 講解舞蹈鏈
http://www.cnblogs.com/grenet/p/3163550.html 講解如何用舞蹈鏈解數獨
代碼靈感主要來源於上面的博客,而且舞蹈鏈求解比較快,所以我也作了多解數組至少算2種結果
舞蹈鏈求解的具體流程就參照上面博客吧,下面把個人代碼貼上:算法
package main import ( "fmt" "log" "time" "io/ioutil" "flag" "github.com/jan-bar/golibs" ) const ( LenGrid = 9 /* 數獨都有9行9列格子 */ Length = LenGrid * LenGrid /* 數獨有81個元素 */ NineDance = 9 * Length /* 81*9 建立出9個舞蹈鏈,分別表明填入的數字 */ FourDance = 4 * Length /* 81*4 約束條件 */ MinInitial = 1000000000 /* 最小min的初值 */ ) type Node struct { r, c int /* 標識第r行,第c列 */ up *Node down *Node left *Node right *Node } var ( SudokuData [Length + 1]int /* 保存數獨數據 */ Mem1 [Length + 1]int /* 保存數獨結果1 */ Mem2 [Length + 1]int /* 保存數獨結果2 */ Mem = &Mem1 /* 用mem操做2個結果內的值 */ Cnt [FourDance + 1]int /* 0-324 用於記錄0-324列,這一列有多少個結點 */ Scnt = 0 /* 記錄數獨結果個數,本程序最多找到2個就退出 */ Head Node /* 頭結點 */ All [NineDance*FourDance + 99]Node /* 0-236294 構建729*324+99列的舞蹈鏈 */ AllCnt int /* 舞蹈鏈的遊標 */ Row [NineDance]Node /* 0-728 構建729列的舞蹈鏈,用於1-9的填入,每一個數字用81列來表示 */ Col [FourDance]Node /* 0-323 構建324列的舞蹈鏈,用於知足4個約束條件 */ ) func init() { fr := flag.String("f", "Sudoku.txt", "input data file!") flag.Parse() byt, err := ioutil.ReadFile(*fr) if err != nil { log.Fatal(err.Error()) } var cnt = 0 for _, v := range byt { if v >= '0' && v <= '9' { if cnt < Length { /* 數獨只有81個元素 */ SudokuData[cnt] = int(v - '0') } cnt++ } } if cnt != Length { /* 不管如何只有81個數字輸入 */ log.Fatal("輸入文件只能有81個數字!") } SudokuData[cnt] = MinInitial /* 標識結束符 */ AllCnt = 1 /* 舞蹈鏈從位置1開始 */ Head.left = &Head /* 頭結點的左邊是頭結點 */ Head.right = &Head /* 頭結點的右邊是頭結點 */ Head.up = &Head /* 頭結點的上面是頭結點 */ Head.down = &Head /* 頭結點的下面是頭結點 */ Head.r = NineDance /* 行數等於729 */ Head.c = FourDance /* 列數等於324 */ for cnt = 0; cnt < FourDance; cnt++ { Col[cnt].c = cnt /* 324列舞蹈鏈 用0-323賦值給c */ Col[cnt].r = NineDance /* 把 729 賦給 r */ Col[cnt].up = &Col[cnt] /* 它的上面等於本身 */ Col[cnt].down = &Col[cnt] /* 它的下面等於本身 */ Col[cnt].left = &Head /* 它的左邊等於頭結點 */ Col[cnt].right = Head.right /* 它的右邊等於頭結點的右邊 */ Col[cnt].left.right = &Col[cnt] /* 它的左邊的右邊等於本身 */ Col[cnt].right.left = &Col[cnt] /* 它的右邊的左邊等於本身 */ } for cnt = 0; cnt < NineDance; cnt++ { Row[cnt].r = cnt /* 729行舞蹈鏈,行數等於i */ Row[cnt].c = FourDance /* 列數等於324 */ Row[cnt].left = &Row[cnt] /* 它的左邊等於本身 */ Row[cnt].right = &Row[cnt] /* 它的右邊等於本身 */ /* 頭結點下邊行的編號從上到下是728到0 */ Row[cnt].up = &Head /* 它的上邊等於頭結點 */ Row[cnt].down = Head.down /* 它的下邊等於頭結點的下邊 */ Row[cnt].up.down = &Row[cnt] /* 它的上邊的下邊等於本身 */ Row[cnt].down.up = &Row[cnt] /* 它的下邊的上邊等於本身 */ } /* 訪問全部行,數獨舞蹈鏈中的第i行 表示 數獨中的第r行第c列中填入數字val */ for cnt = 0; cnt < NineDance; cnt++ { var ( r = cnt / 9 / 9 % 9 /* 0-80 r爲0 81-161 r爲1 …… 648-728 r爲8 表示數獨中的行 映射:舞蹈鏈行->數獨行 */ c = cnt / 9 % 9 /* 0-8 c爲0 9-17 c爲1 18-26 c爲2 …… 72-80爲8 循環直至720-728爲8 81個爲一週期 表示數獨中的列 映射:舞蹈鏈行->數獨列 */ val = cnt%9 + 1 /* 0爲1 1爲2 2爲3 …… 8爲9 9個爲一週期 表示數字1-9 映射:舞蹈鏈行->1-9數字 */ ) if SudokuData[r*9+c] == 0 || SudokuData[r*9+c] == val { /* r表示第r行,c表示第c列,若是數獨的第r行第c列是0-9 */ /* 若是數獨的第r行第c列是0號則它的全部行都創建舞蹈鏈結點 */ /* 若是數獨的第r行第c列是數字則它的指定行都創建舞蹈鏈結點 */ Link(cnt, r*9+val-1) /* 處理約束條件1:每一個格子只能填一個數字 0-80列 */ Link(cnt, Length+c*9+val-1) /* 處理約束條件2:每行1-9這9個數字只能填一個 81-161列 */ tr := r / 3 tc := c / 3 Link(cnt, Length*2+(tr*3+tc)*9+val-1) /* 處理約束條件3:每列1-9的這9個數字都得填一遍 */ Link(cnt, Length*3+r*9+c) /* 處理約束條件4:每宮1-9的這9個數字都得填一遍 */ } } /* 把728個行結點所有刪除 */ for cnt = 0; cnt < NineDance; cnt++ { Row[cnt].left.right = Row[cnt].right /* 每一行左邊的右邊等於行數的右邊 */ Row[cnt].right.left = Row[cnt].left /* 每一行右邊的左邊等於行數的左邊 */ } } /** * 主程序入口 * http://aperiodic.net/phil/scala/s-99/ * https://www.newdoku.com/zh/sudoku.php * http://www.cnblogs.com/grenet/p/3145800.html 講解舞蹈鏈 * http://www.cnblogs.com/grenet/p/3163550.html 講解如何用舞蹈鏈解數獨 **/ func main() { var tStart = time.Now() /* 開始時間 */ Solve(1) var useTime = time.Since(tStart) /* 計算用時 */ /* 下面打印數獨,初始化數據和打印都不計入運算時間 */ switch Scnt { case 2: PrintSudoku(1) PrintSudoku(2) fmt.Print(" 2個或者多個解的數獨") case 1: PrintSudoku(1) fmt.Print(" 1個解的數獨") default: fmt.Print(" 此數獨無解") } fmt.Println(",計算耗時:", useTime) fmt.Scanln() /* 避免一閃而逝 */ } /** * 用鏈表解釋就是一直插在第一個結點,之前的結點右推。 * 第r行,第c列 **/ func Link(r, c int) { Cnt[c]++ /* 第c列的結點增長了一個 */ t := &All[AllCnt] /* 將指針指向下一個,就像線性表添加元素同樣 */ AllCnt++ t.r = r /* t的行數等於r */ t.c = c /* t的列數等於c */ t.left = &Row[r] /* t的左邊等於第r行結點 */ t.right = Row[r].right /* t的右邊等於第r行結點的右邊 */ t.left.right = t /* t的左邊的右邊等於t */ t.right.left = t /* t的右邊的左邊等於t */ t.up = &Col[c] /* t的上邊等於第c列結點 */ t.down = Col[c].down /* t的下邊等於第c列下邊 */ t.up.down = t /* t的上邊的下邊等於t */ t.down.up = t /* t的下邊的上邊等於t */ } /** * 刪除這列的結點和結點所在行的結點 **/ func Remove(c int) { var t, tt *Node /* 刪除列結點 */ Col[c].right.left = Col[c].left /* 該列結點的右邊的左邊等於該列結點的左邊 */ Col[c].left.right = Col[c].right /* 該列結點的左邊的右邊等於該列結點的右邊 */ for t = Col[c].down; t != &Col[c]; t = t.down { /* 訪問該列的全部結點 直到回到列結點 */ for tt = t.right; tt != t; tt = tt.right { /* 訪問該列全部結點所在的每一行 */ Cnt[tt.c]-- /* 該列的結點減小一個 */ /* 刪除該結點所在行中的一個結點 */ tt.up.down = tt.down /* 該結點的上邊的下邊等於該結點的下邊 */ tt.down.up = tt.up /* 該結點的下邊的上邊等於該結點的上邊 */ } /* 刪除該結點 */ t.left.right = t.right /* t的左邊的右邊等於t的右邊 */ t.right.left = t.left /* t的右邊的左邊等於t的左邊 */ } } /** * 恢復一個節點 **/ func Resume(c int) { var t, tt *Node /* 遍歷該列結點 */ for t = Col[c].down; t != &Col[c]; t = t.down { t.right.left = t /* 恢復t結點 */ t.left.right = t /* 恢復t結點 */ for tt = t.left; tt != t; tt = tt.left { /* 一直訪問左邊,直到回到t */ Cnt[tt.c]++ tt.down.up = tt tt.up.down = tt } } Col[c].left.right = &Col[c] Col[c].right.left = &Col[c] } /** * 計算數獨 **/ func Solve(k int) { var ( t, tt *Node min = MinInitial tc int ) if Head.right == &Head { /* 獲得一個數獨結果 */ if Scnt == 0 { /* 首次獲得結果 */ for tc = 0; tc <= Length; tc++ { Mem2[tc] = Mem1[tc] } Mem = &Mem2 /* 將下一次計算的結果寫到Mem2中 */ } Scnt++ /* 這裏第一種解決方案獲得後,返回繼續 選行 來看有沒有第二種解決方案 */ return } //fmt.Println(k) /* 打印每次查找的行 */ /* 從頭結點開始一直向右 直到回到頭結點 挑選結點數量最小的那一行,若是數量小於等於1直接用這行 */ for t = Head.right; t != &Head; t = t.right { if Cnt[t.c] < min { min = Cnt[t.c] tc = t.c if min <= 1 { break } } } /* min==0的時候會把列刪除而後再把列恢復而後返回,說明以前選錯了行致使出現告終點爲0的列,從新往下選擇一行。 */ Remove(tc) /* 移除這一列 */ /* 掃描這一列 直到 回到列結點 */ for t = Col[tc].down; t != &Col[tc]; t = t.down { Mem[k] = t.r /* mem[k]存儲t的行數,最後能夠經過行數來推斷數獨的幾行幾列填入了哪一個數字 */ /* 若是沒有這一步的話,在下面for循環的過程當中會陷入死循環 */ t.left.right = t /* 經檢查這兩個指針所指向的地址不一樣 */ /* 開始訪問t的右邊 直到回到t。可是因爲t在remove(tc)的過程當中左右被跳過,因此tt!=t可能會一直成立,因此須要上一步來保證能回到t */ for tt = t.right; tt != t; tt = tt.right { Remove(tt.c) /* 移除該行中全部帶結點的列 */ } /* 等到該行的全部結點都刪除之後,把t結點完全地刪除 */ t.left.right = t.right Solve(k + 1) /* 給下一個找行 */ if Scnt >= 2 { /* 這裏找到2個解就退出 */ return } /* 同上,避免死循環 */ t.right.left = t /* 恢復全部被刪除的列 */ for tt = t.left; tt != t; tt = tt.left { Resume(tt.c) } t.right.left = t.left /* 恢復t結點 */ } Resume(tc) /* 恢復tc列,一旦跑出來了說明以前選錯了行,且若是一直回溯到一開始而後沒有更多的行能夠選擇且scnt爲0就說明沒有解決方案 */ } /** * 打印數獨 * 這裏須要win32api * 將計算獲得的數據上不一樣顏色 **/ func PrintSudoku(res int) { var ( i, tmp int ans [Length]int api = golibs.NewWin32Api() mem = &Mem1 ) if res == 2 { /* 肯定打印那個結果 */ mem = &Mem2 } for i = 1; i <= Length; i++ { ans[mem[i]/9%Length] = mem[i]%9 + 1 } fmt.Println(" ---------+---------+---------") for i = 1; i <= Length; i++ { if i%3 == 1 { fmt.Print("|") } if tmp = ans[i-1]; tmp > 0 { if SudokuData[i-1] == 0 { /* 該位置是計算獲得的,標紅色 */ api.TextBackground(golibs.ForegroundRed | golibs.ForegroundIntensity) } fmt.Printf(" %d ", tmp) /* 下面把前景色重置爲白色 */ api.TextBackground(golibs.ForegroundRed | golibs.ForegroundGreen | golibs.ForegroundBlue) } else { fmt.Print(" . ") } if i < Length { if i%27 == 0 { fmt.Println("|\n|---------+---------+---------|") } else if i%9 == 0 { fmt.Println("|\n| | | |") } } } fmt.Println("|\n ---------+---------+---------") }
用該方法求解【世界最難數獨】,速度也是嗖嗖的:api
而且使用舞蹈鏈解法是能夠解多個答案的數獨,不過有多解的數獨嚴格來說不能稱之爲數獨。數組
算法真是奇妙的東西,出了能夠解決生活和工做中的各類問題,提升效率,還能破解遊戲。雖然玩數獨頗有趣,破解數獨彷佛對於咱們這些程序員來講更刺激吧。緩存