Arborescências Os algoritmos de busca sugerem o seguinte conceito, que pode ser entendido como uma generalização do conceito de caminho simples. Uma arborescência é uma sequência da forma (v[0], a[1], v[1], a[2], v[2], ..., a[k], v[k]), onde v[0], ..., v[k] são vértices distintos dois a dois, a[1], ..., a[k] são arcos distintos dois a dois, e, para cada j, a[j] é um arco da forma (v[i], v[j], com i < j). Para cada j, dizemos que a ponta inicial de a[j] é o predecessor (pai) do vértice v[j]. O primeiro vértice da sequência, v[0], é a raiz da arborescência. O número de j é o índice ou posição do vértice v[j] na arborescência. Para qualquer vértice w de uma arborescência, alguma sequência da arborescência é um caminho simples que começa na raiz v[0] da arborescência e vai até w: trata-se da sequência (v[0], ..., pppw, ppw, pw, w), onde pw é o predecessor de w, ppw é o predecessor de pw, etc. Uma arborescência é um dígrafo em que: Não existem vértices com grau de entrada1 maior que 1, Existe exatamente um vértice com grau de entrada 0, Cada um dos vértices é término de um caminho com origem no único vértice que tem grau de entrada nulo. O único vértice com grau de entrada nulo é a raiz da arborescência. Todos os vértices diferentes da raiz têm grau de entrada igual a 1. Arborescências são conhecidas por vários outros sinônimos: árvore radicada (rooted tree), árvore enraizada, árvore dirigida (directed tree), árvore de busca (search tree). Exemplo 1: o conjunto de arcos abaixo define uma arborescência com raiz 0. 0–1 1–2 1–3 3–4 3–5 0–6 6–7 6–8 6–9 0–10 10–11 Exemplo 2: digamos que os vértices do grafo são 0, 1, 2, 3, 4, 5, 6, 7, 8 e os arcos são a, b, c, d, e, f, g, h, i, j. Ponta inicial 0 0 2 6 6 6 1 1 3 8 a b c d e f g h i j Arco 0 2 6 0 2 4 3 3 7 5 Ponta final A sequencia (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. O caminho que vai de 6 a 2 na arborescência é (6, d, 0, b, 2). 1 O grau de entrada (indegree) de um vértice w num dígrafo é o número de arcos que têm ponta final w. 1 Para todo vértice v de uma arborescência, existe um e apenas um caminho simples que começa na raiz e termina em v. Toda arborescência com v vértices tem exatamente v–1 arcos. Todo vértice w de uma arborescência, exceto a raiz, tem um pai. Trata-se do único vértice v tal que v-w é um arco. Para qualquer vértice v de uma arborescência, cada vértice adjacente a v é um filho de v. Vértice sem filhos são chamados folha (leaves) ou vértices externos (external nodes). Em uma arborescência, um vértice v é ancestral de um vértice w se o único caminho que vai da raiz até w passa por v. Nessas mesmas condições, diz-se que w é um descendente de v. Todo vértice é descendente da raiz. Um ancestral próprio de um vértice w é qualquer ancestral de w exceto o próprio w. Um descendente próprio de um vértice v é qualquer descendente de v que seja diferente de v. Para qualquer vértice v da arborescência, seja D(v) o conjunto de todos os descendentes de v. O conjunto de todos os arcos que tem ambas as pontas em D(v) define uma arborescência com raiz v. Dizemos que esta é a subarborescência com raiz. Representação de arborescências Para representar uma arborescência basta registrar o predecessor de cada vértice. v[0] e 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: v[j]->pred é o predecessor de v[j]. Podemos convencionar que v[0]->pred vale v[0]. Para todo w fora da arborescência, w->pred vale NULL. Varredura em preordem Uma varredura em preordem de uma arborescência é uma maneira sistemática de visitar os vértices da arborescência. Uma descrição recursiva da varredura em preordem: 1. Visite a raiz, 2. Para cada filho v da raiz, faça uma varredura em preordem da subarborescência com raiz v. Quando a busca em profundidade é aplicada a uma arborescência com raiz 0 ela faz uma varredura em preordem: static int conta, pre[maxV]; A função preordem recebe uma arborescência G com raiz r e visita os vértices de G em preordem. A ordem em que os vértices são visitados é registradas no vetor pre. void preordem(Grafo G, Vertice r){ conta = 0; visitaR(G, r); } void visitaR(Grafo G, Vertice v){ link p; 2 pre[v] = conta++; for (p = G->adj[v]; p != NULL; p = p->prox) visitaR(G, p->w); } Vetor de pais O vetor de pais de uma arborescência é um vetor (parent), tal que, para cada vértice w distinto da raiz, parent[w] é o pais de w. Exemplo: eis o vetor de pais da arborescência do exemplo 1. v 0 1 2 3 4 5 6 7 8 9 10 11 parent[v] 0 0 1 1 3 3 0 6 6 6 0 10 Dado o vetor de pais, parent, de uma arborescência, é fácil determinar o caminho que leva da raiz a um dado vértice: basta inverter a sequência impressa pelo seguinte fragmento de código: Vertice x; for (x = v; parent[x] != x; x = parent[x]) printf("%d-", x); printf("%d", x); Exercícios 1. Quantos arcos tem uma arborescência com t vértices? 2. Escreva uma função que decida se um dado grafo é ou não uma arborescência. Em caso afirmativo, a função deve devolver a raiz da arborescência. Em caso negativo, a função deve devolver -1. 3. Escreva uma função que construa uma arborescência aleatória com V vértices. Cada vértice deve ter no máximo f filhos. 4. Visite os vértices da arborescência abaixo em preordem. 0–1 1–2 1–3 3–4 3–5 0–6 6–7 6–8 6–9 0–10 10–11 Registre a ordem em que os vértices são visitados num vetor pré indexado pelos vértices. 5. Considere a arborescência descrita abaixo. 0–1 1–2 1–3 0–4 4–5 4–6 Qual a raiz? Considere a seguinte numeração dos vértices: v 0 1 2 3 4 5 6 7 pre[v] 0 1 2 4 3 4 5 6 Essa numeração corresponde a uma varredura em preordem? 6. Defina varredura em pós-ordem de uma arborescência. Escreva uma função que execute a varredura de uma arborescência em pós-ordem. 3