11.4 生成树:n皇后问题
抛开西洋棋的规则,简单点就是,如果你在下面的图中放入一个棋子,则灰色部分都不能用了:
即左右,对角线的空格都不能使用了,在这种情况下,问在n*n
的格子中如何放置最多这样的黑色格子。
这里以4*4
的格子为例:
展示了一种摆法。
在这里要用到深度优先搜索,也叫做回溯的算法来找。
首先说一下找的核心原理,就是从第1行开始找,找到一个合适的位置,比如(0,0)
(数组下标是从0开始的)。
再来从第二列开始找合适的位置,结合上面的图我们就可以找到第二行合适的位置为(2,1)
:
但是在添加了(2,1)
作为新的点之后:
导致下一行没有可以使用的位置了,所以我就就回溯到上一行,使用下一个可以使用的点:
这样再回到第3行,就还有一个位置可以使用。
到这里就完成了使用回溯算法来实现n皇后问题了。
对应的代码:
<?php
/**
* n皇后问题的核心代码
* @param $queens array 存储每次递归后的结果
* @param $mapLength int n的大小
* @param int $rowIndex 本地递归的行数
* @param int $offset 回溯时使用,用来跳过前一次设置的值
* @return false
*/
function setQueen(&$queens,$mapLength,$rowIndex=0,$offset=0){
// 设置递归停止条件
if ($rowIndex<0 || $rowIndex>=$mapLength){
return false;
}
// 获取下一个皇后的下标
$columnIndex=getAvaliableNode($queens,$mapLength,$offset);
// 递归到最后一行时,发现没有结果就停止,防止陷入死循环
if (($rowIndex+1)==$mapLength && is_bool($columnIndex)){
return false;
}
// 正常获取到皇后的位置,则继续下一次递归
if (!is_bool($columnIndex)){
$queens[$rowIndex]=$columnIndex;
setQueen($queens,$mapLength,$rowIndex+1);
}else{
// 没有获取到正确的皇后位置,回溯 & 重新开始获取值
array_pop($queens);
setQueen($queens,$mapLength,$rowIndex-1,$offset+1);
}
}
/**
* 计算下一个可以使用的空格下标
* @param $queens array 已设定的点
* @param $mapLength int n的值
* @param int $offset 回溯时,跳过之前采用的点
* @return false|int
*/
function getAvaliableNode($queens,$mapLength,$offset=0){
$nextRow=count($queens);
$unavaliableColumnNumber=[];
foreach ($queens as $rowNumber=>$columnNumber){
// 计算该列不能使用
$unavaliableColumnNumber[$columnNumber]=1;
// 对角线不能使用
$unavaliableColumnNumber[$columnNumber+($nextRow-$rowNumber)]=1;
$unavaliableColumnNumber[$columnNumber-($nextRow-$rowNumber)]=1;
}
for ($i=0;$i<$mapLength;$i++){
if (!isset($unavaliableColumnNumber[$i])){
$offset--;
if ($offset<0){
return $i;
}
}
}
return false;
}
$queens=[];
setQueen($queens,5);
print_r($queens);