搜索(DFS和BFS、树和图的存储、树和图的遍历)(排列数字、八皇后问题、走迷宫、树的重心问题、树中点的层次)

搜索

1. DFS和BFS

DFS:深度优先遍历。时间复杂度 O ( h ) O(h) O(h)。不能保证搜到最短路。

BFS:宽度优先遍历。时间复杂度 O ( 2 n ) O(2^n) O(2n)。可以保证第一次搜到的就是最短路。

题目要求最短/最优之类的,一般都使用BFS。

题目比较奇怪,对复杂度要求高的,一般都用DFS。

注意两个概念

  • 回溯:在DFS中,这条路走不通了,回退到父节点
  • 剪枝:提前判断这个方法是不合法的

1. 1 DFS

  • 想清楚顺序:用什么顺序遍历所有方案
  • 注意回溯的时候要恢复现场
排列数字

有n个位置,从第一个位置开始选择,要填入什么数字

#include <iostream>

using namespace std;

const int N = 10;

int n;
int path[N];
bool vis[N];  // vis[i]表示数字i已经被放到位置上了


void dfs(int u)
{
    if (u == n) {
        for (int i = 0; i < n; i++) {
            cout << path[i] << " ";
        }
        cout << endl;
        return;
        
    }
    
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            path[u] = i;
            vis[i] = true;
            dfs(u + 1);
            vis[i] = false;
        }
    }
}



int main()
{
    cin >> n;
    
    dfs(0); // 从第一个位置开始看
    
    return 0;
}
八皇后问题
  1. 方法一:类似全排列的扩展。一行一行枚举,看每一个行的皇后要放到哪一列。
#include <iostream>

using namespace std;

const int N = 20;

int n;
// 记录棋盘
int g[N][N];
// col指每一列只能有一个,一共有n列
// dg表示正对角线:从左下角到右上角,一共有2n-1条
// udg表示反对角线:从左上角到右下角,一共有2n-1条
bool col[N], dg[N], udg[N];

void dfs(int u)
{
    if (u == n) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                printf("%c", g[i][j]);
            }
            puts("");
        }
        puts("");
        return;
    }
    // 枚举第u行的皇后应该放在哪一列
    for (int i = 0; i < n; i++) {
        if (!col[i] && !dg[i + u] && !udg[i - u + n]) {
            g[u][i] = 'Q';
            col[i] = dg[i + u] = udg[i - u + n] = true;
            dfs(u + 1);
            col[i] = dg[i + u] = udg[i - u + n] = 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;
}
  1. 一个格子一个格子的枚举
#include <iostream>

using namespace std;

const int N = 20;

int n;
// 记录棋盘
int g[N][N];
// dg表示正对角线:从左下角到右上角,一共有2n-1条
// udg表示反对角线:从左上角到右下角,一共有2n-1条
bool row[N], col[N], dg[N], udg[N];

void dfs(int x, int y, int s)
{
    if (y == n) {
        y = 0;
        x++;
    }
    if (x == n) {
        if (s == n) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    printf("%c", g[i][j]);
                }
                puts("");
            }
            puts("");
        }
        return;
    }
    // 不放皇后
    dfs(x, y + 1, s);
    
    // 放皇后
    if (!row[x] && !col[y] && !dg[y + x] && !udg[x - y + n]) {
        row[x] = col[y] = dg[y + x] = udg[x - y + n] = true;
        g[x][y] = 'Q';
        dfs(x, y + 1, s + 1);
        row[x] = col[y] = dg[y + x] = udg[x - y + n] = false;
        g[x][y] = '.';
    }
}


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

1. 2 BFS

步骤:

  1. 把初始状态放到队列中
  2. 写while循环,当队列不空时
走迷宫
#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

typedef pair<int, int> PII;
const int N = 110;

int n, m;
int g[N][N];  // 存迷宫
int d[N][N]; // 每个点到起点的距离
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};

int bfs()
{
    memset(d, -1, sizeof(d));
    d[0][0] = 0;
    
    queue <PII> q;
    q.push({0, 0});
    
    while (!q.empty()) {
        PII t = q.front();
        q.pop();
        
        for (int i = 0; i < 4; i++) {
            int x = t.first + dx[i];
            int y = t.second + dy[i];
            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.push({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;
}



2. 树和图的存储

  • 树是特殊的图(无环连通图)。

    所以只需要考虑图怎么存储。

  • 图包括有向图和无向图。

    对于无向图,就建立两条边。

    所以只要考虑有向图怎么存储。

  • 有向图的两种存储方法

    • 邻接矩阵:二维数组

    • 邻接表:每一个节点都有一个单链表,表示这个节点可以走到哪些点。

      插入时,插入到头节点之后。

#include <cstring>
#include <iostream>
#include <algorithm>

uisng namespace std;

const int N = 100010, M = N * 2;

// h存链表的头节点,h[i]表示节点i指向的第一个节点值
// e存每一个节点的值
// ne存每一个节点的下一个节点
int h[N], e[M], ne[M], idx;

void add(int a, int b)
{
    e[idx] = b;  // 建立一个新的节点:第idx个节点的值为b
    ne[idx] = h[a];  // 该节点的下一个节点指向
    h[a] = idx++;  // 节点a指向第idx个节点,然后idx加一
}

int main()
{
    // 初始,头节点均为-1,表示空
    memset(h, -1, sizeof(h));
}

3. 树和图的遍历

复杂度均是O(n+m),n为节点,m为边

3. 1 深度优先遍历

dfs框架
#include <cstring>
#include <iostream>
#include <algorithm>

uisng namespace std;

const int N = 100010, M = N * 2;

int h[N], e[M], ne[M], idx;
bool vis[N];

void add(int a, int b)
{
    e[idx] = b;  
    ne[idx] = h[a];
    h[a] = idx++;
}

void dfs(int u)
{
    vis[u] = true; // 标记已经被搜过
    
    // 遍历该节点的邻接边
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!vis[j]) {
            dfs(j);
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
}

树的重心问题
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010, M = N * 2;

int n;
int h[N], e[M], ne[M], idx;
bool vis[N];
int ans = N;

void add(int a, int b)
{
    e[idx] = b;  
    ne[idx] = h[a];
    h[a] = idx++;
}

int dfs(int u)
{
    vis[u] = true; // 标记已经被搜过
    
    int sum = 1; // 以u为根节点的子树的大小,1表示当前点
    int res = 0; // 删掉之后,联通块节点的最大值
    
    // 遍历该节点的邻接边
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!vis[j]) {
            int s = dfs(j);
            res = max(res, s);
            sum += s;
        }
    }
    
    res = max (res, n - sum);
    ans = min(res, ans);
    
    return sum;
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof(h));
    for (int i = 0; i < n; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs(1);
    
    cout << ans << endl;
    return 0;
}

3. 2 宽度优先遍历

树中点的层次
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];

void add(int a, int b) 
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int bfs() 
{
    int hh = 0, tt = 0;
    q[0] = 1;
    
    memset(d, -1, sizeof(d));
    d[1] = 0;
    
    while (hh <= tt) {
        int t = q[hh++];
        
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (d[j] == -1) {
                d[j] = d[t] + 1;
                q[++tt] = j;
                
            }
        }
    }
    return d[n];
}



int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    cout << bfs() << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值