Linguagem C - Árvores Binárias

Neste artigo, falarei sobre o que é e como implementar uma estrutura de dados chamada Árvore Binária. Com tempos de pesquisa, inserção e remoção expressivamente melhores que de listas encadeadas, esta estrutura é usada principalmente em bancos de dados e sistemas de arquivos.

[ Hits: 51.261 ]

Por: Enzo de Brito Ferber em 07/05/2015 | Blog: http://www.maximasonorizacao.com.br


Código Completo e Comentado



/* arvore.c
 *
 * Linguagem C - Árvores Binárias
 * Viva O Linux
 *
 * Enzo Ferber
 * 2015
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define FOLHA		(Folha)malloc(sizeof(struct folha))
#define CMDSTR		500

typedef struct folha* Folha;

struct folha {
	int	info;

	Folha	esquerda;
	Folha	direita;
};

Folha inserir (Folha, int), deletar (Folha, int), procurar (Folha, int);
void imprimir_arvore (Folha, int);
void ordenada(Folha), preordenada(Folha), posordenada(Folha);
void cmd (void), help (void);

/* @ Folha inserir (Folha raiz, int info)
 *
 * Argumentos
 * ----------
 *	raiz	ponteiro para 'struct folha', estrutura de dado básica para
 *		construção da árvore
 *	info	nova informação a ser inserida
 *
 * Retorno
 * -------
 *	NULL	em caso de erro
 *	Folha	em caso de sucesso (nó 'raiz')
 */
Folha inserir (Folha raiz, int info)
{
	if (!raiz) {
		/* significa que o ponteiro é nulo, e que está é a posição
		 * para inserção.
		 *
		 * 1. Alocar e checar memória
		 */
		if (!(raiz = FOLHA)) {
			perror("inserir:malloc()");
			return NULL;
		}

		/* 2. Cópia da Informação
		 * 3. Ponteiros de referência
		 * 4. Retorno da nova folha
		 */
		raiz->info = info;
		raiz->esquerda = raiz->direita = NULL;

		return raiz;
	} else if (info > raiz->info)
		raiz->direita = inserir (raiz->direita, info);
	else
		raiz->esquerda = inserir (raiz->esquerda, info);

	/* retorna o ponteiro raiz
	 *
	 * Isso é necessário pois a função inserir é recursiva, e seu retorno
	 * é sempre atribuido ao mesmo ponteiro passado como argumento 'raiz',
	 *
	 * raiz->esquerda = inserir(raiz->esquerda,info)
	 * raiz->direita  = inserir(raiz->direta,  info)
	 */
	return raiz;
}

/* @ Folha deletar (Folha raiz, int info)
 *
 * Argumentos
 * ----------
 *	raiz	raiz principal da arvore
 *	info	informação procurada para deletar
 *
 *  Retorno
 *  -------
 *	raiz	em ambos os casos
 */
