Árvore AVL de pessoas
Árvore AVL de pessoas

Como criar uma Árvore Binária Balanceada AVL com Struct Pessoa?

Até o momento criamos Árvores Binárias de Busca Balanceada (AVL) apenas com números inteiros. Nesta aula vamos aprender a criar uma Árvore Balanceada do tipo AVL com a Struct Pessoa em cada nó. Uma Árvore Binária Balanceada de Pessoas.

Vamos iniciar definindo nossa struct Pessoa. Cada Pessoa terá um nome (um vetor de caracteres de tamanho 25), uma idade (que será um número inteiro) e um cpf (que aqui também trataremos como um número inteiro). Caso queira, você pode incluir outros campos ou mesmo substituir alguns desses. A idade, por exemplo, pode ser substituída por uma struct data, com três inteiros, dia, mês e ano.

// struct Pessoa

typedef struct{
    char nome[25];
    int idade;
    int cpf;
}Pessoa;

Agora vamos criar o da nossa Árvore Binária Balanceada AVL de Pessoas. Basicamente a única diferença em relação a uma Árvore AVL de números é que, no lugar de um campo inteiro temos um campo do tipo Pessoa que acabamos de criar. Esse campo é um ponteiro, por isso o asterisco ( * ). Os demais campos permanecem como já conhecemos de uma árvore balanceada, temos dois ponteiros, um para a subárvore a direita e outro para a subárvore a esquerda. Por fim, temos um campo do tipo short para guardar a altura da subárvore.

// Nó da nossa árvore binária AVL de Pessoas

typedef struct no{
    Pessoa *pessoa;
    struct no *esquerdo, *direito;
    short altura;
}No;

Agora, vamos criar uma função que vai receber o endereço de uma Pessoa, vai alocar memória para um novo nó, vai salvar a Pessoa neste nó e retornar o endereço deste nó para quem chamou a função. Essa função irá facilitar o processo de inserção em nossa árvore. Um ponto importante aqui é não esquecer de inicializar os campos do nó com zero para a altura e NULL para os ponteiros das subárvores esquerda e direita.

// Função para criar um novo nó da nossa árvore

No* novoNo(Pessoa *x){
    No *novo = malloc(sizeof(No));

    if(novo){
        novo->pessoa = x;
        novo->esquerdo = NULL;
        novo->direito = NULL;
        novo->altura = 0;
    }
    else
        printf("\nERRO ao alocar nó em novoNo!\n");
    return novo;
}

Agora, precisamos de uma função auxiliar que irá nos ajudar a manter a altura de cada nó atualizada corretamente. Como cada subárvore pode ter uma altura diferente, a altura do nó raiz será a altura da maior subárvore mais 1. Esta função irá receber dois valores (a altura de duas subárvores) e retornar o maior.

// Função que retorna o maior dentre dois valores

short maior(short a, short b){
    return (a > b)? a: b;
}

A função a seguir é outra função auxiliar que recebe o endereço de um nó e retorna sua altura, caso seja diferente de NULL, ou -1 se for NULL. Esta função será útil quando formos calcular o fator de balanceamento de um nó.

//  Retorna a altura de um nó ou -1 caso ele seja null

short alturaDoNo(No *no){
    if(no == NULL)
        return -1;
    else
        return no->altura;
}

A função a seguir faz uso da função anterior (alturaDoNo) para calcular o fator de balanceamento de um nó, que será utilizado para definir se a árvore está balanceada ou se é necessário realizar alguma rotação.

//   Calcula e retorna o fator de balanceamento de um nó

short fatorDeBalanceamento(No *no){
    if(no)
        return (alturaDoNo(no->esquerdo) - alturaDoNo(no->direito));
    else
        return 0;
}

Agora vamos iniciar as funções que farão as rotações para balancear nossa árvore. A função a seguir realiza uma rotação à esquerda. Observe alguns pontos importantes: primeiro, não verifico a necessidade da rotação dentro da função, isso é feito fora e quando a função for chamada significa que já sei que é necessário realizar uma rotação à esquerda; segundo, estude estre trecho de código sempre com papel e lápis na mão. É difícil compreender o que está acontecendo apenas olhando o código. Desenhe uma pequena árvore no papel e simule a execução de cada linha, isso ajudará na compreensão. Observe também que agora estamos fazendo uso das funções alturaDoNo, que definimos acima, para descobrir a altura das subárvores esquerda e direita, e da função maior, para descobrir qual subárvore possui a maior altura.

