每日四题打卡-3.27:DFS-n-皇后问题/BFS-走迷宫/BFS-八数码/树与图的深度优先遍历-树的重心

1、DFS-n-皇后问题

n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

1_597ec77c49-8-queens.png

现在给定整数n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数n。

输出格式

每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。

其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤91≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

操作跟dfs最初的代码类似

思路1:搜索全排列:枚举每一行皇后放到哪个位置上去,判断当前方案是否合法,剪枝操作。

//步骤跟dfs排列数字一样,不同在于判断条件:
// g[N][N]用字符串来记录找到的方案,col[i]同一列只能有一个,对角线/反对角线标记,dg[u - i], udg[n - u + i]
//最后注意恢复现场
#include <iostream>
using namespace std;
const int N = 20;//开两倍,正反对角线
int n;
char g[N][N];//用字符串来记录找到的方案
//需要开bool数组,如果该点为true则表示该点被用过了
bool col[N], dg[N], udg[N];

void dfs(int u)
{
    if (u == n){
        for (int i = 0; i < n; i ++) puts(g[i]);
        puts("");//换行
        return;
    }
    for (int i = 0; i < n; i ++)//<n不是<= n
    {
        if (!col[i] && !dg[u + i] && !udg[n - u + i])//问题:u + i,n - u + i
        {
            //将Q放到第i个位置
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[n - u + i] = true;////记录i已被用过
            dfs(u + 1);
            //恢复现场
            col[i] = dg[u + i] = udg[n - u + i] = false;
            g[u][i] = '.';
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            g[i][j] = '.';
    dfs(0);
    return 0;
}

思路2:挨个枚举所有格子

#include <iostream>
 
using namespace std;
 
const int N = 20;//开两倍正反对角线
 
int n;
char g[N][N];//用字符串来记找到的方案
 
bool row[N], col[N], dg[N], udg[N];//col同一列只能有一个,正对角线,反对角线都开一个
//当前位置需要填哪些数,需要开bool数组,如果该点为true则表示该点被用过了
 
//挨个枚举所有格子 
void dfs(int x, int y, int s)
{
    //判断y == n,如果出界,变到下一行格子 x ++
    if (y == n) y = 0, x ++;
    
    if (x == n)//如果枚举完最后一行
    {
        if (s == n)//找到一组解
        {
            for (int i = 0; i < n; i ++) puts(g[i]);//输出
            puts("");
        }
        return;
    }
    //不放皇后,直接递归到下一个格子 y + 1
    dfs(x, y + 1, s);
    
    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])//如果该数没被用过
        {
            //放皇后
            g[x][y] = 'Q';//将i放到当前位置
            row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;//更新记录,都已经放上皇后
            dfs(x, y + 1, s + 1);//递归到下一层
            //当dfs结束之后,需要恢复现场
            row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;//恢复现场
            g[x][y] = '.';
        }
    
}
 
 
int main()
{
    //共一行,包含一个整数n
    cin >> n;
    
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            g[i][j] = '.';
    
    //输入:x, y, s:左上角开始搜并记录有多少个皇后s
    dfs(0, 0, 0);//从第0个位置开始
    
    return 0;
}
 

2、BFS-走迷宫

扩展从0-8层

注意:只有当边权都是1是才能用BFS

给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0或1,其中0表示可以走的路,1表示不可通过的墙壁。

最初,有一个人位于左上角(1, 1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角(n, m)处,至少需要移动多少次。

数据保证(1, 1)处和(n, m)处的数字为0,且一定至少存在一条通路。

输入格式

第一行包含两个整数n和m。

接下来n行,每行包含m个整数(0或1),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤1001≤n,m≤100

输入样例:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

输出样例:

8
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
int g[N][N];//存地图
int d[N][N];//存每个点到起点的距离

PII q[N*N];//定义队列

int bfs()
{
    int hh = 0, tt = 0;//定义队头队尾
    q[0] = {0, 0};
    //将所有距离初始化成-1
    memset(d, -1, sizeof d);
    d[0][0] = 0;//表示已经走过了
    //枚举四个方向dx dy方向向量
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    //如果队列不为空,取出队头元素
    while (hh <= tt)
    {
        //队列操作
        auto t = q[hh ++];
        //每一次枚举四个方向
        for (int i = 0; i < 4; i ++)
        {
            int x = t.first + dx[i], y = t.second + dy[i];
            //在边界内(x >= 0 && x < n && y >= 0 && y < m )并且没走过g[x][y] == 0,d[x][y] == -1
            if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
            {//在区间内,第一次搜到才是最短距离,才去更新它
                d[x][y] = d[t.first][t.second] + 1;//更新
                q[++ tt] = {x, y};//将x,y点加进来
            }
        }
    }
    return d[n - 1][m - 1];
}

int main()
{
    //将整个地图读进来
    cin >> n >> m;
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < m; j ++)
            cin >> g[i][j];
    
    cout << bfs() << endl;
    return 0;
}

