回溯:
递归调用代表开启一个分支, 如果希望这个分支返回后某些数据恢复到分支开启前的状态以便重新开始, 就要使用到回溯技巧, 全排列的交换法, 数独, 部分和, 用到了回溯. 下一个状态在开始之前需要利用到之前的状态, 此时需要进行回溯, 因为之前的状态对现在的状态存在着影响.
剪枝:
深度优先搜索的时候如果已经明确从当前状态无论如何转移都不会存在 (更优) 解, 就应该终止往下的继续搜索, 这种方法叫做剪枝. 数独和部分和里面有剪枝: 属于隐性的剪枝, 当尝试去填一个数字的时候发现某些数字不行那么会把这个这条支路给剪断. 当使用 if 条件的时候把限定条件加进去然后在 if 里面使用调用 dfs, 这也是剪枝的一种. 高级的剪枝在调用 dfs 之前对情况进行预判来进行提前剪枝.
题目: n 皇后问题
请设计一种算法, 解决著名的 n 皇后问题. 这里的 n 皇后问题指在一个 n*n 的棋盘上放置 n 个棋子, 使得每行每列和每条对角线上都只有一个棋子, 求其摆放的方法数. 给定一个 int n, 请返回方法数, 保证 n 小于等于 15.
注意: 因为题目中只要求求出摆放的方法数, 而不需要求解出其中的过程那么我们就不需要进行回溯, 如果需要给出摆放的过程那么就需要进行回溯. 此外这个题目不需要进行回溯的另外一个原因是回退到平行状态的时候我们不需要考虑同行的棋子对现在退回来的状态的影响, 因为位于 for 循环中我们进入平行状态的同一行的下一列考虑这个位置上是否可以摆放, 所以我们不用进行回溯只需要记录其中的摆放的方法数即可.
代码:
public class N 皇后问题 {
- static int n;
- static int cnt;
- static int[] rec; // 下标代表行, 值代表列
- public static void main(String[] args) {
- n = 4;
- rec = new int[4];
- dfs(0);
- System.out.println(cnt);
- }
- /**
- * @param row 当前正在处理的行
- */
- private static void dfs(int row) {
- if (row == n) {
- cnt++;
- return;
- }
- // 关于检查这个点能不能放的问题 想法就是将将所有不能放的地方的值存为 - 1, 发现不太行.
- // 依次尝试在某列上放一个皇后
- for (int col = 0; col < n; col++) {
- boolean ok = true;
- // 检验这个皇后是否和之前已经放置的皇后有冲突
- for (int i = 0; i < row; i++) {
- // 正对角线 x-y 相同, 副对角线 x+y 相同
- if (rec[i] == col || i + rec[i] == row + col || rec[i] - i == col - row) {
- ok = false;
- break;
- }
- }
- /* ======= 这里可以认为是剪枝 ======= */
- // 这一行的这一列可以放
- if (ok) {
- rec[row] = col; // 标记
- dfs(row + 1); // 继续找下一行
- // rec[row]=0; // 恢复原状, 这种解法这里是否恢复状态都行
- }
- }
- }
- }
结果:
来源: http://www.bubuko.com/infodetail-2942217.html