递归
递归简单的来讲:就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁
递归的机制
1.当程序执行到一个方法时,就会开辟一个受保护的独立的空间(栈空间)
2.每个空间的数据是独立的,不会相互影响
3.如果方法中使用的是引用类型的变量(比如数组),就会共享该引用类型
4.递归必须向退出递归的条件逼近,否则就是无线递归了
拿上述的代码举个例子当如下时
就会报一个栈溢出的错
5.当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
递归的简单案例
这里先来分析下简单的打印问题,来了解下递归时的一个思路
下面这段代码里分别是一个数字打印的代码和一个阶乘问题的代码
public class RecursionTest {
public static void main(String[] args) {
test(4);
int factorial = factorial(3);
System.out.println(factorial);
}
//打印问题
public static void test(int n){
if(n>2){
test(n-1);
}
System.out.println("n="+n);
}
//阶乘问题
public static int factorial(int n){
if(n==1){
return 1;
}else{
return factorial(n-1)*n;
}
}
}
程序执行方法的时候就会开辟一个独立的空间 栈,遇到递归时压栈,只有当栈顶的方法执行结束,才能弹栈执行下一个程序
同样对于该打印问题如果我们将递归的方式更改成不靠近递归结束的方向会放生什么呢?
public class RecursionTest {
public static void main(String[] args) {
test(4);
// int factorial = factorial(3);
// System.out.println(factorial);
}
//打印问题
public static void test(int n){
if(n>2){
test(n+1);
}
System.out.println("n="+n);
}
这里会报一个栈溢出的错误
同理对于递归问题的分析就更为简单,这里就不在多说。
迷宫回溯问题
对于迷宫来说大家的想法大部分都是如此吧:通常的玩法一般是一开始顺着入口往后面走,遇到岔路口,就选择其中一条路往后走,走到此路无路可走的时候 ,就再退回到岔路口,然后再去选另外的一条路走,每次走到此条路不通时就返回到上一个岔路口另选一条路走 …经历这般 “摸爬滚打”之后,最后才可能走到出口。容易想到以同样的思路递归实现迷宫问题。
下面先用一个二维数组来模拟一个简单的迷宫
这个是个8行7列的二维数组,其中1代表墙壁,0则是表示可选择的通路
我们的起点是maze[1][1]而出口是maze[6][5]
当然我们有多种策略去进行路的选择 先下,在右,在上,在左是我下边使用的一种选择策略,不同的选择策略得出的路径可能不同,路的长短也可能不同。
别的就不多说了,代码其实较为简单,就是回溯的应用 还是需要自己的思考,也不难
public class Maze {
public static void main(String[] args) {
int[][] maze=new int[8][7];
//构建迷宫的基础样子
//将改为数组的第一行和最后一行的值全部设置为1
for(int i=0;i<7;i++){
maze[0][i]=1;
maze[7][i]=1;
}
//将该二维数组的第一列和最后一列的值全部设置为1
for (int i=0;i<8;i++){
maze[i][0]=1;
maze[i][6]=1;
}
maze[3][2]=1;
maze[3][1]=1;
//遍历下这个二维数组看下是否是想要的迷宫的样子
for(int i=0;i<8;i++){
for(int j=0;j<7;j++){
System.out.print(maze[i][j]+" ");
}
System.out.println();
}
setWay(maze,1,1);
for(int i=0;i<8;i++){
for(int j=0;j<7;j++){
System.out.print(maze[i][j]+" ");
}
System.out.println();
}
}
//maze表示当前的迷宫
//i,j表示小球的起初的位置
//如果小球能到maze的65的位置则表示最后的通路找到
//当maze中maze[][]=0;表示没有走过,等于1表示是墙,2表示当前路走过,3表示走过但是走不通
//在走迷宫时,需要确定一个策略:先走下边。下边不同再走右边,右边不同走上边,上边不通走左边。
//如果该点走不通在回溯
/**
*
* @param maze 给定的迷宫地图
* @param i 从地图的那个位置出发
* @param j
* @return 如果返回true则表示找到通路 如果返回false则表示没找到通路
*/
public static boolean setWay(int[] [] maze,int i,int j){
if(maze[6][5]==2){
return true;
}else{
if(maze[i][j]==0){//如果这个点还没有走过
maze[i][j]=2;
if(setWay(maze,i+1,j)){//向下走
return true;
}else if(setWay(maze,i,j+1)){//向右走
return true;
}else if(setWay(maze,i-1,j)){//向上走
return true;
}else if(setWay(maze,i,j-1)){//向左走
return true;
}else{
maze[i][j]=3;//该点是走不通的是死路
return false;
}
}else{//不是0的话按照前面的注释则表示会是 1 表示是强,2表示已经走过,3表示走过是个死路
return false;
}
}
}
}
按照我这种选择方向的策略来的话得出的路径如下
当然如上所说,选择方向的策略不同得出的路线是有出入的。
要得出具体坐标自己去便利就好了。
八皇后问题
八皇后问题的介绍
八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例,该问题是国际西洋棋棋手提出的,在8*8的格的国际象棋中摆放8个皇后,使其不能相互攻击,即任意两个皇后都不能处于同一行,同一列或者同一斜线上,问有多少种摆法。
使用到了回溯算法
高斯认为有76中方案,1854年在柏林的象棋杂志上不同的作者发表了40种解,后来有人用图论的方法解出92种结果,
算法的思路分析
1.第一个皇后先放在第一行的第一列
2.第二个皇后放在第二行的第一列,判断是否可行,如果不行继续放在第二列、第三列、,依次把所有的列放完来找到一个合适的
3.继续第三个皇后,还是第一列第二列。。。直到第8个皇后也能放在一个不冲突的位置,算是找到一个正确解
4.当得到一个正确解时,在栈退回到上一个栈时,就会开始回溯,即将第一个皇后放在第一列的所有解,全部得到
5.然后回头再将第一个皇后放在第一行的第二列,继续循环执行上述的步骤
说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法用一个数组来解决问题 arr[i]=val 来记录解法的数组
val表示第i+1个皇后,放在第i+1行的第val+1列
其实画个数组的图来逐步分析还是蛮好理解的
代码在下边,注释也很详细
public class EightQueens {
//
int max=8;
//定义数组array保存皇后的放置位置
int[] array=new int [max];
static int count=0;
public static void main(String[] args) {
EightQueens eightQueens = new EightQueens();
eightQueens.check(0);
System.out.println(count);
}
//放置第n个皇后
//check是每一次递归时进入到check方法中都有一套for循环
private void check(int n){
if(n==max){//n=8了,在放第9个皇后了,8个皇后已经放好了
print();
return;
}
//依次放入皇后,判断是否冲突
for(int i=0;i<8;i++){
//先把当前皇后放到该行的第一列
array[n]=i;
if(judge(n)){
//不冲突就开始放n+1个皇后,即开始递归
check(n+1);
}
//如果冲突就继续执行 array[n]=i,即将array[n]放置在本行的后移的一个位置
}
}
//当我们放置第n个皇后时,就去检测该皇后是否和前边已经摆放的皇后冲突了
private boolean judge(int n){
for(int i=0;i<n;i++){
//array[i]==array[n] 判断第n个皇后是否和前边的皇后在同一列
//Math.abs(n-i)==Math.abs(array[n]-array[i])判断是否和前边的皇后在同一斜线
//在同一个斜线,在棋盘中斜率的绝对值应该就是1的,就是横纵坐标的差的绝对值就是相同的 就表示在同一斜线
//这里的话判断是否在同一行是没有必要的,因为我们用一个数组去存放解法的关系
if(array[i]==array[n] || Math.abs(n-i)==Math.abs(array[n]-array[i])){
return false;
}
}
return true;
}
//写一个方法,可以将数组的位置输出
private void print(){
count++;
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
}
}
结果如下
算了截图太长了 不如下了[狗头]
就记录在这吧
这周和好兄弟们喝了酒,聊了聊天。好久没这么开心了,