Lista de exercícios

Aula prática 10: árvores AVL

8 exercícios sobre fator de balanceamento, quatro rotações e manutenção de AVL, com traces em C e soluções ilustradas.

Esta lista retoma a ABB da prática 9 e exercita o conteúdo da aula de AVL. Use Resposta esperada para comparação e autocorreção, sem entrega ou correção individual. Faça os desenhos no papel ou na ferramenta que preferir. Os exercícios com código pedem trace manual, sem implementar um programa. O tempo estimado inclui tentativa e conferência.

Convenções: os exercícios são independentes. A altura conta níveis: árvore vazia tem altura 0 e folha tem altura 1. O fator de balanceamento é FB = altura da esquerda − altura da direita. Uma AVL mantém a ordem da ABB e FB em {-1, 0, 1} em todos os nós. Isso não exige uma árvore completa nem a menor altura possível.

Cada no_avl guarda chave, altura e os ponteiros de filhos esquerda e direita. O tipo arvore_avl é um ponteiro para nó:

/* Cada nó guarda a chave, a altura da subárvore e os endereços dos dois filhos. */
typedef struct no_avl {
    int chave;
    int altura;
    struct no_avl *esquerda;
    struct no_avl *direita;
} no_avl;

/* O tipo representa um endereço de nó. arvore_avl* aponta para a caixa da raiz. */
typedef no_avl *arvore_avl;

/* Aloca apenas a caixa da raiz e cria uma árvore vazia, como na ABB da aula 9. */
static arvore_avl *criar_avl(void) {
    arvore_avl *arvore = malloc(sizeof(*arvore));
    /* A caixa só pode receber uma raiz vazia quando sua alocação existe. */
    if (arvore == NULL) {
        return NULL;
    }
    *arvore = NULL;
    return arvore;
}

Como na prática 9, arvore_avl *arvore = criar_avl(); aloca a caixa da raiz. arvore aponta para a caixa, *arvore guarda o endereço do primeiro nó e (*arvore)->chave acessa sua chave. arvore == NULL indica falha de criação. Uma caixa válida com *arvore == NULL representa a árvore vazia. Inserção, busca e remoção recebem arvore, como em inserir_avl(arvore, 5).

Nos exercícios de rotação, no_avl *raiz aponta diretamente para a subárvore tratada. A caixa permanece no mesmo endereço durante inserções e remoções, mesmo quando seu conteúdo muda. Ao encerrar, liberar_avl(arvore) libera os nós e a caixa. Depois, o chamador faz arvore = NULL.

h nas figuras representa altura. NULL indica ausência de filho. Os trechos pressupõem <stdlib.h> e <stdbool.h>. bool usa true e false. O operador condição ? valor1 : valor2 escolhe um dos dois valores. Por exemplo, no == NULL ? 0 : no->altura retorna 0 para uma subárvore vazia. const indica um acesso somente para consulta.

Exercício 1: calcular o FB e verificar a árvore inteira

Observe as ligações. As chaves já obedecem à propriedade da ABB.

FB = altura da subárvore esquerda − altura da subárvore direita.

O nível do nó indica sua posição a partir da raiz da árvore, que está no nível 1. A altura da subárvore é a quantidade de nós no maior caminho de sua própria raiz até uma folha. Uma folha tem altura 1 e uma subárvore vazia tem altura 0. Calcule das folhas para os pais: altura do pai = 1 + maior altura entre suas subárvores esquerda e direita.

Raiz 40, com uma cadeia 20, 10 e 5 à esquerda. À direita, 70 tem filhos 60 e 80, e 60 tem filho esquerdo 50. As alturas e os fatores ainda precisam ser calculados

Calcule a altura e o FB de cada nó. A árvore é AVL? Justifique com os fatores encontrados.

Solução

Explicação

Comece pelas folhas, com altura 1 e FB 0. Depois calcule cada pai usando as alturas dos filhos. Um filho ausente contribui com altura 0.

Na raiz, as duas subárvores têm altura 3. Ainda assim, é necessário verificar seus descendentes. O critério da AVL vale para todos os nós.

Objetivo de aprendizagem: calcular o fator com seu sinal e detectar um desequilíbrio que a verificação isolada da raiz não encontra.

Resposta esperada

A tabela segue dos filhos para os pais:

