go 实现暴力破解数独

一切罪恶的来源是昨晚睡前玩了一把数独,找虐的选了个最难的模式,做了一个多小时才做完,然后就睡不着了..........程序员不能受这委屈,今天咋样也得把这玩意儿破解了

破解思路(暴力破解加深度遍历)

  1. 把数独看做是一个二维数组,并且这个数组已经填了一部分数字,空格填数字0
  2. 依次遍历这个数组,找到第一个空格,他所能填的数字为同一行,同一列以及所在小九宫格均未出现的数字,所能填的数字可能有多个,依次进行尝试(正确的那个数字肯定能走到最后,错误的数字在后面一定会发生矛盾,矛盾就是有一个空格1-9都不能填)
  3. 填完一个空格之后,就形成了新的数独,递归重复这个操作即可

代码实现

Go 复制代码
package main

import (
	"fmt"
	"os"
)
func shuzu_print(shuzu [9][9]int) {
	fmt.Println("------------------------")
	for i := 0; i < 9; i++ {
		fmt.Println(shuzu[i])
	}
}

func main() {
	shuzu := [9][9]int{ // 初始化数独
		{2, 0, 0, 0, 4, 0, 0, 1, 0},
		{6, 1, 7, 0, 0, 3, 0, 0, 5},
		{0, 5, 0, 0, 0, 0, 0, 0, 3},
		{0, 0, 9, 0, 3, 6, 5, 7, 8},
		{0, 6, 5, 0, 8, 0, 0, 4, 2},
		{0, 0, 8, 4, 1, 5, 0, 6, 9},
		{0, 0, 0, 3, 0, 0, 0, 9, 0},
		{0, 9, 2, 0, 7, 0, 8, 3, 0},
		{8, 3, 0, 9, 2, 0, 0, 5, 7},
	}
	shuzu_print(shuzu)// 打印
	handle(shuzu)

}
func handle(shuzu [9][9]int) {
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if i == 8 && j == 8 { // 已经填完了,这个exit 是为了打断所有尝试
				shuzu_print(shuzu)
				os.Exit(1)
				return
			}
			if shuzu[i][j] != 0 {
				continue
			}
			set := make(map[int]struct{}) // map 实现set,存储同行,同列,所在小九宫格已经存在的数字
			for k := 0; k < 9; k++ {
				if shuzu[i][k] != 0 {
					set[shuzu[i][k]] = struct{}{}
				}
			}
			for k := 0; k < 9; k++ {
				if shuzu[k][j] != 0 {
					set[shuzu[k][j]] = struct{}{}
				}
			}
			i_3 := i / 3
			j_3 := j / 3
			for ii := i_3 * 3; ii < (i_3+1)*3; ii++ {
				for jj := j_3 * 3; jj < (j_3+1)*3; jj++ {
					if shuzu[ii][jj] != 0 {
						set[shuzu[ii][jj]] = struct{}{}
					}
				}
			}
			if len(set) == 9 { // 如果同行,同列,所在小九宫格1-9均存在了,那么发生矛盾,此支线走不下去
				return
			}
			for kk := 1; kk < 10; kk++ {
				if _, ok := set[kk]; !ok {
					shuzu[i][j] = kk
					handle(shuzu)// 同行,同列,所在小九宫格1-9不存在的数字均要进行尝试
				}
			}
		}
	}

}

跑了一下没问题,而且是秒出结果,但是很悲剧,下面这个例子(就是我昨晚那个关卡的例子)就不行了,二十分钟都跑不完,调试了一下,0行1列的位置可以填3,8,9(正解是8),但是拿3去尝试的时候,一直到第二行填完才出现矛盾,并且这个过程大概花了五六秒的时间,也太暴力了吧

Go 复制代码
	shuzu := [9][9]int{
		{1, 0, 4, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 5, 0, 0, 0},
		{0, 6, 0, 0, 8, 0, 0, 7, 3},
		{0, 0, 0, 8, 0, 1, 9, 0, 0},
		{6, 5, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 3, 0, 0, 0, 0, 8},
		{0, 2, 0, 0, 3, 0, 0, 0, 7},
		{0, 0, 0, 0, 0, 7, 1, 3, 0},
		{4, 7, 0, 0, 0, 0, 8, 9, 0},
	}

思考和优化

人在做这个的时候,会先把容易填的填完,不会按照顺序说一定要先填哪一个,尝试在这儿进行如下优化

  1. 不按顺序填,遍历所有需要填的空格,当某个空格只有唯一选项时,直接填,填了就是一个新的数独了,如果没有唯一选项的空格时,依次挑选唯二,唯三,唯四选项的空格进行尝试

代码实现

Go 复制代码
package main

import (
	"fmt"
	"time"
)

func shuzu_print(shuzu [9][9]int) {
	fmt.Println("------------------------")
	for i := 0; i < 9; i++ {
		fmt.Println(shuzu[i])
	}
}

var num int = 0 // 记录一下函数执行的次数

