状态空间图
用于呈现问题求解的过程。其中问题被划分为多个状态,图中每个节点代表一种可能的状态,节点之间的连线则代表状态转换的操作,从起始状态到目标状态的路径,就是问题的解。
以假币问题为例,6枚硬币,其中存在1枚假币。如何用秤测量3次,识别出假币?
下面的状态空间图展示了问题求解的过程。
其中,节点代表问题的状态。以带*的节点为例,[C1 C2 C3 C4] 代表在这4枚硬币中存在假币。C1C2 : C5C6,表示将C1C2放左侧,C5C6放右侧,称量两侧重量是否相等。
节点之间的连线,代表了导致状态转换的操作,这里是称量结果相等,或不相等。
回溯法
回溯法将问题求解划分为多个状态,状态之间的转换需要符合问题的约束条件。回溯法首先尝试不断深入探索状态空间图,当第n步没有符合约束的合法转换时,回溯法退回第n-1步,并撤销第n-1步的操作,重新选择其他合法的状态,若第n-1步仍然没有其他合法状态转换,则继续退回第n-2步,以此类推。
回溯法求解n皇后问题
在n皇后问题中,以回溯法求解步骤为:
确定约束条件:
棋盘上任意两个子,不能在同一行,同一列,同一对角线
求解步骤:
1.第n步,确定第n行的落子,即每一步从1~n列选择1个合法的列m,其中m的取值必须符合约束条件,并且约定,优先选择序号小的列。
2.当第n步,没有合法的列可选,则退回n-1步,撤销n-1步的选择,在第n-1步中尝试下一个合法的列。
Java代码:
package cn.lin.test;
import java.util.Arrays;
/**
* 八皇后问题
* 回溯:
* 当状态不合法,回溯到上一步,重新选择
* 约束条件:
* 不能在同一行、同一列、对角线
* @author ljf
*
*/
public class NQuenesSolver {
// n皇后
private final int n;
/**
* 路径:数组下标对于行,数组元素值对应列
*/
private int[] path;
// 当前探索到第几行
private int latestRow;
public NQuenesSolver(int n) {
this.n = n;
path = new int[n];
//初始-1,表示每一行都没放皇后
Arrays.fill(path, -1);
latestRow = 0;
}
/**
*
* @return 每1行中,棋子所在列
*/
public int[] search() {
while (true) {
//无条件向前走
moveForward();
//状态合法吗?
if (!checkState()) {
//还可以向前走吗?
if (hasChoice()) {
continue;
} else {
//回溯
reset();
continue;
}
}
System.out.println("lastestRow:" + latestRow + ", path:" + Arrays.toString(path));
if (isSolved()) {
return path;
}
//下一行
latestRow++;
}
}
boolean hasChoice() {
return path[latestRow] < n - 1;
}
private void moveForward() {
path[latestRow]++;
}
//先会判断状态是否合法,如果有n个合法棋子,说明问题已解
private boolean isSolved() {
if (latestRow == n - 1) {
return true;
}
return false;
}
/**
* 回溯
*/
private void reset() {
while (true) {
path[latestRow] = -1;
latestRow--;
if (hasChoice()) {
break;
}
}
}
private boolean checkState() {
if (latestRow == 0) {
return true;
}
//只确定最后一个皇后是否合法
for (int i = 0; i < latestRow; i++) {
if (!isLegal(i, path[i], latestRow, path[latestRow])) {
return false;
}
}
return true;
}
private boolean isLegal(int x1, int y1, int x2, int y2) {
//不在同一行或同一列
if (x1 == x2 || y1 == y2) {
return false;
}
//计算斜率,对角线上,则斜率为1或-1
double slash = (y2 - y1) * 1.0 / (x2 - x1);
//不在对角线
if (slash == -1 || slash == 1) {
return false;
}
return true;
}
public static void main(String[] args) {
NQuenesSolver eightQuenes = new NQuenesSolver(8);
System.out.println(Arrays.toString(eightQuenes.search()));
}
}
输出: