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. 各位大佬,如果还有可以优化的点,欢迎赐教
相关推荐
煎鱼eddycjy5 小时前
新提案:由迭代器启发的 Go 错误函数处理
go
煎鱼eddycjy5 小时前
Go 语言十五周年!权力交接、回顾与展望
go
不爱说话郭德纲1 天前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
秋落风声3 天前
【滑动窗口入门篇】
java·算法·leetcode·go·哈希表
0x派大星5 天前
【Golang】——Gin 框架中的模板渲染详解
开发语言·后端·golang·go·gin
0x派大星5 天前
【Golang】——Gin 框架中的表单处理与数据绑定
开发语言·后端·golang·go·gin
三里清风_6 天前
如何使用Casbin设计后台权限管理系统
golang·go·casbin