[leetcode] sudoku solver:暴力还是优化

1. backtracking

Sudoku是典型的backtracking问题,有关backtracking的问题《The Algorithm Design Manual》 7.1章解释的最详细易懂。Backtracking的定义如下:

Backtracking is a systemic way to iterate through all the possible configurations of a search space.

简而言之,backtracking就是通过遍历所有组合,并从中找出符合条件的结果集的一种方法。因此,一种非常直观的backtracking算法可以描述如下:

// array: 保存了[0, step-1]每一步的选择
// step: 当前是第几步

backtracking(array, step)
{
	// 判断前step步的选择是否可以构成一个结果
	if (is_a_solution(array, step)) {
		process_solution(array, step);
	} else {
		step++;
		// 获取下一步的所有选择
		condidates = construct_candidates(array, step);
		foreach c in condidates {
			// 对每一种选择,继续递归下去
			array[step] = c;
			make_move(array, step);
			backtracking(array, step);
			unmake_move(array, step);
		}
	}
}

其中重要函数有这样几个:

  • is_a_solution:判断当前组合是否是一个期望的结果
  • process_solution:处理结果,例如打印出来
  • construct_candidates:获取下一步的所有选择。注意,这里并没有说明如何选择下一步该如何进行,大多数情况下,这个函数还应当有选择下一步的功能
  • make_move:向前进一步。通常这意味着在array中填充这一步的所选择的的值
  • unmake_move:向后退一步。通常这意味着在array中将这一步的值清空

对于数独问题,套用上面的算法,大概步骤如下:

#define DIMENSION 3;
bool solve(vector<vector<char> > &board, int step) {
	// 1. is_a_solution
	if (step == DIMENSION*DIMENSION*9) return true;

	// 2. get next square and its all candidates
	int row = 0, col = 0;   // position to move next, start from 0;
	set<char> possible_values;
	get_next_square(board, row, col, possible_values, step);

	for (set<char>::iterator it = possible_values.begin(); 
			it != possible_values.end(); ++it) {
		board[row][col] = *it;				// make_move
		if (solve(board, step+1)) return true;
		board[row][col] = EMPTY;			// unmake_move
	}

	return false;
}

2. 如何选择下一步

backtracking主要有两种应用:

  • 获取所有组合。典型问题:
    • Letter Combinations of a Phone Number:给出电话号码,求电话号码对应的所有字符串
    • Generate Parentheses:求所有n对括号"()"组成的字符串
    • Combination Sum:给定一组数字和一个target值,求所有和等于target的组合(组合中每个数字可以出现多次)
    • Combination Sum2:和Combination Sum问题一样,区别是组合中每个数字只能出现一次
    • Permutations:求一组数字的全排列
    • Permutations2:和Permutations问题一样,区别是给定的一组数字有重复,并且要求结果集中不能有重复的组合
  • 从所有的组合中找出符合条件的结果集。典型问题:

第一类问题需要遍历所有解,有些特殊的情况无非是结果集中需要去重,这些都可以通过精细地选择”下一步的值“来做到。例如,在每一步中,可以对”这一步可选的值“做排序,相同的值只选一次,这样可以解决绝大多数”结果集去重“问题(例如Combination Sum2和Permutations2)

第二类问题与第一类问题有着根本不同。第二类问题可以在遍历一组组合的过程中,如果发现当前的组合已经不可能满足条件,则无需遍历完,即可在中途丢提当前的组合,直接跳到下一种组合。

考虑数独问题,首先,如果我们在构造一个组合的过程中,发现某个格子填入任何值都不可能满足条件,那么当前的组合无需再计算下去,必然是之前某些步出错。无需再计算当前还没有填充的其他格子的值,直接丢弃当前解,跳到上一步尝试其他值即可。

说道这里,其实还有一个最关键的问题没有细说,那就是如何选择下一步?

例如sudoku问题,最直观的做法是随机选择一个还是空白的格子,还能再优化吗?考虑这样的情况:假设当前空白的格子中,有一个格子有5种可能的值,有一个格子只有1个可能的值,那么应当先选择哪个格子?显然,选择只有1个可能值的格子更好。填充了这个格子,能够减少其他未填充格子的可选择值,也就降低了unmake_move的次数。

