求解公式第三部它是如何得来的,求过程。

求解,夏洛克第三季第三集,夏洛克被子弹击中思想过程里,他到一个屋子,有一个男的被锁着。是谁?_百度知道
求解,夏洛克第三季第三集,夏洛克被子弹击中思想过程里,他到一个屋子,有一个男的被锁着。是谁?
是莫里亚蒂么?还是谁?
我有更好的答案
是的 就是莫里亚蒂。你可以看一下S3E3的剧情第三季第3集 最后的誓言该剧根据《弥尔沃顿》改编。华生受到了一个朋友的请求,来到了一个年轻人吸毒的聚集地帮忙寻找这位朋友吸毒的儿子。他没有想到的是自己在这里居然遇到了夏洛克。为了一件案子,夏洛克开始重新吸毒,麦考夫相当生气,但是夏洛克却坚持声称自己是因为破案所以才开始吸毒。华生把夏洛克带回贝克街,惊讶的发现夏洛克竟然有了女友,这位女友正是自己婚礼上的伴娘洁琳。但随后华生发现,夏洛克之所以和自己的伴娘约会是因为夏洛克在查的案件。夏洛克假装要求婚,混进了马格努森的办公室,华生也和夏洛克一起,然而就在夏洛克发现马格努森被人威胁的时候,威胁者转过头,这个人竟然是华生的新婚妻子玛丽,玛丽本想杀了夏洛克但因恻隐之心只将夏洛克打伤使其沉默。夏洛克用尽自己所有的知识保护了自己,重伤之下得以存活。原来玛丽曾经是政府特工,因为厌倦了刀头舔血的生活所以隐居下来,和华生结婚。华生从清醒过来的夏洛克那里得知了玛丽的身份,但是最终选择了原谅玛丽。玛丽的资料被马格努森掌握用来威胁对方,除此以外马格努森也拥有许多隐秘的资料用来威胁西方的各个国家,麦考夫对此也受到了相当大的威胁。为了保护玛丽,夏洛克和华生再次找到了马格努森,但是马格努森也是一名出色的犯罪者,记忆能力相当高超。夏洛克以为对方有隐秘的仓库用来储存威胁的资料,但是最终发现这些资料只存在于马格努森的记忆里。为了保护玛丽的身份不被发现,也为了保护麦考夫和国家的利益,夏洛克毅然开枪在警察面前打死了马格努森,自己因此获罪。为了保护自己的弟弟,麦考夫只能把自己的弟弟远送国外,送到东欧做卧底半年。华生和玛丽送夏洛克离开。在夏洛克离开后,莫里亚蒂的视频被播放到了全国的电视信号上,原来莫里亚蒂竟然在当初的坠楼案中没有身亡,一切迷雾重重……为了揭穿莫里亚蒂未亡的真相,夏洛克再次被麦考夫召回伦敦,解决案件。【侦探的心跳都停了,要不是思维殿堂里莫娘的神助攻他或许就死了】【经过了一系列理性的思考,对中枪现状的分析,对倒下方向的分析等等(中间还慌过变成了个小孩版的自己),侦探对死亡的恐惧还是让他慌乱了,无所适从了,并把自己关在了一个小房间里。有趣的是这里也关着莫教授!这个房间好像是牢笼一样,装着卷福心里困兽一样的教授,而且穿着和铁链等都非常特殊,强制,残暴(恕我专业知识不足,不能更精确的描述出这里关押精神病人一样的设备,眼巴巴坐等心理学大神的解释!!)】望采纳!
采纳率:62%
是莫里亚蒂,你前后看就知道了
不是,看着不像。。。
他在吸毒老窝里认识并带回来的人,也是吸毒的
不是,带回来的是个瘦子,锁着的不是。。。
=-=反正本人看了N遍还是觉得就是那个人
为您推荐:
其他类似问题
夏洛克的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。登录网易通行证
使用网易通行证(含网易邮箱)帐号登录
提交您的投诉或建议
视频画面花屏
视/音频不同步
播放不流畅
分享给朋友:
扫描分享给微信好友和朋友圈
扫一扫分享给微信好友和朋友圈
通过代码可以让这个视频在其它地方上播放!
复制FLASH代码
复制HTML代码
复制页面地址
使用公开课APP下载视频
扫描二维码 手机继续看
扫描二维码在手机上继续观看,
还可分享给您的好友。
没有公开课客户端?
登录后才能查看我的笔记
暂时没有笔记!
确定删除笔记?
即将播放下一集,请您保存当前的笔记哦!
对字幕纠错要登录哦!
内容不能少于3个字
判断(-7,7)这个点在哪个象限。
本段视频先讲述了怎么在xy平面内根据坐标画点,然后讲述了怎么根据点找坐标,最后讲解了一个关于坐标和点的应用题。
列出时间与地上积雪量的关系式并作图。
画出已知线性方程的图像。
在坐标平面上标出点(6,-8)。
本节课用代入法和画图法解决了某个点是否是二元一次方程的一个解的问题。
本集视频通过应用题的情景设置,融入坐标与直线绘图的内容,直观展示了一次函数的性质,帮助观者更好的理解基本函数和基本概念。
本节课通过几个例子使大家对画图法有大致的了解,知道曲线所代表的变量之间的变化关系。
本课讲了怎么根据直线的方程求解x和y截距。具体方法是:令方程中x等于0,求得y0,y轴截距就是(0,y0);令方程中y等于0,求得x0,x轴截距就是(x0,0)。
本段视频介绍了如何找出2y+1/3x=12方程直线上的x轴截距和y轴截距。
本段视频介绍了任选图表中直线上的两点,算坐标变化量,求其斜率Δy/Δx的方法。
本段视频介绍了如何计算由表格内数据定义的线性函数的斜率。
本段视频通过讲述求解六条直线的斜率,详细讲述了斜率的几何定义和数学求解方法。
本集演示直线方程程序。
讲述如何求斜率。
讲述如何求解Y轴截距。
本集讲述直线方程的解法。
本课讲了求解图中直线的斜率,方法是用x变化量除以y变化量。
求出经过已知两个点的直线的斜率。
本集视频示范了当给出直线上两点坐标、求直线斜率的方法,深化了斜率的定义,帮助观者理解记忆。
本集视频通过一个例题,示范了给出两点求直线斜率的公式运算和图像理解,帮助观者快速记忆并深刻理解运算内涵。
做出已知方程y=1/3x-2的图像。
本片讲述斜率及y轴截距程序。
本片继续讲述直线方程。
第27题,选择图线表示的方程;第28题,选择在线上的点;第29题,由斜率和一个点写出线的方程;第30和31题,由几组数对写出方程;第32题,由方程判断线l和q的关系。
根据给出的方程,找出两个截距,再画出直线。利用这个解一个应用题,写出方程,画出对应的直线。
本集视频求解了给出斜截式方程的直线的x、y轴截距,帮助观者巩固基本概念,同时系统了解做题步骤,提高解题速度。
本片讲述直线绘图。
本片讲述如何用斜截式画图。
本段视频讲述了六个根据条件求直线斜截式方程的问题。
本段视频讲述了4个直线点斜式方程的问题,并且讲述了直线的点斜式方程和斜截式方程相互转换的问题。
本课讲了如何已知直线斜率和经过的一个点,求解直线的斜截式方程。
本集视频通过一个例题,示范了题目给出直线斜率和一个线上点写斜截式方程的步骤,帮助观者巩固基本概念,理清思路,快速解题。
已知一条直线的斜率,并经过一个点,求该直线的方程。
[第35课]求解直方程第3部分
求经过点(-1,6)和点(5,-4)的直线的方程
本段视频讲述了6个习题,前三个是讲述了什么是直线方程的标准形式,后三个讲述了标准形式、斜截式和点斜式的相互转化问题。
本段视频首先讲述了怎么用取点的方法画一次方程的图像问题,然后讲解了关于一次方程图像的应用题,最后讲解了一个根据图像找点的问题。
讲述正变模型。
如何判定是否为线性函数。
讲述如何转换为斜截式。
该视频根据已知直线上的两点,写出了三种不同形式的方程。
该视频介绍了判断平行直线的方法以及两条相互平行的直线的特点。
该视频继续练习找出互相平行的直线以及作图。
根据给出的三条直线的表达式判断这三条直线是否平行。
该视频介绍了直线相互垂直的概念及其判别方法并进行了练习。
根据给出的条件,求出直线B的方程。
根据已知条件,列出直线方程。
本段视频讲述了两个关于直线图像的应用题。
本段视频讲述了3个例题,前两个是根据点求直线斜率,然后判断直线关系的问题,最后一题是已知垂直条件求直线方程的问题。
本段视频讲述了一个用Excel解决线性回归并进行数据预测的问题。
本段视频讲述了几个求坐标轴上两点之间距离的的例题,并推导出了两点之间距离公式和勾股定理的关系。
本段视频主要讲述了两点之间中点的坐标求法和中点公式的推导。
在数轴及左边平面上画出不等式所表示的图像。
画出不等式y&3x+5的图像。
画出不等式的图像。
根据给出的图像,写出符合条件的不等式。
第21和22题,选择正确的陈述;第23题,计算图线的y轴截距;第24、25和26题,图形和不等式的对应。
这堂课讲的是:已知不等式或不等式组,通过画图求出解集,着重讲了有没有等号的区别。
本段视频介绍了如何确定有序数对(3,5)和(1,-7)是否为不等式5x-3y≥25的解。
本集视频示范了二元一次不等式方程的作图步骤,即先作出临界直线、再确定分区的方法,帮助观者建立解题的系统观念,巩固了基本内容。
本集讲解了在坐标平面上画出已知点的练习。具体讲解了画出已知点(8,10)、(6,10)及(5,7)的练习。
本集讲解在直角坐标系上画出已知点,并指出点所在的象限的练习,具体讲解了三个点:(4,-1)、(8,-4)和(-5,5)。
本集讲解了3个“辨别已知的若干点中,哪个点没有在坐标平面上画出来”的练习。
本集讲解了3个文字题,是对坐标平面上的点进行理解的练习。(1)已知米莉娜的家和购物中心的坐标,要求画出两点并求两点距离。(2)找出给定线段的中心点坐标。(3)已知A、B两镇坐标与火车线路上一车站坐标,求A和B哪个距离车站更近的问题。
本集讲解了3个在坐标平面上将已知点关于坐标轴作对称的练习。
本集讲解了分别用代入法和图像法,验证序偶(3,-4)是否为方程5x+2y=7的解的练习。
本集讲解了根据表格给出的点,用代入法确定生成这些点的线性方程的练习。
本集是关于线性方程的应用题,具体讲解了已知玩两款游戏的时间总共是45分钟,要用方程表示玩两款游戏的时间的关系,并判断方程是否为线性方程。
本集是关于线性方程的练习,具体讲解了求表格中所缺少值,使得表格给出的x和y表示一个线性方程。
本集视频详细示范了线性关系的概念和示例,帮助观者牢记线性关系的根本含义,提高解题效率。
本集视频示范了从直线一般方程求解x轴截距的方法,即令y=0求解x。帮助观者巩固基本运算的知识,为以后更深层次的学习打下基础。
本集视频介绍了用直线上的点坐标确定直线方程并求y轴截距的方法,帮助观者融会贯通,培养解题的连贯性。
本集视频示范了线性方程解应用题的例题,帮助观者更好地理解线性关系的概念,培养灵活解题的思路。
本集讲解了由表格表示出速率,要求与其他方程表示的速率对比的两个应用题。具体为(1)玛丽亚和纳迪娅驾车的速度问题。(2)两款黑胶唱片的转速快慢问题。
本集视频通过比较大小的形式,帮观者复习了通过直线方程和图像判断直线斜率的方法,拓展解题思路,提高解题速度。
本集视频介绍了正比关系的概念和图像,帮助观者整合所学知识,为以后深层次学习打下基础。
本集视频示范了利用正比例函数解应用题的步骤和方法,帮助观者熟悉基本运算和解题套路,提高解题效率。
本集视频介绍了给定几组值判断是否是正比例函数的方法,即变量之比保持恒定,复习了正比例函数的定义与概念,提高解题效率。
本集视频介绍了给出正比例函数斜率来作图的方法,从另一种方向复习了正比例函数的含义和方程,帮助观者巩固基础知识。
本集视频示范了用给定函数几组值来作图并求函数斜率的方法,帮助观者培养灵活的解题思路。
本集视频介绍了利用正比例函数方程作图的方法,并分析函数的斜率,xy值的变化量的关系。帮助观者更好地掌握斜率的概念,提高解题速度。
本集视频介绍了根据函数图象求函数斜率的方法,巩固了函数基本运算。
本集视频示范了应用题列式并处理的技巧和方法,帮助观者更好理解出题人的意图,并依此来有效地解题。
本集视频利用判断对错的题目形式,练习了四个应用题列方程的问题,帮助观者提高解题速度,同时巩固了对正比例函数的理解。
本集视频示范了由正比例函数几组值确定函数方程的步骤方法,与观者一起复习了正比例函数的概念和定义,巩固了基础知识。
本集视频介绍了函数平均变化率的概念,即一个区间左右顶点连线的斜率,扩展了基础知识,培养灵活的解题思路。
本集视频利用龟兔赛跑的时间-距离图象,分析各自的速度、比赛情况,寓教于乐,与观者一起增进分析、解题能力。
本集讲解了根据表格所列出的数据,求y(x)在区间-5&x&-2上的变化率的练习题。
本集视频示范了利用方程和图像分析函数变化趋势、斜率的方法,帮助观者查漏补缺,积累解题经验和思路。
本集视频介绍了通过几组值和图象判断直线斜率的方法,帮助观者熟悉一次函数的性质。
本集视频介绍了利用一次函数几个点及图像判断比较斜率大小的方法,帮助观者整合所学知识,灵活解题。
本集视频利用求解应用题的方式,帮助观者巩固一次函数斜率的概念和运算,提高解题效率。
本集视频示范了根据一次函数几组值来分析、判断有关函数论述的过程,与观者一起明确了解题流程,提高解题速度。
本集视频介绍了根据一次函数的方程和几组值比较y轴截距的办法,帮助观者将一次函数的几种表示方法分析透彻并灵活掌握。
本集视频分析了距离对时间的函数的概念,帮助观者更好地理解一次函数的定义和相应运算。
本集视频介绍了利用一次函数几组值求y轴截距的方法,帮助观者熟悉一次函数基本概念,巩固了解题思路。
本集视频从定性和定量上来分析了水池放水的问题,使观者更透彻地理解一次函数的定义与概念,同时培养了灵活解题的思路。
本集视频介绍了应用题中一次函数关系的分析方法和过程,帮助观者更好地读懂题意,快速解题。
本集视频示范了应用题分析题意列方程的方法,帮助观者更好的理解一次函数及其方程的含义。
本集视频介绍了直线方程与直线上的点的坐标的关系,即点在直线上则坐标满足直线方程,并通过此对应关系来求解直线方程中的未知数。
本集视频示范了根据几组值求解一次函数方程的过程,帮助观者积累解题经验,提高解题效率。
本集视频介绍了一次函数点斜式和斜截式方程的概念和应用,帮助观者巩固一次函数方程的各种表示方式,并熟练运用。
本集视频示范了点斜式方程变换成斜截式方程的方法,帮助观者熟练掌握一次函数方程的各种表示方法,提高解题速度。
本集讲解勾股定理,具体介绍了勾股定理的定义(表示形式和注意事项等)、作用以及两个具体的应用例子。
本集视频通过对斜截式方程的分析,帮助观者更透彻掌握直线方程的各种表示方法和意义,同时复习了两条垂直直线的方程之间的关系。
本集视频示范了求点到直线距离的一种方法,即作垂线确定交点、求两点之间距离,既复习了垂线与直线斜率的关系,又介绍了两点之间的距离公式,帮助观者对所学知识查漏补缺。
本集视频借助相似三角形的知识,证明了直线斜率处处相等,帮助观者更透彻地理解斜率的定义,从来更好地掌握一次函数及其方程的题目。
本集视频利用一个例题,与观者一起复习了直线斜率的定义、相似三角形的性质,提高解题速度。
本集视频结合直线斜率的定义和相似三角形的特点,帮助观者甄别三角形相似的条件,开拓解题思路。
本集视频与观者一起探讨了直线斜截式方程的含义,并示范了两点式方程的用法,帮助观者巩固一次函数的基础知识。
本集视频利用平行线和截线的几何知识,帮助观者复习了三角形相似的条件和判断依据,积累解题经验。
本集是关于画图的一个文字题,已知Naomi家和学校的位置坐标,要求先在图中标出,然后求出Naomi走路上学需要经过几个街区。
本集是关于画图的一个文字题。Achilles向乌龟发起挑战进行10公里赛跑,并让乌龟先跑2小时,然后他再匀速奔跑。要求从已知曲线图中选出描述他的速度和时长关系的图像。
本集是关于作图比例选择的练习题。已知一些数据点和四个候选比作,要求选出对已知数据点作图时最恰当的比例。
本集是关于图像斜率和坐标单位的理解的练习题。已知轮子转数为0.35转每秒,要求从候选表述中选出对图像描述正确的选项。
本集是平均变化率的计算习题。已知函数用方程y=1/8x^3-x^2给出,要求在四个候选区间中,选出y(x)的平均变化率为1/2的区间。
本集是斜率的直观认知练习(1)。根据要求,在给出的图像中,作出一条斜率为负,且斜率比已知蓝色直线大的直线。2
学校:可汗学院
讲师:Salman Khan
授课语言:英文
类型:国际名校公开课 可汗学院
课程简介:这一部分主要介绍了坐标平面,斜率,截距等概念,以及如何画出线性方程和线性不等式的图像来。主要内容包括:坐标平面,坐标点,X、Y截距,直线的斜率,如何根据方程和不等式画出图像等等。视频由可汗学院免费提供,详见:(All Khan Academy materials are available for free at )
扫描左侧二维码下载客户端使用dancing links算法求解数独 - 简书
使用dancing links算法求解数独
使用dancing links算法求解数独
博文来自这里:
算法的实现来自
我这里只是解读了一下代码,备忘.
精确覆盖问题的定义
给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1.
例如:如下的矩阵:
就包含了这样一个集合(第1、4、5行).
如何利用给定的矩阵求出相应的行的集合呢?我们可以采用回溯法.
假定给定如下的矩阵(矩阵1):
先假定选择第1行,如下所示:
如上图中所示,红色的那行是选中的一行,这一行中有3个1,分别是第3、5、6列。
由于这3列已经包含了1,故,把这三列往下标示,图中的蓝色部分。蓝色部分包含3个1,分别在2行中,把这2行用紫色标示出来.
根据定义,同一列的1只能有1个,故紫色的两行,和红色的一行的1相冲突。
那么在接下来的求解中,红色的部分、蓝色的部分、紫色的部分都不能用了,把这些部分都删除,可以得到一个新的矩阵(矩阵2):
行分别对应矩阵1中的第2、4、5行.
列分别对应矩阵1中的第1、2、4、7列.
于是问题就转换为一个规模小点的精确覆盖问题.
在新的矩阵中再选择第1行,如下图所示:
还是按照之前的步骤,进行标示。红色、蓝色和紫色的部分又全都删除,导致新的空矩阵产生,而红色的一行中有0(有0就说明这一列没有1覆盖)。说明,第1行选择是错误的.
那么回到之前,选择第2行,如下图所示:
按照之前的步骤,进行标示。把红色、蓝色、紫色部分删除后,得到新的矩阵(矩阵3):
行对应矩阵2中的第3行,矩阵1中的第5行.
列对应矩阵2中的第2、4列,矩阵1中的第2、7列.
由于剩下的矩阵只有1行,且都是1,选择这一行,问题就解决.
于是该问题的解就是矩阵1中第1行、矩阵2中的第2行、矩阵3中的第1行。也就是矩阵1中的第1、4、5行.
在求解这个问题的过程中,我们第1步选择第1行是正确的,但是不是每个题目第1步选择都是正确的,如果选择第1行无法求解出结果出来,那么就要推倒之前的选择,从选择第2行开始,依此类推.
从上面的求解过程来看,实际上求解过程可以如下表示
从矩阵中选择一行;
根据定义,标示矩阵中其他行的元素;
删除相关行和列的元素,得到新矩阵;
如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5;
说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7;
求解结束,把结果输出;
求解结束,输出无解消息.
从如上的求解流程来看,在求解的过程中有大量的缓存矩阵和回溯矩阵的过程。而如何缓存矩阵以及相关的数据(保证后面的回溯能正确恢复数据),也是一个比较头疼的问题(并不是无法解决)。以及在输出结果的时候,如何输出正确的结果(把每一步的选择转换为初始矩阵相应的行)。
于是算法大师Donald E.Knuth(《计算机程序设计艺术》的作者)出面解决了这个方面的难题。他提出了DLX(Dancing Links X)算法。实际上,他把上面求解的过程称为X算法,而他提出的舞蹈链(Dancing Links)实际上并不是一种算法,而是一种数据结构。一种非常巧妙的数据结构,他的数据结构在缓存和回溯的过程中效率惊人,不需要额外的空间,以及近乎线性的时间。而在整个求解过程中,指针在数据之间跳跃着,就像精巧设计的舞蹈一样,故Donald E.Knuth把它称为Dancing Links(中文译名舞蹈链)。
Dancing Links的核心是基于双向链的方便操作(移除、恢复加入).
我们用例子来说明.
假设双向链的三个连续的元素,A1、A2、A3,每个元素有两个分量Left和Right,分别指向左边和右边的元素。由定义可知
A1.Right = A2, A2.Right = A3;
A2.Left = A1, A3.Left = A2;
在这个双向链中,可以由任一个元素得到其他两个元素,A1.Right.Right = A3, A3.Left.Left = A1等等.
现在把A2这个元素从双向链中移除(不是删除)出去,那么执行下面的操作就可以了:
A1.Right = A3, A3.Left = A1;
那么就直接连接起A1和A3。A2从双向链中移除出去了。但仅仅是从双向链中移除了,A2这个实体还在,并没有删除。只是在双向链中遍历的话,遍历不到A2了。
那么A2这个实体中的两个分量Left和Right指向谁?由于实体还在,而且没有修改A2分量的操作,那么A2的两个分量指向没有发生变化,也就是在移除前的指向。即A2.Left = A1和A2.Right = A3.
如果此时发现,需要把A2这个元素重新加入到双向链中的原来的位置,也就是A1和A3的中间。由于A2的两个分量没有发生变化,仍然指向A1和A3。那么只要修改A1的Right分量和A3的Left就行了。也就是下面的操作:
A1.Right = A2, A3.Left = A2;
仔细想想,上面两个操作(移除和恢复加入)对应了什么?是不是对应了之前的算法过程中的关键的两步?
移除操作对应着缓存数据、恢复加入操作对应着回溯数据。而美妙的是,这两个操作不再占用新的空间,时间上也是极快速的
在很多实际运用中,把双向链的首尾相连,构成循环双向链表.
Dancing Links用的数据结构是交叉十字循环双向链.
而Dancing Links中的每个元素不仅是横向循环双向链中的一份子,又是纵向循环双向链的一份子。
因为精确覆盖问题的矩阵往往是稀疏矩阵(矩阵中,0的个数多于1),Dancing Links仅仅记录矩阵中值是1的元素。
Dancing Links中的每个元素一般而言,有6个分量。分别是--Left指向左边的元素、Right指向右边的元素、Up指向上边的元素、Down指向下边的元素、Col指向列标元素、Row指示当前元素所在的行。
Dancing Links还要准备一些辅助元素(为什么需要这些辅助元素?没有太多的道理,大师认为这能解决问题,实际上是解决了问题):
Ans():Ans数组,在求解的过程中保留当前的答案,以供最后输出答案用。
Head元素:求解的辅助元素,在求解的过程中,当判断出Head.Right = Head (也可以是Head.Left = Head)时,求解结束,输出答案。Head元素只有两个分量有用。其余的分量对求解没啥用。
C元素:辅助元素,称列标元素,每列有一个列标元素。本文开始的题目的列标元素分别是C1、C2、C3、C4、C5、C6、C7。每一列的元素的Col分量都指向所在列的列标元素。列标元素的Col分量指向自己(也可以是没有)。在初始化的状态下,Head.Right = C1, C1.Right = C2, ..., C7.Right = Head, Head.Left = C7等等。列标元素的分量Row = 0,表示是处在第0行。
下图就是根据题目构建好的交叉十字循环双向链(构建的过程后面的详述).
交叉十字循环双向链
就上图解释一下:
每个绿色方块是一个元素,其中Head和C1、C2、...、C7是辅助元素。橙色框中的元素是原矩阵中1的元素,给他们标上号(从1到16).
左侧的红色,标示的是行号,辅助元素所在的行是0行,其余元素所在的行从1到6。
每两个元素之间有一个双向箭头连线,表示双向链中相邻两个元素的关系(水平的是左右关系、垂直的是上下关系)。
单向的箭头并不是表示单向关系,而因为是循环双向链,左侧的单向箭头和右侧的单向箭头(上边的和下边的)组成了一个双向箭头,例如元素14左侧的单向箭头和元素16右侧的单项箭头组成一个双向箭头,表示14.Left=16、16.Right=14;同理,元素14下边的单项箭头和元素C4上边的单向箭头组成一个双向箭头,表示14.Down=C4、C4.Up=14。
接下来,利用图来解释Dancing Links是如何求解精确覆盖问题。
首先判断Head.Right=Head?若是,求解结束,输出解;若不是,求解还没结束,到步骤2(也可以判断Head.Left=Head?)
获取Head.Right元素,即元素C1,并标示元素C1(标示元素C1,指的是标示C1、和C1所在列的所有元素、以及该元素所在行的元素,并从双向链中移除这些元素)。如下图中的紫色部分。
交叉十字循环双向链
如上图可知,行2和行4中的一个必是答案的一部分(其他行中没有元素能覆盖列C1),先假设选择的是行2.
选择行2(在答案栈中压入2),标示该行中的其他元素(元素5和元素6)所在的列首元素,即标示元素C4和标示元素C7,下图中的橙色部分。
注意的是,即使元素5在步骤2中就从双向链中移除,但是元素5的Col分量还是指向元素C4的,这里体现了双向链的强大作用。
交叉十字循环双向链
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链
一下子空了好多,是不是转换为一个少了很多元素的精确覆盖问题?,利用递归的思想,很快就能写出求解的过程来。我们继续完成求解过程
获取Head.Right元素,即元素C2,并标示元素C2。如下图中的紫色部分。
交叉十字循环双向链
如图,列C2只有元素7覆盖,故答案只能选择行3.
选择行3(在答案栈中压入3),标示该行中的其他元素(元素8和元素9)所在的列首元素,即标示元素C3和标示元素C6,下图中的橙色部分。
交叉十字循环双向链
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链
获取Head.Right元素,即元素C5,元素C5中的垂直双向链中没有其他元素,也就是没有元素覆盖列C5。说明当前求解失败。要回溯到之前的分叉选择步骤(步骤2)。那要回标列首元素(把列首元素、所在列的元素,以及对应行其余的元素。并恢复这些元素到双向链中),回标列首元素的顺序是标示元素的顺序的反过来。从前文可知,顺序是回标列首C6、回标列首C3、回标列首C2、回标列首C7、回标列首C4。表面上看起来比较复杂,实际上利用递归,是一件很简单的事。并把答案栈恢复到步骤2(清空的状态)的时候。又回到下图所示:
交叉十字循环双向链表
由于之前选择行2导致无解,因此这次选择行4(再无解就整个问题就无解了)。选择行4(在答案栈中压入4),标示该行中的其他元素(元素11)所在的列首元素,即标示元素C4,下图中的橙色部分。
交叉十字循环双向链表
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链表
获取Head.Right元素,即元素C2,并标示元素C2。如下图中的紫色部分。
交叉十字循环双向链
如图,行3和行5都可以选择
选择行3(在答案栈中压入3),标示该行中的其他元素(元素8和元素9)所在的列首元素,即标示元素C3和标示元素C6,下图中的橙色部分。
交叉十字循环双向链表
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链
获取Head.Right元素,即元素C5,元素C5中的垂直双向链中没有其他元素,也就是没有元素覆盖列C5。说明当前求解失败。要回溯到之前的分叉选择步骤(步骤8)。从前文可知,回标列首C6、回标列首C3。并把答案栈恢复到步骤8(答案栈中只有4)的时候。又回到下图所示:
交叉十字循环双向链表
由于之前选择行3导致无解,因此这次选择行5(在答案栈中压入5),标示该行中的其他元素(元素13)所在的列首元素,即标示元素C7,下图中的橙色部分。
交叉十字循环双向链
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链
获取Head.Right元素,即元素C3,并标示元素C3。如下图中的紫色部分。
交叉十字循环双向链
如上图,列C3只有元素1覆盖,故答案只能选择行3(在答案栈压入1)。标示该行中的其他元素(元素2和元素3)所在的列首元素,即标示元素C5和标示元素C6,下图中的橙色部分。
交叉十字循环双向链表
把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示:
交叉十字循环双向链
因为Head.Right=Head。故,整个过程求解结束。输出答案,答案栈中的答案分别是4、5、1。表示该问题的解是第4、5、1行覆盖所有的列。如下图所示(蓝色的部分):
交叉十字循环双向链
从以上的14步来看,可以把Dancing Links的求解过程表述如下
Dancing函数的入口
判断Head.Right=Head?,若是,输出答案,返回True,退出函数。
获得Head.Right的元素C
获得元素C所在列的一个元素
标示该元素同行的其余元素所在的列首元素
获得一个简化的问题,递归调用Daning函数,若返回的True,则返回True,退出函数。
若返回的是False,则回标该元素同行的其余元素所在的列首元素,回标的顺序和之前标示的顺序相反
获得元素C所在列的下一个元素,若有,跳转到步骤6
若没有,回标元素C,返回False,退出函数。
之前的文章的表述,为了表述简单,采用面向对象的思路,说每个元素有6个分量,分别是Left、Right、Up、Down、Col、Row分量。
但在实际的编码中,用数组也能实现相同的作用。例如:用Left()表示所有元素的Left分量,Left(1)表示元素1的Left分量.
在前文中,元素分为Head元素、列首元素(C1、C2等)、普通元素。在编码中,三种元素统一成一种元素。如上题,0表示Head元素,1表示元素C1、2表示元素C2、...、7表示元素C7,从8开始表示普通元素。这是统一后,编码的简便性。利用数组的下标来表示元素,宛若指针一般。
如何利用Dancing links算法来求解数独
舞蹈链(Dancing Links)算法在求解精确覆盖问题时效率惊人。
那利用舞蹈链(Dancing Links)算法求解数独问题,实际上就是下面一个流程
把数独问题转换为精确覆盖问题;
设计出数据矩阵;
用舞蹈链(Dancing Links)算法求解该精确覆盖问题;
把该精确覆盖问题的解转换为数独的解.
数独的规则
数独(Sudoku)是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含1-9,不重复。 每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。
如下图所示,就是一个数独的题目:
首先看看数独问题(9*9的方格)的规则
每个格子只能填一个数字;
每行每个数字只能填一遍,对于上面题目中的第一行,一共9个空格,但是1~9这9个数字只能出现1遍,也就是说,第1行余下的空格中,已经不能填写6,5,9,3了;
每列每个数字只能填一遍,对于上面的图,第1列的其他空格已经不能填入9,
1, 4, 2了;
每宫每个数字只能填一遍,所谓的宫,代表的是图中比较大的3*3的格子.
那现在就是利用这个规则把数独问题转换为精确覆盖问题。
可是,直观上面的规则,发现比较难以转换为精确覆盖问题。因此,把上面的表述换个说法。
每个格子只能填一个数字;
每行1-9的这9个数字都得填一遍(也就意味着每个数字只能填一遍);
每列1-9的这9个数字都得填一遍;
每宫1-9的这9个数字都得填一遍。
这样理解的话,数独问题转换为精确覆盖问题就相对简单多了。关键就是如何构造精确覆盖问题中的矩阵.
我们把矩阵的每个列都定义成一个约束条件。
第1列定义成:(1,1)填了一个数字
第2列定义成:(1,2)填了一个数字
第9列定义成:(1,9)填了一个数字
第10列定义成:(2,1)填了一个数字
第18列定义成:(2,9)填了一个数字
第81列定义成:(9,9)填了一个数字
至此,用第1-81列完成了约束条件1:每个格子只能填一个数字
第N列(1≤N≤81)定义成:(X,Y)填了一个数字。
N、X、Y之间的关系是:X = INT((N - 1)/ 9)+1; Y = ((N-1) Mod 9) + 1; N =(X - 1) × 9 + Y.
第82列定义成:在第1行填了数字1
第83列定义成:在第1行填了数字2
第90列定义成:在第1行填了数字9
第91列定义成:在第2行填了数字1
第99列定义成:在第2行填了数字9
第162列定义成:在第9行填了数字9
至此,用第82-162列(共81列)完成了约束条件2:每行1-9的这9个数字都得填一遍
第N列(82≤N≤162)定义成:在第X行填了数字Y。
N、X、Y之间的关系是:X = INT(N - 81 - 1)/ 9)+ 1; Y = ((N - 81 - 1)Mod 9)+ 1; N = (X - 1)× 9 + Y + 81.
第163列定义成:在第1列填了数字1
第164列定义成:在第1列填了数字2
第171列定义成:在第1列填了数字9
第172列定义成:在第2列填了数字1
第180列定义成:在第2列填了数字9
第243列定义成:在第9列填了数字9
至此,用第163-243列(共81列)完成了约束条件3:每列1-9的这9个数字都得填一遍
第N列(163≤N≤243)定义成:在第X列填了数字Y。
N、X、Y之间的关系是:X = INT((N - 162 - 1) / 9) + 1; Y =((N - 162 - 1)Mod 9) + 1; N = (X - 1)× 9 + Y + 162.
第244列定义成:在第1宫填了数字1
第245列定义成:在第1宫填了数字2
第252列定义成:在第1宫填了数字9
第253列定义成:在第2宫填了数字1
第261列定义成:在第2宫填了数字9
第324列定义成:在第9宫填了数字9
至此,用第244-324列(共81列)完成了约束条件4:每宫1-9的这9个数字都得填一遍
第N列(244≤N≤324)定义成:在第X宫填了数字Y。
N、X、Y之间的关系是:X = INT((N - 243 - 1) / 9)+ 1; Y = ((N - 243 - 1)Mod 9)+ 1; N = (X - 1) × 9 + Y + 243.
至此,用了324列完成了数独的四个约束条件,矩阵的列定义完成.
那接下来,就是把数独转换为矩阵.
数独问题中,每个格子分两种情况。有数字的格子、没数字的格子。
有数字的格子
以例子来说明,在(4,2)中填的是7
把(4,2)中填的是7,解释成4个约束条件
在(4,2)中填了一个数字。
在第4行填了数字7
在第2列填了数字7
在第4宫填了数字7(坐标(X,Y)到宫N的公式为:N = INT((X - 1) / 3) × 3 + INT((Y - 1) / 3)+ 1).
那么这4个条件,分别转换成矩阵对应的列为
在(4,2)中填了一个数字。对应的列N = (4 - 1)×9 + 2 = 29
在第4行填了数字7。对应的列N = (4 - 1) × 9+ 7 + 81 = 115
在第2列填了数字7。对应的列N = (2 - 1)× 9 + 7 + 162 = 178
在第4宫填了数字7。对应的列N = (4 - 1)× 9 + 7 + 243 = 277
于是,(4,2)中填的是7,转成矩阵的一行就是,第29、115、178、277列是1,其余列是0。把这1行插入到矩阵中去。
没数字的格子
还是举例说明,在(5,8)中没有数字
把(5,8)中没有数字转换成:
(5,8)中填的是1,转成矩阵的一行就是,第44、118、226、289列是1,其余列是0。
(5,8)中填的是2,转成矩阵的一行就是,第44、119、227、290列是1,其余列是0。
(5,8)中填的是3,转成矩阵的一行就是,第44、120、228、291列是1,其余列是0。
(5,8)中填的是4,转成矩阵的一行就是,第44、121、229、292列是1,其余列是0。
(5,8)中填的是5,转成矩阵的一行就是,第44、122、230、293列是1,其余列是0。
(5,8)中填的是6,转成矩阵的一行就是,第44、123、231、294列是1,其余列是0。
(5,8)中填的是7,转成矩阵的一行就是,第44、124、232、295列是1,其余列是0。
(5,8)中填的是8,转成矩阵的一行就是,第44、125、233、296列是1,其余列是0。
(5,8)中填的是9,转成矩阵的一行就是,第44、126、234、297列是1,其余列是0。
把这9行插入到矩阵中。由于这9行的第44列都是1(不会有其他行的44列会是1),也就是说这9行中必只有1行(有且只有1行)选中(精确覆盖问题的定义,每列只能有1个1),是最后解的一部分。这就保证了最后解在(5,8)中只有1个数字。
这样,从数独的格子依次转换成行(1行或者9行)插入到矩阵中。完成了数独问题到精确覆盖问题的转换。
接下来求解精确覆盖问题就可以交给舞蹈链(Dancing Links)算法了.
实际的编码过程可能并不完全按照上面的算法进行,因为我们要加快运行的速度,以及简化编码的复杂度。
这里需要注意一下,对于上面的元素C1, C2...,我们下面统一称作表头元素,而不是列标元素。
为了方便编码,我们可以将上图中的head,表头以及每个十字链中的每一个元素统一都用Node这个结构来表示,每一个Node都长成这样:
typedef Node C
struct Node
Node* // 每一个节点都有上下左右4个指针域
Column* // 用于指向表头元素
// 记录该元素所在列的表头元素在columns_数组中的下标
// 用于记录这一列一共有多少元素,一般这个域只供表头元素使用
为了完成我们的算法,我们提供了这么一些辅助的数据结构:
struct Dance
Column* root_; // root_表示上面的head
inout_; // 输入的谜题
Column* columns_[400]; // columns用于记录每一列的头部,即表头
vector&Node*& stack_; // 用于存储选择的列
nodes_[kMaxNodes]; // 我们使用数组来实现算法,事先分配好,避免new
cur_node_; // 指示器,表示nodes_中已经分配了的node的数目
下面是一些常量的定义:
const int kMaxNodes = 1 + 81 * 4 + 9 * 9 * 9 * 4;
const int kMaxColumns = 400; // 列的数目最多不超过400个
const int kRow = 100, kCol = 200, kBox = 300;
在Dance结构体中,定义了这么一个初始化结构的构造函数,我们一步一步来分析.这里需要说明一下的是输入的数据,它是一个包含81个int型数据的数组,类似于这种形式:
0代表要填入的数字,其余的表示都已经填入。
Dance(int inout[81]) : inout_(inout), cur_node_(0)
{ // inout表示输入的谜题,输入是一个包含81个int型数据的数组,
//从左至右,从上到下表示了每一个格子中填的值
// 0表示带填入的数据,否则表示该位置已经填入了值
// 注意cur_node_被初始化为了0,表示nodes_数组从头开始分配
下面是具体的代码分析:
/* Dance结构的构造函数Dance */
stack_.reserve(100); // 事先分配好空间
root_ = new_column(); // head
root_-&left = root_-&right = root_;
memset(columns_, 0, sizeof(columns_));
bool rows[N][10] = { false }; // 0~9一共10个数
bool cols[N][10] = { false };
bool boxes[N][10] = { false };
我们看一下new_column函数具体干了什么?
Column* new_column(int n = 0) // 构建一个新列
Column* c = &nodes_[cur_node_++]; // 分配一个节点
memset(c, 0, sizeof(Column));
// 让节点内所有的指针都指向自己
c-&right =
c-&name = // name用于表示该节点代表的列,0代表head
我们继续来分析Dance的构造函数:
/* Dance结构的构造函数Dance */
for (int i = 0; i & N; ++i) { // N = 81
int row = i / 9;
// 获得第i个元素所在行
int col = i % 9;
// 所在的列
int box = row / 3 * 3 + col / 3;
// 所在的宫
int val = inout[i]; // 获得第i个元素的值
rows[row][val] = // 表示在第row行中val已经被填了
cols[col][val] = // 在第col列中val已经被填了
boxes[box][val] = // 在第box宫中val已经被填了
/* Dance结构的构造函数Dance */
// 下面的代码对于上面的算法做了改进,主要干的事情是,将那些已经填入了数字的列剔除了.
// 这样的话,可以加快程序的执行速度.
for (int i = 0; i & N; ++i) { // 初始化第1~81列
if (inout[i] == 0) { // 第i格需要填写,将不需要填写的列剔除掉
append_column(i);
// 一共是9行,9列,对于每一行,应该是能够填写9个元素的,但是实际上,可以做一些裁剪
// 那些不能填入的数字对应的列一律剔除
for (int i = 0; i & 9; ++i) {
for (int v = 1; v & 10; ++v) {
if (!rows[i][v]) // 如果rows[i][v]==0,表示第i行可以填入v
append_column(get_row_col(i, v));
if (!cols[i][v]) // 如果cols[i][v]==0,表示第i列可以填入v
append_column(get_col_col(i, v));
if (!boxes[i][v]) // 如果boxes[i][v]==0,表示第i宫可以填入v
append_column(get_box_col(i, v));
看一下append_column是如何定义的吧:
void append_column(int n)
Column* c = new_column(n);
put_left(root_, c); // 将c添加到root_所在的行
columns_[n] = // 记录下表头
void put_left(Column* old, Column* nnew) // 将nnew放在old的左边
// 这里需要特别注意的一点是,一般而言,old代表了一个双向链表
nnew-&left = old-&
nnew-&right =
old-&left-&right =
old-&left =
// 下面的函数主要实现从逻辑下标到实际下标的映射
int get_row_col(int row, int val) // 得到第row行的val对应的下标,这里实际上实现了一个映射函数
// kRow=100,这里需要注意,并不是81,这里是为了简单,如果你想极力
// 减少内存占用,可以考虑将kRow从100变为81,毕竟数组下标从81~99都是空闲的
return kRow + row * 10 +
int get_col_col(int col, int val) // 得到第col列的val所在的下标
return kCol + col * 10 +
int get_box_col(int box, int val) //
得到第box宫的val所在的下标
return kBox + box * 10 +
我们继续来分析Dance的构造函数:
/* Dance结构的构造函数Dance */
// 下面的代码片段开始构建行了,上面的主要是构建交叉十字循环双向链的头部部分
// 下面的话,开始构建链表的`体`了
for (int i = 0; i & N; ++i) {
if (inout[i] == 0) {
int row = i / 9;
int col = i % 9;
int box = row / 3 * 3 + col / 3;
for (int v = 1; v & 10; ++v) {
if (!(rows[row][v] || cols[col][v] || boxes[box][v])) {
// 第row行,第col列,第box宫都可以填入v (这个条件必须满足)
Node* n0 = new_row(i);
Node* nr = new_row(get_row_col(row, v));
Node* nc = new_row(get_col_col(col, v));
Node* nb = new_row(get_box_col(box, v));
// 这里的4个Node对应上面的四个约束条件
// 这里实际上说明了,一旦在n0,nr,nc,nb中任意一个填入了数v,那么其余的都不能填v了.
// nr代表该元素所在的行,nb表示所在的宫,nc代表所在的列
// n0,nr,nc,nb实际上成为了一行,同时双向链表除了标头那一行外,每一行都只有4个元素,不多不少
put_left(n0, nr); // 将nc, nr, nb加入n0所在的行
put_left(n0, nc);
put_left(n0, nb);
关于new_row函数:
Node* new_row(int col) // 构建新行
Node* r = &nodes_[cur_node_++]; // 分配空间
memset(r, 0, sizeof(Node));
r-&right =
// col记录了表头所在的元素在columns_数组中的下标
r-&col = columns_[col]; // 指向表头
put_up(r-&col, r); // 将r加入r-&col所在的列
void put_up(Column* old, Node* nnew) // 将nnew放在old的上面
// 如果old是表头元素,那么nnew就是插入到该列的尾部
// 不过,说实话,nnew在old所在的列的哪个位置并不重要,因为我们并不需要nnew的确切位置
// 只要保证nnew在这一列即可
nnew-&up = old-& // 需要注意的是,Column结构是一个十字交叉链表
nnew-&down =
old-&up-&down =
old-&size++; // size表示这一列增加了一个元素
nnew-&col = // 表头
上面部分的代码,完成了数独问题到解精确覆盖问题的转换,我在这里稍微来理一理:
精确覆盖问题要求解的是,给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1.
而这里的数独问题,我们看得到的,也转化成为了相同的一个问题:
对于0~80列(由于剔除了一些元素,可能没有那么多),这些列恰好包含一个1代表列对应的格子必须填入值.
对于100~199列(由于剔除了一些元素,可能没有那么多),这些列恰好包含一个1代表这些列对应到棋盘上的行必须填入对应的val.
对于200~299列(由于剔除了一些元素,可能没有那么多),这些列恰好包含一个1代表这些列对应到棋盘上的列必须填入对应的val.
对于300~399列(由于剔除了一些元素,可能没有那么多),这些列恰好包含一个1代表这些列对应到棋盘上的宫必须填入对应的val.
而这恰好对应前面算法中的四个约束条件.
这样构建出来的交叉十字循环双向链表,如果选择了某一行,就代表我们在棋盘上对应的格子a上填入了一个数字,这个数字填写了之后,会导致我们不能再选择a对应的列上其它元素所在的行了(每一列只能有1个1).因此要对双向链表进行裁剪.
初始化完成之后,我们就可以正式求解了:
bool solve_sudoku_dancing_links(int unused)
Dance d(board);
return d.solve();
继续来看solve函数,solve函数是使用递归进行求解:
bool solve()
if (root_-&left == root_) { // 运行到了这里表示所有的列都被覆盖到了
for (size_t i = 0; i & stack_.size(); ++i) {
Node* n = stack_[i]; // 取出Node
int cell = -1;
int val = -1;
while (cell == -1 || val == -1) {
// 回忆前面的,在同一行的只有四个元素,n0, nr, nc, nb,通过遍历这四个元素,我们可以知道该怎么来填
// n0中记录的下标(name域)是我们要填入的数据在inout_中的下标(小于100).
// 而nr, nc, nb,我们任取其中一个,就可以知道在inout_[cell]中填入什么数据了
if (n-&name & 100)
cell = n-&
val = n-&name % 10;
inout_[cell] =
Column* const col = get_min_column(); // 获得元素最少的列,这样可以减少计算量
cover(col);
for (Node* row = col-& row != row = row-&down) { // 一个回溯的过程
stack_.push_back(row);
for (Node* j = row-& j != j = j-&right) { // 将row所在行的数据全部删除
cover(j-&col);
if (solve()) { // 将问题的规模缩小,递归可解
stack_.pop_back(); // 运行到了这里说明上面的solve()失败,因此要恢复现场
for (Node* j = row-& j != j = j-&left) {
uncover(j-&col);
uncover(col);
回头看一下cover函数:
void cover(Column* c) // 所谓的cover,表示我们选择了这一列,我们就要将这一列中其它元素所在的行移除
c-&right-&left = c-&
c-&left-&right = c-& // 在表头这一行中删除掉c
for (Node* row = c-& row != row = row-&down) { // 遍历c所在列的元素
for (Node* j = row-& j != j = j-&right) { // 解除该元素所在行
j-&down-&up = j-&
j-&up-&down = j-&
j-&col-&size--;
以及uncover函数:
void uncover(Column* c) // 上面函数的逆函数
for (Node* row = c-& row != row = row-&up) {
for (Node* j = row-& j != j = j-&left) {
j-&col-&size++;
j-&down-&up =
j-&up-&down =
c-&right-&left =
c-&left-&right =
这样的话,函数就完成了.
完整的代码
#include &assert.h&
#include &memory.h&
#include &map&
#include &vector&
#include "sudoku.h"
/* 来看一下dancing links算法 */
typedef Node C
struct Node
const int kMaxNodes = 1 + 81 * 4 + 9 * 9 * 9 * 4;
const int kMaxColumns = 400;
const int kRow = 100, kCol = 200, kBox = 300;
struct Dance
Column* root_;
Column* columns_[400];
vector&Node*& stack_;
nodes_[kMaxNodes];
cur_node_;
Column* new_column(int n = 0)
assert(cur_node_ & kMaxNodes);
Column* c = &nodes_[cur_node_++];
memset(c, 0, sizeof(Column));
c-&right =
void append_column(int n) /*
assert(columns_[n] == NULL);
Column* c = new_column(n);
put_left(root_, c); /* 将c放在root_的左边 */
columns_[n] = /* 记录下列头 */
Node* new_row(int col)
assert(columns_[col] != NULL);
assert(cur_node_ & kMaxNodes);
Node* r = &nodes_[cur_node_++];
memset(r, 0, sizeof(Node));
r-&right =
r-&col = columns_[col];
put_up(r-&col, r);
int get_row_col(int row, int val) /* 得到第row行的val对应的下标,这里实际上实现了一个映射函数 */
return kRow + row * 10 +
int get_col_col(int col, int val) /* 得到第col列的val所在的下标 */
return kCol + col * 10 +
int get_box_col(int box, int val) /*
得到第box宫的val所在的下标 */
return kBox + box * 10 +
Dance(int inout[81]) : inout_(inout), cur_node_(0) /* 这里居然是一个构造函数 */
stack_.reserve(100); /* 事先分配好空间 */
root_ = new_column(); /* 表头元素,并没有加入到column中 */
root_-&left = root_-&right = root_;
memset(columns_, 0, sizeof(columns_));
bool rows[N][10] = { false }; /* 0~9一共10个数 */
bool cols[N][10] = { false };
bool boxes[N][10] = { false };
for (int i = 0; i & N; ++i) {
int row = i / 9;
int col = i % 9;
int box = row / 3 * 3 + col / 3;
int val = inout[i]; /*
rows[row][val] = /* 表示在第row行val已经被填了 */
cols[col][val] = /* 在第col列val已经被填了 */
boxes[box][val] = /* 在第box宫val已经被填了 */
for (int i = 0; i & N; ++i) { /* 初始化第1~81列 */
if (inout[i] == 0) { /* 第i格需要填写 */
append_column(i);
for (int i = 0; i & 9; ++i) {
for (int v = 1; v & 10; ++v) {
if (!rows[i][v]) /* 如果rows[i][v]==0,表示第i行可以填入v */
append_column(get_row_col(i, v));
if (!cols[i][v]) /* 如果cols[i][v]==0,表示第i列可以填入v */
append_column(get_col_col(i, v));
if (!boxes[i][v]) /* 如果boxes[i][v]==0,表示第i宫可以填入v */
append_column(get_box_col(i, v));
for (int i = 0; i & N; ++i) {
if (inout[i] == 0) {
int row = i / 9;
int col = i % 9;
int box = row / 3 * 3 + col / 3;
//int val = inout[i];
for (int v = 1; v & 10; ++v) {
if (!(rows[row][v] || cols[col][v] || boxes[box][v])) {
Node* n0 = new_row(i);
Node* nr = new_row(get_row_col(row, v));
Node* nc = new_row(get_col_col(col, v));
Node* nb = new_row(get_box_col(box, v));
put_left(n0, nr);
put_left(n0, nc);
put_left(n0, nb);
Column* get_min_column()
Column* c = root_-&
int min_size = c-& /* 获得某一列的元素的个数 */
if (min_size & 1) {
for (Column* cc = c-& cc != root_; cc = cc-&right) {
if (min_size & cc-&size) {
min_size = cc-&
if (min_size &= 1)
void cover(Column* c) /* 所谓的cover,表示我们选择了这一列 */
c-&right-&left = c-&
c-&left-&right = c-& /* 在表头中删除掉c */
for (Node* row = c-& row != row = row-&down) { /* 遍历c所在列的元素 */
for (Node* j = row-& j != j = j-&right) { /* 解除该元素所在行 */
j-&down-&up = j-&
j-&up-&down = j-&
j-&col-&size--;
void uncover(Column* c)
for (Node* row = c-& row != row = row-&up) {
for (Node* j = row-& j != j = j-&left) {
j-&col-&size++;
j-&down-&up =
j-&up-&down =
c-&right-&left =
c-&left-&right =
bool solve()
if (root_-&left == root_) { /* 运行到了这里表示所有的列都被覆盖到了 */
for (size_t i = 0; i & stack_.size(); ++i) {
Node* n = stack_[i]; /* 取出Node */
int cell = -1;
int val = -1;
while (cell == -1 || val == -1) {
if (n-&name & 100)
cell = n-&
val = n-&name % 10;
//assert(cell != -1 && val != -1);
inout_[cell] =
Column* const col = get_min_column();
cover(col);
for (Node* row = col-& row != row = row-&down) {
stack_.push_back(row);
for (Node* j = row-& j != j = j-&right) {
cover(j-&col);
if (solve()) {
stack_.pop_back();
for (Node* j = row-& j != j = j-&left) {
uncover(j-&col);
uncover(col);
void put_left(Column* old, Column* nnew) /* 将nnew放在old的左边 */
nnew-&left = old-&
nnew-&right =
old-&left-&right =
old-&left =
void put_up(Column* old, Node* nnew) /* 将nnew放在old的上面 */
// 如果old是表头元素,那么nnew就是插入到该列的尾部
// 不过,说实话,nnew在old所在的列的哪个位置并不重要,因为我们并不是依靠old来确定nnew的位置的
nnew-&up = old-& /* 需要注意的是,Column结构是一个十字交叉链表 */
nnew-&down =
old-&up-&down =
old-&size++; /* size表示这一列增加了一个元素 */
nnew-&col = /* 列头 */
bool solve_sudoku_dancing_links(int unused)
Dance d(board);
return d.solve();
易燃又美味!
如果你以前曾经接触过矩阵,可能了解如何进行矩阵乘法(否则,请阅读D.1节)。若A= (aij) 和B=(bij)是n*n的方阵,则对i, j=1, 2, ... , n, 定义乘积C=A*B中的元素cij为: 我们需要计算n^2个矩阵元素,每个元素是n个值的和。下面过程接收...
归去来兮。 1.1 说明 本篇为《挑战程序设计竞赛(第2版)》读书笔记系列,旨在: 梳理算法逻辑 探索优化思路 深入代码细节 1.2 目录 原文首发于个人博客Jennica.Space,按算法难度划分为初中高三个级别,详细目录及链接如下: 初级篇穷竭搜索贪心动态规划数据结构...
背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcode是什么样的体验? 慢慢有一些赞和感谢, 备受鼓舞, 于是我把所做过的题目用一个script跑了一下,编辑成一篇文章。这个总结页面是这么规划的: 题目名称(答案...
一前言 特征值 奇异值 二奇异值计算 三PCA 1)数据的向量表示及降维问题 2)向量的表示及基变换 3)基向量 4)协方差矩阵及优化目标 5)方差 6)协方差 7)协方差矩阵 8)协方差矩阵对角划
一前言: PCA的实现一般有两种: 一种是用特征值分解去实现的,一种是...
1.把二元查找树转变成排序的双向链表 题目: 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不能创建任何新的结点,只调整指针的指向。 10 / \ 6 14 / \ / \ 4 8 12 16 转换成双向链表 4=6=8=10=12=14=16。 首先我...
吃饭的隔壁桌,一个穿淡蓝色衬衫和紧身西装裤的男人,他的手提包看着十分精致。在这个熟悉的城市,他曾经爱过谁?现在爱着谁?谁又深爱过他?谁又错过了谁?
SpringSchedule配置简单,并且由于属于Spring框架,可以通过Spring来管理bean的生命周期,从而可以降低编程的复杂度。 SpringSchedule的使用 以注解配置为例 配置文件中添加配置添加相关注解配置: 开启定时任务的注解扫描:&task:ann...
童年的记忆里有那么憧景,那么美好东西,当你抱着多年幻想,多年的企盼去寻找时它确不在了,事情往往是这样的,就象小时候听大人讲小树林儿大河泡也有美丽的日出,而当你长大能走到河边想去看美丽瞬间时,那儿早已被填平盖起了高楼大厦? 随着年龄的增长,美好东西其实很多,但似乎什么东西都是...
他们是初中同学,虽在一个教室共同读书了三年,但他们并不熟悉。 开始熟络起来已是毕业七年后了。得知她在家乡小镇的小学做实习老师,初中一好友便和她相约一起吃饭。来到约好的饭店才发现,和好友一块儿的还有他。毕竟老同学一场,饭桌上,聊着分开后这几年的经历,很快大家就熟悉起来了。饭后...
有一个身高188cm的男朋友是什么体验? X先生身高188cm,我身高165cm 我的平行视线里,是他宽厚的肩。 抬头才能能勉强看到他... 单眼皮的眼睛和左眼角的泪痣, 向上的嘴角和若隐若现的酒窝。 他的视力2.0,我的视力0.2。 因为他,好神奇, 我的世界变得选择性清...

我要回帖

更多关于 单变量求解 的文章

 

随机推荐