什么是回溯法?
回溯来源于基本的枚举:
void print(序列A, 集合S) { if (S为空) 输出序列A else 按照“从小到大的顺序”依次考虑S中的每个元素 { print(A的新序列, S-{v}); //递归 } }
其中,测试元素通过一个for循环加入,递归通过cur下标指示当前位置。很明显,这是一个对解答树的先序遍历过程,叶节点就是问题的解。
但是,当问题规模扩大时枚举量指数级的扩大。问题在于枚举将解的判断置于叶节点,而忽视了搜索路径中的判断。因此回溯就应运而生,也就是在测试节点加入时就测试合理性,(合理) ? 递归:剪枝。对于不同的问题,递归框架明确,最重要的是确定判决条件。
八皇后问题:皇后不同行、同列、同对角线。对角线相当于y=x+c和y=-x+c这两组平行线。程序:
#include <stdio.h> int maze[8]; //保存各行选择的列 int cnt = 0; //解的个数 void dfs(int row) { if (row == 8) { //递归结束条件 cnt++; for (int i = 0; i < 8; i++) printf("%d ", maze[i]); printf("\n"); } else { for (int i = 0; i < 8; i++) {//遍历cur行上的所有可能解 int ok = 1; for (int j = 0; j < row; j++) {//判断与先前的节点时候冲突 if (maze[j] == i || (j - maze[j]) == (row - i) || (j + maze[j]) == (row + i)) {//同列 同对角线 ok = 0; break; } } if (ok) {//不冲突则加入,继续执行递归 maze[row] = i; dfs(row + 1); }//否则剪枝,测试下一个可能解 } } } int main() { dfs(0); printf("cnt = %d\n", cnt); return 0; }
最后共有92个解。但是事实上只有12个独立解,如何进行进一步优化是更深入的问题。本文旨在学习回溯的基本框架