回溯法——八皇后问题

八皇后问题

在一个8×8的棋盘上放置8个皇后,使得他们不相互攻击。每个皇后的攻击范围是同行同列和同对角线,求出所有解的个数

回溯法——八皇后问题_第1张图片

回溯法——八皇后问题_第2张图片

首先简化问题,如果符合要求放满8个皇后,那么肯定保证每一行和每一列有且仅有一个皇后。那么显然问题就变成了如何在一行或者一列放满8皇后。接着就可以转化为求符合要求的8的全排列,那么求全排列我们显然可以用dfs来解决

既然是逐行放置的,那么皇后肯定不会横向攻击,因此只需要检查是否纵向或者斜向攻击即可。那么怎么判断呢?我们可能会想到用一个二维的vis数组来把可攻击的区域全部标记。这样显然时间复杂度有点大。通过紫书的学习,有一个绝妙的利用棋盘坐标性质的方法,我们设每行每列的坐标值均为0-7:

求出每个格子(x,y)的y-x差值,会发现同一主对角线的差值相同

回溯法——八皇后问题_第3张图片

求出每个格子(x,y)的x+y的和,会发现同一副对角线的和相同

回溯法——八皇后问题_第4张图片

那么我们由ans==ans[j]判断是否同一列,cur-ans[cur]==j-ans[j]判断是否同一主对角线,cur+ans[cur]==j+ans[j]判断是否同一副对角线:

int ans[1005];
int n,cnt;
void solve(int cur){   //cur初始为0
    if(cur==n) cnt++;     //只要走到这里,已经放满8个皇后就得出一组解
    else for(int i=0;i<n;i++){  //尝试把第cur行放到第i列
        int ok=1;
        ans[cur]=i;  
        for(int j=0;j<cur;j++) //检查是否和前面放过的皇后冲突
        if( ans[cur]==ans[j] || cur-ans[cur]==j-ans[j] || cur+ans[cur]==j+ans[j] ){ ok=0; break; }
        if(ok) solve(cur+1);
    }
}

似乎节点数很难进一步减少了,但是程序效率可以继续提高:引入一个二维数组vis[3][maxn],vis[0][i]标识第i列是否冲突,vis[1][cur+i]标识副对角线是否冲突,鉴于判断y-x差值时可能为负数,那么就再都加上n变成正数,即vis[2][cur-i+n]判断主对角线是否冲突

bool vis[3][1005];
void solve(int cur){
    if(cur==n) cnt++;
    else for(int i=0;i<n;i++){
        if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){
            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;
        }
    }
}

你可能感兴趣的:(算法)