Graph theory - common algorithm templates

Storage of Graphs

adjacency matrix

int g[N][N];

List forward star

// Head insertion adjacency table
N Is the number of points, M Number of points
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) { // Insert an edge from a to b with a weight of c
  e[idx] = b;
  w[idx] = c;
  ne[idx] = h[a];
  h[a] = idx ++;
}
int main() {
  memset(h, -1 , sizeof h);
}

Storage edge

struct edge {
  int a, b, c; // a points to the edge of b whose weight is c
  bool const < (const& e) const { // Here, redefine the structure from small to large when sorting with the less than sign (<)
    return c < e.c;
  }
}edges[N];

shortest path

Naive dijkstra algorithm

  time complexity is O (\ (n ^ {2} \) + m), where n represents the number of points and M represents the number of edges

int g[N][N];  // Store each edge
int dist[N];  // Store the shortest distance from point 1 to each point
bool st[N];   // Store whether the shortest circuit of each point has been determined

// Find the shortest circuit from point 1 to point n. if it does not exist, return - 1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // Find the point with the smallest distance among the points where the shortest path has not been determined
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // Update the distance of other points with t
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

Heap optimized dijkstra algorithm

  time complexity O(nm) n represents the number of points, and m represents the number of edges

typedef pair<int, int> PII;
int n;      // Number of points
int h[N], w[N], e[N], ne[N], idx;       // The adjacency table stores all edges
int dist[N];        // Store the distance from all points to point 1
bool st[N];     // Is the shortest distance to store each point determined

// Find the shortest distance from point 1 to point n. if it does not exist, return - 1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first storage distance, second storage node number

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

Bellman Ford algorithm

  time complexity O(nm) n represents the number of points, and m represents the number of edges

int n, m;       // n represents the number of points and m represents the number of sides
int dist[N];        // dist[x] stores the shortest distance from 1 to X

struct Edge     // Edge, a represents the point, b represents the entry point, and w represents the weight of the edge
{
    int a, b, w;
}edges[M];

// Find the shortest distance from 1 to n. if you can't go from 1 to n, return - 1.
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // If the n-th iteration still relaxes the trigonometric inequality, it indicates that there is a shortest path with a length of n+1. According to the drawer principle, there are at least two identical points in the path, indicating that there is a negative weight loop in the graph.
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

spfa algorithm (Bellman Ford algorithm for queue optimization)

  time complexity: O(m) in the average case, O (nm) in the worst case, n represents the number of points, and M represents the number of edges
  when judging whether there is a negative ring, just count whether other points reaching a certain point are greater than the total points;

int n;      // Total points
int h[N], w[N], e[N], ne[N], idx;       // The adjacency table stores all edges
int dist[N];        // Store the shortest distance from each point to point 1
bool st[N];     // Stores whether each point is in the queue

// Find the shortest distance from point 1 to point n. if you can't walk from point 1 to point n, return - 1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto 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])     // If j already exists in the queue, there is no need to insert j repeatedly
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

floyd algorithm

  the time complexity is O (\ (n ^ {3} \), and N represents the number of points

initialization:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// After the algorithm, d[a][b] represents the shortest distance from a to B
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]);
}

Connected component

tarjan algorithm (strongly connected component of directed graph)

int h[N], e[M], ne[M], idx;
int dfn[N], low[N], ts; // dfn records the timestamp, and low records the minimum timestamp 
int stk[N], top; //   
int ins[N];
int id[N], cnt;
void tarjan(int u) {
	dfn[u] = low[u] = ++ ts;
	stk[++ top] = u, ins[u] = 1;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!dfn[j]) {
			tarjan(j);
			low[u] = min(low[u], low[j]);
		}
		else if (ins[j]) {
			low[u] = min(low[u], dfn[j]);
		}
	}
	if (dfn[u] == low[u]) {
		int y;
		++ cnt;
		do {
			y = stk[top --];
			ins[y] = 0;
			id[y] = cnt;
		} while (y != u);
	}
}

