【算法导论:回溯】两种方法解决八皇后问题N-Queens

1.问题描述

在这里插入图片描述)
根据国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。八皇后问题研究如何将八个皇后放置在8×8的棋盘上,并且使皇后彼此之间不能相互攻击。寻找一共有多少种摆法,输出每种结果的具体摆法。

2.解决思路

为了简便介绍,我们将8皇后问题简述为4皇后。
在这里插入图片描述)
根据题意我们首先想到的是遍历所有可能(深度优先搜索DFS),假设每行放一个棋子,共有4×4×4×4种可能,其状态空间树如下图所示,但这样时间复杂度会很大。
在这里插入图片描述)
我们发现在遍历的过程中,有些搜索是没有必要的。例如在4皇后中,当第一行选择第一个位置时,第二行当选择第1个位置时我们发现不满足题目要求,即该节点无需再向状态空间树的更深层搜索,因为已经不满足题目的要求,因此需要剪枝
在这里插入图片描述)
每次遍历完当前节点时,需要回溯
在这里插入图片描述)
回溯法(非递归)的通用框架:
在这里插入图片描述)

3.实现代码

3.1非递归法

N皇后问题的回溯算法:
在这里插入图片描述)
限制函数Bound Function:PLACE
用于判断当前选择点与前面已经确定的皇后位置是否满足要求:不在同一斜线及同一行列。
在这里插入图片描述)

import java.util.Arrays;

public class Queen8questionM2 {

    static int num = 0;  // 结果数量
    static final int N = 8; // 解决8皇后问题
    static int[] result = new int[N+1]; // 其中一个解的序列,补充:result[1] = 2 表示第1行选择第2个位置
    // 主方法
    public static void main(String[] args) {
        NQUEENS(N);
        System.out.println(num); // 输出结果总数量
    }

    public static void NQUEENS(int n){
        int k = 1;      //从第1行开始
        result[k]  = 0; // 清空该行的选择

        while (k>0){
            result[k] = result[k] + 1;
            // 在 k 这一行中,找一个位置
            while ((result[k]<=N)&&(!PLACE(k))){ // 遍历所有位置,直到可以与之前的位置共存(即满足题目要求)
                    result[k] ++; //寻找该行下一个位置
            }
            // 判断找到 k 这一列的合法位置result[k] 是否越界
            if(result[k]<=N){ // 如果没越界
                if(k == N){ // 如果以及到了最后一行
                    num ++;// 结果数量+1
                    System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));;// 输出当前结果
                }else {
                    k = k + 1; // 进入下一行,继续寻找
                    result[k] = 0; // 清空该行的选择,准备从下一行的第一个开始
                }
            }else{ // 越界
                result[k] = 0; //清空该行的选择(此行可有可无,因为每次k=k+1时都顺便清空选择了)
                k = k - 1; // 回溯
            }
        }
    }

    /**
     * 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
     * @param k
     * @return
     */
    private static boolean PLACE(int k) {
        for (int i = 1;i<k;i++){
            // 判断result[k]的正上方没有节点,并且不在一条斜线
            if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
                return false;
            }
        }
        return true;
    }
}

输出结果:
在这里插入图片描述

3.2 递归法

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author wzpeng
 * @Date 2021-12-17 下午3:27
 * @Description 八皇后问题(递归回溯法)
 */
public class Queen8question {

    static int num = 0;  // 结果数量
    static final int N = 8; // 解决8皇后问题
    static int[] result = new int[N+1]; // 其中一个解的序列

    public static void main(String[] args) {
        queen(1);// 从第1行开始找
    }

    public static void queen(int i){

        if(i > 8){ // 1-8 这8行已经找完
            num++;// 找到一个解,
            // 输出该解的序列(每行摆放位置)
            System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));
            return;
        }
        for(int j=1;j <= N;j++){// 遍历每一行的8个位置(数字1-8)
            // 选择j位置时
            int k;
            for(k = 1;k < i;k++){// 循环判断和之前每一行是否冲突
                // 判断是否在同一列 以及 是否位于同一斜线
                if (result[k]==j||Math.abs(k - i)==Math.abs(result[k]-j)){
                    break;// 剪枝
                }
            }
            // 和之前的点都没有冲突时
            if(k==i){
                result[i] = j; // 记下该点的位置
                queen(i+1); // 寻找下一层
            }
        }
    }
}

输出结果:
在这里插入图片描述

4. 力扣51 N皇后

完整IDE代码,包含主函数

//按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 
//
// n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 
//
// 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 
//
// 
// 
// 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 
// 
// 
//
// 
//
// 示例 1: 
// 
// 
//输入:n = 4
//输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
//解释:如上图所示,4 皇后问题存在两个不同的解法。
// 
//
// 示例 2: 
//
// 
//输入:n = 1
//输出:[["Q"]]
// 
//
// 
//
// 提示: 
//
// 
// 1 <= n <= 9 
// 
//
// Related Topics 数组 回溯 👍 1627 👎 0

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * N 皇后
 * @author WZP
 * @date 2023-02-04 16:24:07
 */