NóAlturaFB
5, 50 e 8010
1021
2032
6021
7031
4040

Não é AVL: o nó 20 tem FB 2, embora a raiz tenha FB 0.

Confira as alturas e os fatores dentro dos nós:

A mesma árvore do enunciado, com altura e FB em todos os nós. O nó 20 tem altura 3 e FB 2. A raiz 40 tem altura 4 e FB 0, mas a árvore não é AVL

Exercício 2: rastrear uma rotação simples à esquerda

Observe os filhos e as alturas armazenadas antes da chamada.

Estado inicial com raiz apontando para 20. O nó 20 tem filhos 10 e 40, o nó 40 tem filhos 30 e 50, e 50 tem filho direito 60

Faça um trace manual de raiz = rotacionar_esquerda(raiz);, com raiz apontando para 20. Registre as mudanças de ponteiros e alturas e desenhe a árvore resultante.

Apoio em C: altura_no consulta a altura armazenada. atualizar_altura recalcula a altura de um nó a partir dos filhos. rotacionar_esquerda devolve a nova raiz da subárvore.

/* Uma subárvore vazia não acrescenta níveis à altura do pai. */
static int altura_no(const no_avl *no) {
    return no == NULL ? 0 : no->altura;
}

/* Recalcula a altura após atualizar as alturas dos filhos de um nó existente. */
static void atualizar_altura(no_avl *no) {
    const int altura_esquerda = altura_no(no->esquerda);
    const int altura_direita = altura_no(no->direita);
    no->altura = 1 + (altura_esquerda > altura_direita ? altura_esquerda : altura_direita);
}

/* Caso RR: raiz e raiz->direita devem existir. A subárvore intermediária muda de pai. */
static no_avl *rotacionar_esquerda(no_avl *raiz) {
    no_avl *nova_raiz = raiz->direita;
    no_avl *subarvore_transferida = nova_raiz->esquerda;
    nova_raiz->esquerda = raiz;
    raiz->direita = subarvore_transferida;
    atualizar_altura(raiz);
    atualizar_altura(nova_raiz);
    return nova_raiz;
}
Solução

Explicação

No caso RR, o filho direito assume a raiz local. O nó 30 precisa continuar entre 20 e 40 na ordem da ABB, por isso passa a ser filho direito de 20.

A altura de 20 é atualizada antes da altura de 40. A nova raiz depende da altura já corrigida de seu filho. A atribuição no chamador guarda a raiz devolvida pela função.

Objetivo de aprendizagem: rastrear a rotação RR, preservando a subárvore intermediária e atualizando as alturas na ordem necessária.

Resposta esperada

Os números da tabela identificam os nós apontados:

MomentoEstado ou ação
Entradaraiz aponta para 20.
Referências locaisnova_raiz aponta para 40 e subarvore_transferida para 30.
Primeira ligaçãonova_raiz->esquerda = raiz faz esquerda de 40 apontar para 20.
Transferênciaraiz->direita = subarvore_transferida faz direita de 20 apontar para 30.
AlturasAtualiza 20 para altura 2, depois 40 para altura 3.
RetornoA função devolve 40. A variável raiz do chamador passa a apontar para 40.

Confira o resultado. As ligações alteradas aparecem em verde e com traço mais espesso:

Após a rotação RR, a raiz é 40. O nó 20 tem filhos 10 e 30, e o nó 50 mantém 60 à direita. As alturas e os fatores finais aparecem nos nós

Exercício 3: rastrear uma rotação simples à direita

Observe o estado inicial desta outra árvore.

Estado inicial com raiz apontando para 60. O nó 60 tem filhos 40 e 70, o nó 40 tem filhos 30 e 50, e 30 tem filho esquerdo 20

Faça um trace manual de raiz = rotacionar_direita(raiz);, com raiz apontando para 60. Registre as mudanças de ponteiros e alturas e desenhe a árvore resultante. Use também as auxiliares do exercício 2.

Apoio em C:

/* Caso LL: raiz e raiz->esquerda devem existir. A rotação não aloca nem libera nós. */
static no_avl *rotacionar_direita(no_avl *raiz) {
    no_avl *nova_raiz = raiz->esquerda;
    no_avl *subarvore_transferida = nova_raiz->direita;
    /* Preserva a subárvore intermediária antes de substituir a ligação de seu antigo pai. */
    nova_raiz->direita = raiz;
    raiz->esquerda = subarvore_transferida;
    /* Atualiza primeiro o nó rebaixado, pois a altura do promovido depende dele. */
    atualizar_altura(raiz);
    atualizar_altura(nova_raiz);
    return nova_raiz;
}
Solução

