Google Classroom

Unidade 5 · Grafos e árvores · laboratório

Laboratório de árvores

Toque nos nós de uma árvore para ver pai, filhos, irmãos, ancestrais, descendentes, a subárvore de cada nó e o menor ancestral comum de dois nós, e veja os níveis, a altura e a aridade da árvore. Depois, acompanhe os três percursos, monte árvores binárias estritas, completas e perfeitas e use uma árvore de busca binária: busca, inserção, remoção e balanceamento, cada operação animada passo a passo. Cada algoritmo vem com o código em Python ao lado. Mais abaixo, para cada capítulo, estão os conceitos e as definições, como vistos em aula.

  1. 01NotaçõesÁrvore livre e enraizada, pai, filho, irmão, ancestral, folha, subárvore, menor ancestral comum, aridade, profundidade, nível e altura.
  2. 02PercursosPré-ordem, in-ordem e pós-ordem, passo a passo, com o código, e a sua vez de percorrer.
  3. 03Tipos de árvore bináriaEstrita, completa e perfeita, montando a árvore nó a nó.
  4. 04Árvore de busca bináriaBusca, inserção, remoção e balanceamento, pela mediana e com rotações, cada operação animada e com o código.

Laboratório

    Para consulta

    Definições

    Os termos de cada capítulo do laboratório, cada um com um exemplo desenhado, como na Leitura 08. A aba acompanha o capítulo aberto no laboratório.

    Os nomes das partes de uma árvore e de quem é quem nela, como na Leitura 08. A profundidade é de cada nó; a altura é da árvore inteira.

    Árvore

    Dez vértices e nove arestas, sem nenhum ciclo simples.

    Um grafo conexo e sem ciclos simples. Com n vértices, tem exatamente n − 1 arestas, e entre dois vértices quaisquer há um único caminho simples.

    Árvore livre e enraizada

    A mesma árvore: livre, à esquerda, e enraizada em B, à direita.

    Na árvore livre, nenhum vértice é especial. Ao escolher uma raiz, cada aresta passa a ligar um pai a um filho, do lado da raiz para longe dela. Toda árvore enraizada tem uma raiz, menos a árvore vazia, que não tem nenhum nó.

    Pai, filho e irmão

    Para B, em vermelho: o pai é R; os filhos, E e F; os irmãos, C e D.

    Todo nó, menos a raiz, tem um único pai: o vizinho dele no caminho até a raiz. Os outros vizinhos são os filhos. Irmãos são filhos do mesmo pai.

    Ancestral e descendente

    Os ancestrais de D, circulados, são A e R; os descendentes, em rosa, são E e F.

    Os ancestrais de um nó são o pai, o pai do pai e assim por diante, até a raiz. Os descendentes são os filhos, os filhos dos filhos e assim por diante, até as folhas.

    Folha e nó interno

    As folhas, em rosa: C, E, F e G. R, A, B e D não são folhas: são os nós internos.

    Folha é um nó sem filhos. Nó interno é qualquer nó que não seja folha. Numa árvore de um nó só, a raiz é uma folha.

    Subárvore

    A subárvore de A, dentro do contorno: A e os seus descendentes, C, D, E e F. É uma árvore, com raiz A.

    Cada nó, junto com todos os seus descendentes, forma uma árvore com raiz nele: a subárvore daquele nó. As subárvores dos filhos de um nó não têm nó em comum, e tudo o que vale para árvores vale para cada subárvore.

    Menor ancestral comum

    C e E, em vermelho, sobem até A, circulado: o menor ancestral comum dos dois. R, tracejado, também é ancestral comum, mas fica mais acima.

    Entre os ancestrais comuns de dois nós u e v, o menor ancestral comum é o mais fundo, o mais distante da raiz. Aqui, cada nó também conta como ancestral de si mesmo: se u é ancestral de v, o menor ancestral comum é o próprio u. O único caminho entre u e v sobe de u até ele e depois desce até v.

    Aridade

    O número no canto é quantos filhos o nó tem. R, com 3, tem mais filhos que qualquer outro nó: a aridade da árvore é 3.

    A aridade de uma árvore enraizada é o maior número de filhos de um nó. Uma árvore em que cada nó tem no máximo m filhos é m-ária; a árvore binária é a 2-ária. A aridade depende da raiz escolhida: no laboratório, trocar a raiz pode mudá-la.

    Profundidade

    O número no canto é a profundidade. F tem profundidade 3: o caminho de F até a raiz R, em vermelho, tem 3 arestas.

    A profundidade de um nó é o número de arestas no caminho da raiz até ele. A raiz tem profundidade 0, os filhos dela, 1, e assim por diante.

    Nível

    O nível 2, em negrito, reúne C, D e E. C e E estão no mesmo nível, mas não são irmãos: os pais são A e B.

    O nível k reúne os nós de profundidade k (alguns livros começam a contar do 1). Nós do mesmo nível não são necessariamente irmãos; quando os pais são diferentes, a Leitura 08 sugere chamá-los de primos.

    Altura da árvore

    F é o nó mais fundo, com profundidade 3: a altura da árvore é 3.

    A altura é da árvore, não do nó: a maior profundidade entre os seus nós, isto é, o número do último nível. Uma árvore de um nó só tem altura 0; a árvore vazia, −1.

    Centro da árvore

    O número no canto é a altura que a árvore teria com raiz naquele vértice. C, com 2, é o centro: dele, o vértice mais longe está a duas arestas.

    O centro de uma árvore reúne os vértices que ficam mais perto de todos os outros: os que tornam menor a distância até o vértice mais longe. Escolhido como raiz, um vértice do centro deixa a árvore com a menor altura possível. Toda árvore tem um centro com um ou dois vértices.

    Os três jeitos de percorrer uma árvore binária. A árvore é a mesma nos três desenhos; o número no canto é a ordem em que cada nó é anotado. Todos visitam a subárvore esquerda inteira antes da direita; o que muda é quando a raiz de cada subárvore é anotada.

    Pré-ordem

    F, B, A, D, C, E, G, I, H.

    Anota a raiz da subárvore; depois percorre a subárvore esquerda e, por fim, a direita: raiz, esquerda, direita.

    In-ordem (em ordem)

    A, B, C, D, E, F, G, H, I: em ordem alfabética, porque esta é uma árvore de busca.

    Percorre a subárvore esquerda, anota a raiz e depois percorre a direita: esquerda, raiz, direita. Numa árvore de busca binária, dá as chaves em ordem crescente.

    Pós-ordem

    A, C, E, D, B, H, I, G, F: a raiz F fica por último.

    Percorre a subárvore esquerda, depois a direita, e só então anota a raiz: esquerda, direita, raiz.

    Formas especiais de árvore binária, das menos às mais exigentes, e duas que aparecem na prática: a degenerada, o pior caso da árvore de busca, e a completa guardada num vetor.

    Árvore binária

    C tem só o filho direito; D, E e F não têm filhos.

    Árvore enraizada em que cada nó tem no máximo dois filhos, chamados esquerdo e direito. A subárvore esquerda de um nó é a subárvore do filho esquerdo dele; a direita, a do filho direito.

    Estrita

    Três nós internos (A, B e E) e quatro folhas, em rosa.

    Todo nó tem 0 ou 2 filhos. Numa árvore estrita, o número de folhas é sempre o número de nós internos mais 1.

    Completa

    Os níveis 0 e 1 estão cheios; no nível 2, só falta a última casa, tracejada.

    Todos os níveis estão cheios, menos talvez o último, que se enche da esquerda para a direita. Dito de outro modo: lendo as casas nível por nível, cada nível da esquerda para a direita, nenhuma casa vazia aparece antes de um nó.

    Perfeita

    Altura 2, com todos os níveis cheios.

    Todos os níveis estão cheios. Com altura h, a árvore tem 2h+1 − 1 nós, e 2h deles são folhas.

    Degenerada

    Cada nó tem um filho só: os quatro nós formam um caminho, e a altura é 3 = n − 1.

    Árvore binária em que cada nó tem no máximo um filho: ela é um caminho, e a altura chega a n − 1, a maior possível com n nós. É o que acontece com a árvore de busca quando as chaves entram em ordem crescente, como no capítulo 4: uma busca pode precisar de n comparações, como numa lista.

    Árvore completa num vetor

    Lendo por níveis, cada nó ganha uma posição. Os filhos de B, na posição 2, estão nas posições 4 e 5.

    Uma árvore completa cabe num vetor sem casas vazias: lendo os nós nível por nível, a partir da posição 1, o nó da posição i tem os filhos nas posições 2i e 2i + 1 e o pai na posição ⌊i/2⌋. Assim, não é preciso guardar ponteiros. É desse jeito que se guarda um heap, a estrutura das filas de prioridade.

    Árvore binária com as chaves em ordem: menores à esquerda, maiores ou iguais à direita. Cada operação desce da raiz comparando, um nível por vez. Depois vêm a altura das subárvores e o fator de balanceamento, que mostram quando a árvore pende para um lado, e as duas formas de balancear a árvore que aparecem na Leitura 08: com rotações e pela mediana.

    Árvore de busca binária

    À esquerda de 50, só chaves menores; à direita, maiores ou iguais.

    Árvore binária com uma chave em cada nó, maior que todas as chaves da subárvore esquerda e menor que (ou igual a) todas as da direita: chaves iguais vão para a direita. A in-ordem lista as chaves em ordem crescente.

    Busca

    Buscar 60: 60 > 50, direita; 60 < 70, esquerda; achou em 3 comparações.

    Começa na raiz e, a cada nó, compara: chave menor desce para a esquerda; maior, para a direita. Para ao achar a chave ou ao cair numa casa vazia, depois de no máximo altura + 1 comparações.

    Inserção

    45 desce por 50, 30 e 40 e entra à direita de 40.

    Desce como na busca, com as chaves iguais seguindo para a direita, e põe a chave nova na casa vazia onde a descida parou: ela sempre entra como folha.

    Remoção

    20 não tem filhos: basta o ponteiro de 30 para ele, 30.esq, passar a ser None.

    Primeiro a busca acha o nó; o que vem depois depende de quantos filhos ele tem. Folha: sai direto. Um filho: o filho sobe para o lugar dele. Dois filhos: o nó recebe a chave do sucessor, o descendente mais à esquerda da subárvore direita, que então sai do lugar antigo. Use “Anterior” e “Próximo” para ver os três casos; em cada um, o botão embaixo do desenho muda os ponteiros, um passo por vez.

    Altura das subárvores

    O número no canto é a altura da subárvore de cada nó, calculada de baixo para cima, em quatro etapas.

    Cada nó é a raiz de uma subárvore, e a altura dela sai das alturas das subárvores dos filhos, com −1 para a subárvore vazia. Como cada nó precisa das alturas dos filhos, o cálculo segue a pós-ordem: primeiro as folhas, por último a raiz. Muitas implementações da árvore AVL guardam essa altura em cada nó e a atualizam a cada inserção ou remoção; assim, o fator de balanceamento sai na hora, sem percorrer a subárvore de novo.

    def altura(no):
        if no is None:
            return -1
        return 1 + max(altura(no.esq), altura(no.dir))

    Fator de balanceamento

    O número no canto é o fator. Em 30, a subárvore esquerda tem altura 2 e a direita, 0: fator +2, maior que 1.

    O fator de balanceamento de um nó é a altura da subárvore esquerda menos a altura da subárvore direita; a subárvore vazia tem altura −1. Fator positivo: o nó pende para a esquerda; negativo, para a direita. Quando todo fator é −1, 0 ou 1, a altura da árvore fica perto de log₂ n, onde n é a quantidade de nós na árvore, e a busca é rápida.

    Rotação

    Rotação à direita em 30, uma linha do código por vez: f = no.esq, no.esq = f.dir, f.dir = no e return f. A subárvore direita de 20, só o 25, em rosa, passa a ser a esquerda de 30, e a in-ordem continua 10, 20, 25, 30, 40.

    Uma rotação troca um nó de lugar com um dos filhos sem estragar a ordem das chaves. Na rotação à direita em no, o filho esquerdo, f = no.esq, sobe e no desce para a direita: no.esq passa a ser f.dir, f.dir passa a ser no, e quem apontava para no passa a apontar para f. Na rotação à esquerda, é o filho direito que sobe. Só esses três ponteiros mudam, e a in-ordem continua a mesma: a árvore continua sendo de busca. O botão embaixo do desenho roda o código uma linha por vez.

    Balanceamento por rotações

    30 tem fator +2, e o filho esquerdo, 20, pende para o mesmo lado (+1): uma rotação à direita em 30 basta, e todos os fatores ficam 0.

    Quando um nó fica com fator +2 ou −2, uma ou duas rotações perto dele deixam todos os fatores de novo entre −1 e 1, sem mudar a in-ordem. A árvore AVL faz essas rotações sempre que uma inserção ou remoção deixa algum nó com fator menor que −1 ou maior que 1; assim, a altura fica sempre perto de log₂ n, onde n é a quantidade de nós. As árvores rubro-negras também giram, mas seguem outra regra, com cores nos nós.

    Balanceamento pela mediana

    Aperte o botão três vezes, uma para cada etapa: primeiro entra a chave do meio, 40; depois 20 e 60, as do meio de cada metade; por fim, 10, 30, 50 e 70.

    A outra forma da Leitura 08: de tempos em tempos, refazer a árvore do zero. Com as chaves em ordem crescente, que é a in-ordem, insere primeiro a do meio, que vira a raiz, e depois, do mesmo jeito, as do meio de cada metade. Diferente das rotações, que consertam a árvore aos poucos, no lugar, aqui a árvore inteira é construída de novo.