ツリー幅の計算


14

無向グラフのツリー幅は、グラフ理論で非常に重要な概念です。小さいツリー幅でグラフを分解すると、高速で実行される多数のグラフアルゴリズムが発明されました。

ツリー幅は、多くの場合、ツリー分解の観点から定義されます。ウィキペディアの好意によるグラフとそのグラフのツリー分解を以下に示します。

ここに画像の説明を入力してください

ツリー分解とは、各頂点が次のプロパティを持つ元のグラフの頂点のサブセットに関連付けられているツリーです。

  • 元のグラフのすべての頂点は、少なくとも1つのサブセットにあります。
  • 元のグラフのすべてのエッジには、少なくとも1つのサブセットに両方の頂点があります。
  • サブセットに特定の元の頂点が含まれる分解内のすべての頂点が接続されます。

上記の分解がこれらの規則に従っていることを確認できます。ツリー分解の幅は、その最大サブセットのサイズから1を引いたものです。したがって、上記の分解では2です。グラフのツリー幅は、そのグラフのツリー分解の最小幅です。


この課題では、接続された無向グラフが与えられ、そのツリー幅を見つける必要があります。

ツリーの分解を見つけるのは困難ですが、ツリー幅を計算する方法は他にもあります。ウィキペディアのページには詳しい情報がありますが、ツリー幅を計算するためのアルゴリズムでよく使用される、そこに記載されていないツリー幅の計算方法の1つは、最小消去順序幅です。この事実を使用した論文についてはこちらをご覧ください。

消去順序では、グラフのすべての頂点を一度に1つずつ消去します。各頂点が削除されると、その頂点のすべての隣接点を互いに接続するエッジが追加されます。これは、すべての頂点がなくなるまで繰り返されます。削除順序幅は、削除される頂点がこのプロセス中に持つ隣接の最大数です。treewidthは、消去順序幅のすべての順序の最小値に等しくなります。以下に、この事実を使用してツリー幅を計算するプログラムの例を示します。

import itertools
def elimination_width(graph):
    max_neighbors = 0
    for i in sorted(set(itertools.chain.from_iterable(graph))):
        neighbors = set([a for (a, b) in graph if b == i] + [b for (a, b) in graph if a == i])
        max_neighbors = max(len(neighbors), max_neighbors)
        graph = [edge for edge in graph if i not in edge] + [(a, b) for a in neighbors for b in neighbors if a < b]
    return max_neighbors

def treewidth(graph):
    vertices = list(set(itertools.chain.from_iterable(graph)))
    min_width = len(vertices)
    for permutation in itertools.permutations(vertices):
        new_graph = [(permutation[vertices.index(a)], permutation[vertices.index(b)]) for (a, b) in graph]
        min_width = min(elimination_width(new_graph), min_width)
    return min_width

if __name__ == '__main__':
    graph = [('a', 'b'), ('a', 'c'), ('b', 'c'), ('b', 'e'), ('b', 'f'), ('b', 'g'),
            ('c', 'd'), ('c', 'e'), ('d', 'e'), ('e', 'g'), ('e', 'h'), ('f', 'g'), ('g', 'h')]
    print(treewidth(graph))

例:

[(0, 1), (0, 2), (0, 3), (2, 4), (3, 5)]
1

[(0, 1), (0, 2), (1, 2), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (3, 4), (4, 6), (4, 7), (5, 6), (6, 7)]
2

[(0, 1), (0, 3), (1, 2), (1, 4), (2, 5), (3, 4), (3, 6), (4, 5), (4, 7), (5, 8), (6, 7), (7, 8)]
3

