数独这个游戏很适合锻炼大脑思考,因为规则很简单,所以很适合我写代码拿来破解。因此就有了这篇随笔了。
首先我想经过本身的思考完成数独的求解,而后再到网上抄答案。提供一个【在线玩数独】的网站。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
而且使用舞蹈链解法是能够解多个答案的数独,不过有多解的数独严格来说不能称之为数独。数组
算法真是奇妙的东西,出了能够解决生活和工做中的各类问题,提升效率,还能破解游戏。虽然玩数独颇有趣,破解数独彷佛对于咱们这些程序员来讲更刺激吧。缓存