Explicação

No caso LL, o filho esquerdo assume a raiz local. O nó 50 muda de pai, pois sua chave deve continuar maior que 40 e menor que 60. Os nós 30, 20 e 70 mantêm suas ligações.

Objetivo de aprendizagem: rastrear a rotação LL e preservar a subárvore situada entre a raiz antiga e a raiz promovida.

Resposta esperada

MomentoEstado ou ação
Entradaraiz aponta para 60.
Referências locaisnova_raiz aponta para 40 e subarvore_transferida para 50.
Primeira ligaçãodireita de 40 passa a apontar para 60.
Transferênciaesquerda de 60 passa a apontar para 50.
AlturasAtualiza 60 para altura 2, depois 40 para altura 3.
RetornoA função devolve 40. O chamador atualiza raiz para 40.

Confira a árvore final:

Após a rotação LL, a raiz é 40. O nó 30 mantém 20 à esquerda, e o nó 60 tem filhos 50 e 70. Nenhum nó foi perdido

Exercício 4: rastrear o rebalanceamento de 20

Observe o estado da subárvore que será passada a rebalancear.

Raiz 20, com filhos 10 e 60. O nó 60 tem filhos 40 e 70, e 40 tem filhos 30 e 50. As alturas armazenadas estão corretas

Faça um trace manual de raiz = rebalancear(raiz);, com raiz apontando para 20. Registre os fatores consultados, as rotações e o retorno. Desenhe a árvore resultante. Use as rotações dos exercícios 2 e 3.

Apoio em C: fator_balanceamento calcula o FB. rebalancear devolve a raiz corrigida, ou a mesma raiz quando não precisa de rotação. As subárvores dos filhos já devem estar corrigidas antes de tratar o pai.

/* Uma subárvore vazia não acrescenta níveis à altura do pai. */
static int altura_no(const no_avl *no) {
    return no == NULL ? 0 : no->altura;
}

/* Recalcula a altura após atualizar as alturas dos filhos de um nó existente. */
static void atualizar_altura(no_avl *no) {
    const int altura_esquerda = altura_no(no->esquerda);
    const int altura_direita = altura_no(no->direita);
    no->altura = 1 + (altura_esquerda > altura_direita ? altura_esquerda : altura_direita);
}

/* Caso RR: raiz e raiz->direita devem existir. A subárvore intermediária muda de pai. */
static no_avl *rotacionar_esquerda(no_avl *raiz) {
    no_avl *nova_raiz = raiz->direita;
    no_avl *subarvore_transferida = nova_raiz->esquerda;
    nova_raiz->esquerda = raiz;
    raiz->direita = subarvore_transferida;
    atualizar_altura(raiz);
    atualizar_altura(nova_raiz);
    return nova_raiz;
}

/* Caso LL: raiz e raiz->esquerda devem existir. A rotação não aloca nem libera nós. */
static no_avl *rotacionar_direita(no_avl *raiz) {
    no_avl *nova_raiz = raiz->esquerda;
    no_avl *subarvore_transferida = nova_raiz->direita;
    /* Preserva a subárvore intermediária antes de substituir a ligação de seu antigo pai. */
    nova_raiz->direita = raiz;
    raiz->esquerda = subarvore_transferida;
    /* Atualiza primeiro o nó rebaixado, pois a altura do promovido depende dele. */
    atualizar_altura(raiz);
    atualizar_altura(nova_raiz);
    return nova_raiz;
}

/* O fator usa altura da esquerda menos altura da direita, com o sinal adotado na aula. */
static int fator_balanceamento(const no_avl *no) {
    return no == NULL ? 0 : altura_no(no->esquerda) - altura_no(no->direita);
}

