算法竞赛入门经典第七章暴力求解法——回溯法(1.八皇后问题)

学习紫书从此节开始突然觉得难度提升了一个等级,分析一个程序需要花不少时间(因为自己太渣)。“回溯法”这个算法是非常重要的甚至是搞算法的必须要掌握的一个高级算法,它的技巧就是“碰到就回解”。当然,它还是需要使用**“递归”**。那我就跟着紫书的顺序继续整理下去吧!


####-1.八皇后问题
这个问题,呃,熟悉到简直不能再熟悉了,把它理解好其实就能够理解“回溯”的精髓了。
简单介绍一下这个问题:就是在n*n的格子中,摆放n个皇后,而在国际象棋中,皇后是可以横向攻击也能纵向攻击,也能斜向攻击,那么摆放这n个皇后的时候就需要避免这些皇后互相攻击,即不能在同一行同一列同一对角线上。最典型的就是八皇后,即在8×8的格子里摆放8个皇后。
我们思考一下,如果我们把八个皇后在这个8×8的格子里做个全排列然后判断是否符合条件,这样是不是可行的。很明显,这样的时间复杂度是非常大的,所以我们需要设计一个更优化的算法。那么我们就每一行摆一个皇后,这是一个循环,然后再在其中一列中摆一个皇后,这又是一个循环,发现冲突回到上一个过程。

我们模拟一下四皇后问题(其实只是规模上小一点而已),下图就是假定的一个棋盘(4*4),0表示空位置,1表示皇后。我们就按照刚才的摆放思路来摆一摆。

0  0  0  0              1  0  0  0  
0  0  0  0  	        0  0  0  0  
0  0  0  0 	    ->      0  0  0  0  
0  0  0  0	            0  0  0  0  
1.这是初始的棋盘         2.先摆第一行第一列
啥都没有	            
	             
1  0  0  0              1  0  0  0  
0  0  1  0  	        0  0  1  0  
0  0  0  0 	    ->      0  0  0  0  
0  0  0  0	            0  0  0  0  
3.然后摆第二行           4.再摆第三行
而第一二列摆不了          我们发现哪列都摆不了都冲突
(列和对角线冲突)
所以摆第三列
 
1  0  0  0              1  0  0  0  
0  0  0  1  	        0  0  0  1  
0  0  0  0 	    ->      0  1  0  0  
0  0  0  0	            0  0  0  0  	
5.只能回到第3步          6.摆第三行
即重新需要摆第二行         发现只能摆第二列
第三列发现不行
就摆第四列	

1  0  0  0              0  1  0  0  
0  0  0  1  	        0  0  0  0  
0  1  0  0 	    ->      0  0  0  0  
0  0  0  0	            0  0  0  0  	
7.摆第四行               8.回到摆第一行的地步
发现又失败了             因为第二行所有位置都不行
那么我们接下来回到哪呢    所以摆第一行时候换到第二列

0  1  0  0               0  0  1  0  
0  0  0  1  	         0  0  0  0  
1  0  0  0 	    ->       0  0  0  0  
0  0  1  0	             0  0  0  0
9—12.按照刚才的思路发      13.又回到摆第一行的时候
现当第一行摆放第二列时      然后继续按照刚才的思路摆
依次摆放二三四行都有        接下来不在赘述
不冲突的摆放情况,
说明这个成功了。

喷血、、画这个过程真是坑!其实画一个解答树会更简单,但是我希望把整个思想过程直观地描述下来。

代码如下:

#include<cstdio>
#include<iostream>
using namespace std;

const int maxn = 100010;
int n,tot=0;
int A[maxn];

/*cur代表填数的当前行*/
void search(int cur)
{
	if(cur == n) tot++;//当cur等于行数,那么就代表摆放成功
	else for(int i=0;i<n;i++)
	{
		int ok = 1;
		A[cur] = i;//尝试把第cur行的皇后放入第i列	
		for(int j=0;j<cur;j++)
		{
			if(A[cur]==A[j]||cur-A[cur]==j-A[j]||cur+A[cur]==j+A[j])//判断是否同列同主对角线和同副对角线 
			{
				ok = 0;	break;//如果是,则不递归并退循环,回溯 
			}
		}
		if(ok) search(cur+1);//如果合法,则递归 
	}
}
int main()
{
	cin>>n;
	search(0);
	cout<<tot;
	return 0;
}

好了,下面问题明天继续整理。


后记:
今天刷洛谷回顾一些经典算法,发现上面的代码过不去一个点,原因是因为判断同列对角线有点耗时,所以使用一个二维数组vis[ 2 ] [ ]来判断。

else for(int i=0; i<N; i++)
{
	if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+N])
	{
		A[cur] = i;
		vis[0][i] = vis[1][cur+i] = vis[2][cur-i+N] = 1;
		solve(cur+1);
		vis[0][i] = vis[1][cur+i] = vis[2][cur-i+N] = 0;
	}
}
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值