皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 ≥ 1 或 n1 ≥ 4 时问题有解。

皇后问题是非常著名的问题,作为一个棋盘类问题,毫无疑问,用暴力搜索的方法来做是一定可以得到正确答案的,但在有限的运行时间内,我们很难写出速度可以忍受的搜索,部分棋盘问题的最优解不是搜索,而是动态规划,某些棋盘问题也很适合作为状态压缩思想的解释例题。

进一步说,皇后问题可以用人工智能相关算法和遗传算法求解,可以用多线程技术缩短运行时间。本文不做讨论。

(本文不展开讲状态压缩,以后再说)

 

一般思路:

 

N*N的二维数组,在每一个位置进行尝试,在当前位置上判断是否满足放置皇后的条件(这一点的行、列、对角线上,没有皇后)。

 

优化1:

 

既然知道多个皇后不能在同一行,我们何必要在同一行的不同位置放多个来尝试呢?

我们生成一维数组record,record[i]表示第i行的皇后放在了第几列。对于每一行,确定当前record值即可,因为每行只能且必须放一个皇后,放了一个就无需继续尝试。那么对于当前的record[i],查看record[0...i-1]的值,是否有j = record[k](同列)、|record[k] - j| = | k-i |(同一斜线)的情况。由于我们的策略,无需检查行(每行只放一个)。

public class NQueens {
	public static int num1(int n) {
		if (n < 1) {
			return 0;
		}
		int[] record = new int[n];
		return process1(0, record, n);
	}
	public static int process1(int i, int[] record, int n) {
		if (i == n) {
			return 1;
		}
		int res = 0;
		for (int j = 0; j < n; j++) {
			if (isValid(record, i, j)) {
				record[i] = j;
				res += process1(i + 1, record, n);
			}
		}//对于当前行,依次尝试每列
		return res;
	}
//判断当前位置是否可以放置
	public static boolean isValid(int[] record, int i, int j) {
		for (int k = 0; k < i; k++) {
			if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
				return false;
			}
		}
		return true;
	}
	public static void main(String[] args) {
		int n = 8;
		System.out.println(num1(n));
	}
}

优化2:

 

分析:棋子对后续过程的影响范围:本行、本列、左右斜线。

黑色棋子影响区域为红色

本行影响不提,根据优化一已经避免

本列影响,一直影响D列,直到第一行在D放棋子的所有情况结束。

 

左斜线:每向下一行,实际上对当前行的影响区域就向左移动

比如:

尝试第二行时,黑色棋子影响的是我们的第三列;

尝试第三行时,黑色棋子影响的是我们的第二列;

尝试第四行时,黑色棋子影响的是我们的第一列;

尝试第五行及以后几行,黑色棋子对我们并无影响。

 

右斜线则相反:

随着行序号增加,影响的列序号也增加,直到影响的列序号大于8就不再影响。

 

我们对于之前棋子影响的区域,可以用二进制数字来表示,比如:

每一位,用01代表是否影响。

比如上图,对于第一行,就是00010000

尝试第二行时,数字变为00100000

第三行:01000000

第四行:10000000

 

对于右斜线的数字,同理:

第一行00010000,之后向右移:00001000,00000100,00000010,00000001,直到全0不影响。

 

同理,我们对于多行数据,也同样可以记录了

比如在第一行我们放在了第四列:

第二行放在了G列,这时左斜线记录为00100000(第一个棋子的影响)+00000010(当前棋子的影响)=00100010。

到第三行数字继续左移:01000100,然后继续加上我们的选择,如此反复。

 

这样,我们对于当前位置的判断,其实可以通过左斜线变量、右斜线变量、列变量,按位或运算求出(每一位中,三个数有一个是1就不能再放)。

具体看代码:

注:怎么排版就炸了呢。。。贴一张图吧

public class NQueens {
	public static int num2(int n) {
		// 因为本方法中位运算的载体是int型变量,所以该方法只能算1~32皇后问题
		// 如果想计算更多的皇后问题,需使用包含更多位的变量
		if (n < 1 || n > 32) {
			return 0;
		}
		int upperLim = n == 32 ? -1 : (1 << n) - 1;
        //upperLim的作用为棋盘大小,比如8皇后为00000000 00000000 00000000 11111111
        //32皇后为11111111 11111111 11111111 11111111
		return process2(upperLim, 0, 0, 0);
	}

