N皇后问题(递归回溯)
今天讲了N后问题,现在来复习一下。
N后问题就是在N*N格的棋盘上面放置彼此不受攻击的n个皇后。这里的皇后指的是国际象棋中的皇后,按照国际象棋的规则,皇后可以攻击当前行和当前列还有同一斜线的棋子。简单来说,就是n个皇后的位置不可以在同一行,同一列,同一斜线。因为这几天学习的是回溯算法,很简单的想到了回溯。这个问题也是经典的回溯算法习题之一。
下面来想一下问题所包含的条件,明显的条件就是棋盘是n*n,而且很简单就想到n个皇后不可以在同一列,就是每一列都会有一个皇后。下面就是隐式的条件了,同一行和同一列都可以很简单的解决,同一斜线不是很好想。后来发现同一斜线的皇后都是皇后的位置差和皇后所在的列相等。就是皇后的位置斜率为1。翻译为代码语言就是,皇后所在的位置的差的绝对值等于皇后的列的差的绝对值。这个后面还会讲一下。条件都讲完了,下面就说一说算法的主体。
算法的主体是由递归构成,递归的参数就是当前考虑的皇后的列,为了下面的计算方便,这里将皇后的个数和数组都传了进去。为什么传进去数组?因为自己一开始想的是一个二维数组,这个也是大家容易想到的,但是后来发现,解的形式和数组的下标有关,干脆将二维数组变为了一维数组,将皇后所在的列变为数组的下标。这样,就省去了很多的无用功。递归的边界就是k的位置大于等于n。递归的主体是一个for循环,将循环的值赋值给数组当前的位置,同时检验当前位置是否正确,如果正确就进行下一个递归(k的值加一),不正确就进行下一次循环。讲到了检验k的位置,就说一说这个方法,很简单,就是判断当前位置是不是符合问题的条件,要有一个返回值,方便后面的调用。检验的时候也是要用到for循环。
算法的主体讲完了,下面就直接粘贴代码,代码如下:
package sf; import java.util.Scanner; //N后问题 public class demo7 { public static int num=0;//累加和 //判断当前位置是否正确 public static Boolean judge(int[] p,int k) { for(int i=0;i<k;i++) { //绝对值的判定和列的判定。(不用判定行) if((Math.abs(i-k)==Math.abs(p[i]-p[k]))||(p[i]==p[k])) { return false; } } //要注意这个return的位置,在这里是为了让for循环能够一直循环下去,不被中间的位置打断,可以将 //这个return放到for循环中体验一下。会发现,解的个数会变多。 return true; } //算法主体 递归回溯 k为当前考虑位置 public static void Queen(int[] p,int n,int k) { //这里的方法主体还是要多想一想 if(k>=n) { num++; //打印结果 for(int i=0;i<n;i++) System.out.print(p[i]); System.out.println(""); }else{ for(int j=1;j<=n;j++) { //System.out.println(k); //将皇后的位置赋值给数组中的元素,相当于放置皇后 p[k]=j; if(judge(p, k)) { //递归下一个位置 Queen(p, n, k+1); } } } } public static void main(String[] args) { System.out.println("请输入皇后的个数:"); Scanner sc=new Scanner(System.in); int n=sc.nextInt(); int []p=new int[n]; Queen(p,n,0); //将结果打印出来,如果只是打印这一个计数,程序的运行会快一点。 System.out.println(n+"皇后问题的解共:"+num+"种"); } }
算法的代码就是这样,算法的主体还是不好想的,我想了一会,感觉还是存在着一些问题,但是代码执行的结果正确,就不在想了。要注意算法中的for循环的指针的大小,这里还是要想一想的,虽然只有简单的一维数组。下面就讲一讲自己的一些想法,这个算法就是简单的递归求解,说的明白一点,就是穷举法,这就是这个算法的不足之处。只是在简单的列举之后,并没有进行函数限制。这个和回溯的思想有些不一样,回溯的思想是算法的主体还要有函数限制,将后面的解的一些错误的子树直接去掉,这个算法没有做到这一步,算法的实现还是有很大的提升空间,大概在求16皇后问题的时候需要100秒就是算法的大概极限了(这个也是在网上看到的),自己还是一个菜鸟,现在就是想一想。还是理解回溯的思想,在穷举的时候对于结果进行函数限制,方便后面的列举,大大加快算法的效率。