Undirected graph biconnected component

Biconnected components of edges

  summary: maximal connected block without bridge

Bridge  
 o-o Bridge o-o
 | | ↓ | |
 o-o - o-o 
How to find the bridge? x
            /
           y
x and y There is a bridge between them <=> dfn[x] < low[y] y You can't go up anyway x And low[y] = dfn[y] Namely y Is the highest point of the connected block

int h[N], e[M], ne[M], idx;
int dfn[N], low[N], ts; // dfn records the timestamp, and low records the minimum timestamp 
int stk[N], top;    
int ins[N];
int id[N], cnt;
int is_bridge[M];
void tarjan(int u, int from) {
	dfn[u] = low[u] = ++ ts;
	stk[++ top] = u;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!dfn[j]) {
			tarjan(j, i);
			low[u] = min(low[u], low[j]);
			if (dfn[u] < low[j]) {
				is_bridge[i] = is_bridge[i ^ 1] = true;
			}
		}
		else if (i != (from ^ 1)) {
			low[u] = min(low[u], dfn[j]);
		}
	}
	if (dfn[u] == low[u]) {
		++ cnt;
		int y;
		do (
			y = stk[top --];
			id[y] = cnt;
		)
		while (y != u);
	}
}


Doubly connected components of points

  summary: the most common block without cut point

    Cut point 
 o-o   o-o
 |  \↓/  |
 o-o-o-o-o 

     z
     |    
     x   
     |
     y

x Is the cut point:
  low[y] <= dfn[x]
  x Is the root node or x Is not a root node, but has at least two child nodes

int h[N], e[M], ne[M], idx;
int dfn[N], low[N], ts; // dfn records the timestamp, and low records the minimum timestamp 
int stk[N], top;    
int ins[N];
int id[N], cnt;
int is_cut[N];
void tarjan(int u) {
	dfn[u] = low[u] = ++ ts;
	int cnt = 0;
	for (int i = h[u]; i != -1; i = ne[i]) {
		int j = e[i];
		if (!dfn[j]) {
			tarjan(j);
			low[u] = min(low[u], low[j]);
			if (low[j] >= dfn[u]) {
				is_cut[u] = 1; 
			}
		}
	}
}

minimum spanning tree

Undirected graph

Naive prim algorithm

  the time complexity is O (\ (n ^ {2} \) + m), where n represents the number of points and M represents the number of edges

int n;      // n represents the number of points
int g[N][N];        // Adjacency matrix, storing all edges
int dist[N];        // Stores the distance from other points to the current minimum spanning tree
bool st[N];     // Stores whether each point is already in the spanning tree


// If the graph is not connected, inf (the value is 0x3f3f3f3f) is returned; otherwise, the sum of tree edge weights of the minimum spanning tree is returned
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        if (i && dist[t] == INF) return INF;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}

Kruskal algorithm

  the time complexity is O(mlogm), n represents the number of points, and m represents the number of edges

int n, m;       // n is the number of points and m is the number of sides
int p[N];       // Parent node array of the join query set

struct Edge     // Storage edge
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // Core operation of parallel query set
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // Initialize and query set

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // If two connected blocks are not connected, the two connected blocks are merged
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

Directed graph

Zhu Liu algorithm

