搜索与图论

文章目录

搜索与图论

数据结构 空间
DFS 回溯、剪枝 stack O(h) 不具有最短性 最优性剪枝,不合法剪枝
BFS queue O(2^h) 最短路

深度优先搜索 DFS

DFS是一个执着的人会一直尽可能往回走,走到头再回溯,回溯的过程能往前邹尽可能往前走---yxc

顺序,搜索流程是一个树

843. n-皇后问题 - AcWing题库

搜索顺序:

  1. 全排列思路:枚举每一行皇后可以放在哪个位置,注意剪枝(提前判断,当前方案不合法直接回溯)O(n*n!)

    c++ 复制代码
    #include <iostream>
    
    using namespace std;
    const int N = 20;
    int n;
    char g[N][N];
    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 ++)
        {
            if(!col[i] && !dg[u + i] && !udg[n - u + i])
            {
                g[u][i] = 'Q';
                col[i] = dg[u + i] = udg[n - u + i] = true;
                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. 每一个位置放和不放O(2n2)

    c++ 复制代码
    #include <iostream>
    
    using namespace std;
    const int N = 20;
    int n;
    char g[N][N];
    bool col[N], dg[N], udg[N], row[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 ++) puts(g[i]);
                puts("");
            }
            return;
        }
        //不放皇后
        dfs(x, y + 1, s);
        //放
        if(!row[x] && !col[y] && !dg[y + x] && !udg[n + y - x])
        {
            g[x][y] = 'Q';
            row[x] = col[y] = dg[y + x] = udg[n + y - x] = true;
            dfs(x, y + 1, s + 1);
            row[x] = col[y] = dg[y + x] = udg[n + y - x] = 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;
    }

宽度优先搜索 BFS

稳重的人,每一次扩展一层

dp可以理解为特殊的最短路,即没有环的最短路

框架:

844. 走迷宫 - AcWing题库

复制代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int g[N][N], d[N][N];
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int n, m;

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

prv存储从哪个点过来的

复制代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int g[N][N], d[N][N];
PII prv[N][N];
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int n, m;

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

树与图的存储

树是无环连通图

  • 有向图

  • 无向图(特殊有向图)

  1. 邻接矩阵 g[a][b]
  2. 邻接表
复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;

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

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

846. 树的重心 - AcWing题库

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

//dfs(u)返回以u为根的树的点的数量

//sum 记录当前u为根的树的大小

// res存删除当前点之后,每个连通块大小的最大值

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
bool st[M];
int n;
int ans = N;//全局答案,最大的最小值

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
//返回以u为根的树的点的数量
int dfs(int u)
{
    st[u] = true;//已经被搜
    int sum = 1, res = 0;
    //sum 记录当前u为根的树的大小
    // res存删除当前点之后,每个连通块大小的最大值
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];//j存储当前节点对应图的编号
        if(!st[j]) 
        {
            int s = dfs(j);//s表示当前子树的大小
            res = max(res, s);
            sum += s;
        }
    }
    res = max(res, n - sum);
    ans = min(ans, res);
    return sum;
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n;

    for(int i = 1; i <= n - 1; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs(1);//随便挑一个点
    cout << ans << endl;
    return 0;
}

树与图的深度优先遍历

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
bool st[M];

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

void dfs(int u)
{
    st[u] = true;//已经被搜
    
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];//j存储当前节点对应图的编号
        if(!st[j]) dfs(j);
    }
    
}

int main()
{
    memset(h, -1, sizeof h);
    dfs(1);//随便挑一个点
}

树与图的宽度优先遍历

847. 图中点的层次 - AcWing题库

拓扑排序

有向图才会有拓扑序列,对于每条边,起点在终点前面

有环一定没有拓扑序

可证明有向无环图一定有拓扑图,所以有向无环图也被称为拓扑图

如何求拓扑序

度:入度和出度

所有当前入度为0的点都可以作为起点

入度为零意味着没有任何一条边指向当前点

宽搜过程

848. 有向图的拓扑序列 - AcWing题库

题目概述

给定一个有向图,包含 n n n 个点和 m m m 条边,可能存在重边和自环。要求输出该图的任意一个拓扑序列,如果不存在拓扑序列(即图中存在环),则输出 − 1 -1 −1。

解题思路

拓扑排序是针对有向无环图(DAG)的一种线性排序方法,使得对于图中的每一条有向边 ( u , v ) (u, v) (u,v), u u u 在排序中总是位于 v v v 的前面。如果图中存在环,则无法进行拓扑排序。

