Calcular a largura da árvore

14

A largura de árvore de um gráfico não direcionado é um conceito muito importante na Teoria dos Graficos. Foram inventadas toneladas de algoritmos de gráficos que são executados rapidamente se houver uma decomposição do gráfico com pequena largura de árvore.

A largura da árvore é geralmente definida em termos de decomposições de árvores. Aqui está um gráfico e uma decomposição em árvore desse gráfico, cortesia da Wikipedia:

insira a descrição da imagem aqui

Uma decomposição em árvore é uma árvore em que cada vértice está associado a um subconjunto dos vértices do gráfico original, com as seguintes propriedades:

  • Cada vértice no gráfico original está em pelo menos um dos subconjuntos.
  • Cada aresta no gráfico original possui ambos os vértices em pelo menos um dos subconjuntos.
  • Todos os vértices na decomposição cujos subconjuntos contêm um determinado vértice original estão conectados.

Você pode verificar se a decomposição acima segue essas regras. A largura de uma decomposição de árvore é do tamanho de seu maior subconjunto, menos um. Portanto, são dois para a decomposição acima. A largura da árvore de um gráfico é a menor largura de qualquer decomposição de árvore desse gráfico.


Nesse desafio, você receberá um gráfico conectado e não direcionado e deverá encontrar a largura da árvore.

Embora seja difícil encontrar decomposições de árvores, há outras maneiras de calcular a largura da árvore. A página da Wikipedia tem mais informações, mas um método de calcular a largura da árvore não mencionado lá, que é frequentemente usado em algoritmos para calcular a largura da árvore, é a largura mínima para pedidos de eliminação. Veja aqui um artigo sobre esse fato.

Em uma ordem de eliminação, elimina-se todos os vértices de um gráfico, um de cada vez. Quando cada vértice é eliminado, as arestas são adicionadas conectando todos os vizinhos desse vértice. Isso é repetido até todos os vértices desaparecerem. A largura da ordem de eliminação é o maior número de vizinhos que qualquer vértice que está sendo eliminado possui durante esse processo. A largura da árvore é igual ao mínimo em todos os pedidos da largura do pedido de eliminação. Aqui está um exemplo de programa usando esse fato para calcular a largura da árvore:

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))

Exemplos:

[(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

Você receberá o gráfico como entrada e deverá retornar a largura da árvore como saída. O formato de entrada é flexível. Você pode usar uma lista de arestas, um mapa de adjacência ou uma matriz de adjacência como entrada. Se você quiser usar outro formato de entrada, pergunte nos comentários. Você pode assumir que a entrada está conectada e pode construir essa suposição no seu formato de entrada, por exemplo, usando uma lista de arestas.

EDIT: Operações internas que calculam a largura da árvore não são permitidas. Peço desculpas por não especificar isso com antecedência.

O menor código vence.

isaacg
fonte
Como um gráfico é formalmente uma tupla (V,E), isso seria uma entrada válida?
ბიმო
@Bruce_Forte Absolutely.
Isaacg

Respostas:

7

Oitava, 195 bytes

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

Uma função que assume como entrada uma matriz de adjacência. Consome grande quantidade de memória e, portanto, é inútil se o número de vértices for maior que 10-12.

  • não há necessidade, no endfunctionentanto, ele deve ser adicionado ao tio.

Experimente online!

Ungolfed:

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
rahnema1
fonte
5

SageMath, 29 bytes não- concorrentes *

lambda L:Graph(L).treewidth()

* Esta resposta foi postada antes da mudança do OP da pergunta "Builtins is banned", então eu a tornei não-competitiva.

Demonstração Online!

rahnema1
fonte
1
Whelp. Isso não é inspirador. Infelizmente, terei que banir os builtins, desculpe por isso.
Isaacg
@isaacg Sem problemas. Eu tenho outra coisa na minha mão :)
rahnema1
@isaacg, esta resposta não viola uma brecha padrão?
PyRulez
rahnema1, veja minha edição. Builtins são banidos, portanto, essa resposta não é permitida. Por favor, excluí-lo ou marcá-lo como não concorrentes
isaacg
@isaacg Obrigado, marquei-o como não-competitivo.
rahnema1
5

Haskell (Lambdabot), 329 321 245 bytes

Aqui está minha solução, graças à flexibilidade da entrada que funciona em gráficos com gráficos contendo qualquer tipo de instância 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

Experimente online!

Versão não destruída:

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))
ბიმო
fonte