int dnf[N], low[N], ts;
int stk[N], ins[N], top;
int id[N], cnt;
int d[N][N], bd[N][N]; // D stores the distance between two points, d[a][b] is the distance from a to B, and bd is the backup array 
void tarjan(int u) {
    dfn[u] = low[u] = ++ ts;
    stk[++ top] = u, ins[u] = 1;
    int j = pre[u];
    if (!dfn[j]) {
        tarjan(j);
        low[u] = min(low[u], low[j]);
    }else if (ins[j]) low[u] = min(low[u], dfn[j]);
    if (low[u] == dfn[u]) {
        int y;
        ++ cnt;
        do {
            y = stk[top --];
            ins[y] = 0;
            id[y] = cnt;
        }while (y != u);
    }
    
} 
int zhuliu() {
	int ret = 0;	
	while (true) {
		// Record the minimum entry edge of each point 
		for (int i = 1; i <= n; ++ i) {
			pre[i] = i;
			for (int j = 1; j <= n; ++ j) {
				if (d[pre][i] > d[j][i]) pre[i] = j; 
			}
		}
		// Judge whether there is a ring 
		memset(dfn, 0, sizeof dfn)l
		ts = cnt = 0;
		for (int i = 1; i <= n; ++ i) {
			if (!dfn[i]) tarjan(i);
		}
		if (cnt == n) {
			for (int i = 2; i <= n; ++ i) ret += d[pre[i][i]];
			break;
		} 
		
		// Shrinking point
		for (int i = 1; i <= cnt; ++ i) {
			for (int j = 1; j <= cnt; ++ j) {
				bd[i][j] = INF;
			}
		} 
		
		for (int i = 1; i <= n; ++ i) {
			for (int j = 1; j <= n; ++ j) {
				if (d[i][j] < INF && id[i] != id[j]) {
					int a = id[i], b = id[j];
					if (id[pre[j]] == id[j]) bd[a][b] = min(bd[a][b], d[i][j] - d[pre[j]][j]);
					else bd[a][b] = min(bd[a][b], d[i][j]);
				}
			}
		}
		n = cnt;
		memcpy(d, bd, sizeof bd);
		
	}
	return ret;
	
}

Euler path and Euler loop

  necessary and sufficient conditions for the existence of Euler path:
    directed graph: the exit degree of the starting point is 1 greater than the entrance degree, the end point is 1 greater than the exit degree, and the access degrees of other points are the same or all points are the same
    undirected graph: there can only be 0 or 2 points with odd degrees

Hierholzer algorithm

int g[N][N];
int ans[1100], cnt;
// Starting from the starting point, each edge traversed is deleted immediately
void Hierholzer(int u)
{
    for (int i = 1; i <= n; i ++ )
        if (g[u][i])
        {
            g[u][i] --, g[i][u] -- ; // This is an undirected graph
            dfs(i);
        }
    ans[ ++ cnt] = u;
}

  existence conditions of Euler circuit:
    digraph: all points have the same degree of access
    undirected graph: the degrees of all points are even

int h[N], e[M], ne[M], idx;
int type, n, m;
int seq[M], cnt; // seq record Euler loop 
void dfs(int u) {
	for (int i = h[u]; i != -1; i = h[u]) {
		if (st[i]) {
			h[u] = ne[i];
			continue;
		}
		// When type is 1, it is an undirected graph, and when type is 2, it is a directed graph 
		if (type == 1) st[i ^ 1] = 1; // Mark reverse edge 
		int t;
		if (type == 1) {
			t = i / 2;
			if (i & 1) t = -t; 
		} 
		else t = i;
		h[u] = ne[i];
		dfs(e[i]);
		seq[cnt ++] = t;
	}
}

Network flow

Edmonds Karp algorithm

  time complexity O(n\(m^{2}\)) n represents the number of points, and M represents the number of edges

int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], pre[N]; // d record the flow and pre record the incoming side
int n, m, S, T;// 
bool st[N];

bool bfs()
{
    int hh = 0, tt = 0;
    memset(st, false, sizeof st);
    q[0] = S, st[S] = true, d[S] = INF;
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (!st[ver] && f[i])
            {
                st[ver] = true;
                d[ver] = min(d[t], f[i]);
                pre[ver] = i;
                if (ver == T) return true;
                q[ ++ tt] = ver;
            }
        }
    }
    return false;
}

int EK()
{
    int r = 0;
    while (bfs())
    {
        r += d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1])
            f[pre[i]] -= d[T], f[pre[i] ^ 1] += d[T];
    }
    return r;
}

  expense flow

int h[N], e[M], f[M], w[M], ne[M], idx;
int q[N], d[N], pre[N], incf[N]; // d record the weight, pre record the incoming side, and incf record the maximum flow to a certain place
bool st[N];