/* Restaura uma subárvore após inserir ou remover em uma AVL anteriormente válida. */
static no_avl *rebalancear(no_avl *raiz) {
    /* A remoção de uma folha pode deixar uma subárvore sem altura para atualizar. */
    if (raiz == NULL) {
        return NULL;
    }
    atualizar_altura(raiz);
    const int fator = fator_balanceamento(raiz);

    /* O excesso de altura à esquerda exige uma correção LL ou LR. */
    if (fator > 1) {
        /* O filho inclinado para dentro exige a primeira rotação do caso LR. */
        if (fator_balanceamento(raiz->esquerda) < 0) {
            raiz->esquerda = rotacionar_esquerda(raiz->esquerda);
        }
        return rotacionar_direita(raiz);
    }
    /* O excesso de altura à direita exige uma correção RR ou RL. */
    if (fator < -1) {
        /* O filho inclinado para dentro exige a primeira rotação do caso RL. */
        if (fator_balanceamento(raiz->direita) > 0) {
            raiz->direita = rotacionar_direita(raiz->direita);
        }
        return rotacionar_esquerda(raiz);
    }
    return raiz;
}
Solução

Explicação

O nó 20 está mais alto à direita, mas seu filho 60 está mais alto à esquerda. Esse é o caso RL. A primeira rotação reorganiza o filho direito, e a segunda corrige a raiz local.

As transferências preservam 50 à esquerda de 60 e 30 à direita de 20. A sequência em Em-Ordem continua a mesma.

Objetivo de aprendizagem: selecionar RL pelos fatores e rastrear a composição das duas rotações sem perder as subárvores intermediárias.

Resposta esperada

MomentoEstado ou ação
Fator da raizAtualiza altura de 20 para 4. fator recebe -2, pois 1−3=−21 - 3 = -2.
Fator do filhofator_balanceamento(raiz->direita) calcula 2 - 1 = 1, no nó 60.
Primeira rotaçãorotacionar_direita(raiz->direita) promove 40, transfere 50 para esquerda de 60 e atualiza as alturas de 60 para 2 e de 40 para 3.
Retorno ao paidireita de 20 recebe o endereço de 40.
Segunda rotaçãorotacionar_esquerda(raiz), com raiz em 20, promove 40 e transfere 30 para direita de 20. Atualiza 20 para altura 2 e 40 para altura 3.
Retorno finalrebalancear devolve 40. O chamador atualiza raiz para 40.

Confira o resultado da RL, dupla à esquerda:

Raiz final 40. À esquerda, 20 tem filhos 10 e 30. À direita, 60 tem filhos 50 e 70. Todos os fatores finais são zero

Exercício 5: rastrear o rebalanceamento de 80

Observe a subárvore com raiz 80.

Raiz 80, com filhos 40 e 90. O nó 40 tem filhos 20 e 60, e 60 tem filhos 50 e 70

Faça um trace manual de raiz = rebalancear(raiz);, com raiz apontando para 80. Registre os fatores consultados, as rotações e o retorno. Desenhe a árvore resultante. Use as funções dos exercícios 2 a 4.

Solução

Explicação

O nó 80 está mais alto à esquerda, mas seu filho 40 está mais alto à direita. Esse é o caso LR. A correção começa no filho esquerdo e termina na raiz local.

Objetivo de aprendizagem: selecionar LR pelos fatores e executar suas duas rotações na ordem correta.

Resposta esperada

MomentoEstado ou ação
Fator da raizAtualiza altura de 80 para 4. fator recebe 2, pois 3−1=23 - 1 = 2.
Fator do filhofator_balanceamento(raiz->esquerda) calcula 1 - 2 = -1, no nó 40.
Primeira rotaçãorotacionar_esquerda(raiz->esquerda) promove 60, transfere 50 para direita de 40 e atualiza as alturas de 40 para 2 e de 60 para 3.
Retorno ao paiesquerda de 80 recebe o endereço de 60.
Segunda rotaçãorotacionar_direita(raiz), com raiz em 80, promove 60 e transfere 70 para esquerda de 80. Atualiza 80 para altura 2 e 60 para altura 3.
Retorno finalrebalancear devolve 60. O chamador atualiza raiz para 60.

Confira o resultado da LR, dupla à direita:

Raiz final 60. À esquerda, 40 tem filhos 20 e 50. À direita, 80 tem filhos 70 e 90. Todos os fatores finais são zero

Exercício 6: acompanhar duas inserções consecutivas

Observe a AVL inicial. A caixa da raiz já existe, e *arvore aponta para o nó 50.