Folha deletar (Folha raiz, int info) {
	/* para deletar um nó, é necessário que esse nó exista.
	 * Então, vamos procurar pelo nó.
	 *
	 * A remoção de folhas em uma árvore é um pouco mais detalhada que sua
	 * similar para listas encadeadas.
	 *
	 * Existem alguns pontos a se considerar para remover uma folha:
	 *	1. A folha é também uma raiz (contém subárvores)?
	 *	2.
	 */
	Folha filho, n_raiz;

	/* se chegou ao final da arvore e nao encontrou nada para deletar....
	 */
	if (!raiz) return NULL;

	/* primeira comparação do programa:
	 *	Achamos a informação procurada?
	 */
	if (raiz->info == info) {
		/* 1. Existe uma raiz direita?
		 *	(uma raiz direita contém todos os elementos MAIORES que
		 *	a raiz que estamos excluindo).
		 *
		 * 2. Caso ela exista, ela será a nova raiz.
		 * 3. Os atuais elementos *menores* deverão ser anexados nos
		 *    menores elementos da nova raiz (segundo passo).
		 */
		if (raiz->direita) {
			/* 2. a nova raiz é a subárvore da direita, que contem
			 * todos os elementos MAIORES que a raiz a ser removida
			 */
			n_raiz = filho = raiz->direita;

			/* 3. a subárvore esquerda da nova raíz é percorrida
			 * até onde não houverem mais elementos. Isso nos dará
			 * o menor elemento maior que a folha esquerda de nossa
			 * raiz a ser removida.
			 */
			while(filho->esquerda)
				filho = filho->esquerda;

			/* o menor elemento maior que a raiz
			 * torna-se
			 * raiz de todos os elementos menores que a raiz
			 * removida
			 */
			filho->esquerda = raiz->esquerda;

			/* libera a memória do ponteiro */
			free (raiz);

			/* 1 + 2: nova raiz */
			return n_raiz;

		} else {
			/* caso não haja uma raiz direita (elementos MAIORES)
			 * que nossa raiz, a nova raiz da árvore seja o único
			 * elemento restante: a subárvore esquerda da árvore,
			 * ou os antigos elementos menores que a raiz removida.
			 */
			n_raiz = raiz->esquerda;

			/* libera a memória do ponteiro */
			free (raiz);

			/* retorno da nova raiz */
			return n_raiz;
		}

	} else if (info > raiz->info) {
		/* Caso a informação procurada seja maior que a informação na
		 * raiz, devemos então procurar pela informação na subárvore
		 * da direita (esta operação deverá ser congruente em todo o
		 * seu programa.
		 *
		 * Você não pode ordenar a inserção de um jeito e a remoção de
		 * outro!
		 */
		raiz->direita = deletar(raiz->direita, info);
	} else {
		/* Caso a informação seja menor que a raiz, procuraremos por ela
		 * nas subárvores da esquerda.
		 */
		raiz->esquerda = deletar(raiz->esquerda, info);
	}
	/* Lembre-se que as árvores binárias são estruturas de dados recursivas,
	 * e por esse motivo, é sempre bom saber o que se está retornando.
	 *
	 * Assim como na função de inserção, as chamadas recursivas à deletar()
	 * esperam como retorno uma raíz! Para onde a raiz->esquerda vai
	 * apontar quando a função retornar?
	 *
	 *	1. Se ela não for excluida, vai ser a própria raiz->esquerda
	 *	2. Caso seja excluída, será determinada pelo algoritmo no bloco
	 *	   if(raiz->info == info).
	 *
	 * Aqui, no final do escopo da função, significa que é um retorno de
	 * chamadas recursivas à deletar (já que o bloco raiz->info == info)
	 * retorna por si só. Já que este é um retorno das chamadas recursivas,
	 * deve retornar a raiz na qual foi chamada.
	 */
	return raiz;
}

Folha procurar (Folha raiz, int info)
{
	/* como todas as funções relativas a arvores binárias são recursivas,
	 * todas necessitam de um parametro de "parada de recursão", então,
	 * sempre que acharmos um nó NULO, retornamos (até porque não dá pra
	 * imprimir informações de um nó que não existe.
	 */
	if (!raiz) return NULL;

	if (info > raiz->info) return procurar(raiz->direita, info);
	else return procurar (raiz->esquerda, info);

	/* falhas, compilação, etc */
	return NULL;
}

