Graph Theory Algorithm

93 阅读3分钟

基础遍历算法

  • BFS
  • DFS

DFS

后序遍历(Postorder)

有时候一些算法的实现前需要给每一个节点一个Id,这个遍历顺序通常一个很好的选择。而且翻转的后续遍历是一个拓扑排序(topological order)。

回边(Back Edge)和横跨边(Cross Edge)

image.png 如果后序遍历的时候,发现了一个点之前已经给了对应的后序遍历的index,说明通过这个点可以回去之前的点,那么这个边就叫做回边。回边这个概念在找环和强连通分量(Strongly Connected Component, SCC)中非常有用。 如果这个回去的点在另外一个分支,这个就是一个横跨边。

无环图 DAGs

没有环的图

拓扑排序(Topological Sort)

这个是一个顺序, 一个点必须出现在所有他的前驱点的后面。这个顺序必须满足全部的边(u,v),u出现在v之前。

怎么找到一个拓扑排序

使用DFS后序遍历,假设我们有一个stack,一个node的所有的children nodes的都会在这个node推入stack前被放进去,这样可以保证最后的拓扑顺序可以保证这个node的全部边。

强连通分量(Strongly Connected Components, SCC)

每一个SCC里面的点都可以到任意其他的点。

Tarjan’s Algorithm

思路

使用了回路的概念,如果找到了一个回路,说明也找到了一个强连接因为这个点可以通过环到任何的环内其他的点。但是需要分辨是回路还是横跨路,所有需要记录一个点是否在stack里面,如果在stack里面说明是回路。

代码
int dfs_index[MAX] // 分配的postorder id
int lowlink[MAX] //记录可以回到最上面的哪一个点
stack<int> s
bool instack[MAX] //记录是否在stack
int scc_counter; //记录现在的scc个数
int which_scc[MAX]; //记录每一个在哪一个scc
void connect(int v)
    static int i = 1;
    lowlink[v] = dfs_index[v] = i++
    s.push(v)
    in_stack[v] = true
while (查看连接的点 w):
    if (dfs_index[w] == -1):
        connect(w)
        lower_link[v] = min(lower_link[v], low_link[w])
    else if (in_stack[w]):
        lower_link[v] = min(lower_link[v], lower_link[w])
    if (lower_link[v] = dfs_index[v])
        ++scc_counter
        int w;
        while (w !- v):
            w = s.top()
            s.pop()
            which_scc[w] = scc_counter
            in_stack[w] = false
            
for (int v = 0; v < n; ++v) if (!dfs_index[v]) connect(v);

Kosaraju’s Algorithm

思路

对普通的图做dfs postorder遍历,然后把图的边翻转过来,在做一次遍历。第一次遍历如果u->v,而且第2次遍历v->u,那么说明u和v可以在一个SCC里面。第一次dfs的遍历顺序会储存在stack里面。第2次dfs的时候,如果没有其他边可以找了,就从stack里面pop一个出来,这个会在一个新的scc里面因为同一个scc里面的node可以遍历过去找到。

代码
int postorder[MAX]
// 做一次正常的dfs,顺序记录在postorder里面
dfs()
// 图翻转做一次dfs
dfs_r():
    if (seen_r [ u]) 
        return ; 
    seen_r [ u] = true ;
    scc [ u] = mark ; 
    for (int v : edges_r [ u])
        dfs_r ( v , mark);
        
int compute_sccs() { 
    int sccs = 0; 
    for (int i = 1; i <= n ; i++) 
        if (!seen [ i]) dfs ( i); 
    for (int i = p - 1; i >= 0; i--) { int u = postorder [ i]; 
        if (!seen_r [ u]) // ignore visited vertices 
            dfs_r ( u , sccs++); } 
    return sccs ; 
}

min spaning tree

  • prim algorithm
    • 把点分为两个部分,每一次选择一个最小的edge,端点分别在两个部分。
  • Kruskal algorithm
    • 把edge排序,然后如果端点在同一个connected component就不加进去。