建议先阅读: H 图 Graph


原理

本章介绍图论中的高级算法:拓扑排序、强连通分量(Tarjan)、多源最短路径(Floyd-Warshall)、含负权边的单源最短路径(Bellman-Ford)、网络流等。

算法总览

算法用途时间复杂度条件
Kahn 拓扑排序DAG 线性排序O(V+E)有向无环图
Tarjan SCC强连通分量O(V+E)有向图
Floyd-Warshall全源最短路径O(V^3)任意权图(无负环)
Bellman-Ford单源最短路径(负权)O(VE)可检负环
SPFABellman-Ford 队列优化O(VE) 最坏平均较快
Dinic最大流O(V^2 E)流量网络

实现

拓扑排序(Kahn 算法)

#include <stdlib.h>
 
// 邻接表图结构(沿用 H_图的定义)
// 返回结果需要调用者 free
int* topological_sort(int V, int** adj, int* adj_sizes, int* result_size) {
    int* in_degree = calloc(V, sizeof(int));
    for (int u = 0; u < V; u++)
        for (int j = 0; j < adj_sizes[u]; j++)
            in_degree[adj[u][j]]++;
 
    int* queue = malloc(V * sizeof(int));
    int head = 0, tail = 0;
    for (int i = 0; i < V; i++)
        if (in_degree[i] == 0) queue[tail++] = i;
 
    int* result = malloc(V * sizeof(int));
    int ri = 0;
    while (head < tail) {
        int u = queue[head++];
        result[ri++] = u;
        for (int j = 0; j < adj_sizes[u]; j++) {
            int v = adj[u][j];
            if (--in_degree[v] == 0)
                queue[tail++] = v;
        }
    }
    free(in_degree);
    free(queue);
 
    if (ri < V) {  // 存在环
        free(result);
        *result_size = 0;
        return NULL;
    }
    *result_size = ri;
    return result;
}

Tarjan SCC

#include <stdlib.h>
#include <string.h>
 
typedef struct {
    int* dfn, *low, *scc_id;
    int* stack;
    int* on_stack;
    int timer, scc_count, stack_top;
    int V;
    int** adj;
    int* adj_sizes;
} TarjanSCC;
 
void tarjan_init(TarjanSCC* ts, int V) {
    ts->V = V;
    ts->dfn = calloc(V, sizeof(int));
    ts->low = calloc(V, sizeof(int));
    ts->scc_id = calloc(V, sizeof(int));
    ts->stack = malloc(V * sizeof(int));
    ts->on_stack = calloc(V, sizeof(int));
    ts->timer = 0;
    ts->scc_count = 0;
    ts->stack_top = 0;
}
 
void tarjan_destroy(TarjanSCC* ts) {
    free(ts->dfn); free(ts->low); free(ts->scc_id);
    free(ts->stack); free(ts->on_stack);
}
 
static void tarjan_dfs(TarjanSCC* ts, int u) {
    ts->dfn[u] = ts->low[u] = ++ts->timer;
    ts->stack[ts->stack_top++] = u;
    ts->on_stack[u] = 1;
 
    for (int j = 0; j < ts->adj_sizes[u]; j++) {
        int v = ts->adj[u][j];
        if (!ts->dfn[v]) {
            tarjan_dfs(ts, v);
            if (ts->low[v] < ts->low[u]) ts->low[u] = ts->low[v];
        } else if (ts->on_stack[v]) {
            if (ts->dfn[v] < ts->low[u]) ts->low[u] = ts->dfn[v];
        }
    }
 
    if (ts->dfn[u] == ts->low[u]) {
        ts->scc_count++;
        while (1) {
            int v = ts->stack[--ts->stack_top];
            ts->on_stack[v] = 0;
            ts->scc_id[v] = ts->scc_count;
            if (v == u) break;
        }
    }
}
 
int tarjan_solve(TarjanSCC* ts) {
    for (int i = 0; i < ts->V; i++)
        if (!ts->dfn[i]) tarjan_dfs(ts, i);
    return ts->scc_count;
}

Floyd-Warshall

#include <limits.h>
#include <stdlib.h>
 
typedef struct {
    long long** dist;
    int V;
} FloydWarshall;
 
void floyd_init(FloydWarshall* fw, int V) {
    fw->V = V;
    fw->dist = malloc(V * sizeof(long long*));
    for (int i = 0; i < V; i++) {
        fw->dist[i] = malloc(V * sizeof(long long));
        for (int j = 0; j < V; j++)
            fw->dist[i][j] = (i == j) ? 0 : LLONG_MAX / 2;
    }
}
 
void floyd_destroy(FloydWarshall* fw) {
    for (int i = 0; i < fw->V; i++) free(fw->dist[i]);
    free(fw->dist);
}
 
void floyd_add_edge(FloydWarshall* fw, int u, int v, int w) {
    fw->dist[u][v] = w;
}
 
// 返回 1 成功,0 表示存在负环
int floyd_solve(FloydWarshall* fw) {
    int V = fw->V;
    for (int k = 0; k < V; k++)
        for (int i = 0; i < V; i++)
            for (int j = 0; j < V; j++)
                if (fw->dist[i][k] < LLONG_MAX / 2 &&
                    fw->dist[k][j] < LLONG_MAX / 2 &&
                    fw->dist[i][k] + fw->dist[k][j] < fw->dist[i][j])
                    fw->dist[i][j] = fw->dist[i][k] + fw->dist[k][j];
 
    for (int i = 0; i < V; i++)
        if (fw->dist[i][i] < 0) return 0;
    return 1;
}
 