func main() {

	shuzu := [9][9]int{
		{0, 0, 0, 0, 0, 0, 0, 0, 0},
		{5, 9, 0, 8, 0, 0, 7, 0, 0},
		{0, 0, 0, 0, 2, 1, 8, 0, 0},
		{0, 3, 7, 0, 0, 0, 0, 0, 0},
		{0, 5, 0, 7, 9, 0, 0, 0, 0},
		{0, 0, 0, 0, 3, 0, 1, 8, 0},
		{0, 0, 5, 0, 0, 2, 0, 0, 0},
		{8, 1, 0, 0, 0, 0, 0, 4, 0},
		{0, 0, 6, 0, 8, 0, 9, 0, 3},
	}
	shuzu_print(shuzu)
	t1 := time.Now()
	fmt.Println(t1)
	result := handle2(shuzu)
	shuzu_print(result)
	fmt.Println(time.Now().Sub(t1)) // 记录一下花费的时间

}

type Left struct { // 定义一个结构体,表示i行j列剩余可以填的数字
	i    int
	j    int
	len  int
	left []int
}

func check(shuzu [9][9]int) bool {// 检查数组有没有填完
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if shuzu[i][j] == 0 {
				return false
			}
		}
	}
	return true

}
// 这个里面还用了goto,loop ,让函数有个返回
func handle2(shuzu [9][9]int) [9][9]int {
	num += 1
	left_map := map[int]Left{}

	if check(shuzu) {
		println("hhhhhhhhhhhhhhhh")// 填完了,真开心hhhhh
		shuzu_print(shuzu)
		fmt.Println(num) // 看一下该函数执行了多少次
		return shuzu
	}
	new_shuzu := shuzu
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if shuzu[i][j] != 0 {
				continue
			}
			set := make(map[int]struct{}) // 同行同列同小组已经存在的数字
			for k := 0; k < 9; k++ {
				if shuzu[i][k] != 0 {
					set[shuzu[i][k]] = struct{}{}
				}
			}
			for k := 0; k < 9; k++ {
				if shuzu[k][j] != 0 {
					set[shuzu[k][j]] = struct{}{}
				}
			}
			i_3 := i / 3
			j_3 := j / 3
			for ii := i_3 * 3; ii < (i_3+1)*3; ii++ {
				for jj := j_3 * 3; jj < (j_3+1)*3; jj++ {
					if shuzu[ii][jj] != 0 {
						set[shuzu[ii][jj]] = struct{}{}
					}
				}
			}
			left_len := 9 - len(set)
			if left_len == 0 {
				goto Loop // 出现矛盾的时候,跳出执行
			}
			if left_len == 1 { // 只剩一个可填,直接处理
				for kk := 1; kk < 10; kk++ {
					if _, ok := set[kk]; !ok {
						shuzu[i][j] = kk
						new_shuzu = handle2(shuzu)
						goto Loop// 直接跳出循环了
					}
				}
			} else {
				_, ok := left_map[left_len]
				if !ok {
					left_num := []int{}
					for kk := 1; kk < 10; kk++ {
						if _, ok := set[kk]; !ok {
							left_num = append(left_num, kk)
						}
					}

					left_map[left_len] = Left{i, j, left_len, left_num} //对于每种剩余可填长度,记录一个即可
				}

			}

		}
	}
	for k := 2; k < 10; k++ { // 依次找剩余可填长度为2,3,4....,找到一个即可
		left, ok := left_map[k]
		if ok {
			for _, value := range left.left {
				shuzu[left.i][left.j] = value
				new_shuzu = handle2(shuzu)
				if check(new_shuzu) {
					goto Loop
				}
			}
			break
		}
	}
Loop:
	// fmt.Println("跳出循环")
	return new_shuzu
}

运行结果:函数执行3760次,10ms运行完成,基本满意,终于不用我想破脑壳做一个小时了

问题及优化

  1. 代码写的很粗糙,命名也是随手写的,理解哈
  2. 其实人在做数独的时候,不仅可以看到每个空格可填的数字,还可以看到可排除了,根据所在小九宫格其他的可填和可排除的,这样的话,可以进一步减少函数执行的次数。
  3. 这里面9*9 是用数组,对于go来说,数组作为函数的参数是值传递,也就是说9*9 的数组,复制了3760次,如果用切片的话,可以节省这个复制,但是用切片的话,如果试错了,往回走的整个链路都要进行恢复,这个想一想应该还是可以优化出来的
  4. 各位大佬,如果还有可以优化的点,欢迎赐教
相关推荐
我是前端小学生7 小时前
Go语言中的方法和函数
go
探索云原生11 小时前
在 K8S 中创建 Pod 是如何使用到 GPU 的: nvidia device plugin 源码分析
ai·云原生·kubernetes·go·gpu
自在的LEE18 小时前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
Gvto2 天前
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
go·smtp·mailtrap
白泽来了2 天前
【Go进阶】手写 Go websocket 库(一)|WebSocket 通信协议
开源·go
witton2 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland
非凡的世界2 天前
5个用于构建Web应用程序的Go Web框架
golang·go·框架·web
湫qiu2 天前
6.5840 Lab-Key/Value Server 思路
后端·go
我是前端小学生3 天前
Go语言中的init函数
go
我是前端小学生3 天前
Go语言中内部模块的可见性规则
go