Sunteți pe pagina 1din 8

Python Help

PUBLICADO POR

ELIAS DORNELES
POSTADO NO

2 DE JANEIRO DE 2015
PUBLICADO EM

UNCATEGORIZED
COMENTÁRIOS

DEIXE UM COMENTÁRIO

Colorindo grafos — ou, como escolher cores
para um mapa automaticamente

Imagine que você tenha um mapa com algumas áreas delimitadas (países, estados, etc), e
deseja colorir cada área com uma cor diferente das áreas vizinhas.

Para um mapa pequeno (digamos, com no máximo 7 áreas), você pode simplesmente atribuir
uma cor para cada área e se dar por satisfeito. Mas para um mapa com muitas áreas, você
provavelmente quer usar um número mínimo de cores: muitas cores diferentes vão deixar o
mapa confuso.

Esse é um típico problema a ser resolvido com coloração de grafos
(https://pt.wikipedia.org/wiki/Colora%C3%A7%C3%A3o_de_grafos), uma área da ciência da
computação explorada ativamente ainda hoje (http://scholar.google.com.br/scholar?
as_ylo=2014&q=graph+coloring+articles). Existe uma gama de problemas que podem ser
resolvidos com técnicas desse tipo — outro exemplo popular é o quebra‑cabeça Sudoku
(https://pt.wikipedia.org/wiki/Sudoku).

Hoje vamos mostrar um exemplo resolvendo esse problema usando um algoritmo simples
para achar a configuração de cores mínima.

Veja a representação visual do grafo para colorir um mapa dos países da América do Sul:
(https://pythonhelp.files.wordpress.com/2015/01/south_america.png)
Colorização do grafo dos países da América do Sul usando 4 cores

Ao fim desse post, você terá aprendido a gerar colorizações e imagens para grafos como esse. 

Escolhendo uma representação para o grafo

Existem várias maneiras de representar grafos com diferentes relações custo‑benefício por
operação, você escolhe a mais apropriada dependendo do tipo de grafo
(https://pt.wikipedia.org/wiki/Teoria_dos_grafos#Tipos_de_grafos) e do problema que você vai
resolver. As duas representações mais comuns são matriz de adjacências
(http://pt.wikipedia.org/wiki/Matriz_de_adjac%C3%AAncia) e lista de adjacências
(http://pt.wikipedia.org/wiki/Lista_de_adjac%C3%AAncia) — as demais são geralmente
variações dessas.

Para o nosso problema, vamos simplesmente usar um dicionário Python mapeando os nós
adjacentes:

grafo = {
    'A': ['B', 'C'],
    'B': ['A'],
    'C': ['A'],
}

Essa representação contém um pouco de duplicação, mas é interessante porque deixa trivial
obter os nós do grafo fazendo grafo.keys() e consultar os nós adjacentes de um nó com grafo[nó].
Implementando o algoritmo

Vamos usar um algoritmo de colorização de grafos simples: começamos testando uma
configuração com N cores, e caso detectamos que não seja possível, tentamos com N+1.
Repetimos isso até encontrarmos uma solução ou atingirmos o limite de tentativas válidas.

Veja o código:

1 def try_coloring(graph, num_colors):
2     assert num_colors >= 0, "Invalid number of colors: %s" % num_colors
3     colors = {}
4  
5     def neighbors_have_different_colors(nodes, color):
6         return all(color != colors.get(n) for n in nodes)
7  
8     for node, adjacents in graph.items():
9  
10         found_color = False
11  
12         for color in range(num_colors):
13             if neighbors_have_different_colors(adjacents, color):
14                 found_color = True
15                 colors[node] = color
16                 break
17  
18         if not found_color:
19             return None
20  
21     return colors
22  
23  
24 def find_graph_colors(graph):
25     for num_colors in range(1, len(graph)):
26         colors = try_coloring(graph, num_colors)
27         if colors:
28             return colors

Temos duas funções:

try_coloring() recebe um grafo e um número de cores para tentar. Ela tenta construir uma
configuração de cores para o grafo, atualizando um dicionário que mapeia as cores para cada
nó. Caso encontre uma configuração de cor válida a função devolve o dicionário; caso
contrário, devolve None.

find_graph_colors() recebe um grafo e simplesmente aciona a função try_coloring() com
diferentes valores para o número de cores, até que encontre uma configuração válida (ou
esgote as tentativas). Também devolve a configuração encontrada ou None caso não for
possível.
Coloque o código acima em um arquivo graph_coloring.py, e experimente chamar a função
try_coloring() usando o shell para o nosso grafo exemplo:

1 >>> from graph_coloring import *
2 >>> grafo = {
3 ...     'A': ['B', 'C'],
4 ...     'B': ['A'],
5 ...     'C': ['A'],
6 ... }
7 >>> try_coloring(grafo, 1)
8 >>> try_coloring(grafo, 2)
9 {'A': 0, 'C': 1, 'B': 1}

Repare como a tentativa de colorir com apenas uma cor não funciona, mas a segunda já fornece
uma configuração de cores válida para o nosso pequeno grafo. A propósito, a sessão acima está
suplicando para ser usada como doctest
(https://pythonhelp.wordpress.com/2012/06/04/testando‑codigo‑doctest/) para a função
try_coloring(). 

Bem, esse algoritmo é um pouco ingênuo e definitivamente não‑otimizado, pois envolve um
certo retrabalho a cada vez que tenta uma configuração de cores nova. Isso não é um problema
para os grafos que vamos usar, então se preocupar com performance agora é desnecessário,
mas é legal perceber onde podemos mexer caso seja necessário melhorá‑lo.

Para o caso específico de mapas, existe um teorema afirmando que é sempre possível resolver
esse problema usando 4 cores (https://pt.wikipedia.org/wiki/Teorema_das_quatro_cores). Isso
funciona porque os grafos que representam mapas são sempre grafos planares
(https://pt.wikipedia.org/wiki/Grafo_planar), isto é, podem ser representados num plano sem
nenhuma aresta se cruzando — o que reduz as possibilidades de conexões entre os vértices.

Gerando uma representação visual

Uma maneira interessante de validar o nosso trabalho acima (e mais divertida do que usando
testes de unidade) é gerar uma representação visual do grafo com as respectivas cores.

Para isso, vamos usar a suite open source de software para visualização de grafos Graphviz
(http://www.graphviz.org) (instale no Debian/Ubuntu/Mint com sudo apt‑get install graphviz;
há pacotes também para Windows (http://www.graphviz.org/Download_windows.php) e Mac
(http://www.graphviz.org/Download_macos.php)).

Iniciação ao uso do GraphViz

O Graphviz usa uma linguagem própria para descrever grafos chamada DOT
O Graphviz usa uma linguagem própria para descrever grafos chamada DOT
(http://www.graphviz.org/content/dot‑language), que você pode explorar usando a aplicação
GraphViz Workspace (http://graphviz‑dev.appspot.com).

Você também pode criar um arquivo.dot manualmente usando seu editor de texto favorito, e
testar a saída com o comando dot. Crie um arquivo com o seguinte conteúdo que descreve
nosso grafo de exemplo:

graph {
    A ‐‐ B;
    A ‐‐ C;
}

Compile uma imagem PNG com o comando dot:

dot ‐Tpng ‐o resultado.png arquivo.dot

Se você tem o ImageMagick (http://www.imagemagick.org) instalado (no
Debian/Ubuntu/Mint: sudo apt‑get install imagemagick), pode visualizar a imagem
diretamente fazendo pipe do comando dot para o comando display
(http://www.imagemagick.org/script/display.php):

dot ‐Tpng arquivo.dot | display

(https://pythonhelp.files.wordpress.com/2015/01/grafo1.png)

Para gerarmos grafos coloridos, vamos gerar uma representação do grafo que lista as
conexões/arestas do grafo e imprime a configuração de cores por último, semelhante ao
exemplo seguinte:
graph {
    A ‐‐ B;
    A ‐‐ C;
    A [style="filled",fillcolor="#ffffb2"];
    B [style="filled",fillcolor="#fd5d3c"];
    C [style="filled",fillcolor="#41b6c4"];
}

(https://pythonhelp.files.wordpress.com/2015/01/grafo2.png)

Para uma documentação mais completa sobre como usar a ferramenta dot para desenhar
grafos, veja o documento Drawing graphs with dot (http://www.graphviz.org/pdf/dotguide.pdf).

Gerando a representação DOT

Eis a nossa gloriosa função para gerar a representação do nosso grafo usando a linguagem
DOT:

1 PALETTE = ('#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462
2            '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f
3  
4  
5 def generate_dot(graph, colors=None, palette=PALETTE):
6     assert len(set(colors.values())) <= len(palette), (
7         "Too few colors in the palette")
8  
9     edges = []
10     for node, adjacents in graph.items():
11         for n in adjacents:
12             if not ((node, n) in edges or (n, node) in edges):
13                 edges.append((node, n))
14  
15     result = 'graph {\n'
16     result += ''.join('    "{}" ‐‐ "{}";\n'.format(*e) for e in edges)
17  
18     if colors:
19         style = '    "{}" [style="filled",fillcolor="{}",shape=box];\n'
20         result += ''.join(style.format(node, palette[color])
21                           for node, color in colors.items())
22     result += '}\n'
23  
24     return result

A função recebe um grafo na representação de dicionário que combinamos no começo, um
dicionário mapeando os números das cores para os nós do grafo (opcional) e uma paleta de
cores (usada para obter as cores propriamente ditas, indexadas pelos números do dicionário de
cores).

Nota: as cores da paleta fornecida são de uma das paletas disponíveis no site do GraphViz
(http://www.graphviz.org/doc/info/colors.html#brewer) baseadas no fantástico trabalho da
Cynthia Brewer (http://colorbrewer2.org).

Exemplo de saída da função generate_dot() para o nosso grafo de exemplo, usando uma paleta
própria:

1 >>> from graph_coloring import *
2 >>> grafo = {
3 ... 'A': ['B', 'C'],
4 ... 'B': ['A'],
5 ... 'C': ['A'],
6 ... }
7 >>> colors = try_coloring(grafo, 2)
8 >>> colors
9 {'A': 0, 'C': 1, 'B': 1}
10 >>> print(generate_dot(grafo, colors, palette=['red', 'yellow']))
11 graph {
12  "A" ‐‐ "B";
13  "A" ‐‐ "C";
14  "A" [style="filled",fillcolor="red",shape=box];
15  "C" [style="filled",fillcolor="yellow",shape=box];
16  "B" [style="filled",fillcolor="yellow",shape=box];
17 }

Gerando a imagem com GraphViz para essa mesma saída, obtemos a imagem:

(https://pythonhelp.files.wordpress.com/2015/01/grafo3.png)

Finalizando
Veja o exemplo completo aqui (https://gist.github.com/eliasdorneles/f3e4a38068edd5f33608)
(baixar graph_coloring.py
(https://gist.github.com/eliasdorneles/f3e4a38068edd5f33608/raw/d647bfeb00b99746631c47defa
a2ed2f48ffdc37/graph_coloring.py)), contendo o código mostrado nesse post mais a geração do
grafo do mapa da América Latina mostrado no começo do post.

Desafio: experimente rodar com Python 2 e Python 3, tem uma sutil diferença no resultado.
Consegue sacar o quê e por quê? Poste nos comentários. 

About these ads


(http://wordpress.com/about-
these-ads/)
Blog no WordPress.com. | O tema Zoren.

Seguir

Seguir “Python Help”

Crie um site com WordPress.com

S-ar putea să vă placă și