Finding Dominators in Directed Graphs (Lengauer-Tarjan Algorithm)

概要

\(r\) を始点とする有向グラフ \(G = (V, A, r)\) において、各頂点 \(v\) を直近で支配する頂点 (an immediate dominator) を求める。 始点 \(r\) から頂点 \(v\) に到達する際に必ず頂点 \(u\) を通るとき、頂点 \(u\) は頂点 \(v\) の dominator となる。

グラフ \(G\) の DFS Tree \(T\) を構築し \(T\) 上の行きがけ順を求め、そこから semidominator を求める。 そして、この semidominator を元に各頂点の immediate dominator を求める。

ここでは、頂点 \(v\) の semidominator \(sdom(v)\) は、 \(v\) への semidominator path が存在する頂点で、 (\(T\) 上の行きがけ順で) 最小となる頂点 \(u\) として定義する。
また、 semidominator path は (\(G\) 上の) path \(P = (u = v_0, v_1, ..., v_{k-1}, v_k = v)\) に対し (\(T\) 上の行きがけ順で) \(v_i > v \; (1 \le i \le k-1)\) を満たす path として定義する。

計算量

\(N = |V|\), \(M = |A|\) とする

  • simple version の EVAL-LINK を用いた実装: \(O(M \log N)\)

  • sophisticated version の EVAL-LINK を用いた実装: \(O(M \alpha(M, N))\) (\(\alpha\) はアッカーマン関数の逆数)

def dfs_preorder(N, G, s):
    order = [-1]*N
    order[s] = 0
    vs = [0]*N
    vs[0] = s
    prt = [-1]*N
    stk = [s]
    it = [0]*N

    cur = 0
    while stk:
        v = stk[-1]
        while it[v] < len(G[v]):
            w = G[v][it[v]]; it[v] += 1
            if order[w] == -1:
                prt[w] = v
                stk.append(w)

                order[w] = cur = cur+1
                vs[cur] = w
                break
        else:
            stk.pop()
    return vs, order, prt

def dominator(N, G, s):
    vs, semi, prt = dfs_preorder(N, G, s)
    doms = [-1]*N

    RG = [[] for i in range(N)]
    for v in range(N):
        for w in G[v]:
            RG[w].append(v)
    bucket = [[] for i in range(N)]

    *anc, = range(N)
    *label, = range(N)

    def s_link(v, w):
        anc[w] = v
    def s_eval(v):
        if v == anc[v]:
            return v
        compress(v, anc[v])
        return label[v]
    def compress(v, w):
        if w == anc[w]:
            return w
        anc[v] = u = compress(w, anc[w])
        if semi[label[w]] < semi[label[v]]:
            label[v] = label[w]
        return u

    for w in reversed(vs[1:]):
        for v in bucket[w]:
            u = s_eval(v)
            if semi[u] < semi[v]:
                doms[v] = u
            else:
                doms[v] = w
        for v in RG[w]:
            su = semi[s_eval(v)]
            if su < semi[w]:
                semi[w] = su
        if vs[semi[w]] != prt[w]:
            bucket[vs[semi[w]]].append(w)
        else:
            doms[w] = prt[w]
        s_link(prt[w], w)
    for v in bucket[s]:
        doms[v] = s
    for w in vs[1:]:
        if doms[w] != vs[semi[w]]:
            doms[w] = doms[doms[w]]
    doms[s] = s
    return doms

Verified

  • AOJ: "0294: Catch a Thief": source (Python3, 1.31sec)

def dfs_preorder(N, G, s):
    order = [-1]*N
    order[s] = 0
    vs = [0]*N
    vs[0] = s
    prt = [-1]*N
    stk = [s]
    it = [0]*N

    cur = 0
    while stk:
        v = stk[-1]
        while it[v] < len(G[v]):
            w = G[v][it[v]]; it[v] += 1
            if order[w] == -1:
                prt[w] = v
                stk.append(w)

                order[w] = cur = cur+1
                vs[cur] = w
                break
        else:
            stk.pop()
    return vs, order, prt

def dominator(N, G, s):
    vs, semi, prt = dfs_preorder(N, G, s)
    doms = [-1]*N

    RG = [[] for i in range(N)]
    for v in range(N):
        for w in G[v]:
            RG[w].append(v)
    bucket = [[] for i in range(N)]

    *anc, = range(N)
    *label, = range(N)
    size = [1]*N; child = [-1]*N

    def s_link(v, w):
        s = w
        while child[s] != -1 and semi[label[w]] < semi[label[child[s]]]:
            ccsz = (size[child[child[s]]] if child[child[s]] != -1 else 0)
            if size[s] + ccsz >= 2*size[child[s]]:
                anc[child[s]] = s
                child[s] = child[child[s]]
            else:
                size[child[s]] = size[s]
                anc[s] = s = child[s]
        label[s] = label[w]
        size[v] += size[w]
        if size[v] < 2*size[w]:
            s, child[v] = child[v], s
        while s != -1:
            anc[s] = v
            s = child[s]
    def s_eval(v):
        if anc[v] == v:
            return label[v]
        compress(v, anc[v])
        if semi[label[anc[v]]] >= semi[label[v]]:
            return label[v]
        return label[anc[v]]
    def compress(v, w):
        if w == anc[w]:
            return w
        anc[v] = u = compress(w, anc[w])
        if semi[label[w]] < semi[label[v]]:
            label[v] = label[w]
        return u

    for w in reversed(vs[1:]):
        for v in bucket[w]:
            u = s_eval(v)
            if semi[u] < semi[v]:
                doms[v] = u
            else:
                doms[v] = w
        for v in RG[w]:
            su = semi[s_eval(v)]
            if su < semi[w]:
                semi[w] = su
        if vs[semi[w]] != prt[w]:
            bucket[vs[semi[w]]].append(w)
        else:
            doms[w] = prt[w]
        s_link(prt[w], w)
    for v in bucket[s]:
        doms[v] = s
    for w in vs[1:]:
        if doms[w] != vs[semi[w]]:
            doms[w] = doms[doms[w]]
    doms[s] = s
    return doms

Verified

  • AOJ: "0294: Catch a Theif": source (Python3, 1.26sec)

参考

  • Lengauer, Thomas, and Robert Endre Tarjan. "A fast algorithm for finding dominators in a flowgraph." ACM Transactions on Programming Languages and Systems (TOPLAS) 1.1 (1979): 121-141.

  • Georgiadis, Loukas, Robert E. Tarjan, and Renato F. Werneck. "Finding dominators in practice." Journal of Graph Algorithms and Applications 10.1 (2006): 69-94.


戻る