AVL inicial com raiz 50. O nó 25 tem filhos 10 e 35, e o nó 80 tem filhos 65 e 95. A altura inicial é 3

Faça o trace manual de inserir_avl(arvore, 5) e, em seguida, inserir_avl(arvore, 2). Para cada chamada, registre o retorno e as atualizações relevantes e desenhe a árvore resultante. Use rebalancear e suas auxiliares dos exercícios 2 a 4.

Apoio em C: o tipo resultado_insercao_avl usa nomes para distinguir os resultados. AVL_INSERIDO significa inserção realizada. Os outros valores indicam duplicata, falta de memória e referência inválida. criar_no_avl cria uma folha, e inserir_no devolve a raiz da subárvore após a tentativa de inserção.

/* Resultados explícitos distinguem duplicata, falta de memória e referência inválida. */
typedef enum {
    AVL_INSERIDO,
    AVL_CHAVE_DUPLICADA,
    AVL_SEM_MEMORIA,
    AVL_ARVORE_INVALIDA
} resultado_insercao_avl;

/* Aloca uma folha. sizeof(*no) mede o nó, e não o ponteiro. */
static no_avl *criar_no_avl(int chave) {
    no_avl *no = malloc(sizeof(*no));
    /* Informa a falha antes de escrever em um endereço inválido. */
    if (no == NULL) {
        return NULL;
    }
    no->chave = chave;
    no->altura = 1;
    no->esquerda = NULL;
    no->direita = NULL;
    return no;
}

/* Desce como em uma ABB e corrige os ancestrais no retorno da recursão. */
static no_avl *inserir_no(no_avl *raiz, int chave, resultado_insercao_avl *resultado) {
    /* Somente uma posição vazia pode receber a nova folha. */
    if (raiz == NULL) {
        no_avl *no = criar_no_avl(chave);
        *resultado = no == NULL ? AVL_SEM_MEMORIA : AVL_INSERIDO;
        return no;
    }
    /* Uma chave já presente não cria outro nó nem altera a árvore. */
    if (chave == raiz->chave) {
        *resultado = AVL_CHAVE_DUPLICADA;
        return raiz;
    }
    /* A ordem da ABB determina o único ramo que pode receber a chave. */
    if (chave < raiz->chave) {
        raiz->esquerda = inserir_no(raiz->esquerda, chave, resultado);
    } else {
        raiz->direita = inserir_no(raiz->direita, chave, resultado);
    }
    /* Uma inserção rejeitada ou sem memória não alterou a subárvore. */
    if (*resultado != AVL_INSERIDO) {
        return raiz;
    }
    return rebalancear(raiz);
}

/* Atualiza o conteúdo da caixa, preservando seu endereço mesmo quando a raiz muda. */
static resultado_insercao_avl inserir_avl(arvore_avl *arvore, int chave) {
    /* Sem caixa, não existe uma posição válida para guardar a raiz. */
    if (arvore == NULL) {
        return AVL_ARVORE_INVALIDA;
    }
    resultado_insercao_avl resultado = AVL_SEM_MEMORIA;
    *arvore = inserir_no(*arvore, chave, &resultado);
    return resultado;
}
Solução

Explicação

Inserir 5 aumenta as alturas do caminho, mas mantém todos os fatores no intervalo permitido. Inserir 2 exige uma correção na subárvore que começa em 10.

O retorno dessa subárvore atualiza esquerda de 25. A raiz geral continua sendo 50. A recursão permite corrigir uma raiz interna sem perder a ligação com seu pai.

Objetivo de aprendizagem: acompanhar a manutenção da AVL durante a inserção, distinguindo atualização de altura de rotação e reconectando uma raiz interna.

Resposta esperada

Primeira chamada: inserir 5. A tabela acompanha a descida e o retorno:

MomentoEstado ou ação
DescidaVisita 50, 25 e 10, seguindo esquerda.
Posição vaziaCria a folha 5, com altura 1. resultado recebe AVL_INSERIDO.
Retorno em 10esquerda recebe 5. Altura 2, FB 1, sem rotação.
Retorno em 25Altura 3, FB 1, sem rotação.
Retorno em 50Altura 4, FB 1, sem rotação.
Saída*arvore continua apontando para 50. Retorno AVL_INSERIDO.