// função para a rotação à esquerda

No* rotacaoEsquerda(No *r){
    No *y, *f;

    y = r->direito;
    f = y->esquerdo;

    y->esquerdo = r;
    r->direito = f;

    r->altura = maior(alturaDoNo(r->esquerdo), alturaDoNo(r->direito)) + 1;
    y->altura = maior(alturaDoNo(y->esquerdo), alturaDoNo(y->direito)) + 1;

    return y;
}

A seguir temos uma função bastante similar à anterior, para realizar uma rotação à direita.

// função para a rotação à direita

No* rotacaoDireita(No *r){
    No *y, *f;

    y = r->esquerdo;
    f = y->direito;

    y->direito = r;
    r->esquerdo = f;

    r->altura = maior(alturaDoNo(r->esquerdo), alturaDoNo(r->direito)) + 1;
    y->altura = maior(alturaDoNo(y->esquerdo), alturaDoNo(y->direito)) + 1;

    return y;
}

Agora precisamos de duas rotações duplas. A seguir temos uma função para realizar uma rotação dupla Esquerda Direita. Observe que uma rotação dupla nada mais é que duas rotações simples, uma para a esquerda e outra para a direita.

// Rotação dupla Esquerda Direita

No* rotacaoEsquerdaDireita(No *r){
    r->esquerdo = rotacaoEsquerda(r->esquerdo);
    return rotacaoDireita(r);
}

Agora temos nossa segunda rotação dupla, Direita Esquerda. Na função anterior realizamos primeiro uma rotação a esquerda, manipulando a subárvore esquerda. Agora, realizamos primeiro uma rotação a direita, manipulando a subárvore da direita.

// Rotação dupla Direita Esquerda

No* rotacaoDireitaEsquerda(No *r){
    r->direito = rotacaoDireita(r->direito);
    return rotacaoEsquerda(r);
}

A seguir temos uma das funções mais importantes em uma árvore balanceada, a função que realiza o balanceamento de uma árvore balanceada. Esta função recebe o endereço do nó que está desbalanceado, verifica o tipo de rotação necessária para o balanceamento e, por fim, retorna a nova raiz da subárvore, agora balanceada.

/*
    Função para realizar o balanceamento da árvore após uma inserção ou remoção
    Recebe o nó que está desbalanceado e retorna a nova raiz após o balanceamento
*/

No* balancear(No *raiz){
    short fb = fatorDeBalanceamento(raiz);

    // Rotação à esquerda
    if(fb < -1 && fatorDeBalanceamento(raiz->direito) <= 0)
        raiz = rotacaoEsquerda(raiz);

    // Rotação à direita
    else if(fb > 1 && fatorDeBalanceamento(raiz->esquerdo) >= 0)
        raiz = rotacaoDireita(raiz);

    // Rotação dupla à esquerda
    else if(fb > 1 && fatorDeBalanceamento(raiz->esquerdo) < 0)
        raiz = rotacaoEsquerdaDireita(raiz);

    // Rotação dupla à direita
    else if(fb < -1 && fatorDeBalanceamento(raiz->direito) > 0)
        raiz = rotacaoDireitaEsquerda(raiz);

    return raiz;
}

A seguir apresentamos a função inserir. Esta função recebe a raiz de nossa árvore e a pessoa a ser inserida na árvore, realiza a inserção, verifica a necessidade de balanceamento e, por fim, retorna a raiz da árvore balanceada. Como estamos trabalhando com o tipo Pessoa e estamos usando o cpf como chave, não aceitamos cpf duplicado, ou seja, caso tente inserir uma pessoa com o cpf exatamente igual ao cpf de uma pessoa já existente na árvore, esta inserção não será feita.

/*
    Insere um novo nó na árvore
    raiz -> raiz da árvore
    x -> valor a ser inserido
    Retorno: endereço do novo nó ou nova raiz após o balanceamento
*/

