9
8

More than 5 years have passed since last update.

全域木を列挙する

Last updated at Posted at 2016-04-11

はじめに

約30年前に大学で習った、全域木の列挙アルゴリズムをPythonで実装してみましたので、ご紹介します。

全域木とは、元のグラフの全ての点を含む木のことです。

アルゴリズム

  • グラフの式表現を求めて、式表現を展開して列挙します。
  • 例えば、三角形のグラフで各辺をそれぞれa,b,cとすると、式表現は(abc)となり、これを展開するとab/ac/bcとなります。
  • グラフの式表現は以下のように求められます。
    • 辺の最初の式表現として、アルファベット1文字を持たせます。
    • グラフは、式表現を維持しながら辺または辺と点を削除できます。
    • グラフが1点になったときに式表現が求められます。
  • グラフGの式表現(Expr(G))は、任意の1つの辺Eを選んで以下のように変形できます。(標準ルール)
    • Expr(G) = (((Expr(E)), Expr(GからEを削除)), (Expr(E), Expr(GからEを縮約1)))

式表現には、以下の種類があります。

  • (A, B): Aの要素とBの要素の和集合です。
  • (A, B): Aの要素とBの要素の積集合(組合せて文字を並べたもの)です。
  • (A): Aの要素ごとに、1文字づつ消去して展開します。例えば、(abc)は、abとacとbcの3つに展開されます。

先ほどの標準ルールだけでもできますが、計算量を減らすために、特定のケースのルールを追加します。(標準ルールから導かれます)

  • 辺Eが自己ループの場合: Expr(G) = ((Expr(E)), Expr(GからEを削除))
  • 辺Eの片方の端点の次数(接続する辺の数)=1の場合: Expr(G) = (Expr(E), Expr(GからEを縮約))
  • 辺Eと辺Fが点Vで接続しており、点Vの次数=2の場合: Expr(E) = (Expr(E), Expr(F))として、辺Fを縮約した後のグラフについて求めます。

Pythonで実行してみる

クラス定義をします。

python3
from itertools import combinations, product
from collections import namedtuple
Union = namedtuple('Union', 'l r') # 和
Produ = namedtuple('Produ', 'l r') # 積
Combi = namedtuple('Combi', 'e') # 組
class Edge:
    def __init__(self, u, v, e):
        self.u = u
        self.v = v
        self.e = e
    def __repr__(self):
        return '<%s %s %s>'%(self.u, self.v, self.e)
class Graph:
    def __init__(self, num_nodes, edges):
        self.nodes = [[] for _ in range(num_nodes)]
        self.edges = []
        for i, (u, v) in enumerate(edges):
            self.edges.append(Edge(u, v, chr(97 + i)))
            self.nodes[u].append(i)
            self.nodes[v].append(i)
    def __repr__(self):
        return str(self.edges)
    def spanning_tree(self):
        res = Graph._reduct(self.nodes.copy(), self.edges.copy())
        return sorted(Graph._expand(res))
    @staticmethod
    def _reduct(nodes, edges):
        if not edges:
            return '' if len(nodes) == 1 else None
        for i, e in enumerate(edges): # 自己ループ
            if e.u == e.v:
                Graph._erase(nodes, edges, i)
                return Produ(l=Combi(e=e.e), r=Graph._reduct(nodes, edges))
        for con in nodes: # 次数=1
            if len(con) == 1:
                e = edges[con[0]]
                Graph._erase(nodes, edges, con[0])
                return Produ(l=e.e, r=Graph._reduct(nodes, edges))
        for con in nodes: # 次数=2
            if len(con) == 2:
                e = edges[con[0]]
                edges[con[0]] = Edge(e.u, e.v, Produ(l=edges[con[0]].e,
                                                     r=edges[con[1]].e))
                Graph._shrink(nodes, edges, con[1])
                return Graph._reduct(nodes, edges)
        e = edges[0]
        nodes2, edges2 = nodes.copy(), edges.copy()
        Graph._erase(nodes, edges, 0)
        Graph._shrink(nodes2, edges2, 0)
        return Union(l=Produ(l=Combi(e=e.e), r=Graph._reduct(nodes, edges)),
                     r=Produ(l=e.e, r=Graph._reduct(nodes2, edges2)))
    @staticmethod
    def _erase(nodes, edges, k):
        for a, con in enumerate(nodes):
            nodes[a] = [b if b < k else b-1 for b in con if b != k]
        del edges[k]
    @staticmethod
    def _shrink(nodes, edges, k):
        e = edges[k]
        dn = max(e.u, e.v)
        sn = e.u+e.v-dn
        nodes[sn] = nodes[sn] + nodes[dn]
        for a, con in enumerate(nodes):
            nodes[a] = [b if b < k else b-1 for b in con if b != k]
        for a, ed in enumerate(edges):
            u = sn if ed.u == dn else ed.u if ed.u < dn else ed.u-1
            v = sn if ed.v == dn else ed.v if ed.v < dn else ed.v-1
            edges[a] = Edge(u, v, ed.e)
        del edges[k]
        del nodes[dn]
    @staticmethod
    def _expand(ex):
        if ex is None:
            return set()
        elif isinstance(ex, str):
            return set(ex) if ex else {''}
        elif isinstance(ex, Combi):
            exe = Graph._expand(ex.e)
            return set.union(*(set(''.join(s) for s in
                combinations(e, len(e)-1)) for e in exe))
        exl = Graph._expand(ex.l)
        exr = Graph._expand(ex.r)
        if isinstance(ex, Union):
            return exl.union(exr)
        return {''.join(sorted((i+j))) for i, j in product(exl, exr)}

三角グラフで試してみます。

python3
g = Graph(3, [(0,1), (1,2), (2,0)])
print(g.spanning_tree())
>>>
['ab', 'ac', 'bc']

image

4点の完全グラフで試してみます。

python3
g = Graph(4, [(0,1), (1,2), (2,3), (3,0), (0,2), (1,3)])
print(g.spanning_tree())
>>>
['abc', 'abd', 'abf', 'acd', 'ace', 'acf', 'ade', 'aef',
 'bcd', 'bce', 'bde', 'bdf', 'bef', 'cdf', 'cef', 'def']

ちなみに、このPythonは85行ですが、Cで書くと500行で、C#で書くと330行ほどでした。

以上


  1. 辺を縮約するとは、その辺を長さ0にして、両端の点を1つに変える操作です。 

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8