回溯:
递归调用代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启前的状态以便重新开始,就要使用回溯技巧
全排列的交换法 数独 部分和 用到了回溯
全排列:
public class 全排列二 {
public static void main(String[] args) {
ArrayList<String> res = f2("abcd");
for(String s:res) {
System.out.println(s);
}
}
public static ArrayList<String> f2(String s){
ArrayList<String> res = new ArrayList<>();
char[] arr = s.toCharArray();
Arrays.sort(arr);
getPermutationCore(arr,0,res);
return res;
}
private static void getPermutationCore(char[] arr, int k, ArrayList<String> list) {
if(k==arr.length) {
list.add(new String(arr));
}
for(int i=k;i<arr.length;i++) {
swap(arr,k,i);//把从K开始的每个字符都交换到K位置,
//arr[i]交换到k位置后 后面的字符变动位置也是一种排列 所以递归调用自身把从K+1开始后的每个字符换到K+1位置
getPermutationCore(arr,k+1,list);//递归调用下一个
swap(arr,k,i);//要换回来 保持初始状态一样 这个叫回溯
}
}
private static void swap(char[] arr, int k, int i) {
char temp = arr[k];
arr[k] = arr[i];
arr[i] = temp;
}
}
数独:
/*
你一定听说过“数独”游戏。
如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700
程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764
再例如,输入:
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
程序应该输出:
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452
*/
//数独游戏只有唯一解,每个位置都是填1-9那么就可以遍历1-9然后在行列和小9宫格都符合时就填进去然后递归下一个
//但是如果遇到走不通了就会返回 那么要记得回溯 重新填 说明这个填的虽然在现在看来可以,但是在后续填其他的位置就不行了
//但是总有一组解是可以的!
public class 数独游戏 {
public static void main(String[] args) {
//用char[][]来存输入的数字
Scanner sc = new Scanner(System.in);
char[][] arr = new char[9][];
//列没有声明会自动计算
for (int i = 0; i < 9; i++) {
arr[i] = sc.nextLine().toCharArray();
}
dfs(arr, 0, 0);
}
public static void dfs(char[][] arr,int x,int y) {
//因为9行9列,那么如果一直深搜到arr[8][8]都赋值了 那么下一次就是arr[9][0] 那么就表示找到那组解了该输出了
if(x==9) {
print(arr);
System.exit(0);//直接停止程序而不是返回
}
//为0那么就要填数字然后继续深搜下去
if(arr[x][y]=='0') {
for(int i=1;i<=9;i++) {
//检查在行列和小九宫格是否可以填这个数字
if(check(arr,x,y,i)) {
arr[x][y] = (char)('0'+i);//可以填就填上去 注意强制转换
//然后递归下去,到下一个位置
//要注意可能会换行是X+1,y又变成0 所以这里要推出一个公式来方便深搜下一个位置
dfs(arr,x+(y+1)/9,(y+1)%9);//下一状态
}
}
//如果递归下去某层出现错误无法继续深搜下去那么就会往上返回,那么上一层又有可能每个数字都不不能继续深搜下去,
//那么又要返回更上一层,那么在返回之前因为arr[x][y]赋值了 那么要重新设置为'0',
//然后等待找到一个真正合适的一组解一直深搜到基值条件输出
arr[x][y] = '0';//回溯
//如果不为0那么也是继续下一个位置
}else {
dfs(arr,x+(y+1)/9,(y+1)%9);
}
}
private static void print(char[][] arr) {
for(int i=0;i<9;i++) {
System.out.println(new String(arr[i]));
}
}
private static boolean check(char[][] arr, int x, int y, int i) {
for(int k=0;k<9;k++) {
//检查列
if(arr[k][y]==(char)('0'+i))
return false;
//检查行
if(arr[x][k]==(char)('0'+i))
return false;
}
//检查小九宫格
//因为9个小宫格都是固定的 每次要从最左上方开始
//所以不是从0就是3或者6,那么(x/3)*3就可以了
//y也一样
//边界就是0开始要小于3 3开始小于6 6开始小于9 那么就是(x/3+1)*3
for(int k=(x/3)*3;k<(x/3+1)*3;k++) {
for(int j=(y/3)*3;j<(y/3+1)*3;j++) {
// if(arr[k][j]==(char)('0'+i)); 多了个分号 相当于if语句后 是一句空语句 然后再执行return false所以不会输出东西
if(arr[k][j]==(char)('0'+i))
return false;
}
}
return true;
}
}