八皇后问题回溯求解
2015-02-28 08:55
369 查看
八皇后问题
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
回溯算法分析
(1)首先有8个皇后,按8列次放,第一次放第0列,第二次放第1列...第八次把最后一个皇后放在第7列。
(2)在步骤(1)的每次方法中,先从该列的行首位开始检查,如果不能放置,接着检查该列第二个位置,依次检查下去,直到在该列找到一个可以放置一个皇后的位置,然后保存当前位置坐标,转到下一列重复上述方法。
(3)如果检查了该列所有的位置均不能放置一个皇后,说明上一列皇后放置的位置无法让所有的皇后都找到自己合适的位置,因此就要回溯到前一列,回溯后把该列皇后的列坐标+1(j = b[executeNum--] + 1)。
(4)如何检查两个皇后的位置冲突,需要满足两点的X坐标不相等,Y坐标不相等,即不再同一行也不在同一列。两个坐标在同一条斜线上的判断方法:x+y==a+b || x-y==a-b
(5)当第八次能成功放置时,打印8个皇后的坐标。然后回溯到上一列继续寻找,同步骤(3)
回溯算法图示
当在第五列上放置失败时,回溯到第四列,让其b坐标在原坐标上加1之后的位置继续尝试。 如下图所示,在第4列第二次找到(4,7)后,发现第五列还是放置失败,这时第四列所有位置都已经尝试完,继续回溯到第三列。这时第三列可以放置的位置是(3,6) |
按上述方法,第一次找到一组放下八皇后是如下位置: 1:(0, 0)(1, 4)(2, 7)(3, 5)(4, 2)(5, 6)(6, 1)(7, 3) |
求解结果92种
实现代码:
public class EightQueen { public static void main(String [] args) { int[] a = new int[8]; int[] b = new int[8]; int count = 0; int j = 0; for (int executeNum=0; executeNum<8; ) { if(setLocation(a, b, executeNum, j)) { j=0; if (executeNum==7) { count++; System.out.print(count + ":"); showEightQueensLocation(a, b); executeNum = 6; j = b[6] + 1; } else { executeNum++; } } else { executeNum--; if (executeNum<0) {break;} j = b[executeNum] + 1; } } } public static boolean setLocation (int[] a, int[] b, int executeNum, int startY) { int j = startY; for (; j<8; j++) { boolean flag = true; for (int i=0; i<executeNum; i++) { if (!isAllowedToPut(a[i], b[i], executeNum, j)) { flag = false; break; } } if (flag) { a[executeNum] = executeNum; b[executeNum] = j; break; } } return (j<8); } public static boolean isAllowedToPut (int a, int b, int x, int y) { return !(x==a || y==b || x+y==a+b || x-y==a-b); } public static void showEightQueensLocation(int[] a, int[] b) { for (int i=0; i<8; i++) { System.out.print("(" + a[i] +", " + b[i] + (i==7 ? ")\n" : ")")); } } }
输出所有结果:
1:(0, 0)(1, 4)(2, 7)(3, 5)(4, 2)(5, 6)(6, 1)(7, 3) 2:(0, 0)(1, 5)(2, 7)(3, 2)(4, 6)(5, 3)(6, 1)(7, 4) 3:(0, 0)(1, 6)(2, 3)(3, 5)(4, 7)(5, 1)(6, 4)(7, 2) 4:(0, 0)(1, 6)(2, 4)(3, 7)(4, 1)(5, 3)(6, 5)(7, 2) 5:(0, 1)(1, 3)(2, 5)(3, 7)(4, 2)(5, 0)(6, 6)(7, 4) 6:(0, 1)(1, 4)(2, 6)(3, 0)(4, 2)(5, 7)(6, 5)(7, 3) 7:(0, 1)(1, 4)(2, 6)(3, 3)(4, 0)(5, 7)(6, 5)(7, 2) 8:(0, 1)(1, 5)(2, 0)(3, 6)(4, 3)(5, 7)(6, 2)(7, 4) 9:(0, 1)(1, 5)(2, 7)(3, 2)(4, 0)(5, 3)(6, 6)(7, 4) 10:(0, 1)(1, 6)(2, 2)(3, 5)(4, 7)(5, 4)(6, 0)(7, 3) 11:(0, 1)(1, 6)(2, 4)(3, 7)(4, 0)(5, 3)(6, 5)(7, 2) 12:(0, 1)(1, 7)(2, 5)(3, 0)(4, 2)(5, 4)(6, 6)(7, 3) 13:(0, 2)(1, 0)(2, 6)(3, 4)(4, 7)(5, 1)(6, 3)(7, 5) 14:(0, 2)(1, 4)(2, 1)(3, 7)(4, 0)(5, 6)(6, 3)(7, 5) 15:(0, 2)(1, 4)(2, 1)(3, 7)(4, 5)(5, 3)(6, 6)(7, 0) 16:(0, 2)(1, 4)(2, 6)(3, 0)(4, 3)(5, 1)(6, 7)(7, 5) 17:(0, 2)(1, 4)(2, 7)(3, 3)(4, 0)(5, 6)(6, 1)(7, 5) 18:(0, 2)(1, 5)(2, 1)(3, 4)(4, 7)(5, 0)(6, 6)(7, 3) 19:(0, 2)(1, 5)(2, 1)(3, 6)(4, 0)(5, 3)(6, 7)(7, 4) 20:(0, 2)(1, 5)(2, 1)(3, 6)(4, 4)(5, 0)(6, 7)(7, 3) 21:(0, 2)(1, 5)(2, 3)(3, 0)(4, 7)(5, 4)(6, 6)(7, 1) 22:(0, 2)(1, 5)(2, 3)(3, 1)(4, 7)(5, 4)(6, 6)(7, 0) 23:(0, 2)(1, 5)(2, 7)(3, 0)(4, 3)(5, 6)(6, 4)(7, 1) 24:(0, 2)(1, 5)(2, 7)(3, 0)(4, 4)(5, 6)(6, 1)(7, 3) 25:(0, 2)(1, 5)(2, 7)(3, 1)(4, 3)(5, 0)(6, 6)(7, 4) 26:(0, 2)(1, 6)(2, 1)(3, 7)(4, 4)(5, 0)(6, 3)(7, 5) 27:(0, 2)(1, 6)(2, 1)(3, 7)(4, 5)(5, 3)(6, 0)(7, 4) 28:(0, 2)(1, 7)(2, 3)(3, 6)(4, 0)(5, 5)(6, 1)(7, 4) 29:(0, 3)(1, 0)(2, 4)(3, 7)(4, 1)(5, 6)(6, 2)(7, 5) 30:(0, 3)(1, 0)(2, 4)(3, 7)(4, 5)(5, 2)(6, 6)(7, 1) 31:(0, 3)(1, 1)(2, 4)(3, 7)(4, 5)(5, 0)(6, 2)(7, 6) 32:(0, 3)(1, 1)(2, 6)(3, 2)(4, 5)(5, 7)(6, 0)(7, 4) 33:(0, 3)(1, 1)(2, 6)(3, 2)(4, 5)(5, 7)(6, 4)(7, 0) 34:(0, 3)(1, 1)(2, 6)(3, 4)(4, 0)(5, 7)(6, 5)(7, 2) 35:(0, 3)(1, 1)(2, 7)(3, 4)(4, 6)(5, 0)(6, 2)(7, 5) 36:(0, 3)(1, 1)(2, 7)(3, 5)(4, 0)(5, 2)(6, 4)(7, 6) 37:(0, 3)(1, 5)(2, 0)(3, 4)(4, 1)(5, 7)(6, 2)(7, 6) 38:(0, 3)(1, 5)(2, 7)(3, 1)(4, 6)(5, 0)(6, 2)(7, 4) 39:(0, 3)(1, 5)(2, 7)(3, 2)(4, 0)(5, 6)(6, 4)(7, 1) 40:(0, 3)(1, 6)(2, 0)(3, 7)(4, 4)(5, 1)(6, 5)(7, 2) 41:(0, 3)(1, 6)(2, 2)(3, 7)(4, 1)(5, 4)(6, 0)(7, 5) 42:(0, 3)(1, 6)(2, 4)(3, 1)(4, 5)(5, 0)(6, 2)(7, 7) 43:(0, 3)(1, 6)(2, 4)(3, 2)(4, 0)(5, 5)(6, 7)(7, 1) 44:(0, 3)(1, 7)(2, 0)(3, 2)(4, 5)(5, 1)(6, 6)(7, 4) 45:(0, 3)(1, 7)(2, 0)(3, 4)(4, 6)(5, 1)(6, 5)(7, 2) 46:(0, 3)(1, 7)(2, 4)(3, 2)(4, 0)(5, 6)(6, 1)(7, 5) 47:(0, 4)(1, 0)(2, 3)(3, 5)(4, 7)(5, 1)(6, 6)(7, 2) 48:(0, 4)(1, 0)(2, 7)(3, 3)(4, 1)(5, 6)(6, 2)(7, 5) 49:(0, 4)(1, 0)(2, 7)(3, 5)(4, 2)(5, 6)(6, 1)(7, 3) 50:(0, 4)(1, 1)(2, 3)(3, 5)(4, 7)(5, 2)(6, 0)(7, 6) 51:(0, 4)(1, 1)(2, 3)(3, 6)(4, 2)(5, 7)(6, 5)(7, 0) 52:(0, 4)(1, 1)(2, 5)(3, 0)(4, 6)(5, 3)(6, 7)(7, 2) 53:(0, 4)(1, 1)(2, 7)(3, 0)(4, 3)(5, 6)(6, 2)(7, 5) 54:(0, 4)(1, 2)(2, 0)(3, 5)(4, 7)(5, 1)(6, 3)(7, 6) 55:(0, 4)(1, 2)(2, 0)(3, 6)(4, 1)(5, 7)(6, 5)(7, 3) 56:(0, 4)(1, 2)(2, 7)(3, 3)(4, 6)(5, 0)(6, 5)(7, 1) 57:(0, 4)(1, 6)(2, 0)(3, 2)(4, 7)(5, 5)(6, 3)(7, 1) 58:(0, 4)(1, 6)(2, 0)(3, 3)(4, 1)(5, 7)(6, 5)(7, 2) 59:(0, 4)(1, 6)(2, 1)(3, 3)(4, 7)(5, 0)(6, 2)(7, 5) 60:(0, 4)(1, 6)(2, 1)(3, 5)(4, 2)(5, 0)(6, 3)(7, 7) 61:(0, 4)(1, 6)(2, 1)(3, 5)(4, 2)(5, 0)(6, 7)(7, 3) 62:(0, 4)(1, 6)(2, 3)(3, 0)(4, 2)(5, 7)(6, 5)(7, 1) 63:(0, 4)(1, 7)(2, 3)(3, 0)(4, 2)(5, 5)(6, 1)(7, 6) 64:(0, 4)(1, 7)(2, 3)(3, 0)(4, 6)(5, 1)(6, 5)(7, 2) 65:(0, 5)(1, 0)(2, 4)(3, 1)(4, 7)(5, 2)(6, 6)(7, 3) 66:(0, 5)(1, 1)(2, 6)(3, 0)(4, 2)(5, 4)(6, 7)(7, 3) 67:(0, 5)(1, 1)(2, 6)(3, 0)(4, 3)(5, 7)(6, 4)(7, 2) 68:(0, 5)(1, 2)(2, 0)(3, 6)(4, 4)(5, 7)(6, 1)(7, 3) 69:(0, 5)(1, 2)(2, 0)(3, 7)(4, 3)(5, 1)(6, 6)(7, 4) 70:(0, 5)(1, 2)(2, 0)(3, 7)(4, 4)(5, 1)(6, 3)(7, 6) 71:(0, 5)(1, 2)(2, 4)(3, 6)(4, 0)(5, 3)(6, 1)(7, 7) 72:(0, 5)(1, 2)(2, 4)(3, 7)(4, 0)(5, 3)(6, 1)(7, 6) 73:(0, 5)(1, 2)(2, 6)(3, 1)(4, 3)(5, 7)(6, 0)(7, 4) 74:(0, 5)(1, 2)(2, 6)(3, 1)(4, 7)(5, 4)(6, 0)(7, 3) 75:(0, 5)(1, 2)(2, 6)(3, 3)(4, 0)(5, 7)(6, 1)(7, 4) 76:(0, 5)(1, 3)(2, 0)(3, 4)(4, 7)(5, 1)(6, 6)(7, 2) 77:(0, 5)(1, 3)(2, 1)(3, 7)(4, 4)(5, 6)(6, 0)(7, 2) 78:(0, 5)(1, 3)(2, 6)(3, 0)(4, 2)(5, 4)(6, 1)(7, 7) 79:(0, 5)(1, 3)(2, 6)(3, 0)(4, 7)(5, 1)(6, 4)(7, 2) 80:(0, 5)(1, 7)(2, 1)(3, 3)(4, 0)(5, 6)(6, 4)(7, 2) 81:(0, 6)(1, 0)(2, 2)(3, 7)(4, 5)(5, 3)(6, 1)(7, 4) 82:(0, 6)(1, 1)(2, 3)(3, 0)(4, 7)(5, 4)(6, 2)(7, 5) 83:(0, 6)(1, 1)(2, 5)(3, 2)(4, 0)(5, 3)(6, 7)(7, 4) 84:(0, 6)(1, 2)(2, 0)(3, 5)(4, 7)(5, 4)(6, 1)(7, 3) 85:(0, 6)(1, 2)(2, 7)(3, 1)(4, 4)(5, 0)(6, 5)(7, 3) 86:(0, 6)(1, 3)(2, 1)(3, 4)(4, 7)(5, 0)(6, 2)(7, 5) 87:(0, 6)(1, 3)(2, 1)(3, 7)(4, 5)(5, 0)(6, 2)(7, 4) 88:(0, 6)(1, 4)(2, 2)(3, 0)(4, 5)(5, 7)(6, 1)(7, 3) 89:(0, 7)(1, 1)(2, 3)(3, 0)(4, 6)(5, 4)(6, 2)(7, 5) 90:(0, 7)(1, 1)(2, 4)(3, 2)(4, 0)(5, 6)(6, 3)(7, 5) 91:(0, 7)(1, 2)(2, 0)(3, 5)(4, 1)(5, 4)(6, 6)(7, 3) 92:(0, 7)(1, 3)(2, 0)(3, 2)(4, 5)(5, 1)(6, 6)(7, 4)
求解结果24种(去掉等效重复位置)
上面方法虽然得出有92种放置方法,实际上去掉等效重复的后只有24种。什么算等效的呢?可通过旋转90度、180度、270度得到同样的坐标位置,即可以认为这两种方法实际是等效的,就像是你从国际象棋棋盘的侧面看,不能认为该棋局就不是同一盘了。比如下面的两组: 1 :(0, 0)(1, 4)(2, 7)(3, 5)(4, 2)(5, 6)(6, 1)(7, 3) 89:(0, 7)(1, 1)(2, 3)(3, 0)(4, 6)(5, 4)(6, 2)(7, 5) 第89种方法明显可以由第一种顺时针旋转90度获得,因此在现实意义中,有时它们只能算作一种求解方法。图解如下 |
/** * 八皇后问题求解<br /> * 求解思路:每行每列各只有一个皇后,每条斜线上也只能放置一个皇后<br /> * 第一次在第0列放一个皇后<br /> * 第二次在第1列放第二个皇后,保证和第一个皇后不同行且在不同斜线上<br /> * ...<br /> * 第n次在第n-1列放第n个皇后,保证和前面步骤所有的皇后不同行且不同斜线上,如果找不到放置位置,则回溯到第(n-1)次换下一个位置<br /> * ...<br /> * 第八次在第7列放第八个皇后,保证和前面步骤所有的皇后不同行且不同斜线上,如果能放置则成功则保存该八个皇后的坐标,回溯到第7列继续找下一种放法<br /> * 最后第0列的最后一个位置都放过后回溯到-1列,结束算法。打印结果。 * @author South Park */ public class EightQueenEx { public static void main(String [] args) { // 保存(100次以内的正确放法)皇后的8个X坐标位置,第九个放置重复区分(初始为0,与其他的放法重复则为1) int [][] horizontalX = new int [100][9]; // 保存(100次以内的正确放法)皇后的8个Y坐标位置 int [][] horizontalY = new int [100][9]; // 依次放置8个皇后当次的X坐标 int[] a = new int[8]; // 依次放置8个皇后当次的Y坐标 int[] b = new int[8]; // 正确放法总数 int count = 0; // 列的坐标位置 int j = 0; // 从第0列开始,到第7列每列放置一个皇后(对应各个皇后的横坐标为0-7) for (int executeNum=0; executeNum<8; ) { // 判断横坐标为executeNum,纵坐标为j的点(executeNum, j)能否放置皇后(不与之前的皇后相遇) if(setLocation(a, b, executeNum, j)) { // 如果当前位置能放,下次放的纵坐标初始化为0 j=0; if (executeNum==7) { // 如果第八个皇后放置成功,保留这次8个皇后的坐标 System.arraycopy(a, 0, horizontalX[count], 0, 8); System.arraycopy(b, 0, horizontalY[count], 0, 8); count++; // 回溯到第七步放置第七个皇后,且纵坐标在原基础上加1 executeNum = 6; j = b[6] + 1; } else { // 如果放置成功的不是第八个皇后,则继续下一步 executeNum++; } } else { executeNum--; // 如果当前次是第一步的话,回溯到上一步即遍历结束,所有放法都已经找出 if (executeNum<0) {break;} // 如果当前位置不能放,回溯到上一步,且纵坐标在原基础上加1 j = b[executeNum] + 1; } } // 输出所有的八皇后放法 // showAllLocations(horizontalX, horizontalY, count); // 输出不重复的八皇后放法(如果可以通过旋转坐标轴90°,180°,270°位置一致,则认为是等效的) showValidatedLocations(horizontalX, horizontalY, count); } /** * 在第executeNum列放置一个皇后,如果能成功返回true,否则返回false */ public static boolean setLocation (int[] a, int[] b, int executeNum, int startY) { int j = startY; for (; j<8; j++) { boolean flag = true; for (int i=0; i<executeNum; i++) { // 判断2个皇后(a[i],b[i])(executeNum,j)是否冲突 if (!isAllowedToPut(a[i], b[i], executeNum, j)) { flag = false; break; } } // 如果当前位置与之前的所有皇后都不冲突,这次皇后放置成功,退出循环 if (flag) { a[executeNum] = executeNum; b[executeNum] = j; break; } } // 如果当前位置与之前的所有皇后都不冲突,则返回ture return (j<8); } /** * 判断2个皇后(a, b)(x, y)是否冲突<br /> * 冲突的话返回false,否则返回true */ public static boolean isAllowedToPut (int a, int b, int x, int y) { return !(x==a || y==b || x+y==a+b || x-y==a-b); } public static void showAllLocations(int[][] a, int[][] b, int count) { for (int i=0; i<count; i++) { System.out.print((i+1)+":"); for (int j=0; j<8; j++) { System.out.print("(" + a[i][j] +", " + b[i][j] + (j==7 ? ")\n" : ")")); } } } public static void showValidatedLocations(int[][] aa, int[][] bb, int count) { for (int i=0; i<count; i++) { aa[i][8] = 0; } for (int i=0; i<count-1; i++) { if (aa[i][8] != 0) { continue; } for (int j=i+1; j<count; j++) { if (aa[j][8] == 1) { continue; } processTheSameLocation(aa[i], bb[i], aa[j], bb[j]); } } System.out.println("============================================="); for (int i=0; i<count; i++) { if (aa[i][8] == 0) { System.out.print((i+1)+":"); for (int j=0; j<8; j++) { System.out.print("(" + aa[i][j] +", " + bb[i][j] + (j==7 ? ")\n" : ")")); } } } } /** * 判断2种放法是否等效<br /> * 等效的话返回把标志位设置1,否则为0 */ public static void processTheSameLocation(int[] a, int[] b, int[] x, int[] y) { // 判断所有的XY是否可以通过AB旋转90°得到 if(y[b[0]] == 7-a[0]) { int i = 1; for (; i<8; i++) { if(!(y[b[i]] == 7-a[i])) { break; } } if (i==8) { x[8] = 1;//把标志位设为1 return; } } // 判断所有的XY是否可以通过AB旋转180°得到 if(y[7-a[0]] == 7-b[0]) { int i = 1; for (; i<8; i++) { if(!(y[7-a[i]] == 7-b[i])) { break; } } if (i==8) { x[8] = 1; return; } } // 判断所有的XY是否可以通过AB旋转270°得到 if(y[7-b[0]] == a[0]) { int i = 1; for (; i<8; i++) { if(!(y[7-b[i]] == a[i])) { break; } } if (i==8) { x[8] = 1; return; } } } }
打印结果:
============================================= 1:(0, 0)(1, 4)(2, 7)(3, 5)(4, 2)(5, 6)(6, 1)(7, 3) 2:(0, 0)(1, 5)(2, 7)(3, 2)(4, 6)(5, 3)(6, 1)(7, 4) 3:(0, 0)(1, 6)(2, 3)(3, 5)(4, 7)(5, 1)(6, 4)(7, 2) 4:(0, 0)(1, 6)(2, 4)(3, 7)(4, 1)(5, 3)(6, 5)(7, 2) 5:(0, 1)(1, 3)(2, 5)(3, 7)(4, 2)(5, 0)(6, 6)(7, 4) 6:(0, 1)(1, 4)(2, 6)(3, 0)(4, 2)(5, 7)(6, 5)(7, 3) 7:(0, 1)(1, 4)(2, 6)(3, 3)(4, 0)(5, 7)(6, 5)(7, 2) 8:(0, 1)(1, 5)(2, 0)(3, 6)(4, 3)(5, 7)(6, 2)(7, 4) 9:(0, 1)(1, 5)(2, 7)(3, 2)(4, 0)(5, 3)(6, 6)(7, 4) 10:(0, 1)(1, 6)(2, 2)(3, 5)(4, 7)(5, 4)(6, 0)(7, 3) 11:(0, 1)(1, 6)(2, 4)(3, 7)(4, 0)(5, 3)(6, 5)(7, 2) 13:(0, 2)(1, 0)(2, 6)(3, 4)(4, 7)(5, 1)(6, 3)(7, 5) 14:(0, 2)(1, 4)(2, 1)(3, 7)(4, 0)(5, 6)(6, 3)(7, 5) 17:(0, 2)(1, 4)(2, 7)(3, 3)(4, 0)(5, 6)(6, 1)(7, 5) 18:(0, 2)(1, 5)(2, 1)(3, 4)(4, 7)(5, 0)(6, 6)(7, 3) 19:(0, 2)(1, 5)(2, 1)(3, 6)(4, 0)(5, 3)(6, 7)(7, 4) 20:(0, 2)(1, 5)(2, 1)(3, 6)(4, 4)(5, 0)(6, 7)(7, 3) 21:(0, 2)(1, 5)(2, 3)(3, 0)(4, 7)(5, 4)(6, 6)(7, 1) 30:(0, 3)(1, 0)(2, 4)(3, 7)(4, 5)(5, 2)(6, 6)(7, 1) 34:(0, 3)(1, 1)(2, 6)(3, 4)(4, 0)(5, 7)(6, 5)(7, 2) 47:(0, 4)(1, 0)(2, 3)(3, 5)(4, 7)(5, 1)(6, 6)(7, 2) 52:(0, 4)(1, 1)(2, 5)(3, 0)(4, 6)(5, 3)(6, 7)(7, 2) 55:(0, 4)(1, 2)(2, 0)(3, 6)(4, 1)(5, 7)(6, 5)(7, 3) 58:(0, 4)(1, 6)(2, 0)(3, 3)(4, 1)(5, 7)(6, 5)(7, 2)