No* inserir(No *raiz, Pessoa *x){
    if(raiz == NULL) // árvore vazia
        return novoNo(x);
    else{ // inserção será à esquerda ou à direita?
        if(x->cpf < raiz->pessoa->cpf)
            raiz->esquerdo = inserir(raiz->esquerdo, x);
        else if(x->cpf > raiz->pessoa->cpf)
            raiz->direito = inserir(raiz->direito, x);
        else
            printf("\nInsercao nao realizada!\nO elemento %d a existe!\n", x->cpf);
    }

    // Recalcula a altura de todos os nós entre a raiz e o novo nó inserido
    raiz->altura = maior(alturaDoNo(raiz->esquerdo), alturaDoNo(raiz->direito)) + 1;

    // verifica a necessidade de rebalancear a árvore
    raiz = balancear(raiz);

    return raiz;
}

A seguir temos uma função para remover um nó da árvore. Assim como uma inserção, a remoção também pode desbalancear a árvore. Então, também precisamos verificar a necessidade de balanceamento da árvore após a remoção. A grande atenção aqui recai sobre as possibilidade de remoção: nó folha, nó com apenas um filho, ou nó com dois filhos.

/*
    Função para remover um nó da Árvore binária
    Pode ser necessário rebalancear a árvore e a raiz pode ser alterada
    por isso precisamos retornar a raiz
*/

No* remover(No *raiz, int chave) {
    if(raiz == NULL){
        printf("Valor nao encontrado!\n");
        return NULL;
    } else { // procura o nó a remover
        if(raiz->pessoa->cpf == chave) {
            // remove nós folhas (nós sem filhos)
            if(raiz->esquerdo == NULL && raiz->direito == NULL) {
                free(raiz);
                printf("Elemento folha removido: %d !\n", chave);
                return NULL;
            }
            else{
                // remover nós que possuem 2 filhos
                if(raiz->esquerdo != NULL && raiz->direito != NULL){
                    No *aux = raiz->esquerdo;
                    while(aux->direito != NULL)
                        aux = aux->direito;
                    Pessoa *pessoaAux;
                    pessoaAux = raiz->pessoa;
                    raiz->pessoa = aux->pessoa;
                    aux->pessoa = pessoaAux;
                    printf("Elemento trocado: %d !\n", chave);
                    raiz->esquerdo = remover(raiz->esquerdo, chave);
                    return raiz;
                }
                else{
                    // remover nós que possuem apenas 1 filho
                    No *aux;
                    if(raiz->esquerdo != NULL)
                        aux = raiz->esquerdo;
                    else
                        aux = raiz->direito;
                    free(raiz);
                    printf("Elemento com 1 filho removido: %d !\n", chave);
                    return aux;
                }
            }
        } else {
            if(chave < raiz->pessoa->cpf)
                raiz->esquerdo = remover(raiz->esquerdo, chave);
            else
                raiz->direito = remover(raiz->direito, chave);
        }

        raiz->altura = maior(alturaDoNo(raiz->esquerdo), alturaDoNo(raiz->direito)) + 1;

        raiz = balancear(raiz);

        return raiz;
    }
}

Para testar se tudo está funcionando é interessante imprimir nossa árvore na tela. Para isso vamos usar dois procedimentos, um para imprimir uma Pessoa e outro que irá percorrer nossa árvore imprimindo a pessoa de cada nó.

// Imprime os dados de uma pessoa

void imprimirPessoa(Pessoa *pessoa){
    printf("Nome: %s CPF: %d Idade: %d\n", pessoa->nome, pessoa->cpf, pessoa->idade);
}

// Percorre a árvore passando por todos os nós chamando a impressão da Pessoa

void imprimir(No *raiz, int nivel){
    int i;
    if(raiz){
        imprimir(raiz->direito, nivel + 1);
        printf("\n\n");

        for(i = 0; i < nivel; i++)
            printf("\t");

        imprimirPessoa(raiz->pessoa);
        imprimir(raiz->esquerdo, nivel + 1);
    }
}

Bom, é isso. A seguir disponibilizo todo o código em sequência da nossa Árvore Binária Balanceada AVL de Pessoas, inclusive com a função main, para que você possa testar em seu computador. Deixo também a aula postada no canal no youtobe e conto com seu like e inscrição. Abraços e bons estudos.

