回溯法、数独与N阶可达问题
回溯法是剪了枝的穷举,这是字面上的说法,不太好理解,不如讲解实例来的酸爽,于是引出了N阶可达问题:
有N个国家,每个国家有若干城市,小明要从中国(任意一个城市)出发,遍历所有国家(假设这个遍历顺序已经定了),最终到达美利坚(任意一个城市)。而城市之间有可能不可达,只有小明尝试过才知道(就是后面的check()函数),求满足要求的一条路径?

从上面的表述中我们已经嗅到了浓浓的穷举屌丝气质——遍历所有组合,但是我们的回溯思想总是基于这样一个简单的事实:如果当前选择导致你走进了死胡同,那么这个选择一定是错误的,同时基于这个错误的后续所有的选择都是错误而无意义的(剪枝)。道理的前半句表明我们要及时回溯,而后半句指出了这样做的优点是剪枝。比如小明要遍历中国---日本---美国,小明选择从中国武汉出发,这个选择是正确的还是错误的尚不明确,但是小明经过许多个check()之后发现,没有从武汉到日本任意一个城市的可达线路,这说明选择从武汉出发这个决定是错误的,应该回溯,重新选择一个中国的起点城市,小明在不知不觉中已经排除了形如(中国武汉市)---(日本XX市)---(美国XX市)的诸多组合,这就是所谓的剪了枝的穷举。
数独问题也是典型的N阶可达问题,下面以一个挖去64个洞的数独为例来具体说明,每个洞有1~9共9种可能性,一共要填64个洞,并且每填写好一个洞对后续的步骤会产生影响。

假设我们是从左到右,从上到下依次填写数字,那么此数独问题可以表达为如下的64阶可达问题:

根据回溯思想,采用递归函数(因为每层的情况是一样的,请读者思考如果不一样该如何编程)依次处理编号为0~80共计81个格子(代码略)。
下面我们用号称世界最难的数独题来测试一下程序,首先在程序同目录下建立sudoku.txt的文件,然后输入以下内容:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
保存然后运行程序,得到如下结果。程序大约运行了60ms,在进行了49584次尝试之后找到了数独的解。

再来一发,号称专杀暴力破解的数独题试一下:

哇,好奇怪,世界最难数独都能在百毫秒内求解,为什么这个数独题居然花了大约37秒?莫非此数独真的有专杀暴力破解的神秘力量?前面我们说回溯只是做了剪枝的工作,下面这幅图展示了回溯到底做了什么:

剪枝后只用搜索红色部分
从本质上说,搜索是一维的,答案位于这个一维地图中的某处,你搜索速度的快慢取决于你的地图和答案在地图中的位置。前面我们尝试的顺序是123456789,现在我们尝试987654321这个搜索顺序,把程序修改一番,再次运行哪个两个数独,结果如下:

