Arborescências em grafos

Os algoritmos de busca sugerem o seguinte conceito, que pode ser entendido como uma generalização do conceito de caminho simples. Uma arborescência (= rooted directed treesearch treeárvore de busca) é uma sequência da forma

(v0, a1, v1, a2, v2, … , ak, vk)

onde v0, … , vk são vértices distintos dois a dois, a1, … , ak são arcos distintos dois a dois, e, para cada jaj é um arco da forma (vi, vj) tal que

i < j.

Para cada j, dizemos que a ponta inicial de aj é o predecessor (ou pai) do vértice vj. O primeiro vértice da sequência, v0, é a raiz da arborescência. O número j é o índice ou posição do vértice vj na arborescência.

Exemplo 1: No grafo definido no exemplo 1 do capítulo O que é um grafo?, a sequência (6, d, 0, f, 4, b, 2) é uma arborescência com raiz 6. O predecessor do vértice 2 é o vértice 0. O índice de 2 é 3 e o índice de seu predecessor é 1.

Para qualquer vértice w de uma arborescência, alguma subsequência da arborescência é um caminho simples que começa na raiz v0 da arborescência e vai até w: trata-se da subsequência (v0, … , pppw, ppw, pww), onde pw é o predecessor de wppw é o predecessor de pw, etc.

Exemplo 2: No exemplo acima, o caminho que vai de 6 a 2 na arborescência é (6, d, 0, b, 2).

Exercícios 1

  1. Quantos arcos tem uma arborescência com t vértices?
  2. [Importante!] Suponha que (v0, a1, v1, a2, v2, … , ak, vk), é uma arborescência. Quantos arcos da arborescência entram em v0? Quantos arcos da arborescência entram em vj, para j = 1, … , k?

Representação de arborescências

Como representar uma arborescência usando as convenções do SGB? Basta registrar o predecessor de cada vértice. É claro que v0 bem como os vértices fora da arborescência não têm predecessor. O predecessor de cada vértice pode ser confortavelmente acomodado em um utility field, digamos pred; assim,

vjpred

é o predecessor de vj. Podemos convencionar que v0pred vale v0 e que wpred vale NULL para todo w fora da arborescência.

É claro que essa representação envolve uma certa perda de informação: se, por exemplo, o grafo tem dois arcos da forma (v0, v1), não saberemos qual dos dois faz parte da arborescência. A prática mostra, entretanto, que é possível conviver com a ambiguidade.

Exercícios 2

  1. Suponha que cada vértice de um grafo do SGB tem um campo pred do tipo Vertex*. Escreva uma função C que diga se pred define uma arborescência ou não. A função deve devolver a raiz da arborescência se a resposta for afirmativa e NULL se a resposta for negativa.
  2. Suponha dado um certo conjunto B de arcos de um grafo (digamos que apertence ≡ 1 se e só se a está em B). Escreva uma função C que decida se B é o conjunto de arcos de uma arborescência. A função deve definir os predecessores dos vértices em caso afirmativo.

Arborescências maximais e territórios

Uma arborescência é maximal se não existe arco no grafo com ponta inicial na arborescência e ponta final fora dela. É fácil perceber que toda arborescência maximal com raiz r

contém todos os vértices do território de r.

É fácil deduzir daí que qualquer arborescência maximal com raiz r tem exatamente T∣ − 1 arcos, onde T é o território de r.

Exemplo 3: Suponha que os vértices do grafo são cidades, que os arcos são estradas de mão única, e que r é a cidade principal. Suponha que as estradas não são pavimentadas e que desejamos pavimentar o menor número possível delas de modo que seja possível viajar de r a qualquer outra cidade (mas não necessariamente de volta) por estradas pavimentadas.

Para evitar confusão com a terminologia de outros textos, convém esclarecer que em grafos simétricos com um só componente as arborescências maximais são conhecidas como árvores geradoras (= spanning trees).

Exercícios 3

  1. Suponha que B é o conjunto de arcos de uma arborescência maximal com raiz r. Mostre que B é mínimo com relação à seguinte propriedade: para qualquer vértice v no território de r, existe um caminho de r a v que só usa arcos de B.

Construção de arborescências maximais

A seguinte variante do algoritmo genérico de busca constrói uma arborescência maximal com raiz r:

rpred = r

declare r ativo

enquanto existe vértice ativo faça

escolha um vértice ativo v

para cada arco a saindo de v faça

w = atip

se wpred não está definido então

wpred = v

declare w ativo

declare v inativo

A análise deste algoritmo prova que toda arborescência maximal com raiz r contém todos os vértices do território de r.

Dependendo de como o conjunto de vértices ativos é administrado, teremos uma arborescência de busca em largura ou uma arborescência de busca em profundidade (ou uma arborescência de busca que não é de nenhum dos tipos anteriores).

Exercícios 4

  1. Implemente o algoritmo acima em C. Use a estrutura de dados do SGB. Use uma lista encadeada para administrar o conjuntos de vértices ativos.
  2. Repita o exercício anterior de modo a construir uma arborescência de busca em largura. Registre, para cada vértice, o seu índice (= posição) na arborescência; use um utility field para isso.
  3. Repita o exercício anterior de modo a construir uma arborescência de busca em profundidade. Registre, para cada vértice, o seu índice (= posição) na arborescência; use um utility field para isso.

Exercícios 5

  1. [Arborescências Maximais de Comprimento Mínimo] Suponha que cada arco do grafo tem um comprimento, que é um número inteiro não-negativo. (Na estrutura de dados do SGB, o comprimento de um arco a é alen.) O comprimento de uma arborescência é a soma dos comprimentos dos seus arcos.

    Problema: Dado um vértice r, encontrar, dentre as arborescências maximais com raiz r, uma que tenha comprimento mínimo.

    Invente um algoritmo que resolva o problema. Escreva o algoritmo em C, usando as estruturas de dados do SGB.

  2. [Bem mais fácil que o anterior] Escreva uma função C que resolva o problema da arborescência maximal de comprimento mínimo (veja exercício anterior) no caso em que o grafo é simétrico e o comprimento de cada arco da forma (v, w) é igual ao comprimento do mais curto dos arcos da forma (w, v).

    O módulo MILES_SPAN do SGB apresenta várias soluções para este problema; as soluções apresentadas lá usam estruturas de dados sofisticadas para tornar o algoritmo mais rápido. O presente exercício não tem esta preocupação: basta escrever uma função simples e razoavelmente eficiente.