但是这样一定比随机选择更快吗?细心的读者能够发现,这样的选择方式,在每次选择下一步的时候,会花费相当的时间去查找”可选择值最少“的空白格子。每一步,我们对所有空白格子,计算它们的可选择值;计算可选择值的过程是查看当前行、当前列、当前9格。其实,这和随机选择一样,最后都会得到时间代价O(n^4)的算法。

3. 更多的优化

其实这里还有继续优化的空间,这里不详细展开,只说一下大概的思路。
  • 使用数组保留每个空白格子的可选值。每当有空白格被填入了数字,重新计算受影响的空白格的可选值(当前行、当前列、当前9格)
  • 不那么严格的选择。例如,我们可以只计算每个9格中的空白格的数量,从空白格最少的9格中,随机选出一个空白格,作为下一步要填充的格子。这是一种不那么严格的选择,好处是每个9格的空白格数量可以快速地计算出,同时保证了unmake_move的次数比随机选择要大大减少。

4. 暴力破解 VS 精细选择

每一步随机选择一个空白格,这其实就是一种暴力破解的方法,这里还有另一种暴力破解的方法,经过计算就会发现,其实两者的算法复杂度基本相当。前文提到的精细选择,如果精细选择的过程没有优化,算法的复杂度其实没有变化,有兴趣的同学可以自己证明和验证。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序说明: Matrix Sudoku Solver 解独矩阵是一款计算机模拟人工思路求解数独的程序。它能利用大部分的人工解法完成对简单、中等、困难、专家以及骨灰级的数独求解。玩家可以将需要求解的数独输入矩阵后,按照提示或结合逻辑求解,也可以按下自动按钮,让程序帮您完成剩下的求解工作,并在得到正确的求解数独后,自动将求解步骤复制到剪贴板,让您可以在其他文本编辑器中粘贴查看,十分的人性化。 功能介绍: 为方便用户使用,本程序分为菜单栏,主界面功能按钮,底部面板提示区。 菜单栏功能介绍: 》选项: 清空:将主界面数独矩阵清空,程序回到初始界面。 读取:读取已存盘的数独文件,并将其显示在主界面数独矩阵中。 保存:保存当前主界面数独,将所有值存档。 启用TTS语音提示:该选项可以开启语音提示功能,开启后程序将自动读出程序底部面板提示区的内容。开启前请确认系统中是否已安装TTS语音引擎。若没有安装,请到微软TTS官网下载语音引擎。 退出:退出程序 》解题: 锁定已知数:将已填入的初局数独锁定,准备求解。 显示候选数提示:开启后鼠标在某个格子悬停会出现该格可以填入的候选数。 提示值自动填入:开启后按下提示按钮会自动将提示值填入格内。 按步提示:锁定后可用,提示当前数独如何求解 自动解题:锁定后可用,自动求解当前数独。 强制求解:锁定后可用,可对无法用自动求解完成的数独进行强制求解,程序必会返回可解或不可解的结果。 重做:锁定后可用,将所有未锁定的值清空。 》主题: 本程序提供多种主题界面选择,让玩家在自己喜爱的主题中求解数独。除了程序自带的五种主题,用户还可以自定义背景,选择自己喜欢的图片作为程序背景。 》帮助: 数独简介:介绍数独由来,发展,规则介绍 程序说明:程序的介绍,程序功能的说明 解法介绍:介绍程序中使用及尚未加入的一些数独逻辑求解方法。 关于:程序信息,开发组信息 主界面功能按钮介绍: 提示:同菜单《解题》中的《按步提示》 锁定:同菜单《锁定》中的《锁定已知数》 保存:同菜单《选项》中的《保存》 读取:同菜单《选项》中的《读取》 重做:同菜单《解题》中的《重做》 清空:同菜单《选项》中的《清空》 自动:同菜单《解题》中的《自动解题》 提示候选数:同菜单《解题》中的《显示候选数提示》 底部面板提示区介绍: 程序求解数独的全部提示信息将全部显示在此处。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值