一、dfs

1. 定义

dfs,即深度优先搜索。顾名思义,即尽可能递归到达最深处,然后再回溯,以此类推,直到遍历完整个图。

2. 解析

  • 通过递归实现。
    主要用于解决找迷宫路径等问题。
    dfs代码比bfs简单。

  • 要领:
    用vis数组维护是否走过,记得清零;
    dfs中continue的条件分三类:出界、已走过、不是所求。不要遗漏了。

3. 模板

const int Fx[8][2] = {{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}};  // 方向
string a[maxn];  // 记录地图
bool vis[maxn][maxm];  // vis数组记录是否走过

void dfs(int x, int y) {
    vis[x][y] = 1;
    for(int k = 0; k < 8; k ++) {
        int nx = x + Fx[k][0], ny = y + Fx[k][1];
        if(nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny] || a[nx][ny] == '*') continue;  // 出界、已走过、不是所求
        dfs(nx, ny);  // 递归
    }
}

4. 例题

Uva-572:https://blog.nowcoder.net/n/8e03fa67e5f5427dace3bdcf7adad4cc


二、bfs

1. 定义

bfs,即广度优先搜索。顾名思义,即先走完这一层,再走下一层,以此类推,直到遍历完整个图。

2. 解析

  • 通过队列实现。
    主要用于解决简单(权值为1)的最短路问题。

  • 要领:
    bfs问题要先想清楚“状态点”如何定义,然后维护相应维度的vis、a数组(地图)
    如果需要打印路径需要用p数组进行父节点的存储。

  • debug时不要太着急,可以将打印信息写的详细一点,这样才能有利于更快地发现问题!
    注意区分Dijkstra模板:bfs用的是普通队列,vis数组的更新位置在push后。

3. 变式

  • 双向bfs
    先通过逆向bfs求出距离d[maxn],然后再进行正向的“bfs”,这样就能实现一些比较复杂的最短路径问题。

  • 复杂状态的表示
    有时后一些状态点具有很多维度(比如八数码),或取值范围很大(超过1e8),这时没有办法通过vis数组来存储,因为会爆空间。
    这时可以考虑这些方法来保存vis状态:set、哈希法(推荐)、康托展开。

4. 模板

const int Fx[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};  // 方向
struct node {  // 队列中的结点
    int x, y, dist;
    node(int x, int y, int dist) : x(x), y(y), dist(dist) {}
};
int a[maxn][maxm], vis[maxn][maxm], ans;

void bfs(int x0, int y0) {
    queue<node> que;
    que.push(node(x0, y0, 0)), vis[x0][y0] = 1;  // 入队后紧接着更新vis
    while(!que.empty()) {
        node u = que.front();
        que.pop();
        if(u.x == n - 1 && u.y == m - 1) {  // 退出条件(可选)
            ans = u.dist;
            break;
        }
        for(int i = 0; i < 4; i ++) {
            int nx = u.x + Fx[i][0], ny = u.y + Fx[i][1];
            if(nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny] || a[nx][ny] == '#') continue;
            que.push(node(nx, ny, u.dist + 1)), vis[nx][ny] = 1;  // 入队后紧接着更新vis
        }
    }
}

5. 例题

Uva-1600:https://blog.nowcoder.net/n/5aeea79a2a14464b9cc101e8d376442e
Uva-1599:https://blog.nowcoder.net/n/7c15d9766ba3450bbea4a55fd554dfc5
Uva-10085:https://blog.nowcoder.net/n/6a23a0608d0c43b6a7422c32beeaffd6
Uva-816:https://blog.nowcoder.net/n/d4265122a1454285b73e6b1f1ddeb9b4


三、并查集

1. 定义

将若干点进行加边联通,每个连通块形成一个代表元Fa[i](i为连通块中的任意一点)。

2. 解析

并查集通常用于解决与连通相关的图论问题。
优化方法有:路径压缩、启发式连接。一般为了方便,只是用路径压缩就足够了。

3. 变式

在一些比较复杂的题目中,除了要维护父子关系,要需要维护其它的关系,这个时候需要 使用额外的辅助数组

4. 模板

  • 普通并查集
int n, Fa[maxn];  // 并查集要使用的Fa数组

int find(int x) {
    return Fa[x] == x ? x : Fa[x] = find(Fa[x]);  // 路径压缩
}

int main() {
    ......
    for(int i = 1; i <= n; i ++) Fa[i] = i;  // Fa初始化
    ......
    for(const auto& e : E) {  // 逐条加边
        int u = find(e.u), v = find(e.v);
        if(u == v) continue;  // 若连通
        Fa[u] = v;   // 若不连通则将其连通
        ......
    }
    .....
}
  • 复杂并查集
int Fa[maxn], dist[maxn];  // 辅助数组:到father的距离(非路径压缩的距离)

int find(int x) {
    if(Fa[x] == x) return x;
    int fx = Fa[x];
    Fa[x] = find(fx);
    dist[x] += dist[fx];  // 在回溯过程中更新,需要用到原father即fx
    return Fa[x];
}

5. 例题

最小生成树专题:https://blog.nowcoder.net/n/d806b758176c4f7e9b1de4895dd36279


四、链式前向星

1. 定义

一种比vector快且省空间的存图方法。

2. 解析

链式向前星 就是一种用一个链表来存放每个结点的所有出边的结构,新的边从链表头插入,用于加快读图。
链表头数组:head[maxn],head[u]指向一条包含了u的所有邻接边的链表
存所有边:E[maxm]
边序号:cnt

3. 模板

struct edge {
    int to, w, next;
    edge() {}
    edge(int to, int w, int next) : to(to), w(w), next(next) {}
} E[maxm];  // 用链式向前星来存图
int cnt, head[maxn];  // head[u]指向一条包含了u的所有邻接边的链表

void addEdge(int u, int v, int w) {
    E[cnt] = edge(v, w, head[u]);  // 从链表的头部插入
    head[u] = cnt ++;  // 更改头指针
}

int main() {
    ......
    fill(head, head + n + 1, -1);  // 初始化链表头,-1表示链表尾部
    cnt = 1;  // cnt表示当前加入的边序号
    while(m --) {
        ......
        addEdge(u, v, w);
        ......
    }
    ......
    for(int i = head[u]; ~i; i = E[i].next) {  // 遍历u的所有出边的方法
        int v = E[i].to, w = E[i].w;
        ......
    }
}

4. 例题

Poj-3159:https://blog.nowcoder.net/n/4ab5d9ec4f3945829d259dc8c23dc5fa


五、判断二分图

1. 定义

判断一个图是否为二分图。

2. 解析

  • 通过 染色法 实现。
    用 1 和 -1 两种颜色给图的点染色,用col[maxn]来存放点的颜色, 0 表示还没染色。
  • 若无法相间染色,则不是二分图,否则是

3. 模板

bool dye(int u, int color){
    col[u] = color; // 先染色
    for(int v : G[u]){
        if(!col[v] && !dye(v, -color)) return 0; // 下一个点没染色 就染上不同色,染不上则失败
        else if(col[v] == color) return 0; // 若下一个点染了色且和当前点颜色相同, 则必不是二分图 
    } 
    return 1;
}

int main() {
    ......
    bool ok = 1;  // 是否是二分图
    for(int i = 1; ok && i <= n; i ++) {
        if(!col[i] && !dye(i, 1)) ok = 0;
    } 
    ......
}

全原创