void imprimir_arvore (Folha raiz, int level)
{
	register int i;

	/* como todas as funções relativas a arvores binárias são recursivas,
	 * todas necessitam de um parametro de "parada de recursão", então,
	 * sempre que acharmos um nó NULO, retornamos (até porque não dá pra
	 * imprimir informações de um nó que não existe.
	 */
	if (!raiz) return ;

	/* essa função é uma derivação da ordenação 'inorder', onde primeiro
	 * a arvore é percorrida até seu elemento mais esquerdo, e então é
	 * impressa a informação da raiz, e então é percorrida suas subárvores
	 * direitas.
	 *
	 * para imprimir de forma 'didática', essa função viaja primeiro pelas
	 * folhas direitas, imprime a raiz e então percorre as subárvores
	 * esquerdas.
	 */
	imprimir_arvore (raiz->direita, level + 1);

	for (i = 0; i < level * 2; i++ ) putchar(' ');
	printf("%d
", raiz->info);

	imprimir_arvore (raiz->esquerda, level + 1);
}

/* @ void ordenada (Folha raiz)
 *
 * Transversalização Ordenada
 * --------------------------
 * Primeiro é visitada a subárvore esquerda, depois a raiz e por último a
 * subárvore direita.
 */
void ordenada (Folha raiz)
{
	if (!raiz) return ;

	ordenada (raiz->esquerda);
	printf ("%d ", raiz->info);
	ordenada (raiz->direita);
}

/* @ void preordenada (Folha raiz)
 *
 * Transversalização Pré-Ordenada
 * ------------------------------
 * Primeiro é visitada a raiz, depois a subárvore esquerda e por último a
 * subárvore direita.
 */
void preordenada (Folha raiz)
{
	if (!raiz) return ;

	printf ("%d ", raiz->info);
	preordenada (raiz->esquerda);
	preordenada (raiz->direita);
}

/* @ void posordenada (Folha raiz)
 *
 * Transversalização Pós-Ordenada
 * ------------------------------
 * Primeiro é visitada a subárvore esquerda, depois a subárvore direita e por
 * último a raiz.
 */
void posordenada (Folha raiz)
{
	if (!raiz) return;

	posordenada (raiz->esquerda);
	posordenada (raiz->direita);
	printf ("%d ", raiz->info);
}

/******************************************************************************
 * Aqui termina todo o código relacionado às árvores binárias e começa o código
 * relative à interface.
 *
 */
void cmd (void)
{
	char cmd[CMDSTR], *arg;
	Folha raiz = NULL;

	help();

	while (1) {
		printf ("ArvoreBinaria> ");
		fgets (cmd, CMDSTR, stdin);

		if (!cmd) continue;

		switch (*cmd) {
		case 'i':
			arg = &cmd[2];
			arg = strtok(arg, " ");
			while (arg) {
				if (!raiz) raiz = inserir (raiz, atoi(arg));
				else inserir (raiz, atoi(arg));

				arg = strtok (NULL, " ");
			}
			break;
		case 'd':
			arg = &cmd[2];
			arg = strtok(arg, " ");
			while (arg) {
				raiz = deletar (raiz, atoi(arg));

				arg = strtok (NULL, " ");
			}
			break;
		case 'm':
			imprimir_arvore (raiz, 0);
			break;
		case 'o':
			ordenada(raiz);
			puts("");
			break;
		case 'r':
			preordenada(raiz);
			puts("");
			break;
		case 'p':
			posordenada(raiz);
			puts("");
			break;
		case 's':
			exit(0);
		case 'h':
			help();
			break;
		default:
			printf("Comando nao reconhecido.
");
			break;
		}

		memset (cmd, 0x0, CMDSTR);
	}
}

/* @ void help (void)
 *
*	i 20	vai inserir o elemento 20 na árvore
 *	d 20	vai remover o elemento 20 da árvore
 *	m	vai mostrar a árvore na tela
 *	o	transversalização ordenada
 *	r	transversalização pré-ordenada
 *	p	transversalização pós-ordenada
 *	s	sai do programa
 *	h	mostra a ajuda
 */
void help (void)
{
	printf ("    |    Arvores Binarias
");
	printf ("    |    Implementacao em C para o Viva O Linux
");
	printf ("    |    
");
	printf ("    |    Autor: Enzo Ferber
");
	printf ("    |    2015
");
	printf ("    |

");

	printf ("		Lista de comandos
");
	printf ("		-----------------
");
	printf ("		i %%d - Inserir um elemento
");
	printf ("		d %%d - Deletar um elemento
");
	printf ("		m    - Mostrar a arvore lateralmente
");
	printf ("		o    - Transversalizacao Ordenada
");
	printf ("		r    - Transversalizacao Pre-Ordenada
");
	printf ("		p    - Transversalizacao Pos-Ordenada
");
	printf ("		s    - Sair do programa
");
	printf ("		h    - Mostra a ajuda

");
}

int main (void)
{
	cmd ();
	return 0;
}

Página anterior    

Páginas do artigo
   1. Introdução
   2. Recursividade
   3. Inserção
   4. Pesquisa
   5. Remoção
   6. Transversalização
   7. Conclusão
   8. Código Completo e Comentado
Outros artigos deste autor

Linguagem C - Listas Duplamente Encadeadas

Linguagem C - Funções Variádicas

Leitura recomendada

Otimização de algoritmos

Tutorial SDL

Guia de Programação em C/GTK 2 - Construindo uma Calculadora Completa

Linguagem C - Listas Duplamente Encadeadas

Algoritmo... como fazer?

  
Comentários
[1] Comentário enviado por fabio em 07/05/2015 - 12:10h

Parabéns pelo trabalho, dissertação completa sobre o assunto.

[2] Comentário enviado por SamL em 09/05/2015 - 12:49h

Completissimo, muito bom, parabéns.

[3] Comentário enviado por EnzoFerber em 09/05/2015 - 13:34h


Muito obrigado, pessoal.

[4] Comentário enviado por pmargreff em 09/05/2015 - 21:09h


Primeiramente, parabéns pelo artigo.
Segundo, gostaria de saber que programa utilizou para fazer as imagens. Abraço.

[5] Comentário enviado por EnzoFerber em 11/05/2015 - 08:44h


[4] Comentário enviado por pablomrg em 09/05/2015 - 21:09h


Primeiramente, parabéns pelo artigo.
Segundo, gostaria de saber que programa utilizou para fazer as imagens. Abraço.


Bom dia.

Para as imagens usei o primo rico do GIMP! Filho da Adobe!

[]'s

[6] Comentário enviado por dianaluz92 em 12/05/2015 - 15:08h


Parabéns pelo trabalho, a parte conceitual está muito clara da para a estrutura da arvore, mas eu queria tirar um duvida se for possível, como inserir um conjunto de elementos na árvore no caso quando o campo info é um ponteiro que deverá receber elemento tipo inteiro,float e um ponteiro char para nome.
Desde já grata,

[7] Comentário enviado por EnzoFerber em 12/05/2015 - 16:19h


[6] Comentário enviado por dianaluz92 em 12/05/2015 - 15:08h


Parabéns pelo trabalho, a parte conceitual está muito clara da para a estrutura da arvore, mas eu queria tirar um duvida se for possível, como inserir um conjunto de elementos na árvore no caso quando o campo info é um ponteiro que deverá receber elemento tipo inteiro,float e um ponteiro char para nome.
Desde já grata,

Boa tarde.
Pelo que entendi, você quer usar árvores para armazenar uma estrutura sua, certo?

typedef struct data {
int i;
float f;
char *nome;
} data_t;

Coloque os ponteiros direita e esquerda na estrutura que está usando.

typedef struct data* data_t;
struct data {
int i;
float f;
char *nome;
data_t esquerda, direita;
};

Agora é só modificar as funções inserir(), deletar(), procurar(), imprimir(), etc.

if (!raiz) {
...
} else if (strcmp(raiz->nome, nome_ptr) > 0) raiz->direita = inserir(raiz->direita, nome_ptr);
else if (strcmp(raiz->nome, nome_ptr) < 0 ) raiz->equerda = inserir(raiz->esquerda, nome_ptr);
else {
// strcmp() == 0
// dado existente
printf("dado existente!");
return raiz;
}
return raiz;

Espero ter ajudado,
[]'s
Enzo Ferber

[8] Comentário enviado por Ragen em 13/05/2015 - 08:41h

Bom dia Enzo,

Parabéns pelo texto. Vejo que hoje muitos do meio acadêmico não tem nem de longe a sua didática - até porque para ser didático, um professor precisa ser profundo conhecedor do tema.

Eu fiz faculdade em 2003 na PUC-Goiás e me recordo das aulas de ICC2 onde fizemos este caso de B-tree. Logo depois a grade mudou, e o curso afundou em total decadência.

Hoje vejo que muitos alunos se formam bacharéis sem ao menos entender o conceito das árvores binárias. Sem entender o que é um ponteiro, endereço de memória - afinal as faculdades estão pondo Java no lugar do C. Moral da história? Os alunos se formam e não sabem programar - então terminam seus cursos e ficam desempregados e não entendem porque nenhum empresa os querem contratar.

Obrigado por compartilhar conosco este material e seu conhecimento!

[9] Comentário enviado por EnzoFerber em 13/05/2015 - 09:04h


[8] Comentário enviado por Ragen em 13/05/2015 - 08:41h

Bom dia Enzo,

Parabéns pelo texto. Vejo que hoje muitos do meio acadêmico não tem nem de longe a sua didática - até porque para ser didático, um professor precisa ser profundo conhecedor do tema.

Eu fiz faculdade em 2003 na PUC-Goiás e me recordo das aulas de ICC2 onde fizemos este caso de B-tree. Logo depois a grade mudou, e o curso afundou em total decadência.

Hoje vejo que muitos alunos se formam bacharéis sem ao menos entender o conceito das árvores binárias. Sem entender o que é um ponteiro, endereço de memória - afinal as faculdades estão pondo Java no lugar do C. Moral da história? Os alunos se formam e não sabem programar - então terminam seus cursos e ficam desempregados e não entendem porque nenhum empresa os querem contratar.

Obrigado por compartilhar conosco este material e seu conhecimento!


Bom dia, Ragen.
Em primeiro lugar, muito obrigado pelos elogios.

Segundo, concordo plenamente com você (Muita gente e eu, na verdade). Uma dessas pessoas é o CEO do StackExchange, que possui um blog muito bom. O nome dele é Joel Spolsky e o blog é o http://www.joelonsoftware.com. Ele escreveu um artigo precisamente sobre a troca de linguagens "mais baixas" pelo Java nas faculdades, e como isto está arruinando o mercado de trabalho e a qualidade dos profissionais. O artigo chama-se "The Perils of JavaSchool". Excelente texto, vale a pena ser lido. (O blog todo, na verdade)
http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html

Pessoalmente, acredito que todos deveriam ter um semestre em Assembly para entender como a CPU/Memória/HD funcionam e interagem - como tudo realmente acontece "embaixo do capô". Faz uma falta danada!

Sobre Joel: http://www.joelonsoftware.com/AboutMe.html
StackExchange: http://www.stackexchange.com

*

http://blog.codinghorror.com/why-cant-programmers-program/
Artigo muito bom também, falando sobre bacharéis que não conseguem programar programas *simples*! Leitura recomendada!


[]'s
Enzo Ferber

[10] Comentário enviado por Ragen em 13/05/2015 - 18:22h


[9] Comentário enviado por EnzoFerber em 13/05/2015 - 09:04h


[8] Comentário enviado por Ragen em 13/05/2015 - 08:41h

Bom dia Enzo,

Parabéns pelo texto. Vejo que hoje muitos do meio acadêmico não tem nem de longe a sua didática - até porque para ser didático, um professor precisa ser profundo conhecedor do tema.

Eu fiz faculdade em 2003 na PUC-Goiás e me recordo das aulas de ICC2 onde fizemos este caso de B-tree. Logo depois a grade mudou, e o curso afundou em total decadência.

Hoje vejo que muitos alunos se formam bacharéis sem ao menos entender o conceito das árvores binárias. Sem entender o que é um ponteiro, endereço de memória - afinal as faculdades estão pondo Java no lugar do C. Moral da história? Os alunos se formam e não sabem programar - então terminam seus cursos e ficam desempregados e não entendem porque nenhum empresa os querem contratar.

Obrigado por compartilhar conosco este material e seu conhecimento!

Bom dia, Ragen.
Em primeiro lugar, muito obrigado pelos elogios.

Segundo, concordo plenamente com você (Muita gente e eu, na verdade). Uma dessas pessoas é o CEO do StackExchange, que possui um blog muito bom. O nome dele é Joel Spolsky e o blog é o http://www.joelonsoftware.com. Ele escreveu um artigo precisamente sobre a troca de linguagens "mais baixas" pelo Java nas faculdades, e como isto está arruinando o mercado de trabalho e a qualidade dos profissionais. O artigo chama-se "The Perils of JavaSchool". Excelente texto, vale a pena ser lido. (O blog todo, na verdade)
http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html

Pessoalmente, acredito que todos deveriam ter um semestre em Assembly para entender como a CPU/Memória/HD funcionam e interagem - como tudo realmente acontece "embaixo do capô". Faz uma falta danada!

Sobre Joel: http://www.joelonsoftware.com/AboutMe.html
StackExchange: http://www.stackexchange.com

*

http://blog.codinghorror.com/why-cant-programmers-program/
Artigo muito bom também, falando sobre bacharéis que não conseguem programar programas *simples*! Leitura recomendada!


[]'s
Enzo Ferber


Opa Enzo,

Valeu pela dica. Eu já conhecia o StackExchange, mas não conhecia o blog do fundador - estou lendo os links que recomendou, realmente ótima leitura!

Se tiver Linkedin, me adiciona lá: https://www.linkedin.com/profile/view?id=80923831

[11] Comentário enviado por EnzoFerber em 18/07/2015 - 12:15h

Pessoal,

Na página 4 há um erro. Na função pesquisa esqueci de fazer justamente a comparação que identifica o elemento procurado.
Para utilizar a função corretamente, favor fazer a seguinte modificação:

Após a linha "if (!raiz) return NULL;"
Colocar:
else if (raiz->info == info) return raiz;

É isso.

[]'s


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts