On chain forward star

Posted by shibiny on Sat, 08 Jan 2022 03:49:32 +0100

Before introducing the chain forward star, let's look at a picture:

Layer 1 1 2 3 4 5 6 1 2 3 4 5

If you use the adjacency table to store this graph, you may get the following table:

vertex First connected point Second connected point Third connected point Fourth connected point
1 4 2 5 \(\tt{NULL}\)
2 3 \(\tt{NULL}\)
3 1 5 \(\tt{NULL}\)
4 \(\tt{NULL}\)
5 \(\tt{NULL}\)

The chained forward star stores the graph in the same way as the adjacency list. It is also stored in the form of a linked list, but the adjacency list stores points, while the chained forward star stores edges.

vertex First connected edge Second connected edge Third connected edge Fourth connected edge
1 1 4 5 \(\tt{NULL}\)
2 2 \(\tt{NULL}\)
3 3 6 \(\tt{NULL}\)
4 \(\tt{NULL}\)
5 \(\tt{NULL}\)

The chained forward star has a \ (\ texttt{head []} \) array, which is used to store all edges starting from this node.

Specifically, the edges starting from each node \ (\ tt u \) are linked with a chain table. In this way, only record \ (\ texttt{head[u]} \) to indicate who is the first edge of the node \ (\ tt u \) edge table.

node The first edge in the edge table The second edge in the edge table The third edge in the edge table The fourth edge in the edge table
\(\texttt{u}\) \(\texttt{head[u]}\) \(\texttt{next[head[u]]}\) \(\texttt{next[next[head[u]]]}\)​ \(\texttt{next[next[next[head[u]]]}\)
1 1 4 5 \(\tt{NULL}\)​
2 2 \(\tt{NULL}\)
3 3 6 \(\tt{NULL}\)

And so on

I'm used to setting the value \ (\ tt{NULL} \) to 0

Concrete implementation

Structure definition

We can use the structure to record the information of each edge.

Since the chained forward star records an edge, it is necessary to record the point pointed by this edge \ (\ tt{to} \), the number of the next edge in the edge list \ (\ tt{next} \), and the weight of this edge \ (\ tt{w} \) (if any)

struct edge {
    int next;
    int to;
    int w;    // If any
} e[N];

Edge function

int cnt = 0;
void add_edge(int u, int v, int w) {
    cnt ++;
    e[cnt].to = v;
    e[cnt].w = w;           // If any
    e[cnt].next = head[u];
    head[u] = cnt;
}

Where \ (\ tt{cnt} \) is used to number edges, the second line in the function is used to record edge information, and the third line in the function is used to add this edge to the edge table of \ (\ tt{u} \)

With such an excellent edge adding function, we can easily complete some graph reading and graph building operations.
To read in a directed graph with \ (\ tt{n} \) nodes and \ (\ tt{m} \) edges:

int n, m;
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add_edge(u, v, w);
    }
    ...
    return 0;
}

To read an undirected graph with \ (\ tt{n} \) nodes and \ (\ tt{m} \) edges:

int n, m;
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add_edge(u, v, w);
        add_edge(v, u, w);
    }
    ...
    return 0;
}

In fact, the essence is to create a forward and reverse directed edge, which together is an undirected edge.

Traversal of adjacent edges

After the graph is established, we only need to read \ (\ texttt{head[u]} \) of each node \ (\ tt{u} \), that is, the first edge starting from node \ (\ tt{u} \), and then cycle to find the next edge until it encounters \ (\ tt{NULL} \) (that is, 0)

for (int i = head[u]; i != 0; i = e[i].next) {
    ...
}

We combine some of the above codes to realize the function of inputting the number of points and edges (in fact, the number of edges is useless) and outputting the edges starting from each point in the order of points.

#include <bits/stdc++.h>
using namespace std;

#define N 10005

struct edge {
    int next, to, w;
} e[N];

int head[N];
int cnt = 0;

void add_edge(int u, int v, int w) {
    cnt ++;
    e[cnt].to = v, e[cnt].w = w;
    e[cnt].next = head[u], head[u] = cnt;
}

int n, m;
int u, v, w;

int main() {
	memset(head, 0, sizeof(int));
    scanf("%d%d", &n, &m)
	read(n), read(m);
	for (int i = 1; i <= m; i ++) {
        scanf("%d%d%d", &u, &v, &w);
		add_edge(u, v, w);
	}
	for (int i = 1; i <= n; i ++)
		if (head[i])
			for (int j = head[i]; j != 0; j = e[j].next)
				printf("%d %d %d\n", i, e[j].to, e[j].c);
		else printf("\n");
	return 0;
}

\(dfs \) traversing the tree

We only need to add \ (dfs \) on the basis of traversing the adjacent edges of points. Remember that undirected graphs need

void dfs(int u, int fa) {
    ...
    for (int i = head[i]; i != 0; i = e[i].next)
        if (e[i].to != fa)    // If it is an undirected graph, it needs to be added to prevent dead circulation
            dfs(e[i].to, u);
}

\(dfs \) traversal graph

The only difference between traversing a graph and a tree is that you need to record whether each point has been passed in the current recursion

void dfs(int u, int fa) {
    ...
    for (int i = head[i]; i != 0; i = e[i].next)
        if (e[i].to != fa && ! vis[e[i].to]){
            // e[i]. to !=  If the sentence FA is an undirected graph, it needs to be added to prevent dead circulation
            vis[e[i].to] = true;
            dfs(e[i].to, u);
            vis[e[i].to] = false;
        }
}

Comparison between chained forward star and adjacency list

After understanding the chained forward star, we need to understand why we use it (instead of using adjacency list).

Many online materials have been compared, such as:

Adjacency list is realized by linked list, which can dynamically add edges,
The chained forward star is implemented with a structure array and is static. You need to know the data range and open the array size at the beginning.
In contrast, the adjacency list is flexible and the chained forward star is easy to write.

Quoted from https://www.cnblogs.com/darlingroot/p/10337095.html

according to @LeeCarry mogul Original link Say: \ (\ texttt{vector} \) adjacency list is different from chained forward star in memory performance. Because \ (\ texttt{vector} \) is extended by default, 2 times more space is applied, so some special abnormal problems may be that the card memory can only be written with chained forward star. He said that recently he looked at the source code analysis of \ (\ texttt{STL} \) and that \ (\ texttt{vector} \) had to copy elements to a new memory block every time it was expanded, which would be much slower.

Therefore, the \ (\ texttt{vector} \) adjacency list is slightly inferior to the chained forward star writing method in terms of memory and speed, and the writing method is not much concise in implementation. Therefore, when the chained forward star can be used (i.e. when the number of sides is known), use the chained forward star writing method.

Topics: Algorithm Graph Theory