3、八数码

在一个3×3的网格中,1~8这8个数字和一个“x”恰好不重不漏地分布在这3×3的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把“x”与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让“x”先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将3×3的初始网格描绘出来。

例如,如果初始网格如下所示:
1 2 3

x 4 6

7 5 8

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出”-1”。

输入样例:

2  3  4  1  5  x  7  6  8 

输出样例

19

思路:

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
 
using namespace std;
 
int bfs(string start)
{
    string end = "12345678x";//结果输出是这样
    
    queue<string> q;//存start字符串
    unordered_map<string, int> d;
    
    q.push(start);//start入队
    d[start] = 0;//距离初始化0
    //xy距离
     //x轴正半轴向下,y轴正半轴向右
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    
    while (q.size())
    {
        auto t = q.front();
        q.pop();//出队
        
        int distance = d[t];//定义交换次数距离
        
        if (t == end) return distance;//如果排序结束,输出最少交换次数
        
        //状态转移
        int k = t.find('x');//找到x位置
        int x = k / 3, y = k % 3;//找到k点所在x的行数y的列数
        //枚举一下,把x上下左右四个数移到x上去
        for (int i = 0; i < 4; i ++)
        {
            //x移动之后变化的坐标
            int a = x + dx[i], b = y + dy[i];////求出'x'在字符串中的的新位置a, b
            if (a >= 0 && a < 3 && b >= 0 && b < 3)//保证a,b不出界
            {
                //与x交换
                swap(t[k], t[a * 3 + b]);//状态更新
                
                if (!d.count(t))//找到新的状态
                {
                    d[t] = distance + 1;//更新距离
                    q.push(t);//把新的状态放到队列里去
                }
                
                swap(t[k], t[a * 3 + b]);//恢复圆状态
                
            }
        }
    }
    return -1;
}
 
int main()
{
    string start;
    //输入占一行,将3×3的初始网格描绘出来。
    for (int i = 0; i < 9; i ++)
    {
        char c;
        cin >> c;
        start += c;
    }
    cout << bfs(start) << endl;
    
    return 0;
}

4、树与图的深度优先遍历-树的重心

给定一颗树,树中包含n个结点(编号1~n)和n-1条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数n,表示树的结点数。

接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边。

输出格式

输出一个整数m,表示重心的所有的子树中最大的子树的结点数目。

数据范围

1≤n≤1051≤n≤105

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

思路:如下图所示,当把1号点删除的时候,其余连通块与1连接的最大的连通块是 4,3,6,9.总共是4个点.同理将2删除,最大连通块是6,。。。。,最后把4删除掉最大连通块个数是5。将删除所有点的最大值存起来求最小值,最优解是最小的4,即把1删除掉。

#include <iostream>
#include <algorithm>
#include <cstring>
 
using namespace std;
 
const int N = 100010, M = N * 2;
 
int n;
int h[N], e[2*N], ne[2*N], idx;//链表定义头节点、节点值、next数组、idx
bool st[N];
 
int ans = N;
 
//链表操作:插入元素
void add(int a, int b)
{
    e[idx] = b; ne[idx] = h[a], h[a] = idx ++;
}
 
 
int dfs(int u)       //dfs过程寻找根的连通的点
{
    st[u] = true;//标记一下这个点已经搜过了
    
    int sum = 1, size = 0;//sum用于记录根子树的个数
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])//这个点没有被访问过
        {
            int s = dfs(j);//s表示当前子数大小
            size = max(size, s);//连通块,子数取max
            sum += s;//s是以u为根节点的子树
        }
    }
    
    size = max(size, n - sum);//通过画图可得n-m 即总的节点-根的子树 即为剩余的连通节点值
    
    ans = min(ans, size);//而size为当前为根的子树的个数 通过比较确认连通块中点的最大数
    
    return sum;
 
}
 
int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < n - 1; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    
    dfs(1);
    cout << ans << endl;
    
    return 0;
}
发布了176 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_27262727/article/details/105134709