方法步骤
  1. 计算入度:统计每个节点的入度(即有多少条边指向该节点)。
  2. 初始化队列:将所有入度为0的节点加入队列。这些节点没有前置依赖,可以直接作为拓扑序列的起始点。
  3. 处理队列:从队列中取出一个节点,将其加入拓扑序列,并移除所有从该节点出发的边(即减少其邻居节点的入度)。如果邻居节点的入度变为0,则将其加入队列。
  4. 检查结果:如果拓扑序列包含所有节点,则排序成功;否则,说明图中存在环,无法进行拓扑排序。

代码解析

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx; // 邻接表存储图
int q[N], d[N]; // q数组模拟队列,d数组存储入度
int n, m;

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

bool topsort() {
    int hh = 0, tt = -1;
    
    // 将所有入度为0的节点加入队列
    for(int i = 1; i <= n; i ++) {
        if(!d[i])
            q[++ tt] = i;
    }
    
    while(hh <= tt) {
        int t = q[hh ++]; // 取出队头节点
        
        // 遍历该节点的所有邻居
        for(int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            d[j] --; // 邻居节点的入度减1
            if(d[j] == 0) // 如果入度为0,加入队列
                q[++ tt] = j;
        }
    }
    
    // 如果队列中的节点数等于n,说明拓扑排序成功
    return tt == n - 1;
}

int main() {
    memset(h, -1, sizeof h); // 初始化邻接表
    
    cin >> n >> m;
    while(m --) {
        int a, b;
        cin >> a >> b;
        d[b] ++; // 更新节点b的入度
        add(a, b); // 添加边a->b
    }
    
    if(topsort()) {
        for(int i = 0; i < n; i ++)
            cout << q[i] << " \n"[i == n];
    } else {
        cout << -1 << endl;
    }
    return 0;
}

代码解释

  1. 邻接表存储图 :使用数组模拟邻接表,h数组存储每个节点的头指针,ene数组分别存储边的终点和下一条边的索引。
  2. 入度数组d:记录每个节点的入度。
  3. add函数 :添加一条从ab的边,并更新b的入度。
  4. topsort函数
    • 初始化队列,将所有入度为0的节点加入队列。
    • 处理队列中的节点,减少其邻居节点的入度,如果邻居节点入度为0则加入队列。
    • 最后检查队列中的节点数是否等于n,如果是则说明拓扑排序成功。
  5. 主函数 :读取输入,构建图,调用topsort函数并输出结果。

复杂度分析

  • 时间复杂度 : O ( n + m ) O(n + m) O(n+m),每个节点和每条边各处理一次。
  • 空间复杂度 : O ( n + m ) O(n + m) O(n+m),存储图结构和队列。

示例输入输出

输入

复制代码
3 3
1 2
2 3
1 3

输出

复制代码
1 2 3

解释:拓扑序列可以是 1 2 31 3 2,代码输出前者。

最短路

建图!!!

考察抽象成图的过程,即建图的能力

朴素 Dijkstra

s表示当前已经确定最短距离的点

  1. dist[1] = 0, dist[i] = 0x3f

  2. for(int i = 1 ~ n)

    1. t <- 不在s中的距离最近的点

    2. s <- t

    3. 用t跟新其他点的距离

849. Dijkstra求最短路 I - AcWing题库

堆优化Dijkstra

在一堆数找最小的数,可以用堆 O(1)

在堆中修改一个数,log(n)

Bellman_Ford

如果有负权回路,最短路不一定存在

复制代码
for n次 (迭代n次)

	for 所有边a, b, w //松弛操作
		dist[b] = min(dist[b], dist[a] + w);
		
dist[b] <= dist[a] + w // 三角不等式

bellmanford 可以用来求负环,时间复杂度较高

853. 有边数限制的最短路 - AcWing题库
题目分析

给定一个n个顶点m条边的有向图,图中可能存在重边和自环,边权可能为负数。要求求出从顶点1到顶点n的最多经过k条边的最短路径距离。如果无法到达顶点n,则输出"impossible"。

算法思路

这道题使用Bellman-Ford算法来解决,因为该算法特别适合处理有边数限制的最短路问题。Bellman-Ford算法的基本思想是通过松弛操作逐步逼近最短路径。

关键步骤:
  1. 初始化距离数组dist,将所有顶点到起点的距离设为无穷大,起点距离设为0。
  2. 进行k次松弛操作,每次操作遍历所有边,尝试通过该边缩短终点到起点的距离。
  3. 使用备份数组backup确保每次松弛操作基于上一次迭代的结果,避免"串联更新"。
  4. 最终检查终点距离,如果仍大于一个较大值(0x3f3f3f3f/2),则认为不可达。