bool spfa()
{
    int hh = 0, tt = 1;
    memset(d, 0x3f, sizeof d);
    memset(incf, 0, sizeof incf);
    q[0] = S, d[S] = 0, incf[S] = INF;
    while (hh != tt)
    {
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int ver = e[i];
            if (f[i] && d[ver] > d[t] + w[i])
            {
                d[ver] = d[t] + w[i];
                pre[ver] = i;
                incf[ver] = min(f[i], incf[t]);
                if (!st[ver])
                {
                    q[tt ++ ] = ver;
                    if (tt == N) tt = 0;
                    st[ver] = true;
                }
            }
        }
    }

    return incf[T] > 0;
}

void EK(int& flow, int& cost)
{
    flow = cost = 0;
    while (spfa())
    {
        int t = incf[T];
        flow += t, cost += t * d[T];
        for (int i = T; i != S; i = e[pre[i] ^ 1])
        {
            f[pre[i]] -= t;
            f[pre[i] ^ 1] += t;
        }
    }
}

Dinic algorithm

int h[N], e[M], ne[M], f[M], idx;
int q[M], cur[N], d[N]; // cur current arc optimization
int n, m, S, T;
bool bfs() {
    forn(i, 0, N) d[i] = -1;
    int hh = 0, tt = 0;
    q[tt ++] = S;
    d[S] = 0;
    cur[S] = h[S];
    while (hh < tt) {
        int t = q[hh ++];
        for (int i = h[t]; i != -1; i = ne[i]) {
            int ver = e[i];
            if (d[ver] == -1 && f[i]) {
                d[ver] = d[t] + 1;
                cur[ver] = h[ver];
                if (ver == T) return true;
                q[tt ++] = ver;
            }
        }
    }
    return false;
}

int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    
    for (int i = cur[u]; i != -1 && flow < limit; i = ne[i]) {
        int ver = e[i];
        cur[u] = i;
        if (d[ver] == d[u] + 1 && f[i]) {
            int t = find(ver, min(f[i], limit - flow));
            if (!t) d[ver] = -1;
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
        
    }
    return flow;
}

int dinic() {
    int r = 0, flow = 0;
    while (bfs()) while (flow = find(S, INF)) r += flow;
    return r;
}

prufer coding

  summary: for a rootless tree, take out the smallest node each time, output its parent node, and delete the node;

int d[N], f[N], p[N]; // d records the degree, f records the parent node, and p records the prufer corresponding code
int n, m;
void tree2prufer() { // Tree to prufer coding
    for (int i = 1; i < n; ++ i) {
        cin >> f[i];
        d[f[i]] ++;
    }
    
    for (int i = 0, j = 1; i < n - 2;) {
        while (d[j]) ++ j;
        p[i ++] = f[j ++];
        while (i < n - 2 && -- d[p[i - 1]] == 0 && p[i - 1] < j) p[i ++ ] = f[p[i - 1]];
    }
    for (int i = 0; i < n - 2; ++ i) {
        cout << p[i] << " ";
    }
    cout << endl;
}
void prufer2tree() { prufer Code to tree
    for (int i = 1; i < n - 1; ++ i) {
        cin >> p[i];
        d[p[i]] ++;
    }
    p[n - 1] = n;
    for (int i = 1, j = 1; i < n; ++ i) {
        while (d[j]) ++ j;
        f[j ++] = p[i];
        while (i < n - 1 && -- d[p[i]] == 0 && p[i] < j) f[p[i]] = p[i + 1], ++ i;
    }
    for (int i = 1; i < n; ++ i) {
        cout << f[i] << " ";
    }
    cout << endl;
}

Huffman coding

  summary: for a pile of nodes, take out the two nodes with the smallest weight each time to form a child node, and the common parent node weight is the sum of the weights of the two nodes

Topological order

  summary: search widely from the point where the penetration is 0

Added by moagrius on Sat, 01 Jan 2022 12:21:35 +0200