Código completo da Árvore Binária Balanceada AVL de Pessoas

#include <stdio.h>
#include <stdlib.h>

typedef struct{
    char nome[25];
    int idade;
    int cpf;
}Pessoa;

typedef struct no{
    Pessoa *pessoa;
    struct no *esquerdo, *direito;
    short altura;
}No;

/*
    Função que cria um novo nó
    x -> valor a ser inserido no nó
    Retorna: o endereço do nó criado
*/
No* novoNo(Pessoa *x){
    No *novo = malloc(sizeof(No));

    if(novo){
        novo->pessoa = x;
        novo->esquerdo = NULL;
        novo->direito = NULL;
        novo->altura = 0;
    }
    else
        printf("\nERRO ao alocar nó em novoNo!\n");
    return novo;
}

/*
    Retorna o maior dentre dois valores
    a, b -> altura de dois nós da árvore
*/
short maior(short a, short b){
    return (a > b)? a: b;
}

//  Retorna a altura de um nó ou -1 caso ele seja null
short alturaDoNo(No *no){
    if(no == NULL)
        return -1;
    else
        return no->altura;
}

//   Calcula e retorna o fator de balanceamento de um nó
short fatorDeBalanceamento(No *no){
    if(no)
        return (alturaDoNo(no->esquerdo) - alturaDoNo(no->direito));
    else
        return 0;
}

// --------- ROTAÇÕES ---------------------------

// função para a rotação à esquerda
No* rotacaoEsquerda(No *r){
    No *y, *f;

    y = r->direito;
    f = y->esquerdo;

    y->esquerdo = r;
    r->direito = f;

    r->altura = maior(alturaDoNo(r->esquerdo), alturaDoNo(r->direito)) + 1;
    y->altura = maior(alturaDoNo(y->esquerdo), alturaDoNo(y->direito)) + 1;

    return y;
}

// função para a rotação à direita
No* rotacaoDireita(No *r){
    No *y, *f;

    y = r->esquerdo;
    f = y->direito;

    y->direito = r;
    r->esquerdo = f;

    r->altura = maior(alturaDoNo(r->esquerdo), alturaDoNo(r->direito)) + 1;
    y->altura = maior(alturaDoNo(y->esquerdo), alturaDoNo(y->direito)) + 1;

    return y;
}

No* rotacaoEsquerdaDireita(No *r){
    r->esquerdo = rotacaoEsquerda(r->esquerdo);
    return rotacaoDireita(r);
}

No* rotacaoDireitaEsquerda(No *r){
    r->direito = rotacaoDireita(r->direito);
    return rotacaoEsquerda(r);
}

/*
    Função para realizar o balanceamento da árvore após uma inserção ou remoção
    Recebe o nó que está desbanlanceado e retorna a nova raiz após o balanceamento
*/
No* balancear(No *raiz){
    short fb = fatorDeBalanceamento(raiz);

    // Rotação à esquerda
    if(fb < -1 && fatorDeBalanceamento(raiz->direito) <= 0)
        raiz = rotacaoEsquerda(raiz);

    // Rotação à direita
    else if(fb > 1 && fatorDeBalanceamento(raiz->esquerdo) >= 0)
        raiz = rotacaoDireita(raiz);

    // Rotação dupla à esquerda
    else if(fb > 1 && fatorDeBalanceamento(raiz->esquerdo) < 0)
        raiz = rotacaoEsquerdaDireita(raiz);

    // Rotação dupla à direita
    else if(fb < -1 && fatorDeBalanceamento(raiz->direito) > 0)
        raiz = rotacaoDireitaEsquerda(raiz);

    return raiz;
}

/*
    Insere um novo nó na árvore
    raiz -> raiz da árvore
    x -> valor a ser inserido
    Retorno: endereço do novo nó ou nova raiz após o balanceamento
*/
No* inserir(No *raiz, Pessoa *x){
    if(raiz == NULL) // árvore vazia
        return novoNo(x);
    else{ // inserção será à esquerda ou à direita?
        if(x->cpf < raiz->pessoa->cpf)
            raiz->esquerdo = inserir(raiz->esquerdo, x);
        else if(x->cpf > raiz->pessoa->cpf)
            raiz->direito = inserir(raiz->direito, x);
        else
            printf("\nInsercao nao realizada!\nO elemento %d a existe!\n", x->cpf);
    }

    raiz->altura = maior(alturaDoNo(raiz->esquerdo), alturaDoNo(raiz->direito)) + 1;

    raiz = balancear(raiz);

    return raiz;
}

