Baixe o app para aproveitar ainda mais
Prévia do material em texto
LÓGICA DE PROGRAMAÇÃO E ESTRUTURAS DE DADOS Professor Marcelo Ferreira Zochio 2 6 ÁRVORES BINÁRIAS Neste bloco, você irá aprender os principais conceitos e aplicações de árvores binárias, saberá como uma máquina decide qual o menor caminho entre um ponto e outro, e também como representar árvores binárias em Python. 6.1 Árvores de busca Quando queremos representar hierarquicamente uma estrutura de dados com mais de uma dimensão, usamos um tipo de estrutura chamada árvore, que consiste em nós e arcos. Veja alguns exemplos de árvores na figura a seguir: Nota que uma árvore é composta pelo nó raiz, do qual saem todos os outros nós e/ou folhas. 3 Essa outra figura contém um exemplo de uma árvore que representa a hierarquia de uma empresa: Podemos representar valores através da estrutura em árvore. Quando uma árvore possui em todos os nós da subárvore esquerda valores numéricos inferiores ao nó raiz e todos os nós da subárvore direita, valores superiores ao nó raiz, e os nós possuem no máximo duas folhas, ela é definida como árvore binária de busca. Veja um exemplo: A busca em árvore binária normalmente é feita usando um processo recursivo. Essa busca começa examinando-se o nó raiz. Se o valor é igual ao valor armazenado na raiz, a busca se encerra. Se o valor é menor do que a raiz, a busca segue pela ramificação esquerda. Caso o valor seja maior do que a raiz, a busca segue pela ramificação direita. 4 Esse processo é repetido até que o valor seja encontrado. Se o valor não for encontrado, então o valor não deve estar presente na árvore. Uma árvore binária de busca aceita outras operações além de busca. Na inserção, a raiz é verificada e introduz-se um nó novo na subárvore da esquerda, se o novo valor for menor do que a raiz, ou na subárvore da direita, se o valor novo for maior do que a raiz. Na operação remoção, você deve considerar algumas regras: Se for para remover apenas uma folha, basta removê-la simplesmente; Se for para remover um nó com apenas uma folha, a folha passa a ocupar o lugar desse nó removido; Se for para remover um nó com duas folhas, pode-se substituir o nó a ser retirado pelo nó sucessor (o nó mais à esquerda da ramificação direita) ou pelo nó antecessor (o nó mais à direita da ramificação esquerda). Veja exemplos nas figuras a seguir: Removendo uma folha 5 Removendo um nó com uma folha Removendo um nó com duas folhas SAIBA MAIS Neste vídeo, você aprenderá mais sobre árvores de busca. Vídeo: Árvore Binária de Busca GUERRA, Rodrigo. Árvore binária de busca. 2014. Disponível em: <https://www.youtube.com/watch?v=XZ0MEDhb4oE>. Acesso em: 15 fev. 2019. https://www.youtube.com/watch?v=XZ0MEDhb4oE 6 6.2 Balanceamento de árvores binárias Dizemos que uma árvore está balanceada quando a altura das duas subárvores da raiz for igual a 1, -1 ou 0. Veja um exemplo de uma árvore desbalanceada: Árvore desbalanceada Note que a profundidade dessa árvore é desigual, medindo 2 na subárvore direita em relação à esquerda. Veja agora um exemplo de uma árvore balanceada: Árvore balanceada Quando uma árvore está desbalanceada, é preciso balanceá-la. Como? Através de rotações. Podemos definir rotação como operação que influencia no balanceamento de uma árvore, sem alterar a sequência de percurso. Há os seguintes tipos de rotações: esquerda simples, direita simples, esquerda dupla e direita dupla. Vejamos cada um deles: 7 Rotação à esquerda simples Rotação à esquerda dupla Rotação à direita simples 8 Rotação à direita dupla 6.3 Algoritmo de Dijkstra O algoritmo de Dijkstra, desenvolvido pelo cientista da computação E.W. Dijkstra, é um algoritmo que calcula o caminho mais econômico em questão de custo entre vértices de um grafo. Escolhido um vértice como ponto de origem, o algoritmo calcula o custo desse vértice para todos os vértices do grafo, e escolhe o caminho mais curto e com menor custo. Veja os grafos a seguir expondo um problema a ser resolvido com esse algoritmo. Qual o caminho mais curto em ter os pontos A e F? 9 Vértice Passo 1 Passo 2 Passo 3 Passo 4 Passo 5 Passo 6 A 0, A - - - - - B 5, A 5, B 5, B - - - C 3, A 3, A - - - - D ∞ 12, C 11, B 11, B - - E ∞ 14, C 14, C 14, D 14, D - F ∞ ∞ ∞ 17, D 17, E 17, E O melhor caminho, partindo de A para chegar até F, é C, B, D, E, F. 6.4 Aplicações práticas de árvores binárias Podemos citar duas entre várias: jogos de xadrez e futebol de robôs. No xadrez, a estrutura em árvore permite analisar as possibilidades de movimentos e posições do tabuleiro em níveis de profundidade. Quando você escolhe os níveis de dificuldade para vencer seu adversário, jogando contra o computador, quanto mais elevado o nível, mais profunda é a análise das jogadas feitas pela máquina, ou seja, ela consegue prever seus próximos lances com mais precisão. Por isso, é mais difícil vencer a máquina em níveis mais altos, e mais ela demora para jogar, pois precisa analisar mais possibilidades. No futebol de robôs, veja a seguinte situação: a partir da figura apresentada a seguir, qual a melhor rota que o robô deve seguir para pegar a bola? 10 Transformaremos o cenário em um sistema de grafos: Para mapear o cenário, traçaremos uma reta entre o ponto inicial (robô) e o ponto final (bola) e também traçaremos retas perpendiculares à essa reta nos outros grafos: Traçaremos um círculo em torno de cada grafo com distância de 2r (2 vezes seu raio) para evitar colisões ao estabelecer a rota do robô. Os pontos de intersecção das retas perpendiculares e os círculos serão considerados grafos do nosso sistema. 11 Então ligamos os grafos uns com os outros a partir do ponto de origem: Os pontos que cruzarem a zona de segurança (os círculos) serão desconsiderados: Então ligamos os outros pontos sem passar pelos círculos: Este é nosso grafo, já com o cálculo dos custos de percurso entre os pontos, considerando a distância entre eles: 12 Aplicando o algoritmo de Dijkstra, chega-se a esta solução de percurso: Mostrando no cenário: 6.5 Exemplos de árvores binárias em Python Como representar e montar uma árvore binária em Python? Veja a seguir. Nesse programa, usaremos conceito de orientação a objetos para representar a seguinte árvore binária: 13 Os valores estão em branco, pois iremos escolhê-los durante a execução do programa. Eis o código: # -*- coding: cp1252 -*- class Tree: def __init__(self, cargo, left=None, right=None): self.cargo = cargo self.left = left self.right = right def printTreeIndented(tree, level=0): if tree == None: return printTreeIndented(tree.right, level+1) print ' '*level + str(tree.cargo) printTreeIndented(tree.left, level+1) def abertura(): a = input("Digite o valor da raiz da árvore: ") b = input("Digite o valor de uma das folhas: ") c = input("Digite o valor da outra folha: ") if b > a and c < a: tree = Tree(a, Tree(c), Tree(b)) printTreeIndented(tree, level=0) 14 elif b < a and c > a: tree = Tree(a, Tree(b), Tree(c)) printTreeIndented(tree, level=0) else: print "Os valores devem ser maiores ou menores que a raiz!" abertura() abertura() A saída é mostrada na figura a seguir: O número mais à esquerda é a raiz; as folhas e nós da ramificação esquerda estão abaixo do número mais à esquerda; as folhas e nós da ramificação direita estão acima do número mais à esquerda. Para criarmosuma árvore com mais ramificações, é só alterarmos o código. Vejamos o trecho alterado para criarmos uma árvore com uma raiz e quatro elementos, com os seguintes formatos: 15 (…) def printTreeIndented(tree, level=0): if tree == None: return printTreeIndented(tree.right, level+1) print ' '*level + str(tree.cargo) printTreeIndented(tree.left, level+1) def printTreeIndented1(tree, level=0): if tree == None: return printTreeIndented1(tree.left, level+1) print ' '*level + str(tree.cargo) printTreeIndented1(tree.right, level+1) def abertura(): a = input("Digite o valor da raiz da árvore: ") b = input("Digite o valor de um nó: ") c = input("Digite o valor de um nó: ") 16 d = input("Digite o valor de uma das folhas: ") e = input("Digite o valor de uma das folhas: ") if b > a and c > b > a and d > c > a and e > d > a: tree = Tree(a, Tree(b, Tree(c, Tree(d, Tree(e))))) #tree = Tree(a, Tree(c), Tree(b)) printTreeIndented(tree, level=0) elif b < a and c < b < a and d < c < a and e < d < a: tree = Tree(e, Tree(d, Tree(c, Tree(b, Tree(a))))) printTreeIndented1(tree, level=0) elif b < a and c < b < a and d < c < e and e > c < b and e > c > d and e > d: tree = Tree(a, Tree(b, Tree(c, Tree(d), Tree(e)))) printTreeIndented(tree, level=0) elif b > a and c > b > a and d < c < e and e > c > b and e > c > d and e > d: tree = Tree(a, Tree(b, Tree(c, Tree(d), Tree(e)))) printTreeIndented1(tree, level=0) elif a > b > d and b > d and a < c < e and c < e: tree = Tree(a, Tree(c, Tree(e)), Tree(b, Tree(d))) printTreeIndented1(tree, level=0) else: print "Os valores devem ser maiores ou menores que a raiz!" 17 abertura() abertura() Conclusão Neste bloco, você aprendeu sobre como trabalhar com árvores binárias, seus principais conceitos e aplicações práticas e também como representá-las na linguagem Python. Por aqui terminamos esta disciplina. Nos vemos nas avaliações! 18 Referências CAPUTO, Rodrigo. Algoritmo de Dijkstra. 2012. Disponível em: <https://www.youtube.com/watch?v=J4TZgD 1As0Q>. Acesso em: 20 dez. 2018. DROZDEK, Adam. Estrutura de Dados e Algoritmos em C++. Tradução de Roberto Enrique Romero Torrejon. São Paulo: CENGAGE, 2017. PERKOVIC, Ljubomir. Introdução à computação usando Python: um foco no desenvolvimento de aplicações. Rio de Janeiro: LTC, 2016.
Compartilhar