八皇后问题解法一(排列筛选法)
- 本篇我们承接上一篇中的思想,想到了一个经典的算法题,八皇后问题:
- 题目:在8*8的国际象棋上摆放8个皇后,使得其互相不能攻击,即任意两个换后不能在同一行,同一列,或者同一对角线上。如下图中所示,就是一个符合预期的摆放方式,问总共有多少中摆放方式。
-
上图中的数字代表此处放置一个皇后,并且从上到下依次是0~7 总共8个。
-
分析:
- 由于8个皇后任意两个不能处在同一行,那么肯定每个皇后占据一行。
- 有上图看,我们必然可以用数组来标识myQueen[8] 数组,数组中i 个数字表示位于第i 行的皇后
- 例如以上图为案例可以用数组 myQueen[8] = {4,2,0,5,7,1,3,6} 来标识这种摆放的可能,
- 就是我们直接用数组下标当成皇后所在的列
- 用数组的下标对应的值当成皇后所在的行
- 那么现在我们的思路就是求出 myQueen数组的全排列,接着在筛选出符合要求的
- 符合要求的情况:
- 我们依据以上思路得到的全排列,天然就不会再同一行,也不会在同一列
- 只需要筛选左右对角线上是否有其他数据,那么只需要对比下标 i 和 j的差值与 i和j 下标对应的value的差值是否相等
- i - j == myQueen[i] -myQueen[j] || j-i == myQueen[i] - myQueen[j]
- 以上判断可以用具体案例来验证此处略。
-
依据如上分析有如下代码:
/*** 八皇后问题* @author liaojiamin* @Date:Created in 15:03 2021/5/26*/
public class EightQueen {private static final List<Integer[]> myQueen = new ArrayList<>();public static void main(String[] args) {Integer[] eight = {0,1,2,3,4,5,6,7};getEightQueenPerMutain(eight, 0);List<Integer[]> realQueen = checkQueen(myQueen);for (Integer[] integers : realQueen) {for (int i = 0; i < integers.length; i++) {System.out.print(integers[i]+",");}System.out.println();}}/*** 检查所有可能的八皇后排列,筛选出符合条件的:* i-j == integer[i] - integer[j] || j-i = integer[i]-integer[j] 情况的都是统一对角的* */public static List<Integer[]> checkQueen(List<Integer[]> myQueen){List<Integer[]> realEightQueen = new ArrayList<>();for (Integer[] integers : myQueen) {boolean isCheck = true;for (int i = 0; i < integers.length; i++) {if(!isCheck){break;}for (int j = i+1; j<integers.length;j++){if((i-j) == (integers[i] - integers[j])|| (j-i) == (integers[i] - integers[j])){isCheck = false;break;}}}if(isCheck){realEightQueen.add(integers);}}return realEightQueen;}/*** 八皇后问题排列解决方案* */public static void getEightQueenPerMutain(Integer[] eight, Integer start){if(eight == null || eight.length <= 1 || start == eight.length-1){Integer[] newEight = new Integer[eight.length];for (int i = 0; i < eight.length; i++) {newEight[i] = eight[i];}myQueen.add(newEight);}for (int i = start; i <= eight.length-1; i++) {Integer temp = eight[start];eight[start] = eight[i];eight[i] = temp;getEightQueenPerMutain(eight, start+1);temp = eight[start];eight[start] = eight[i];eight[i] = temp;}}
}
八皇后问题解法二(动态规划)
-
接上文中,依然可以用数组 myQueen[8] = {4,2,0,5,7,1,3,6} 来标识这种摆放的可能
-
那么上文中将所有排列列举出来后在对排列集合进行筛选得到最终的八皇后集合
-
类似的思路,我们用穷举法将数组myQueen中包含0 ~ 7 的所有数字的可能一一列举,并且实时筛选,这样我们可以一个步骤直接得到想要的集合
-
经过如上分析有如下代码:
/*** 八皇后问题* @author liaojiamin* @Date:Created in 15:03 2021/5/26*/
public class EightQueen {private static final List<Integer[]> myQueen = new ArrayList<>();public static void main(String[] args) {for (int i = 0; i < 8; i++) {Integer[] eight = new Integer[8];getEightQueen(eight, i);}for (Integer[] integers : myQueen) {for (int i = 0; i < integers.length; i++) {System.out.print(integers[i] + ",");}System.out.println();}System.out.println(myQueen.size());}/*** 穷举+筛选* 直接获取八皇后问题最终结果* */public static void getEightQueen(Integer[] queen, int start){for (int i = 0; i < 8; i++) {queen[start] = i;if(checkout(queen, start)){if(start == queen.length -1){Integer[] realQueen = new Integer[8];for (int i1 = 0; i1 < queen.length; i1++) {realQueen[i1] = queen[i1];}myQueen.add(realQueen);}else {getEightQueen(queen, start + 1);}}}}/**
* 校验是否符合八皇后问题规则
*/public static boolean checkout(Integer[] queen, Integer end){Set<Integer> checkRepeat = new HashSet<>();for (int i = 0; i <= end; i++) {if(checkRepeat.contains(queen[i])){return false;}checkRepeat.add(queen[i]);for(int j = i+1; j<= end; j++){if(queen[i] == null || queen[j] == null){return false;}if((i-j) == (queen[i]- queen[j]) || (j-i) == (queen[i] - queen[j])){return false;}}}return true;}
}
八皇后问题解法三(回朔递归)
- 依然可以用数组 myQueen[8] = {4,2,0,5,7,1,3,6} 来标识这种摆放的可能
- 首先将第一个皇后放入第一列位置
- 接着将第二个皇后分别放入第二三四等之后的位置,每次分别去检查放入的位置是否与之前的位置中放入的皇后是否在同一列,同一对角线
- 接着依次对地三个,第四个,直到第八个皇后重复第二步骤,找出每个皇后所有合法的位置
- 方法三 与方法二的实现方式非常类似,区别在于筛选,方法三种的筛选只需要对比最后一个元素与之前的元素是否冲突,在方法三中是每个皇后的位置依次检查,当进行到第5个皇后的时候,能保证前4个皇后的位置都是合法的,无需在检查之前的。
- 具体实现方案如下,方法三实现方案更好理解。
/*** 八皇后问题** @author liaojiamin* @Date:Created in 15:03 2021/5/26*/
public class EightQueen {private static final List<Integer[]> myQueen = new ArrayList<>();public static void main(String[] args) {getEightQueenRetro();print();}public static void print() {for (Integer[] integers : myQueen) {for (int i = 0; i < integers.length; i++) {System.out.print(integers[i] + ",");}System.out.println();}System.out.println(myQueen.size());}/*** 回朔递归*/public static void getEightQueenRetro() {Integer[] eight = new Integer[8];check(eight, 0);}//递归回朔便利每一种可能public static void check(Integer[] eight, int n) {if (n == eight.length) {myQueen.add(Arrays.copyOf(eight, eight.length));return;}for (int i = 0; i < eight.length; i++) {eight[n] = i;if(judge(eight, n)){check(eight, n+1);}}}//筛选public static boolean judge(Integer[] eight, int n){for(int i=0;i<n;i++){if(eight[i] == eight[n] || Math.abs(n-i) == Math.abs(eight[n] - eight[i])){return false;}}return true;}
}
总结
- 以上两个方法的整体思路类似,当题目需要我们按照一定规则摆放若干个数字的时候,我们可以先求出这些数字的所有可能,然后在一一判断每个组合是否满足题目给定的要求。
- 方法一利用上一篇中的排列的算法得出所有排列可能,接着筛选,时间复杂度O( n3 )
- 方法二 利用递归穷举方法得出所有数字的可能,并同时筛选,时间复杂度也是O(n3)
- 两种算法的时间复杂度都并非很优,如有更好的算法思想,求在评论指出
上一篇:数据结构与算法–字符串的排列组合问题
下一篇:数据结构与算法-- 数组中出现次数超过一半的数字(时间复杂度的讨论)