/*
    Função para remover um nó da Árvore binária
    Pode ser necessário rebalancear a árvore e a raiz pode ser alterada
    por isso precisamos retornar a raiz
*/
No* remover(No *raiz, int chave) {
    if(raiz == NULL){
        printf("Valor nao encontrado!\n");
        return NULL;
    } else { // procura o nó a remover
        if(raiz->pessoa->cpf == chave) {
            // remove nós folhas (nós sem filhos)
            if(raiz->esquerdo == NULL && raiz->direito == NULL) {
                free(raiz);
                printf("Elemento folha removido: %d !\n", chave);
                return NULL;
            }
            else{
                // remover nós que possuem 2 filhos
                if(raiz->esquerdo != NULL && raiz->direito != NULL){
                    No *aux = raiz->esquerdo;
                    while(aux->direito != NULL)
                        aux = aux->direito;
                    Pessoa *pessoaAux;
                    pessoaAux = raiz->pessoa;
                    raiz->pessoa = aux->pessoa;
                    aux->pessoa = pessoaAux;
                    printf("Elemento trocado: %d !\n", chave);
                    raiz->esquerdo = remover(raiz->esquerdo, chave);
                    return raiz;
                }
                else{
                    // remover nós que possuem apenas 1 filho
                    No *aux;
                    if(raiz->esquerdo != NULL)
                        aux = raiz->esquerdo;
                    else
                        aux = raiz->direito;
                    free(raiz);
                    printf("Elemento com 1 filho removido: %d !\n", chave);
                    return aux;
                }
            }
        } else {
            if(chave < raiz->pessoa->cpf)
                raiz->esquerdo = remover(raiz->esquerdo, chave);
            else
                raiz->direito = remover(raiz->direito, chave);
        }

        raiz->altura = maior(alturaDoNo(raiz->esquerdo), alturaDoNo(raiz->direito)) + 1;

        raiz = balancear(raiz);

        return raiz;
    }
}

void imprimirPessoa(Pessoa *pessoa){
    printf("Nome: %s CPF: %d Idade: %d\n", pessoa->nome, pessoa->cpf, pessoa->idade);
}

void imprimir(No *raiz, int nivel){
    int i;
    if(raiz){
        imprimir(raiz->direito, nivel + 1);
        printf("\n\n");

        for(i = 0; i < nivel; i++)
            printf("\t");

        imprimirPessoa(raiz->pessoa);
        imprimir(raiz->esquerdo, nivel + 1);
    }
}

int main(){

    int opcao, valor;
    No *raiz = NULL;
    Pessoa *p;

    do{
        printf("\n\n\t0 - Sair\n\t1 - Inserir\n\t2 - Remover\n\t3 - Imprimir\n\n");
        scanf("%d", &opcao);

        switch(opcao){
        case 0:
            printf("Saindo!!!");
            break;
        case 1:
            p = malloc(sizeof(Pessoa));
            printf("\tDigite o nome: ");
            scanf("%s", p->nome);
            printf("Digite o CPF e a Idade: ");
            scanf("%d%d", &p->cpf, &p->idade);
            raiz = inserir(raiz, p);
            break;
        case 2:
            printf("\tDigite o CPF a ser removido: ");
            scanf("%d", &valor);
            raiz = remover(raiz, valor);
            break;
        case 3:
            imprimir(raiz, 1);
            break;
        default:
            printf("\nOcao invalida!!!\n");
        }

    }while(opcao != 0);

    return 0;
}

Deixe um comentário

dezenove + seis =

Wagner Gaspar

Capixaba de São Gabriel da Palha, Espírito Santo. Bacharel em Ciência da Computação pela Universidade Federal do Amazonas e mestre em informática pela Universidade Federal do Espírito Santo.