Após inserir 5, a raiz continua sendo 50, e a nova folha fica à esquerda de 10. Os nós 10, 25 e 50 têm FB 1

Segunda chamada: inserir 2 na árvore que já contém 5.

MomentoEstado ou ação
DescidaVisita 50, 25, 10 e 5, seguindo esquerda.
Posição vaziaCria a folha 2, com altura 1. resultado recebe AVL_INSERIDO.
Retorno em 5esquerda recebe 2. Altura 2, FB 1, sem rotação.
Retorno em 10Altura provisória 3, FB 2. O filho 5 tem FB 1, então aplica LL com rotacionar_direita(raiz), no nó 10.
Rotação e ligação ao pai10 fica sem filhos, com altura 1. A raiz local passa a ser 5, com filhos 2 e 10 e altura 2. esquerda de 25 recebe 5.
Retorno em 25 e 5025 mantém altura 3, FB 1. 50 mantém altura 4, FB 1. Não há outra rotação.
Saída*arvore continua apontando para 50. Retorno AVL_INSERIDO.

Após inserir 2, a raiz geral continua sendo 50. O campo esquerda de 25 aponta para 5, cujos filhos são 2 e 10. As demais subárvores são preservadas

Exercício 7: acompanhar uma remoção

Observe a AVL antes da chamada. A caixa da raiz já existe, e *arvore aponta para 40.

Raiz 40, filho esquerdo 20 e filho direito 60. O nó 20 tem filhos 10 e 30

Faça um trace manual de remover_avl(arvore, 60). Registre as decisões, as alterações e o retorno. Desenhe a árvore resultante. Use rebalancear e suas auxiliares dos exercícios 2 a 4.

Apoio em C: remover_no devolve a raiz da subárvore após a remoção. Para um nó com dois filhos, este programa copia a chave do sucessor, o menor nó da subárvore direita, e remove sua ocorrência antiga. Essa é a política do complemento C da aula de AVL. A implementação de ABB da prática 9 usa outra política.

/* O nó mais à esquerda contém a menor chave de uma subárvore não vazia. */
static no_avl *no_minimo(no_avl *raiz) {
    no_avl *no = raiz;
    /* Descarta a cada passo os nós que não podem conter a menor chave. */
    while (no->esquerda != NULL) {
        no = no->esquerda;
    }
    return no;
}

/* Remove como em uma ABB e rebalanceia os ancestrais afetados no retorno. */
static no_avl *remover_no(no_avl *raiz, int chave, bool *removido) {
    /* Uma posição vazia comprova que a chave procurada não existe. */
    if (raiz == NULL) {
        return NULL;
    }
    /* A igualdade identifica o nó cuja chave deve sair da árvore. */
    if (chave == raiz->chave) {
        *removido = true;
        /* Preserva o filho direito antes de liberar um nó sem filho esquerdo. */
        if (raiz->esquerda == NULL) {
            no_avl *direita = raiz->direita;
            free(raiz);
            return direita;
        }
        /* O filho esquerdo substitui o nó cuja subárvore direita está vazia. */
        if (raiz->direita == NULL) {
            no_avl *esquerda = raiz->esquerda;
            free(raiz);
            return esquerda;
        }
        no_avl *sucessor = no_minimo(raiz->direita);
        raiz->chave = sucessor->chave;
        bool sucessor_removido = false;
        raiz->direita = remover_no(raiz->direita, sucessor->chave, &sucessor_removido);
    } else {
        /* Sem igualdade, a ordem da ABB seleciona o único ramo possível. */
        if (chave < raiz->chave) {
            raiz->esquerda = remover_no(raiz->esquerda, chave, removido);
        } else {
            raiz->direita = remover_no(raiz->direita, chave, removido);
        }
    }
    /* Uma chave ausente deve preservar a subárvore e seus endereços. */
    if (!*removido) {
        return raiz;
    }
    return rebalancear(raiz);
}

/* Informa se uma chave foi removida. Referência inválida ou chave ausente resulta em false. */
static bool remover_avl(arvore_avl *arvore, int chave) {
    /* Rejeita a referência inválida antes de acessar a árvore. */
    if (arvore == NULL) {
        return false;
    }
    bool removido = false;
    *arvore = remover_no(*arvore, chave, &removido);
    return removido;
}
Solução

Explicação