代码解释
复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 550, M = 10010; // 定义顶点和边的最大数量
int n, m, k; // n顶点数,m边数,k边数限制
int dist[N], backup[N]; // dist存储距离,backup用于备份

struct Edge {
    int a, b, w; // 边的起点、终点和权值
} edges[M];

void bellman_ford() {
    memset(dist, 0x3f, sizeof dist); // 初始化距离为无穷大
    dist[1] = 0; // 起点距离设为0
    
    for(int i = 0; i < k; i++) { // 进行k次松弛
        memcpy(backup, dist, sizeof dist); // 备份当前距离数组
        for(int j = 0; j < m; j++) { // 遍历所有边
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], backup[a] + w); // 松弛操作
        }
    }
}

int main() {
    cin >> n >> m >> k;
    for(int i = 0; i < m; i++) {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w}; // 存储所有边
    }
    
    bellman_ford();
    
    // 判断是否可达,注意不是直接比较0x3f3f3f3f因为有负权边
    if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else cout << dist[n] << endl;
    
    return 0;
}
复杂度分析
  • 时间复杂度:O(k*m),其中k是边数限制,m是总边数。
  • 空间复杂度:O(n+m),用于存储距离数组和边集。
注意事项
  1. 必须使用backup数组备份上一次的dist状态,避免在同一轮松弛中多次更新导致的错误。
  2. 判断不可达的条件是dist[n] > INF/2,而不是dist[n] == INF,因为有负权边可能导致距离略小于INF。
  3. 该算法可以检测负权环,但本题有边数限制,所以不需要考虑无限松弛的情况。

SPFA

宽搜优化,队列存所有变小的节点,跟新过谁,再拿他跟新(公司业绩提高才可能加工资

851. spfa求最短路 - AcWing题库
复制代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
int n, m;

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

void spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    st[1] = true;
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    spfa();
    if(dist[n] == 0x3f3f3f3f) puts("impossible");
    else cout << dist[n] << endl;
    return 0;
}
852. spfa判断负环 - AcWing题库
复制代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
int cnt[N];
int n, m;

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

bool spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    queue<int> q;
    for(int i = 1; i <= n; i ++)
    {
        q.push(i);
        st[i] = true;
    }
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return true;
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}

Floyd

求多源汇最短路

初始化:邻接矩阵存储图 d[i, j]

复制代码
for(int k = 1; k <= n; k ++)
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
			d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
854. Floyd求最短路 - AcWing题库
复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 220;
int d[N][N];
int n, m, k;

void floyd()
{
    for(int k = 1; k <= n; k ++)
    {
        for(int i = 1; i <= n; i ++)
        {
            for(int j = 1; j <= n; j ++)
            {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
}

int main()
{
    cin >> n >> m >> k;
    memset(d, 0x3f, sizeof d);
    
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            if(i == j) d[i][j] = 0;
        }
    }
    
    for(int i = 1; i <= m; i ++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        d[a][b] = min(d[a][b], w);
    }
    
    floyd();
    
    while(k --)
    {
        int a, b;
        cin >> a >> b;
        if(d[a][b] > 0x3f3f3f3f / 2) puts("impossible");
        else cout << d[a][b] << endl;
    }
    return 0;
}
相关推荐
oioihoii1 小时前
C++23 新特性:为 std::pair 的转发构造函数添加默认实参
算法·c++23
智者知已应修善业1 小时前
【验证哥德巴赫猜想(奇数)】2021-11-19 15:54
c语言·c++·经验分享·笔记·算法
-qOVOp-2 小时前
zst-2001 历年真题 设计模式
java·算法·设计模式
evolution_language2 小时前
LintCode第68题-二叉树的前序遍历,第67题-二叉树的后序遍历
数据结构·算法·新手必刷编程50题
passionSnail2 小时前
《用MATLAB玩转游戏开发:从零开始打造你的数字乐园》基础篇(2D图形交互)-俄罗斯方块:用旋转矩阵打造经典
算法·matlab·矩阵·游戏程序·交互
yxc_inspire2 小时前
C++STL在算法竞赛中的应用详解
c++·算法·stl
James. 常德 student3 小时前
leetcode-hot-100(哈希)
算法·leetcode·哈希算法
金融小师妹3 小时前
量化解析美英协议的非对称冲击:多因子模型与波动率曲面重构
大数据·人工智能·算法
是店小二呀3 小时前
【算法-哈希表】常见算法题的哈希表套路拆解
数据结构·c++·算法·散列表