回溯法——n皇后问题

问题描述

n皇后问题是指一个在n*n的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行、同一列、同一条对角线上,求合法的方案数。

算法分析

由题意知,限界条件是n个皇后两两均不在同一行、同一列、同一条对角线上,我们设置一个attack二维数组,用来判断是否可以放置皇后,0表示可以放,1表示不可以放。对于在同一行、同一列、同一对角线上的皇后位置均设为1。例如,

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNTExMjA5MDM=,size_20,color_FFFFFF,t_70,g_se,x_16

对应的代码为

public void update(int[][]arr,int x,int y,int n){
        int[] dx = {-1,-1,-1,0,0,1,1,1};
        int[] dy = {-1,0,1,-1,1,-1,0,1};

        //表示当前放置皇后的位置(x,y),将其置1
        arr[x][y]=1;

        //注意i的界限和j的界限
        for(int i=1;i<=n-1;i++){
            for(int j=0;j<8;j++){
                int nx = x+i*dx[j];
                int ny = y+i*dy[j];

                //条件判断
                if(nx>=0&&nx<n&&ny>=0&&ny<n)

                    //在已经放置了皇后的位置基础上,将会产生攻击的位置置1
                    arr[nx][ny]=1;
            }
        }

    }

 从代码中可以看到,这个update更新函数用来更新attack数组,将已经放置了皇后的位置以及会产生攻击的位置全部置1,那么下次遍历的时候这些地方均不能放置皇后。

除了attack数组外,我们还需要设置一个数组queen用来记录放置了皇后的状态,用"."表示没有皇后,"Q"表示放了皇后。

我们已经知道了需要这两个数组,那么肯定需要先初始化数组,也就是最开始没有放置皇后的时候,那么attack数组应该全部为0,queen数组全部为"."。对应的初始化代码为

public void init(int n){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                attack[i][j]=0;
                queen[i][j]='.';
            }
        }
    }

初始化数组后,我们要对数组进行每一行每一列的遍历,例如从第0行第0列开始放置皇后,每放置一个皇后,就要将该位置的queen数组填上"Q",并更新attack数组将该位置所产生的不能放置皇后的位置置1。

public void DFS(int k,int n){

        //当K递归到n时,遍历完了最后一行,找到了一组解,cnt用来记录有几种解
        if(k==n){
            print(n);
            cnt++;
            return;
        }
        for (int j = 0; j < n; j++) {

            //等于0表示这里可以放皇后
            if (attack[k][j] == 0) {
    
                //temp数组用来临时保存attack原来的数组,即初始化时的数组
                //因为当遍历完最后一行时,我们又要开始从第0行的下一列开始遍历
                int[][] temp = new int[n][n];
                copy(temp,attack,n);
                queen[k][j] = 'Q';
                update(attack, k, j, n);

                //递归实现第k+1行的遍历
                DFS(k + 1, n);
                copy(attack, temp, n);
                queen[k][j] = '.';
            }
        }

    }

到此我们的主要代码全部完成。

完整代码

import java.util.Scanner;
public class Queen {

    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    int cnt=0;
    int[][] attack = new int[n][n];
    char[][] queen = new char[n][n];

    public static void main(String[] args){
        System.out.println("输入皇后的个数:");
        Queen queen = new Queen();
        queen.init(queen.n);
        queen.DFS(0,queen.n);
        System.out.println("共有"+queen.cnt+"种解");
    }


    //初始化
    public void init(int n){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                attack[i][j]=0;
                queen[i][j]='.';
            }
        }
    }

    //打印输出数组
    public void print(int n){
        System.out.println("解法:");
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                System.out.print(queen[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println();
    }


    //递归回溯棋盘
    public void DFS(int k,int n){
        if(k==n){
            print(n);
            cnt++;
            return;
        }
        for (int j = 0; j < n; j++) {

            //可以装入的情况下;
            if (attack[k][j] == 0) {
                int[][] temp = new int[n][n];
                copy(temp,attack,n);
                queen[k][j] = 'Q';
                update(attack, k, j, n);
                DFS(k + 1, n);
                copy(attack, temp, n);
                queen[k][j] = '.';
            }
        }

    }


    //备份数组
    public void copy(int[][] a,int[][] b,int n){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                a[i][j]=b[i][j];
            }
        }
    }

    //更新数组
    public void update(int[][]arr,int x,int y,int n){
        int[] dx = {-1,-1,-1,0,0,1,1,1};
        int[] dy = {-1,0,1,-1,1,-1,0,1};
        arr[x][y]=1;

        for(int i=1;i<=n-1;i++){
            for(int j=0;j<8;j++){
                int nx = x+i*dx[j];
                int ny = y+i*dy[j];

                if(nx>=0&&nx<n&&ny>=0&&ny<n)
                    arr[nx][ny]=1;
            }
        }

    }

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值