public class P51_NQueens{
	 public static void main(String[] args) {
	 	 //测试代码
	 	 Solution solution = new P51_NQueens().new Solution();
		 solution.solveNQueens(8);
	 }
	 
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {

	int num = 0;

    public List<List<String>> solveNQueens(int n) {

		int[] result = new int[n+1];

		List<List<String>> finalresult = new ArrayList<>();

		int k = 1;      //从第1行开始
		result[k]  = 0; // 清空该行的选择

		while (k>0){
			result[k] = result[k] + 1;
			// 在 k 这一行中,找一个位置
			while ((result[k]<=n)&&(!PLACE(k,result))){ // 遍历所有位置,直到可以与之前的位置共存(即满足题目要求)
				result[k] ++; //寻找该行下一个位置
			}
			// 判断找到 k 这一列的合法位置result[k] 是否越界
			if(result[k]<=n){ // 如果没越界
				if(k == n){ // 如果以及到了最后一行
					num ++;// 结果数量+1
					System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));;// 输出当前结果
					finalresult.add(generateString(result));
				}else {
					k = k + 1; // 进入下一行,继续寻找
					result[k] = 0; // 清空该行的选择,准备从下一行的第一个开始
				}
			}else{ // 越界
				result[k] = 0; //清空该行的选择(此行可有可无,因为每次k=k+1时都顺便清空选择了)
				k = k - 1; // 回溯
			}
		}

		return finalresult;
    }

	/**
	 * 将其中一个解的答案转换为List<String>
	 * @param queens
	 * @return List<String>
	 */
	public List<String> generateString(int[] queens){
		// 注意,queens中的下标从1开始
		List<String> result=new ArrayList<>();
		for(int i =1;i < queens.length;i++){
			char[] chars=new char[queens.length-1];
			Arrays.fill(chars,'.');
			chars[queens[i]-1]='Q';
			result.add(String.valueOf(chars));
		}
		return result;
	}

	/**
	 * 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
	 * @param k
	 * @return boolean
	 */
	public boolean PLACE(int k,int[] result) {
		for (int i = 1;i<k;i++){
			// 判断result[k]的正上方没有节点,并且不在一条斜线
			if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
				return false;
			}
		}
		return true;
	}
}
//leetcode submit region end(Prohibit modification and deletion)

}

5. 力扣51 N皇后(递归,建议记忆)

递归法相对于非递归更便于理解,建议加强对回溯递归法框架的练习与记忆。

框架


int main(){
	finalresultlist = []
	backtrack(路径,1/选择列表)
}
void backtrack(路径,深度/选择列表)
	if( (深度)满足结束条件/选择列表为空)
		finalresultlist.add(路径) 
		return
	 for 选择 in 选择列表:
        做选择(并判断当前选择是否满足情况)
        backtrack(路径, 深度+1 /新选择列表)
        撤销选择
}

提交代码

本地可运行的完整代码


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * N 皇后
 * @author WZP
 * @date 2023-03-21
 */
public class P51_NQueens{
	 public static void main(String[] args) {
	 	 //测试代码
	 	 Solution solution = new P51_NQueens().new Solution();
		 solution.solveNQueens(4);
	 }
	 
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {

	List<List<String>> finalresult = new ArrayList<>();
	int N;


    public List<List<String>> solveNQueens(int n) {
		int[] result = new int[n+1]; // 注意下标从1开始,result[n]表示第n行存在的位置是第result[n]个
		N = n; // N皇后,保存N的值
		backtrack(result,1); // 从第一行开始找
		return finalresult;
    }


	public void backtrack(int[] result,int n){
		// 满足结束条件
		if( n > N ){
			finalresult.add(generateString(result)); // 将数组结果转换为满足题目要求的string结果
			return;
		}
		// 遍历所有选择
		for(int i = 1; i <= N; i++){
			// 做选择,排除不合法选择
			result[n] = i;
			if(!PLACE(n,result))  {
				continue;// 排除
			}
			// 进入下一层决策树
			backtrack(result,n + 1);
			// 撤销选择
			result[n] = 0;
		}
	}

	/**
	 * 将其中一个解的答案转换为List<String>
	 * @param queens
	 * @return List<String>
	 */
	public List<String> generateString(int[] queens){
		// 注意,queens中的下标从1开始
		List<String> result=new ArrayList<>();
		for(int i =1;i < queens.length;i++){
			char[] chars=new char[queens.length-1];
			Arrays.fill(chars,'.');
			chars[queens[i]-1]='Q';
			result.add(String.valueOf(chars));
		}
		return result;
	}

	/**
	 * 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
	 * @param k
	 * @return boolean
	 */
	public boolean PLACE(int k,int[] result) {
		for (int i = 1;i<k;i++){
			// 判断result[k]的正上方没有节点,并且不在一条斜线
			if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
				return false;
			}
		}
		return true;
	}
}
//leetcode submit region end(Prohibit modification and deletion)
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值