A remoção da folha 60 deixa 40 com FB 2. Seu filho esquerdo, 20, tem FB 0. O código aplica uma rotação simples à direita, pois a condição para a primeira parte de LR exige FB negativo no filho.

O caso de filho com FB zero também pertence à rotação simples. A árvore perde um nó, mas sua altura permanece 3.

Objetivo de aprendizagem: rastrear a remoção de uma folha e escolher a rotação simples quando o filho mais alto tem FB zero.

Resposta esperada

MomentoEstado ou ação
Entradaremovido começa como false. A busca visita 40 e segue direita até 60.
Chave encontradaEm 60, *removido recebe true. Como esquerda é NULL, guarda direita, que também é NULL.
LiberaçãoLibera o nó 60 e devolve NULL. O campo direita de 40 recebe esse retorno.
Verificação de 40Altura 3, FB 2. O filho 20 tem FB 0. A condição fator_balanceamento(raiz->esquerda) < 0 é falsa.
Correçãorotacionar_direita(raiz), no nó 40, promove 20 e transfere 30 para esquerda de 40. Atualiza 40 para altura 2 e 20 para altura 3.
Saída*arvore passa a apontar para 20. Retorno true.

Confira a árvore final:

Após remover 60, a raiz é 20, com filhos 10 e 40. O nó 40 tem filho esquerdo 30 e direita igual a NULL. A altura final é 3

Exercício 8: continuar a correção nos ancestrais

Observe a AVL inicial. As alturas aparecem nos nós para apoiar o trace.

Raiz 50. À esquerda, 20 tem filhos 10 e 30, e 10 tem filho esquerdo 5. À direita, 80 tem filhos 65 e 100. O nó 65 tem filho esquerdo 60. O nó 100 tem filhos 90 e 110, e 110 tem filho direito 120

Faça um trace manual de remover_avl(arvore, 20), com a caixa já criada e *arvore apontando para 50. Registre as decisões, as alterações e o retorno. Desenhe a árvore resultante. Use as funções do exercício 7 e as auxiliares dos exercícios 2 a 4.

Solução

Explicação

Como 20 tem dois filhos, o programa copia a chave 30 para seu registro e libera a antiga ocorrência do sucessor. Depois, a subárvore perde altura e precisa de uma rotação.

A correção local reduz sua altura de 3 para 2. Isso também altera o equilíbrio do ancestral 50. A remoção continua verificando os ancestrais no retorno da recursão.

Objetivo de aprendizagem: rastrear a remoção com sucessor e reconhecer que uma única remoção pode exigir correções em mais de um ancestral.

Resposta esperada

MomentoEstado ou ação
Entrada e descidaremovido = false. Em 50, segue esquerda até 20.
Chave encontradaO nó tem dois filhos. *removido = true. no_minimo(raiz->direita) encontra o sucessor 30.
SubstituiçãoCopia 30 para chave do registro que guardava 20. Inicia sucessor_removido = false e chama a remoção de 30 na subárvore direita.
Remoção do sucessorA antiga folha 30 é liberada, sucessor_removido recebe true e a chamada retorna NULL. O registro que agora guarda 30 fica sem filho direito.
Primeira verificaçãoNa raiz local 30: altura provisória 3, FB 2. Seu filho 10 tem FB 1.
Primeira correçãoLL: rotacionar_direita(raiz), no nó 30, promove 10. O nó 30 fica sem filhos, com altura 1. O nó 10 fica com filhos 5 e 30 e altura 2.
Retorno ao ancestralO campo esquerda de 50 recebe 10. Em 50, as alturas dos filhos agora são 2 e 4, então seu FB é -2.
Segunda verificaçãoO filho direito 80 tem FB -1. Aplica RR com rotacionar_esquerda(raiz), no nó 50.
Segunda correção80 assume a raiz. O nó 65 passa a ser filho direito de 50. Atualiza 50 para altura 3 e 80 para altura 4.
Saída*arvore passa a apontar para 80. Retorno true.

Confira a árvore final, com 11 nós. Apenas o registro da antiga folha sucessora foi liberado:

Resultado da remoção: raiz 80. O nó 50 tem filhos 10 e 65. O nó 10 tem filhos 5 e 30, e 65 mantém 60 à esquerda. O nó 100 mantém 90 à esquerda e 110 à direita, com 120 à direita de 110. A altura final é 4