从n皇后到理解深度优先搜索

先贴一下题目(来自LeetCode第51题)



因为要求解所有的n皇后放置问题,所以这道题很适合使用dfs,下面先介绍一下dfs:

深度优先搜索(dfs)从它的名字上就很容易理解,它在搜索时对每一个节点都做同一件事情:对其某一分支不断的深入,直到不能再深入为止(到达叶节点),然后退回(回溯)到上一节点(父节点),继续其它的搜索。没错,这看上去就是一个递归过程,那时间复杂度岂不是很高?实则不然,我们知道递归时可以用记忆化搜索法实现动态规划,那么dfs同样可以用减枝(下面介绍)来减少时间复杂度,当然还有A*算法什么的,大家可以百度了解下。

对于dfs最重要的两点判断边界条件回溯,这里我写了一个伪代码作为dfs的结构框架:

	void dfs(int n) {
		if(边界条件) {
			数据操作代码;
			return;
		}
		for(n的下一节点i) {
			if(未被访问) {
				标记
				dfs(i);
				回溯
			}
		}

当然格式根据题意会有很大的变换,如需要返回值则要修改void,如有多个参数则需要修改入参,如并不需要单独判断边界则去掉前一个if语句,如子节点明确(二叉树时)则去掉for循环,总之要根据实际情况,不能死板套用。

好了,讲了这么多,现在让我们回到题目上来,为方便理解我画了个草图,如下:


这是一个4皇后的示意图,可以看到按照dfs的方法不断搜索最终有256(n^n)种结果,但是我们注意到这里面明显有错误的分支:


通过题意我们可以很容易的去掉两个错误的分支,在接下来的搜索中同样也可以把错误分支去掉,而不是等到最后再来判断是否满足题意,这样就大大减少了计算量,这种方法就称作剪枝(其实就是伪代码中第二个if语句的作用)。

好了,理论搞明白了我们就开始入手做题了:

首先我们来看看如何判断错误分支,根据题意一个皇后位置确定之后,它的行、列、正切线、反切线这四条线上是不能放置皇后的。我们可以用一层for来控制列其他几个条件做成布尔数组,放入if中去判断,当列的索引等于n时,说明到达叶节点,因为已经进行过筛选,此时直接输出即可,代码如下:

import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author windddy
 * 
 */

public class LC_51_N皇后 {
	List<List<String>> lists;
	public List<List<String>> solveNQueens(int n) {
		lists = new ArrayList<List<String>>();
		//row存储行是否被占用信息
		boolean[] row = new boolean[n];
		//tan存储正切线是否被占用信息
		boolean[] tan = new boolean[2 * n - 1];
		//cot存储反切线是否被占用信息
		boolean[] cot = new boolean[2 * n - 1];
		//q存储皇后位置信息
		int[] q = new int[n];
		//深度优先遍历
		dfs(n, 0, row, tan, cot,q);	
		return lists;
	}
	/**
	 * 
	 * @param n	皇后和棋盘大小
	 * @param col	当前遍历的列
	 * @param row	当前已被占用的行,false:未被占用;true:已被占用
	 * @param tan	当前已被正切的行,false:未被占用;true:已被占用
	 * @param cot	当前已被反切的行,false:未被占用;true:已被占用
	 * @param q	当前已记录的皇后位置,
	 */
	private void dfs(int n, int col, boolean[] row, boolean[] tan, boolean[] cot,int[] q) {
		//判断是否已记录到最后一列
		if(col==n) {
			List<String> list = new ArrayList<String>();
			String s;
			//将记录值加入list
			for (int i = 0; i < n; i++) {
				s = "";
				for (int j = 0; j < n; j++) {
					if(j==q[i]) {
						s +="Q";
					}else {
						s +=".";
					}
				}
				list.add(s);
			}
			lists.add(list);
		}
		//遍历当前列的每一个位置
		for (int i = 0; i < n; i++) {
			//如果该位置已经被标记,就不进入(减枝)
			if (!row[i] && !tan[col + i] && !cot[col - i + n - 1]) {
				//未被标记进如,记录皇后位置,并标记
				q[col] = i;
				row[i]= true;
				tan[col + i] =true;
				cot[col - i + n - 1] = true;
				//继续深入
				dfs(n,col+1,row,tan,cot,q);
				//回溯
				row[i]= false;
				tan[col + i] =false;
				cot[col - i + n - 1] = false;
			}
		}
	}
}

好了,我们OJ一下看看:


只击败了77%,应该还有更好的代码,笔者在这只是抛砖引玉,如有不足之处,恳请大家指正,另外大家也自己去练练吧。

欢迎转载,请注明出处。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值