































































Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Os conceitos fundamentais de árvores binárias de busca e árvores avl, incluindo algoritmos de busca, inserção e remoção. analisa a complexidade computacional desses algoritmos e explora o conceito de balanceamento em árvores, com foco em como transformar árvores desbalanceadas em árvores balanceadas, como as árvores avl, que garantem operações em o(log n). O texto detalha algoritmos e ilustra com exemplos práticos.
Tipologia: Resumos
1 / 71
Esta página não é visível na pré-visualização
Não perca as partes importantes!
































































Apresentação dos conceitos e os principais algoritmos de busca, inserção e remoção para as árvores
binárias de busca, Análise de complexidade computacional dos algoritmos para manipulação das
árvores binárias de busca, Apresentação do conceito de árvore balanceada e um algoritmo estático
capaz de transformar uma árvore binária de busca em uma árvore balanceada, Conceituação de árvores
AVL, Apresentação dos algoritmos dinâmicos para manutenção da propriedade AVL.
A busca, inserção e remoção de informações em uma estrutura de dados é um dos principais problemas
da computação. Ao aprender a utilizar corretamente as estruturas de dados para resolver problemas
específicos, você será capaz de desenvolver sistemas que são muito mais rápidos e eficientes. As
árvores são uma estrutura de dados no qual o problema da busca, inserção e remoção é resolvido em
um tempo proporcional ao logaritmo do número de chaves na estrutura de dados.
Reconhecer os tipos de percursos utilizados em árvores binárias
Definir árvores binárias de busca
Definir o conceito de árvore balanceada
Definir árvores AVL
Neste tema, aprenderemos a utilizar uma estruturas de dados capaz de realizar busca, inserção e
remoção em complexidade computacional de O ( log n). Para tal, partiremos do conceito de árvores
binárias, explorando o conceito de percursos, em seguida, definiremos as árvores binárias de busca e
seus principais algoritmos, comparando a complexidade computacional com as listas lineares e as
estruturas de dados não organizadas.
Explicaremos o conceito de balanceamento e aprenderemos como transformar árvores não balanceadas
em balanceadas e sobre uma estrutura de dados completamente dinâmica capaz de realizar as
operações de busca, inserção e remoção em O(log n), as árvores AVL.
Td é composta pelos nós: “C” e “G”. “B” e “C” são as raízes de Te e Td respectivamente.
Observe que, na árvore binária, existe, por definição, distinção entre a subárvore esquerda e direita. Por
isso, na representação gráfica sempre fica evidente a posição esquerda ou direita do nó subordinado à
raiz.
Na Figura 1, o nó “G” é o filho direito de “C” e não existe filho esquerdo de “C”, por exemplo.
Representação em memória
A forma mais comum de representar uma árvore em memória é utilizando alocação dinâmica. Na
verdade, não representamos a árvore como um todo, mas, sim, uma referência para sua raiz que guarda
a chave (dado) e uma referência para a raiz das subárvores esquerda e direita.
A abstração mais utilizada para representar o nó de uma árvore é o agregado heterogêneo, comumente
chamado de registro (record) ou estrutura (struct) nas linguagens de programação. O agregado
heterogêneo é capaz de armazenar objetos de dados de tipos diferentes acessando-os através do
mesmo identificador e distinguindo-os através de um seletor.
Para representar o nó da árvore, será construído um agregado heterogêneo contendo a chave
associada ao nó, e ponteiros para as raízes das subárvores esquerda e direita.
Em pseudocódigo, utilizaremos a estrutura sintática abaixo para descrever um nó.
registro no { Inteiro chave; registro no *esq; registro no *dir; }
Assim, a árvore é representada por uma referência para sua raiz, ponteiro para raiz, que,
recursivamente, aponta para as raízes das subárvores esquerda e direita.
Convencionamos que a variável “raiz”, declarada da forma abaixo, é um ponteiro para a raiz da árvore.
registro no *raiz;
A árvore vazia é representada quando o ponteiro raiz aponta para o endereço 0, associado à constante
NULA. Assim, para inicializar uma árvore vazia, basta inicializar o ponteiro raiz como nulo.
raiz = NULA;
Para que o conceito fique claro, será utilizada a árvore da Figura 2 para ilustrar a representação em
memória da estrutura.
Fonte: O Autor, 2020.
Figura 2 – Árvore Binária Exemplo.
Na árvore da Figura 2, temos cinco nós, identificados pelos inteiros: 10, 8, 4, 2 e 6.
Cada nó fazendo referência à raiz das subárvores esquerda e direita.
Esquematicamente, em memória, a estrutura é representada como na Figura 3.
Fonte: O Autor, 2020.
Figura 3 – Esquemático da representação de uma árvore em memória.
Observe que o conceito de árvore é recursivo , isto é, a estrutura foi definida em termos recursivos. Por
esta razão, as definições dos percursos também são recursivas.
PERCURSO EM PRÉ-ORDEM
O percurso em pré-ordem é definido recursivamente abaixo.
A partir da raiz r da árvore T , percorre-se a árvore da seguinte forma:
Visita-se a raiz.
Percorre-se a subárvore esquerda de T , em pré-ordem.
Percorre-se a subárvore direita de T , em pré-ordem.
Considerando como a operação de visita a impressão do rótulo contido no nó visitado, veremos o
resultado do percurso em pré-ordem da árvore da Figura 2.
Partindo-se da raiz, nó de rótulo “10”, temos que a primeira operação é a visita do nó, isto é, a impressão do seu rótulo.
Impressão: 10,
Em seguida, visita-se recursivamente a subárvore esquerda, cuja raiz é “8”, e a primeira operação é a visita do nó com rótulo “8”.
Impressão: 10, 8,
Após a visita do nó “8”, percorre-se recursivamente a subárvore esquerda do nó “8”, cuja raiz é o nó com rótulo “2”. A primeira operação deste percurso é a visita, isto é, o rótulo “2” é impresso.
Impressão: 10, 8, 2,
O nó “2” é folha, isto é, não possui subárvores, assim, o percurso da subárvore esquerda do nó “8” é concluído. O próximo passo é percorrer a subárvore direita do nó “8”, cuja raiz é o nó “6”. O primeiro passo é a impressão (visita).
Impressão: 10, 8, 2, 6,
Observe que o nó “6” é folha, o que encerra o percurso da subárvore de raiz “6”. Assim, retornamos ao
seu pai, o nó “8”, que é raiz da subárvore e que já teve seu percurso completo. O Pai de “8” é o nó “10”
que já foi visitado e que já teve sua subárvore esquerda visitada.
O próximo passo é visitar sua subárvore direita em pré-ordem. A raiz da subárvore direita é “4”, que é folha, sendo assim, o percurso pré-ordem da árvore é:
Impressão: 10, 8, 2, 6, 4
Observe que, para cada nó, acessamos seu conteúdo três vezes, porém, somente uma dessas vezes corresponde à visita. No caso do percurso em pré-ordem, a primeira vez.
Graficamente, os momentos de acesso podem ser vistos na Figura 4.
Fonte: O Autor, 2020.
Figura 4 – Acesso ao nó.
Na Figura 4, considere o contorno gráfico da árvore a partir da raiz no sentido anti-horário.
Os algoritmos recursivos para os percursos em pré-ordem, ordem simétrica e pós-ordem são
consequências diretas das definições dos percursos.
Vejamos o algoritmo da pré-ordem.
função pre-ordem (registro no *p) inicio visita (p); se (p->esq != NULO) pre-ordem (p->esq); se (p->dir != NULO) pre-ordem (p->dir); fim
Algoritmo 1 – Percurso em pré-ordem.
Para realizar o percurso em pré-ordem, são necessários três acessos ao nó.
No caso da pré-ordem, no primeiro, executamos a visita.
No segundo, chamamos recursivamente o algoritmo para a subárvore esquerda.
No terceiro, ocorre a chamada do percurso em pré-ordem do ramo direito.
Se temos n nós em uma árvore, o número de acesso ao nó é 3 n.
Assim, a complexidade computacional do percurso em pré-ordem é O(n).
O algoritmo do percurso em ordem simétrica é semelhante, modificamos somente o momento da visita,
resultando no Algoritmo 2.
função simetrica (registro no *p) inicio se (p->esq != NULO) simetrica (p->esq); visita (p); se (p->dir != NULO) simetrica (p->dir); fim
Algoritmo 2 – Percurso em ordem simétrica.
A análise de complexidade é análoga a feita no algoritmo do percurso em pré-ordem. Observe que a
única diferença é a ordem das visitas.
Sendo assim, a complexidade computacional do algoritmo para percurso em ordem simétrica é O(n).
Finalmente, temos o algoritmo para o percurso em pós-ordem (Algoritmo 3), que é resultado direto da
definição como o da pré-ordem e o da ordem simétrica.
função pos-ordem (registro no *p) inicio se (p->esq != NULO) pos-ordem (p->esq); se (p->dir != NULO) pos-ordem (p->dir); visita (p); fim
Algoritmo 3 – Percurso em pós-ordem.
A análise da complexidade é totalmente análoga à análise feita para pré-ordem e a ordem simétrica, o
que faz com que o algoritmo tenha complexidade O(n).
Algoritmo 4 – Algoritmo de pré-ordem não recursivo.
Para verificarmos o funcionamento do algoritmo, analisemos um exemplo e vejamos como os três
momentos do ábaco de “contorno” da árvore ocorre. Para tal, veja a árvore da Figura 6.
Fonte: O Autor, 2020.
Figura 6 – Árvore exemplo.
O algoritmo é inicializado com a pilha vazia. O primeiro passo é inserir a raiz “10” na pilha no momento
Sendo assim, o conteúdo da pilha é:
Pilha: (10,1)
Como a pilha não é vazia, o algoritmo entra em seu laço principal, removendo o nó 10 e o momento “1”
da pilha. Como o momento é igual a “1”, visita-se o nó imprimindo seu conteúdo e insere-se (10,2) e
(8,1), nesta ordem, na pilha. O par (8,1) está no topo da pilha.
Assim, a impressão do percurso e o conteúdo da pilha é:
Impressão 10,
Pilha (10,2), (8,1)
Com a repetição, remove-se o par do topo da pilha (8,1). De forma análoga ao primeiro laço do
algoritmo, visita-se o nó “8” e inserimos (8,2) na pilha.
Como o nó “8” não tem filho esquerdo, nada mais ocorre, resultando em:
Impressão 10, 8,
Pilha (10,2), (8,2)
No novo laço, remove-se o par (8,2) da pilha. O acesso ao nó está no momento “2” e existe nó à direita
de “8”, por isso insere-se (8,3) e (12,1) na pilha, resultando em:
Impressão 10, 8,
Pilha (10,2), (8,3), (12,1)
Na nova iteração, remove-se o par (12,1). Como é o momento “1”, visita-se o nó “12”, inserindo (12,2) na
pilha somente (“12” é folha), resultando em:
Impressão 10, 8, 12,
Pilha (10,2), (8,3), (12,2)
Na próxima iteração, remove-se o par (12,2), como “12” é folha, somente (12,3) é inserido na pilha.
Impressão 10, 8, 12,
Pilha (10, 2), (8, 3), (12,3)
No próximo loop, remove-se (12,3) da pilha e nada ocorre, resultando em:
Impressão 10, 8, 12,
Pilha (10, 2), (8, 3),
Na próxima iteração, remove-se (8,3) da pilha e nada ocorre, resultando em:
Impressão 10, 8, 12,
Pilha (10, 2),
Na próxima iteração, remove-se (10,2) da pilha. Como “10” tem filho direito, será inserida na pilha a
sequência de inserções de (10,3) e (7,1), resultando em:
Impressão 10, 8, 12,
Pilha (10, 3), (7, 1)
Próximo laço de repetição, remove-se (7,1). Como é o acesso no momento “1”, visita-se o nó “7”, insere-
se na pilha (7,2) somente uma vez que o nó “7” não tem filho esquerdo, resultando em:
Impressão 10, 8, 12, 7
Pilha (10, 3), (7,2),
No próximo passo, remove-se (7,2) e insere-se (7,3) na pilha. Como “7” não tem filho direito, o passo
termina, resultando em:
Impressão 10, 8, 12, 7
Pilha (10, 3), (7,3),
No próximo laço, remove-se (7,3), resultando em:
Impressão 10, 8, 12, 7
Pilha (10, 3),
No próximo laço, remove-se a dupla (10,3), resultando em:
Impressão 10, 8, 12, 7
fim
Algoritmo 5 – Algoritmo de ordem simétrica não recursivo.
função pos-ordem-não-recursiva (registro no *p) inicio registro no *aux; inteiro momento; push (p,1); //insere a raiz de T, no momento 1 enquanto (pilha não vazia) inicio pop (aux,momento); if (momento == 1) inicio push (aux,2); //push aux na pilha, momento 2 se (aux->esq != NULO) push (aux->esq,1); fim if (momento == 2) inicio push (aux,3); se (aux->dir != NULO) push (aux->dir,1); fim if (momento == 3) visitar (aux); fim fim
Algoritmo 6 – Algoritmo pós-ordem não recursivo.
O percurso é definido pela recursão, visita raiz, percorrer recursivamente esquerda e direita da raiz
considerada.
2. Dada a árvore abaixo, identifique o percurso em ordem-simétrica:
A alternativa "B " está correta.
O percurso é definido pela recursão, percorrer recursivamente esquerda, visitar a raiz e percorrer
recursivamente a direita da raiz considerada.
Definir árvores binárias de busca
O problema da busca, inserção e remoção é um dos principais objetivos do estudo de estruturas de
dados.
autor/shutterstock
Esta situação lúdica nos remete a algumas conclusões. Para tal, vamos relembrar alguns conceitos.
No estudo de complexidade, sempre levamos em consideração o pior caso, isto é, a análise de
complexidade é feita com a visão do pessimista.
A situação lúdica nos mostra que, na visão do pessimista, sem organização alguma, teremos que tirar n
bolinhas do saco para encontrar a bolinha vermelha.
Isso mostra que, sem nenhuma organização da informação, é possível realizar uma busca com
complexidade computacional de O(n).
Outra conclusão importante é que, para melhorar a busca, isto é, para conseguir um algoritmo mais eficiente, precisamos perseguir a complexidade de O(log n). As árvores são as estruturas de dados que vão viabilizar este objetivo.