go 实现暴力破解数独

news2025/1/19 20:35:06

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

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

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

代码实现

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去尝试的时候,一直到第二行填完才出现矛盾,并且这个过程大概花了五六秒的时间,也太暴力了吧

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

代码实现

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. 各位大佬,如果还有可以优化的点,欢迎赐教

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1416239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32 PWM驱动设计

单片机学习&#xff01; 目录 文章目录 前言 一、PWM驱动配置步骤 二、代码示例及注意事项 2.1 RCC开启时钟 2.2 配置时基单元 2.3 配置输出比较单元 2.4 配置GPIO 2.5 运行控制 三、PWM周期和占空比计算 总结 前言 PWM本质是利用面积等效原理来改变波形的有效值。 一、PWM驱动…

2024最新版Java Development Kit (JDK)安装使用指南

2024最新版Java Development Kit (JDK)安装使用指南 Installation and Configuration Guide of the latest version Java Development Kit (JDK) in 2024 By JacksonML 0. 序言 What is Java? Java is a programming language and computing platform first released by Su…

day23 其他事件(页面加载事件、页面滚动事件)

目录 页面加载事件页面/元素滚动事件页面滚动事件——获取位置 页面加载事件 加载外部资源&#xff08;如图片、外联CSS和JavaScript等&#xff09;加载完毕时触发的事件为什么使用&#xff1a; 有时候需要等页面资源全部处理完毕再做一些事老代码喜欢把script写在head中&…

【JavaScript 漫游】专栏介绍

专栏介绍 本专栏旨在记录 JavaScript 核心语法&#xff0c;作为笔者日常学习和工作中的参考手册和代码示例仓库。 内容上力求覆盖 ES5、DOM、BOM 和 ES6 规范的所有内容。对于常用且重要的知识点&#xff0c;应该详细描述并附带有大量的代码示例。对于在工作场景中很少用到的…

12.14 回退流(血干JAVA系列)

回退流 12.14 回退流【例12.67】操作回退流 12.14 回退流 表 12-23 PushbacklnputStream 类的常用方法 表12-24回退流与输入流的对应 【例12.67】操作回退流 package jiaqi;import java.io.ByteArrayInputStream; import java.io.PushbackInputStream;public class demo43…

E5071C 是德科技网络分析仪

181/2461/8938产品概述&#xff1a; E5071C ENA 矢量网络分析仪&#xff0c;9 kHz 至 20 GHz&#xff0c;配有增强型 TDR 测量选件。 E5071C 是大规模无源元器件测试的理想解决方案。 它具有出色的测量性能&#xff0c;有助于提高测试吞吐量&#xff0c;尤其是与 E5092A 多端…

1948-2022年金融许可信息明细数据

1948-2022年金融许可信息明细数据 1、时间&#xff1a;1948-2022年 2、来源&#xff1a;银监会&#xff08;银监会许可证发布系统&#xff09; 3、指标&#xff1a;来源表、机构编码、机构名称、所属银行、机构类型、业务范围、机构住所、地理坐标、行政区划代码、所属区县、…

视频调色 -- 达芬奇DaVinci Resolve Studio 18中文

达芬奇DaVinci Resolve Studio 18是一款功能强大的视频后期处理软件&#xff0c;集视频编辑、色彩校正、音频后期制作等多项功能于一身。该软件支持多种轨道编辑和时间线管理&#xff0c;使视频剪辑和处理更加高效。其高精度的色彩校正技术&#xff0c;能够精确地对影片进行校色…

PHP语法

#本来是在学命令执行&#xff0c;所以学了学&#xff0c;后来发现&#xff0c;PHP语法和命令执行的关系好像没有那么大&#xff0c;不如直接学php的一些命令执行函数了。# #但是还是更一下&#xff0c;毕竟还是很多地方都要求掌握php作为脚本语言&#xff0c;所以就学了前面的…

Linux第37步_解决“Boot interface 6 not supported”之问题

在使用USB OTG将“自己移植的固件”烧写到eMMC中时&#xff0c;串口会输出“Boot interface 6 not supported”&#xff0c;发现很多人踩坑&#xff0c;我也一样。 见下图&#xff1a; 解决办法&#xff1a; 1、打开终端 输入“ls回车”&#xff0c;列出当前目录下所有的文件…

自然语言处理:transfomer架构

介绍 transfomer是自然语言处理中的一个重要神经网络结构&#xff0c;算是在传统RNN和LSTM上的一个升级&#xff0c;接下来让我们来看看它有处理语言序列上有哪些特殊之处 模型整体架构 原论文中模型的整体架构如下&#xff0c;接下来我们将层层解析各层的作用和代码实现 该…

docker环境搭建及其安装常用软件

centos安装docker Install Docker Engine on CentOS | Docker Docs 下载docker sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io…

Qt6入门教程 12:QAbstractButton

目录 一.状态 二.信号 三.使用 1.自定义按钮 2.多选 3.互斥 QAbstractButton类实现了一个抽象按钮&#xff0c;并且让它的子类来指定如何处理用户的动作&#xff0c;并指定如何绘制按钮。QAbstractButton类是所有按钮控件的基类。 QAbstractButton提供…

SpringCloud-高级篇(十七)

&#xff08;1&#xff09;添加Redis缓存 -缓存预热 前面实现了openResty查询tomcat&#xff0c;但是缓存架构是&#xff0c;先查询Redis&#xff0c;Redis没有在查询tomcat&#xff0c;下面实现Redis的缓存功能了&#xff1a; --appendonly yes 运行的时候基于日志的方式做数…

寒假思维训练计划day16 A. Did We Get Everything Covered?

今天更新一道1月27号晚上div2的C题作为素材&#xff0c;感觉用到了我的构造题总结模型&#xff0c;我总结了一系列的模型和例题。 摘要&#xff1a; Part1 定义"边界贪心法" Part2 题意 Part3 题解 Part4 代码 Part5 思维构造题模型和例题 Part1 边界贪心…

【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)

基于Netty的分布式通信框架实现 前提介绍回顾Dubbo分布式通信框架组成元素程序执行流程消息协议设计实现机制ChannelInboundHandlerAdapter自定义事件处理 ChannelOutboundHandlerAdapter 编(解)码处理器编码过程阶段ChannelOutboundHandlerAdapter序列化实现ChannelOutboundHa…

Android App开发-简单控件(4)——按钮触控和图像显示

3.4 按钮触控 本节介绍了按钮控件的常见用法&#xff0c;包括&#xff1a;如何设置大小写属性与点击属性&#xff0c;如何响应按钮的点击事件和长按事件&#xff0c;如何禁用按钮又该如何启用按钮&#xff0c;等等。 3.4.1 按钮控件Button 除了文本视图之外&#xff0c;按钮…

C++: 内联函数

目录 概念&#xff1a; 与宏的对比&#xff1a; 函数膨胀&#xff1a; 内联函数的特性&#xff1a; 概念&#xff1a; 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函数调 用建立栈帧的开销&#xff0c;内联函数…

DLL劫持之IAT类型(Loadlibrary)

Loadlibrary Loadlibrary的底层是LoadLibraryEx 第三个参数&#xff1a; DONT_RESOLVE_DLL_REFERENCES : 这个标志用于告诉系统将DLL映射到调用进程的地址空间中&#xff0c;但是不调用DllMain并且不加载依赖Dll&#xff08;只映射自己本身&#xff09;。 LOAD_LIBRARY_AS_DA…

【代码随想录-数组】螺旋矩阵 II

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…