[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
4

入力としてグラフを受け取り、ツリー幅を出力として返す必要があります。入力形式は柔軟です。入力として、エッジのリスト、隣接マップ、または隣接行列を使用できます。別の入力形式を使用する場合は、コメントを入力してください。入力が接続されていると仮定し、エッジのリストを使用するなどして、入力形式にその仮定を組み込むことができます。

編集:ツリー幅を計算する組み込み操作は許可されていません。これを前もって指定していないことをおaびします。

最短のコードが優先されます。


グラフは正式にはタプル(V,E)なので、これは有効な入力でしょうか?
ბიმო

@Bruce_Forte絶対に。
-isaacg

回答:


7

オクターブ、195バイト

function x=F(a)r=rows(a);P=perms(s=1:r);x=r;for m=s;b=a;n=0;for z=P(m,:);(T=sum(b)(z))&&{b|=(k=accumarray(nchoosek(find(b(z,:)),2),1,[r r]))|k';n=max(T,n);b(z,:)=0;b(:,z)=0}{4};end;x=min(x,n);end

入力として隣接行列を受け取る関数。大量のメモリを消費するため、頂点の数が10〜12を超える場合は役に立ちません。

  • endfunctionただし、tioに追加する必要はありません。

オンラインでお試しください!

ゴルフをしていない:

function min_width = treewidth(graph_adj)
    Nvertices = rows(graph_adj)
    Permutations = perms(1:Nvertices);                                                            % do not try it for large number of vertices
    min_width = Nvertices;
    for v = 1:Nvertices;
        new_graph=graph_adj;
        max_neighbors=0;
        for p = Permutations(v,:)
            Nneighbors=sum(new_graph)(p);
            if(Nneighbors)>0
                connection=accumarray(nchoosek(find(new_graph(p,:)),2),1,[Nvertices Nvertices]);  % connect all neighbors
                new_graph|=connection|connection';                                                % make the adjacency matrix symmetric
                new_graph(p,:)=0;new_graph(:,p)=0;                                                % eliminate the vertex
                max_neighbors=max(Nneighbors,max_neighbors);
            end
        end
        min_width=min(min_width,max_neighbors);
    end
end

5

SageMath、29バイトの非競合*

lambda L:Graph(L).treewidth()

*この回答は、OPが「ビルチンは禁止されている」という質問を変更する前に投稿したため、競合しないようにしました。

オンラインデモ!


1
わあ それは意外なことです。残念ながら、私はビルトインを禁止しなければなりません、それについて申し訳ありません。
isaacg

@isaacg問題ありません。私の手にはもう一つあります :)
rahnema1

@isaacgは、この回答が標準の抜け穴に違反しないのですか?
-PyRulez

rahnema1、私の編集を参照してください。ビルトインは禁止されているため、この回答は許可されていません。削除するか、非競合としてマークしてください
-isaacg

@isaacgありがとう、非競合としてマークしました。
rahnema1

5

Haskell(Lambdabot)、329 321 245バイト

ここに私のソリューションがあります。入力の柔軟性のおかげで、それはのインスタンスである任意のタイプを含むグラフを持つグラフで動作しますEq

(&)=elem
l=length
t n g s=last$minimum[max(t n g b)$t(n++b)g$s\\b|b<-filterM(\_->[0>1,1>0])s,l b==div(l s)2]:[l[d|d<-fst g,not$d&n,d/=s!!0,(d&)$foldr(\x y->last$y:[x++y|any(&y)x])[s!!0]$join(>>)[e|e<-snd g,all(&(s!!0:d:n))e]]|1==l s]
w=t[]<*>fst

オンラインでお試しください!

ゴルフされていないバージョン:

type Vertex a = a
type Edge a   = [Vertex a]
type Graph a  = ([Vertex a],[Edge a])

vertices = fst
edges = snd

-- This corresponds to the function w
treeWidth :: (Eq a) => Graph a -> Int
treeWidth g = recTreeWidth g [] (vertices g)

-- This is the base case (length s == 1) of t
recTreeWidth graph left [v] =
    length [ w | w <- vertices graph
               , w `notElem` left
               , w /= v
               , w `elem` reachable (subGraph w)
           ]

  where subGraph w = [ e | e <- edges graph, all (`elem` v:w:left) e ]

        reachable g = foldr accumulateReachable [v] (g>>g)
        accumulateReachable x y = if any (`elem` y) x
                                  then x++y
                                  else y

-- This is the other case of t
recTreeWidth graph left sub =
  minimum [ comp sub' | sub' <- filterM (const [False,True]) sub
                      , length sub' == div (length sub) 2
          ]

  where comp b = max (recTreeWidth graph left b)
                     (recTreeWidth graph (left++b) (sub\\b))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.