学习自
回溯法
定义
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
运用回溯法解题关键要素
- 针对给定的问题,定义问题解空间。
- 确定易于搜索的解空间结构。
- 以深度优先方式搜索解空间,并且在搜索过程中使用剪枝函数避免无效搜索。
问题描述:
在n*n的网格中放置n个皇后,使任意两个皇后都不被相互吃掉,共有多少种放置方法。规则是皇后能吃掉同一行、同一列、同一对角线上的任意棋子。
解题思路:
设任意两棋子位置为(Xi,Yi)和(Xj,Yj)
限制条件:
不在同一行 Xi!=Xj,
不在同一列|Yi!=Yj ,
不在同一对角线abs(Xi-Xj)!=abs(Yi-Yj).
通过回溯法进行遍历判断是否铆足条件,从而求出相应的解。
PS:LinkedList是采用链表作为内部数据结构,其增加、删除操作的时间复杂度都是O(1),查找和修改的时间复杂度是O(n).LinkedList的pollLast()方法用于删除尾节点,链表为空时返回null.
具体步骤:
1.通过链表方式
首先定义一个类存放点(皇后)的位置。
class Location{ int x;//对应棋盘的行 int y;//对应棋盘的列 Location(int x,int y){ this.x = x; this.y = y; } public String toString() { return "(" + x + "," + y + ")"; } }
判断是否满足不在同一行、同一列、同一对角线。
/** * 判断位置为loc的皇后是否合法 */ private static boolean isLegalLoc(LinkedList<Location> list, Location loc) { for(Location each : list){ if(loc.x == each.x || loc.y == each.y) //判断是否在同一行或同一列 return false; else if (Math.abs(loc.x - each.x) == Math.abs(loc.y - each.y)) //判断是否在同斜线上 return false; } return true; }
放置方式(回溯算法)
/** * 主要函数,用回溯法。 */ private static void NQueen(LinkedList<Location> list, int x, int y) { if(list.size() == SIZE){ //当list元素个数为SIZE时,表示SIZE个皇后都摆放完毕,打印后即可退出函数。 printLocation(list); //打印皇后摆放方式 return ; } for(int i = x ; i < SIZE ; i++){ Location loc = new Location(i, y); if(isLegalLoc(list, loc)){ list.add(loc); //将第y行的皇后摆放好 NQueen(list, 0, y+1); //开始摆放y+1行的皇后,同样从第0列开始摆放 list.pollLast(); //每次摆放完一个皇后后,都要将其撤回,再试探其它的摆法。 } } }
完整代码:
package test; import java.util.LinkedList; import java.util.Scanner; public class N_quene1 { private static int SIZE = 0;//皇后的个数 private static int count = 0;//记录摆放的方式数 public static void main(String[] args) { // TODO Auto-generated method stub Scanner input = new Scanner(System.in); System.out.println("请输入你要解决几个皇后的问题"); SIZE = input.nextInt(); input.close(); LinkedList<Location> list = new LinkedList<Location>(); NQueen(list, 0, 0); //从棋盘的第0行第0列开始 System.out.println(SIZE + "皇后共有 " + count + "种摆放方式"); } static class Location{ int x;//对应棋盘的行 int y;//对应棋盘的列 Location(int x,int y){ this.x = x; this.y = y; } public String toString() { return "(" + x + "," + y + ")"; } } /** * 主要函数,用回溯法。 */ private static void NQueen(LinkedList<Location> list, int x, int y) { if(list.size() == SIZE){ //当list元素个数为SIZE时,表示SIZE个皇后都摆放完毕,打印后即可退出函数。 printLocation(list); //打印皇后摆放方式 return ; } for(int i = x ; i < SIZE ; i++){ Location loc = new Location(i, y); if(isLegalLoc(list, loc)){ list.add(loc); //将第y行的皇后摆放好 NQueen(list, 0, y+1); //开始摆放y+1行的皇后,同样从第0列开始摆放 list.pollLast(); //每次摆放完一个皇后后,都要将其撤回,再试探其它的摆法。 } } } /** * 判断位置为loc的皇后是否合法 */ private static boolean isLegalLoc(LinkedList<Location> list, Location loc) { for(Location each : list){ if(loc.x == each.x || loc.y == each.y) //判断是否在同一行或同一列 return false; else if (Math.abs(loc.x - each.x) == Math.abs(loc.y - each.y)) //判断是否在同斜线上 return false; } return true; } /** * 打印皇后摆放方式 * @param list */ private static void printLocation(LinkedList<Location> list) { String[][] show = new String[SIZE][SIZE]; for(int i = 0;i<SIZE;i++) { for(int j = 0;j<SIZE;j++) { show[i][j] = "0"; } } for(Location each : list){ System.out.print(each.toString() + " "); show[each.x][each.y] = "1"; } System.out.println(); for(int i =0;i<SIZE;i++) { for(int j=0;j<SIZE;j++) { System.out.print(show[i][j] + " "); } System.out.println(); } System.out.println(); count ++; } }
2.通过数组方式
public static void tria(int[] arr, int i, int n) { if(i >= n) { ++count; } else { for(int j = 0; j < n; j++) { arr[i] = j; if(place(arr, i)) { tria(arr, i+1, n); } } } }
总结
根据思想自己敲了一遍,算是加深自己的记忆程度吧。