Logo Passei Direto
Buscar
Material
páginas com resultados encontrados.
páginas com resultados encontrados.

Prévia do material em texto

UNOESC – Universidade do Oeste do Estado de Santa Catarina 
Ciência da Computação 
Docente: Tiago Heineck 
Discente: Alexandre de Bortoli 
Videira, 02 de novembro de 2022 
 
ÁRVORES 
 
Estruturas tipo árvores – não lineares, não sequencial e não estão todos encadeados. 
Uma árvore é composta por um conjunto de nós. 
Árvore binária – conjunto infinito de elementos. Cada elemento é denominado nó. O 
primeiro é conhecido como raiz da árvore. 
Se todo nó que não é folha numa árvore binária tiver subárvores esquerda e direita 
não-vazias, a árvore será considerada uma árvore estritamente binária. 
Para uma árvore ser estritamente binária, todos os nós devem ter 0 ou 2 filhos. 
De maneira recursiva, podemos definir uma árvore binária como sendo: uma árvore 
vazia ou, um nó raiz tendo duas subárvores, identificadas como a subárvore da direita (sad) e 
a subárvore da esquerda (sae). 
Árvores são estruturas de dados adequadas para representação de hierarquias, como 
exemplo de uma estrutura hierárquica temos os arquivos (documentos) que criamos em um 
computador. 
Denominamos um nó r chamado raiz, este pode conter zero ou mais subárvores, que 
são chamados de filhos do nó pai, r. Nós com filhos são comumente chamados de nós internos, 
e nós que não têm filhos são chamados de filhos ou nós externos. 
Por notação textual podemos representar por uma árvore vazia e as não vazias por 
. Uma árvore é representada pelo endereço do nó raiz, uma árvore vazia é 
representada pelo valor NULL. Para tratar a árvore vazia de forma diferente das outras, é 
importante ter uma operação que diz se uma árvore é ou não vazia. 
Na computação as raízes ficam no topo e as folhas no chão. 
Definição de Ancestral e Descendente: supomos que em uma árvore tenhamos nove 
nós (A até I), no topo temos a raiz (A), a sua subárvore esquerda está enraizada em B e sua 
subárvore direita em C. Nessa estrutura ao lado direito temos os nós sem filhos H e I) e ao 
lado esquerdo temos os nós sem filhos (D e G) que chamamos de folha. Com isso, definimos 
por exemplo, como A sendo um ancestral de G e H como sendo um descente de C. Dois 
nós são irmãos se forem filhos esquerdo e direito do mesmo pai. 
O nível de um nó numa árvore binária tem a raiz da árvore como nível 0 e o nível de 
qualquer outro nó na árvore é um nível a mais que o nível de seu pai. 
REPRESENTAÇÃO DE ÁRVORE BINÁRIA EM C 
• Iniciamos com a função structs, também conhecida como Registros, para definir os 
tipos de dados que agrupam variáveis sob um mesmo tipo de dado. Cada nó deve 
armazenar três informações: a informação propriamente dita, no caso um caractere 
(char info), e dois ponteiros para as subárvores, à esquerda (struct arv* esq) e à direita 
(struct arv* dir); 
• A estrutura da árvore é representada por um ponteiro para o nó raiz, dado o ponteiro 
para o nó raiz da árvore, tem-se acesso aos demais nós. 
• Utilizaremos o comando typedef para definirmos o nome para o tipo determinado que 
no caso será o Arv, ficando desse jeito: “typedef struct arv Arv”; 
• Nesse exemplo estamos descrevendo operações que sejam as mais gerais possíveis, 
e para isso necessitamos incluir a criação de uma árvore vazia, representada pelo valor 
NULL. 
• CONSTRUÇÃO DE ÁRVORES NÃO-VAZIAS: operação que cria um nó raiz dadas a 
informação e as duas árvores (esquerda e direita). A estrutura ficará assim: 
Arv* arv_cria (char c1, Arv* sae, Arv* sad) 
Arv* p=(Arv*) malloc2(sizeof3(Arv)); 
p->info = c; 
p->esq = sae; 
p->dir = sad; 
return p; 
} 
• As duas funções para a criação de árvores, criavazia e cria, representam os dois casos 
da definição recursiva de árvore binária: uma árvore binária (Arv* a;) é vazia 
(a=arv_criavazia( );) ou é composta por uma raiz e duas subárvores 
(a=arv_cria(c,sae,sad);). 
• EXIBIR O CONTEÚDO DA ÁRVORE: esta função percorre recursivamente a árvore, 
visitando todos os nós e imprimindo sua informação. Para imprimir a informação de 
todos os nós da árvore, devemos primeiro testar se ela é vazia. Se não for, imprimimos 
a informação associada à raiz e chamamos (recursivamente) a função para imprimir as 
subárvores. 
Void4 arv_imprime (Arv* a) 
{ 
If (!arv_vazia(a)){ 
 Printf(“%c”5, a->info); 
 Arv_imprime(a->esq); 
 Arv_imprime(a->dir); 
 } 
} 
• LIBERAR A MEMÓRIA ALOCADA PELA ESTRUTURA DA ÁRVORE: um cuidado 
especial a ser tomado é liberar as subárvores antes de liberar o nó raiz, para que o acesso 
a elas não seja perdido antes de serem removidas. Nesse caso, vamos optar por fazer com 
que a função tenha como valor de retorno a árvore atualizada, isto é, uma árvore vazia, 
representada por NULL. 
 