long long floyd_get_dist(FloydWarshall* fw, int u, int v) {
    return fw->dist[u][v];
}

Bellman-Ford

#include <limits.h>
#include <stdlib.h>
 
typedef struct { int from, to, weight; } Edge;
 
// 返回结果需要调用者 free,has_neg_cycle 为 1 表示存在负环
long long* bellman_ford(int V, const Edge* edges, int E, int start, int* has_neg_cycle) {
    long long* dist = malloc(V * sizeof(long long));
    for (int i = 0; i < V; i++) dist[i] = LLONG_MAX / 2;
    dist[start] = 0;
 
    for (int i = 0; i < V - 1; i++) {
        int updated = 0;
        for (int j = 0; j < E; j++) {
            if (dist[edges[j].from] < LLONG_MAX / 2 &&
                dist[edges[j].from] + edges[j].weight < dist[edges[j].to]) {
                dist[edges[j].to] = dist[edges[j].from] + edges[j].weight;
                updated = 1;
            }
        }
        if (!updated) break;
    }
 
    *has_neg_cycle = 0;
    for (int j = 0; j < E; j++) {
        if (dist[edges[j].from] < LLONG_MAX / 2 &&
            dist[edges[j].from] + edges[j].weight < dist[edges[j].to]) {
            *has_neg_cycle = 1;
            break;
        }
    }
    return dist;
}

Dinic 最大流

#include <limits.h>
#include <stdlib.h>
#include <string.h>
 
typedef struct { int to, rev; long long cap; } FlowEdge;
 
typedef struct {
    FlowEdge** graph;
    int* graph_sizes;
    int* graph_caps;
    int* level;
    int* iter;
    int V;
} Dinic;
 
void dinic_init(Dinic* dn, int V) {
    dn->V = V;
    dn->graph = calloc(V, sizeof(FlowEdge*));
    dn->graph_sizes = calloc(V, sizeof(int));
    dn->graph_caps = calloc(V, sizeof(int));
    dn->level = malloc(V * sizeof(int));
    dn->iter = malloc(V * sizeof(int));
}
 
void dinic_destroy(Dinic* dn) {
    for (int i = 0; i < dn->V; i++) free(dn->graph[i]);
    free(dn->graph); free(dn->graph_sizes); free(dn->graph_caps);
    free(dn->level); free(dn->iter);
}
 
static void dinic_add_edge_inner(Dinic* dn, int from, int to, long long cap) {
    if (dn->graph_sizes[from] >= dn->graph_caps[from]) {
        dn->graph_caps[from] = dn->graph_caps[from] ? dn->graph_caps[from] * 2 : 4;
        dn->graph[from] = realloc(dn->graph[from],
                                   dn->graph_caps[from] * sizeof(FlowEdge));
    }
    dn->graph[from][dn->graph_sizes[from]++] = (FlowEdge){to, 0, cap};
}
 
void dinic_add_edge(Dinic* dn, int from, int to, long long cap) {
    dinic_add_edge_inner(dn, from, to, cap);
    dinic_add_edge_inner(dn, to, from, 0);
    int from_idx = dn->graph_sizes[from] - 1;
    int to_idx = dn->graph_sizes[to] - 1;
    dn->graph[from][from_idx].rev = to_idx;
    dn->graph[to][to_idx].rev = from_idx;
}
 
static int dinic_bfs(Dinic* dn, int s, int t) {
    for (int i = 0; i < dn->V; i++) dn->level[i] = -1;
    int* q = malloc(dn->V * sizeof(int));
    int head = 0, tail = 0;
    dn->level[s] = 0; q[tail++] = s;
    while (head < tail) {
        int u = q[head++];
        for (int i = 0; i < dn->graph_sizes[u]; i++) {
            FlowEdge* e = &dn->graph[u][i];
            if (e->cap > 0 && dn->level[e->to] < 0) {
                dn->level[e->to] = dn->level[u] + 1;
                q[tail++] = e->to;
            }
        }
    }
    free(q);
    return dn->level[t] >= 0;
}
 
static long long dinic_dfs(Dinic* dn, int u, int t, long long f) {
    if (u == t) return f;
    for (int* i = &dn->iter[u]; *i < dn->graph_sizes[u]; (*i)++) {
        FlowEdge* e = &dn->graph[u][*i];
        if (e->cap > 0 && dn->level[e->to] == dn->level[u] + 1) {
            long long d = dinic_dfs(dn, e->to, t, f < e->cap ? f : e->cap);
            if (d > 0) {
                e->cap -= d;
                dn->graph[e->to][e->rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}
 
long long dinic_max_flow(Dinic* dn, int s, int t) {
    long long flow = 0;
    while (dinic_bfs(dn, s, t)) {
        for (int i = 0; i < dn->V; i++) dn->iter[i] = 0;
        long long f;
        while ((f = dinic_dfs(dn, s, t, LLONG_MAX)) > 0)
            flow += f;
    }
    return flow;
}

应用场景

  • 拓扑排序: 课程安排、编译依赖、任务调度
  • Tarjan SCC: 社交网络互关圈分析、缩点后 DAG 上 DP
  • Floyd-Warshall: 小规模全源最短路径(V <= 500)
  • Bellman-Ford/SPFA: 含负权边的最短路径、负环检测
  • Dinic: 二分图最大匹配、物流配送网络优化

练习

题号题目难度知识点
P1113杂务普及拓扑排序
P3387缩点提高Tarjan + DAG 上 DP
P3385负环提高Bellman-Ford/SPFA
P3376最大流提高Dinic 网络流

力扣 (LeetCode) 有对应题型,竞赛方向推荐力扣/Codeforces。