	public static int process2(int upperLim, int colLim, int leftDiaLim,
			int rightDiaLim) {
		if (colLim == upperLim) {
			return 1;
		}
		int pos = 0;            //pos:所有的合法位置
		int mostRightOne = 0;   //所有合法位置的最右位置

        //所有记录按位或之后取反,并与全1按位与,得出所有合法位置
		pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
		int res = 0;//计数
		while (pos != 0) {
			mostRightOne = pos & (~pos + 1);//取最右的合法位置
			pos = pos - mostRightOne;       //去掉本位置并尝试
			res += process2(
                     upperLim,                             //全局
                     colLim | mostRightOne,                //列记录
                     //之前列+本位置
					(leftDiaLim | mostRightOne) << 1,      //左斜线记录
                     //(左斜线变量+本位置)左移             
					(rightDiaLim | mostRightOne) >>> 1);   //右斜线记录
                     //(右斜线变量+本位置)右移(高位补零)
		}
		return res;
	}

	public static void main(String[] args) {
		int n = 8;
		System.out.println(num2(n));
	}
}

完整测试代码:

32皇后:结果/时间

暴力搜:时间就太长了,懒得测。。。

public class NQueens {

	public static int num1(int n) {
		if (n < 1) {
			return 0;
		}
		int[] record = new int[n];
		return process1(0, record, n);
	}

	public static int process1(int i, int[] record, int n) {
		if (i == n) {
			return 1;
		}
		int res = 0;
		for (int j = 0; j < n; j++) {
			if (isValid(record, i, j)) {
				record[i] = j;
				res += process1(i + 1, record, n);
			}
		}
		return res;
	}

	public static boolean isValid(int[] record, int i, int j) {
		for (int k = 0; k < i; k++) {
			if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
				return false;
			}
		}
		return true;
	}

	public static int num2(int n) {
		if (n < 1 || n > 32) {
			return 0;
		}
		int upperLim = n == 32 ? -1 : (1 << n) - 1;
		return process2(upperLim, 0, 0, 0);
	}

	public static int process2(int upperLim, int colLim, int leftDiaLim,
			int rightDiaLim) {
		if (colLim == upperLim) {
			return 1;
		}
		int pos = 0;
		int mostRightOne = 0;
		pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
		int res = 0;
		while (pos != 0) {
			mostRightOne = pos & (~pos + 1);
			pos = pos - mostRightOne;
			res += process2(upperLim, colLim | mostRightOne,
					(leftDiaLim | mostRightOne) << 1,
					(rightDiaLim | mostRightOne) >>> 1);
		}
		return res;
	}

	public static void main(String[] args) {
		int n = 32;

		long start = System.currentTimeMillis();
		System.out.println(num2(n));
		long end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");

		start = System.currentTimeMillis();
		System.out.println(num1(n));
		end = System.currentTimeMillis();
		System.out.println("cost time: " + (end - start) + "ms");
	}
}

 

  • 226
    点赞
  • 221
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 112
    评论
皇后问题是指在一个 N×N 的棋盘上放置 N 个皇后,使得它们不能互相攻击。具体来说,皇后可以在同一行、同一列或同一斜线上攻击其他皇后。解决这个问题的一个常见方法是使用回溯算法。 以下是一个用 Python 解决皇后问题的示例代码: ```python def is_safe(board, row, col, N): # 检查当前位置的列是否安全 for i in range(row): if board[i][col] == 1: return False # 检查左上方对角线是否安全 i = row - 1 j = col - 1 while i >= 0 and j >= 0: if board[i][j] == 1: return False i -= 1 j -= 1 # 检查右上方对角线是否安全 i = row - 1 j = col + 1 while i >= 0 and j < N: if board[i][j] == 1: return False i -= 1 j += 1 return True def solve_queens(board, row, N): if row == N: # 打印结果或者保存结果等操作 for i in range(N): for j in range(N): print(board[i][j], end=' ') print() print() return True for col in range(N): if is_safe(board, row, col, N): board[row][col] = 1 solve_queens(board, row + 1, N) board[row][col] = 0 return False # 测试代码 N = 4 board = [[0] * N for _ in range(N)] solve_queens(board, 0, N) ``` 这段代码使用了一个二维列表 `board` 表示棋盘,其中的元素为 0 表示该位置没有皇后,为 1 表示该位置有皇后。`is_safe` 函数用于检查当前位置是否安全,`solve_queens` 函数使用回溯算法来解决皇后问题。在测试代码中,将 N 设置为 4,即解决 4 皇后问题,并打印所有解的棋盘布局。 请注意,由于皇后问题的解存在多个,上述代码只打印了一个解。你可以根据需要对结果进行保存或修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔老大RabbitMQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值