左为世界最难,右边为专杀暴力
上面我们只是改变了搜索顺序,只是一维地图的反转后搜索,相当于地图没变我们换了另一头搜索。在第一程序中,之所以很快能搜索到世界最难数独,而搜索专杀爆破很慢,是因为世界最难的答案离这一端很近,专杀爆破的答案离这一端很远。当我们在不改变地图的情况下换另一端开始搜索,就会出现上面的结果。
故在数独算法中,每个人都说自己的算法是高效的,并给出试验数据。实际上这个说法并不准确,只能说采用不同的方法,地图的规模不同,答案所在的位置也不同。对于同一个数独,你的算法你别人快并不能说明什么,因为必然存在专杀你这种搜索方法的数独存在。你的算法搜索的越快,换个姿势一定就会越慢。
回溯法、数独与N阶可达问题的更多相关文章
- P1074 靶形数独 dfs回溯法
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶 ...
- Leetcode之回溯法专题-37. 解数独(Sudoku Solver)
Leetcode之回溯法专题-37. 解数独(Sudoku Solver) 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次.数字 1 ...
- python常用算法(7)——动态规划,回溯法
引言:从斐波那契数列看动态规划 斐波那契数列:Fn = Fn-1 + Fn-2 ( n = 1,2 fib(1) = fib(2) = 1) 练习:使用递归和非递归的方法来求解斐波那契数 ...
- N-Queens And N-Queens II [LeetCode] + Generate Parentheses[LeetCode] + 回溯法
回溯法 百度百科:回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步又一次选择,这样的走不通就退回再走的技术为回溯法 ...
- 从Leetcode的Combination Sum系列谈起回溯法
在LeetCode上面有一组非常经典的题型--Combination Sum,从1到4.其实就是类似于给定一个数组和一个整数,然后求数组里面哪几个数的组合相加结果为给定的整数.在这个题型系列中,1.2 ...
- 五大常用算法之四:回溯法[zz]
http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html 1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试 ...
- 算法入门经典-第七章 例题7-4-1 拓展 n皇后问题 回溯法
实际上回溯法有暴力破解的意思在里面,解决一个问题,一路走到底,路无法通,返回寻找另 一条路. 回溯法可以解决很多的问题,如:N皇后问题和迷宫问题. 一.概念 回溯算法实际类似枚举的搜索尝试过程,主 ...
- 0-1背包问题——回溯法求解【Python】
回溯法求解0-1背包问题: 问题:背包大小 w,物品个数 n,每个物品的重量与价值分别对应 w[i] 与 v[i],求放入背包中物品的总价值最大. 回溯法核心:能进则进,进不了则换,换不了则退.(按照 ...
- 【LeetCode】回溯法 backtracking(共39题)
[10]Regular Expression Matching [17]Letter Combinations of a Phone Number [22]Generate Parentheses ( ...
随机推荐
- 4行代码实现js模板引擎
在平时编码中,经常要做拼接字符串的工作,如把json数据用HTML展示出来,以往字符串拼接与逻辑混在在一起会让代码晦涩不堪,加大了多人协作与维护的成本.而采用前端模板机制就能很好的解决这个问题. 精妙 ...
- linux 线程回顾
额,时隔两年重新写博客了. 这次看一下thread_cond_wait(pthread_cond_t * cond, pthread_mutex_t *mutex)和thread_cond_signa ...
- Java集合之ArrayList源码分析
1.简介 List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList.Java中分别提供了这两种结构的 ...
- leetcode第17题--4Sum
Problem:Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + ...
- 干净的架构The Clean Architecture
干净的架构The Clean Architecture 这是著名软件大师Bob大叔提出的一种架构,也是当前各种语言开发架构.干净架构提出了一种单向依赖关系,从而从逻辑上形成一种向上的抽象系统. 我们经 ...
- Repository 仓储
Repository 仓储 写在前面 首先,本篇博文主要包含两个主题: 领域服务中使用仓储 SELECT 某某某(有点晕?请看下面.) 上一篇:Repository 仓储,你的归宿究竟在哪?(二)-这 ...
- Xamarin.Android
Xamarin.Android之使用百度地图起始篇 一.前言 如今跨平台开发层出不穷,而对于.NET而言时下最流行的当然还是Xamarin,不仅仅能够让我们在熟悉的Vs下利用C#开发,在对原生态类库的 ...
- Wookmark-jQuery-master 瀑布流插件
Wookmark-jQuery-master 瀑布流插件使用介绍,含个人测试DEMO 要求 必备知识 本文要求基本了解 Html/CSS, JavaScript/JQuery. 开发环境 Dream ...
- 并行编程之PLINQ
并行编程之PLINQ 并行 LINQ (PLINQ) 是 LINQ 模式的并行实现.PLINQ 的主要用途是通过在多核计算机上以并行方式执行查询委托来加快 LINQ to Objects 查询的执行速 ...
- javascript继承之借用构造函数与原型
javascript继承之借用构造函数与原型 在js中,关于继承只有利用构造函数和原型链两种来现实.以前所见到的种种方法与模式,只不过是变种罢了. 借用构造函数 1 2 3 4 5 6 7 8 9 1 ...