1 Char c serve para alocar espaço na memória para algumas variáveis 
2 Abreviatura de memory allocation aloca espaço para um bloco de bytes consecutivos na memória RAM do computador e 
devolve o endereço desse bloco. 
3 Este operador permite saber o número de bytes ocupado por um determinado tipo de variável. É muito usado na alocação 
dinâmica de memória. 
4 Void quer dizer vazio. Ele nos permite fazer funções que não retornam nada e funções que não tem parâmetros. 
5 %c = um único caractere. 
Arv* arv_libera (Arv* a) { 
 If (!arv_vazia(a)){ 
 Arv_libera(a->esq); 
 Arv_libera(a->dir); 
 Free(a); 
} 
Return NULL; 
} 
• Devemos notar que a definição de árvore, por ser recursiva não faz distinção entre 
árvores e subárvores. O cria pode ser usado para acrescentar (“enxertar”) uma subárvore 
em um ramo de uma árvore, e o libera pode ser usado para remover (“podar”) uma 
subárvore qualquer de uma árvore dada. 
 
ANÁLISE DA COMPLEXIDADE DE ÁRVORES 
 
A relação existente entre altura da árvore (h) e número de nós (n) é uma informação 
muito importante. Possuem altura máxima aquelas em que cada nó possui apenas um único 
filho. A altura de tais árvores é igual a n. Já uma árvore completa possui altura mínima. 
Um dos cuidados é fazer a ramificação da árvore para que os elementos da árvore não 
fiquem ou totalmente para a ramificação da esquerda ou totalmente para a direita, nesses 
casos a altura da árvore segue uma estrutura linear e ela acaba sendo igual ao total de 
elementos que você está armazenando ali, esse é o pior cenário pois a árvore degenera para 
algo parecido com uma lista. 
O melhor caso é quando os elementos são distribuídos nível a nível, nesse caso a altura 
será log (n), pois as folhas da árvore é composta pela metade de todos os elementos que 
temos na árvore completa, se tiramos o último nível anterior teremos a metade dessa outra 
metade, com isso temos uma relação logarítmica. 
Assim teremos a distribuição desses elementos na função logarítmica de base 2, ou 
seja, teremos a raiz com um elemento, no próximo nível dois elementos, no próximo 4, depois 
8,16,32 e assim por diante. 
Esse seria o cenário ideal para termos uma árvore completa, pois assim teríamos a 
altura mínima, e conseguiríamos fazer busca, inserção e remoção em O(log(n)). 
O maior desafio é conseguir deixar essa árvore sempre completa, pois em 
determinadas situações você teria que mexer na árvore inteira para conseguir que ela 
continue sendo completa e isso consumiria O(n) e não queremos fazer essas operações que 
passem por todos os nós, pois a quantidade deles pode ser enorme. 
Então ao invés de ter uma preocupação excessiva para que a altura seja igual ao 
logaritmo da quantidade de elementos, utilizamos o proporcional a log (n), podendo ser um 
múltiplo de log (n), por exemplo: 2 vezes log(n) ou 3 vezes log(n). Mesmo que seja dobrado, 
ou triplicado, não teremos problemas, pois log (n) é um numero muito pequeno, por exemplo 
em relação a 1 milhão ele esta na casa de dezenas. 
Com isso concluímos que uma árvore balanceada é proporcional a log(n) (logaritmo 
da quantidade de elementos que existem armazenados na árvore). 
Uma árvore AVL é uma árvore na qual asalturas das subárvores esquerda e direita de 
cada nó diferem no máximo por uma unidade. Se o fator de balanceamento de qualquer nó 
ficar menor do que -1 ou maior do que 1 então a árvore tem que ser balanceada.

Mais conteúdos dessa disciplina