JAVAデータ構造07.再帰迷路問題8クイーン問題
1.再帰的な導入とソリューションのアイデア
再帰:簡単に言うと、再帰とは、メソッドがそれ自体を呼び出し、呼び出されるたびに異なる変数を渡すことを意味します。
再帰は、プログラマーが複雑な問題を解決するのに役立ち(毎回再帰によって解決される問題に注意を払うだけで済みます)、コードを簡潔にすることができます。
再帰呼び出しルール:
- プログラムがメソッドに対して実行されると、(スタックに格納されている)独立したスペースが開かれます。
- 各スペースのデータ(ローカル変数)は独立しています。
- 再帰は、再帰を終了する条件に近づく必要があります。そうでない場合、無限の再帰になり、スタックオーバーフローエラーが発生します。
- メソッドが実行されるか、リターンが検出されると、メソッドが呼び出された場所に戻り、次のプログラムの実行を続行します。
再帰的な問題解決のアイデア:3つの側面に焦点を当てる
- 再帰全体の終了条件を見つけます:再帰はいつ終了する必要がありますか?
- 戻り値を見つける:どのような情報を上位レベルの再帰に返す必要がありますか?
- 単一の再帰タスクを見つけます:この再帰は何をすべきですか?
2.再帰的な場合:
2.1迷路のバックトラックの問題
シナリオ:迷路から逃れる方法を探して、迷路の壁と出口を設定します
説明:
/**
* 说明:
* 1. map 表示地图
* 2. i,j 表示从地图的哪个位置开始出发,起始位置(1,1)
* 3. 如果小球能到map[6][5]位置,则说明通路找到
* 4. 当map[i][j] 为0 表示当前位置还没有走过;为1 表示墙;为2 表示通路可以走并且已经走过;为3 表示该点走过并且上下左右四个位置都走不通
* 5. 策略: 方向判断顺序: 下->右->上->左, 如果该点四个位置都走不通,则回溯到上一个点(递归)
*/
思考分析:
- 再帰全体の終了条件を見つけます。ボールは出口位置に到達します[6] [5] ||再帰的に最初のポイントまでさかのぼり、どこにも見つかりません
- 戻り値を探して、どのような情報を上位層に返す必要がありますか?i、jがブール値を通過できるかどうかのポイントに戻ります
- 単一の再帰タスクを見つける:4つの方向を戦略の順序で渡すことができるかどうかを判断する必要があります。falseではなくtrueを返すことができます
コードの実装:
public class MiGong {
public static void main(String args[]){
int[][] map = new int[8][7];
//初始化迷宫
setMap(map);
showMap(map);
//寻找通路
setWay(map,1,1);
showMap(map);
}
public static void setMap(int[][] map){
//上下两行
for (int i = 0; i <map[i].length ; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//左右两列
for (int i = 0; i <map.length ; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//挡板
map[3][1] = 1;
map[3][2] = 1;
}
public static void showMap(int[][] map){
System.out.println("此时的地图情况:");
for (int[] i:map) {
for (int data: i) {
System.out.print(data+" ");
}
System.out.println();
}
}
/**
* 说明:
* 1. map 表示地图
* 2. i,j 表示从地图的哪个位置开始出发,起始位置(1,1)
* 3. 如果小球能到map[6][5]位置,则说明通路找到
* 4. 当map[i][j] 为0 表示当前位置还没有走过;为1 表示墙;为2 表示通路可以走并且已经走过;为3 表示该点走过并且上下左右四个位置都走不通
* 5. 策略: 方向判断顺序: 下->右->上->左, 如果该点四个位置都走不通,则回溯到上一个点(递归)
* @param map
* @param i
* @param j
*/
/**
* 递归解决问题的思路:
* 1. 找到整个递归的终止条件: 小球到达出口位置[6][5] || 递归回溯到初始点发现走不通
* 2. 找返回值,应该给上一层返回什么信息? 返回该点i,j 是否能走通 boolean
* 3. 找到单次递归的任务: 需要按照策略顺序判断四个方向是否可以走通,可以返回true,不可以返回false
*/
public static boolean setWay(int[][] map ,int i,int j){
if(map[6][5] == 2){
//通路已经找到,递归的终止条件
return true;
}else{
if(map[i][j] == 0){
//表示该点还没有走过
map[i][j] = 2; //假定该点是可以走通的,如果无法走通,则回溯时将值改为3
if(setWay(map,i+1,j)){
//向下走
return true;
}else if(setWay(map,i,j+1)){
//向右走 //elseif 表示 向上走失败后 再向右走
return true;
}else if(setWay(map,i-1,j)){
//向上走
return true;
}else if(setWay(map,i,j-1)){
//向左走
return true;
}else{
map[i][j] = 3;
return false; //四个方向都走不通,就判断走不通 将该点值变为3
}
}else{
// map = 1 2 3 时均不走
return false;
}
}
}
}
結果:
此时的地图情况:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
此时的地图情况:
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
2.2エイトクイーンズ問題
シナリオ:8つのクイーンを8 * 8のチェスボードに配置して、互いに攻撃できないようにします。つまり、2つのクイーンを同じ行、同じ列、または同じ対角線に配置することはできません。配置の種類はいくつあるかを尋ねます。
思考分析:
- 最初の女王は最初の行の最初の列に配置されます。
- 2番目の女王を2番目の行の最初の列に入れて、問題がないかどうかを判断します。ルールを満たしていない場合は、引き続き2番目と3番目の列に入れます...すべての列を順番に並べて、適切なものを見つけます
- 8番目のクイーンを競合しない位置に配置できるようになり、正しい解決策が見つかるまで、3番目のクイーンを続行します。
- 8つの位置すべてを試しても正しい解決策がない場合は、前のピースが間違っているため、遡及的に変更する必要があります;(バックトラックフォーカス)
- 正しい解が得られたら、前のスタックに戻ってバックトラックを開始します。つまり、最初のクイーンの最初の行と最初の列に配置されたすべての正しい解が得られます。
- 1 2 3 4 5ステップの終了後、クイーンが最初の行と最初の列に配置したすべてのソリューションを取得し、最初のクイーンを最初の行と2番目の列に配置し、12345ステップをループしてすべてのソリューションを取得します。
説明:
- 得られた結果は、次のような1次元配列で表されます。arr= [0,4,7,5,2,6,1,3];対応するarr添え字は行を表し、arrの値は列を表します。
コードの実装:
注:チェスピースnが8つの位置すべてを試しても正しい解決策がない場合は、前のチェスピースn-1に戻り、1つ戻し(array [n-1] = i;)、次にトラバースしてnを決定します(最初の列から開始)
public class Queue8 {
//定义皇后的个数
int max = 8;
//定义数组array,保存皇后防止位置的结果,比如arr=[0,4,7,5,2,6,1,3];
int[] array = new int[max];
static int count = 0;
public static void main(String args[]){
Queue8 queue8 = new Queue8();
queue8.check(0);
System.out.println("共找到多少种解法:"+count);
}
/**
* 放置第n个皇后
* 递归解决问题的思路:
* 1. 找到整个递归的终止条件: 当防止第8个皇后结束以后,即n=8时
* 2. 找到单次递归的任务: 一次递归需要将这个皇后应该放的第n行的 8个列从左到右遍历一遍,如果找到不与前n个皇后冲突的格子,则放入皇后,接下来继续防止n+1个皇后
*
* 注意: 如果一个棋子n将8个位置全部尝试后仍然没有正确解,则回回溯到前一个棋子n-1,将其往后移动一位(array[n-1] = i;),再遍历判断n(从第一列开始)
* @param n
*/
public void check(int n){
if(n == 8){
showArray();
return;
}
//依次放入皇后,并判断是否冲突
for (int i = 0; i <max ; i++) {
//先把当前这个皇后放在第n行的第一列
array[n] = i;
if(judge(n)){
//不冲突,就防止下一个皇后,直到第八个放完
check(n+1);
}
//如果冲突了,继续遍历i++, array[n] = i++;
}
}
/**
* 需要一个判断棋子是否冲突的方法
* 判断参数n皇后是否与已经放置的前n-1个皇后冲突
* 冲突规则:
* 1.同一列:array[i] == array[n]
* 2.同一斜线: Math.abs(n-i) == Math.abs(array[n] - array[i]) 两个棋子的行距离等于列距离
* @param n
* @return
*/
public boolean judge(int n){
for (int i = 0; i <n ; i++) {
if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
return false;
}
}
return true;
}
//打印一次结果
public void showArray(){
for (int i = 0; i <8 ; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
count++;
}
}
結果:
0 4 7 5 2 6 1 3
0 5 7 2 6 3 1 4
0 6 3 5 7 1 4 2
0 6 4 7 1 3 5 2
1 3 5 7 2 0 6 4
1 4 6 0 2 7 5 3
1 4 6 3 0 7 5 2
1 5 0 6 3 7 2 4
1 5 7 2 0 3 6 4
1 6 2 5 7 4 0 3
1 6 4 7 0 3 5 2
1 7 5 0 2 4 6 3
2 0 6 4 7 1 3 5
2 4 1 7 0 6 3 5
2 4 1 7 5 3 6 0
2 4 6 0 3 1 7 5
2 4 7 3 0 6 1 5
2 5 1 4 7 0 6 3
2 5 1 6 0 3 7 4
2 5 1 6 4 0 7 3
2 5 3 0 7 4 6 1
2 5 3 1 7 4 6 0
2 5 7 0 3 6 4 1
2 5 7 0 4 6 1 3
2 5 7 1 3 0 6 4
2 6 1 7 4 0 3 5
2 6 1 7 5 3 0 4
2 7 3 6 0 5 1 4
3 0 4 7 1 6 2 5
3 0 4 7 5 2 6 1
3 1 4 7 5 0 2 6
3 1 6 2 5 7 0 4
3 1 6 2 5 7 4 0
3 1 6 4 0 7 5 2
3 1 7 4 6 0 2 5
3 1 7 5 0 2 4 6
3 5 0 4 1 7 2 6
3 5 7 1 6 0 2 4
3 5 7 2 0 6 4 1
3 6 0 7 4 1 5 2
3 6 2 7 1 4 0 5
3 6 4 1 5 0 2 7
3 6 4 2 0 5 7 1
3 7 0 2 5 1 6 4
3 7 0 4 6 1 5 2
3 7 4 2 0 6 1 5
4 0 3 5 7 1 6 2
4 0 7 3 1 6 2 5
4 0 7 5 2 6 1 3
4 1 3 5 7 2 0 6
4 1 3 6 2 7 5 0
4 1 5 0 6 3 7 2
4 1 7 0 3 6 2 5
4 2 0 5 7 1 3 6
4 2 0 6 1 7 5 3
4 2 7 3 6 0 5 1
4 6 0 2 7 5 3 1
4 6 0 3 1 7 5 2
4 6 1 3 7 0 2 5
4 6 1 5 2 0 3 7
4 6 1 5 2 0 7 3
4 6 3 0 2 7 5 1
4 7 3 0 2 5 1 6
4 7 3 0 6 1 5 2
5 0 4 1 7 2 6 3
5 1 6 0 2 4 7 3
5 1 6 0 3 7 4 2
5 2 0 6 4 7 1 3
5 2 0 7 3 1 6 4
5 2 0 7 4 1 3 6
5 2 4 6 0 3 1 7
5 2 4 7 0 3 1 6
5 2 6 1 3 7 0 4
5 2 6 1 7 4 0 3
5 2 6 3 0 7 1 4
5 3 0 4 7 1 6 2
5 3 1 7 4 6 0 2
5 3 6 0 2 4 1 7
5 3 6 0 7 1 4 2
5 7 1 3 0 6 4 2
6 0 2 7 5 3 1 4
6 1 3 0 7 4 2 5
6 1 5 2 0 3 7 4
6 2 0 5 7 4 1 3
6 2 7 1 4 0 5 3
6 3 1 4 7 0 2 5
6 3 1 7 5 0 2 4
6 4 2 0 5 7 1 3
7 1 3 0 6 4 2 5
7 1 4 2 0 6 3 5
7 2 0 5 1 4 6 3
7 3 0 2 5 1 6 4
共找到多少种解法:92