两个版本。思路大体相同,都是用的回溯。不过数据结构上的版本不太容易懂。先写上来吧。
struct Queen {
int x, y;
Queen(int xx, int yy) :x(xx), y(yy) {};
bool operator==(Queen const& q)const
{
return (q.x == x) || (q.y == y) || (x + y == q.x + q.y) || (x - y == q.x - q.y);//在判断这里巧妙的解决了对角线的问题
}
bool operator != (Queen const& q)const
{
return !((*this) == q);
}
};
static int nCheck = 0;
static int nResult = 0;
void placeQueens(int N)
{
stack<Queen> solu;
Queen q(0, 0);
do {
if ((solu.size() >= N) || (q.y >= N))//y值大于N越界不必多说,此外若栈中储存元素达到n
//个,说明此时已经是一组解。所以相当于越界了,回溯
{
q = solu.pop();//这里就是回溯的具体解决方案,将上一个储存元素弹出
q.y++;//并使列增加,这里不管增加后有没有越界,由下一次循//环来判断
}
else//这里用if,else,因为不能保证回溯后是否能继续执 //行,干脆用分支,用下一次的if判断来保证
{
while ((q.y < N) && (solu.find(q) >= 0)//搜寻可放置皇后的位置并记录步数
{
q.y++;
nCheck++;
}
if (q.y <N)//如果找到一个可以放置的地方,就将其入栈,判断能否 //输出完整的解,并搜寻下一行,从第0列开始
{
solu.push(q);
if (solu.size() >= N)
nResult++;
q.x++;
q.y = 0;
}
}while ((q.x >0) || (q.y < N))//退出循环的条件是搜寻到第0行第N列
}
一些总结:
①退出循环的条件是搜寻到第0行第N列
②进入程序后会发生以下几种情况:
(一)满足循环条件,没有越界,进过循环判断后可以放置,并且不是最后一行,那么下一步就是入栈并执行下一行
(二)满足循环条件,没有越界,进过循环判断后可以放置,是最后一行,下一步仍然是执行下一行,但是会回溯
(三)满足循环条件,没有越界,进过循环判断后没有可以放置的位置,那么经过循环之后q.y应该是等于N的,跳过入栈,到下次循环会回溯
(四)solu.size() >= N,即栈满了,此时像越界一样处理,回溯并y++
(五)q.y >=N,越界,需要回溯
③相比于递归来说比较难理解,但是空间复杂度较少,而且通过剪枝也得到了很好的优化。
///
八皇后递归版
const int Normalize = 9;
int Num;
int q[9];
bool s[9];
bool L[17]; //用来存放对角线标记的
bool Y[17];
void try(int col)
{
if (col == 9)
{
Num++;//此时q中存放一个解,可输出
}
for (int row = 1; row <= 8; row++)
{
if (S[row] && R[col + row] && L[col - row + Normalize])
{
q[col] = row;//标记呃,递归,回溯
S[row] = false;
R[col + row] = false;
L[col - row + Normalize] = false;
Try(col + 1);
S[row] = true;
R[col + row] = true;
L[col - row + Normalize] = true;
}
}
}
int main()
{
Num = 0;
for (int i = 0; i < 9; i++)
S[i] = true;
for (int i = 0; i < 9; i++)
{
L[I] = R[i] = true;
}
try(1);
return 0;
}
感觉递归版没啥好说的,简单便捷,回溯也方便》。。。
11.26新增
这次是lisp版的,太简洁了,lisp果然是神书…
(define (queens board-size)
(define (queen-cols k)
(if (=k 0)
(list empty-board)
(filter
(lambda (positions) (safe? k positions))
(flatmap (lambda (rest-of-queens) (map (lambda (new-row) (adjoin-position new-row k rest-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (-k 1))))))
(queen-cols board-size))
(define empty-board '())
;定义空棋盘
(define (adjoin-position new-row k rest-of-queens)
(cons new-row rest-of-queens))
;添加皇后
删除不安全的皇后
(define (safe? k position)
(iter-check (car position)
(cdr position)
1))
(define (iter-check row-of-new-queen rest-of-queens i)
(if (null? rest-of-queens) ; 下方所有皇后检查完毕,新皇后安全
#t
(let ((row-of-current-queen (car rest-of-queens)))
(if (or (= row-of-new-queen row-of-current-queen) ; 行碰撞 (= row-of-new-queen (+ i row-of-current-queen)) ; 右下方碰撞 (= row-of-new-queen (- row-of-current-queen i))) ; 左下方碰撞
#f
(iter-check row-of-new-queen (cdr rest-of-queens) ; 继续检查剩余的皇后 (+ i 1)))))) ; 更新步进值
解释:rest-of-queens是在前k-1列防止k-1个皇后的一种方式,new-row是在第kie放置所考虑的行编号。adjoin-position是将一个新的行列格局加入到一个格局集合;empty-board是一个空的格局集合,safe?是确定新的格局中的kie的皇后是否是安全的。
让我们从表达方式开始说明。
表达出来就是list(6 3 1 7 5 8 2 4)
因为题目要求给出八皇后问题的所有解法,所以 queens 求出的最终结果将是一个二维列表: (list (list 6 3 1 7 5 8 2 4) (list …) (list …) …) 。
(define (safe? k position)
(iter-check (car position)
(cdr position)
1))
(define (iter-check row-of-new-queen rest-of-queens i)
(if (null? rest-of-queens) ; 下方所有皇后检查完毕,新皇后安全
#t
(let ((row-of-current-queen (car rest-of-queens)))
(if (or (= row-of-new-queen row-of-current-queen) ; 行碰撞 (= row-of-new-queen (+ i row-of-current-queen)) ; 右下方碰撞 (= row-of-new-queen (- row-of-current-queen i))) ; 左下方碰撞
#f
(iter-check row-of-new-queen (cdr rest-of-queens) ; 继续检查剩余的皇后 (+ i 1)))))) ; 更新步进值
safe 这个函数,调用了一个iter-check函数,iter-check函数传入的参数有三个(其实是两个),新加入的行,还有之前的行,在这里之前的行我们已经可以保证它是符合规定的。
然后就是遍历检查了,之前存的时候从上向下存的一个好处在这就能体现了,从上向下便利检查每一个数字,并且也要看对角线是否满足要求,如果到最后都检查完了就是成功了,否则就是失败。