Maratona de Haskell - Trigésimo Quarto à Quadragésimo Dia

Fala galera!

No último post nós vimos algumas funções para calcular o tamanho e altura de nossa Binary Search Tree (BST).
Hoje será o último post sobre os exercícios com BST e nós criaremos a função para remover nodos.


Revisão da BST

Vamos apenas recapitular como eram as funções size e height que trabalhamos no post anterior.

Primeiro a função size para trabalhar com uma BST que significa obter o número de elementos que existem em uma BST. BST.

size :: (Ord a) => Tree a -> Int
size Empty = 0
size (Node _ left right) = 1 + (size left) + (size right)

> let tree = build [8,6,9,4,7,11,10,12,8,6,2,3,1]
> tree
Node 8 (Node 6 (Node 4 (Node 2 (Node 1 Empty Empty) (Node 3 Empty Empty)) (Node 6 Empty Empty)) (Node 7 Empty (Node 8 Empty Empty))) (Node 9 Empty (Node 11 (Node 10 Empty Empty) (Node 12 Empty Empty)))

> size tree
13

> let newTree = find tree 6
> newTree
Node 6 (Node 4 (Node 2 (Node 1 Empty Empty) (Node 3 Empty Empty)) (Node 6 Empty Empty)) (Node 7 Empty (Node 8 Empty Empty))

> size newTree
8

Uma função super direta para contar o número de nodos.

Agora vamos ver a função height que nos da a altura de uma BST.

height :: (Ord a) => Tree a -> Int
height Empty = 0
height (Node _ left right) = (+) 1 $ max (height left) (height right)

> newTree
Node 6 (Node 4 (Node 2 (Node 1 Empty Empty) (Node 3 Empty Empty)) (Node 6 Empty Empty)) (Node 7 Empty (Node 8 Empty Empty))

> height newTree
4

A função parece simples, mas não é fácil de entender a computação por trás dela. Se você tem alguma dúvida eu sugiro que visite o post anterior.


Remover um nodo da BST

Nós já temos funções para criar uma BST a partir de uma lista, adicionar um nodo, encontrar um nodo e avaliar se um valor existe ou não em nossa árvore. Agora temos que fazer a última função que é para remover um nodo de nossa BST.
Vamos dizer que temos essa BST:

Podemos iniciar tentando remover o nodo com o número 10. Isso é muito fácil, porque este nodo não tem outros nodos no lado esquerdo ou direito. Nós somente temos que remove-lo.
Vamos ver como é a função delete:

empty :: Tree a -> Bool
empty Empty = True
empty _ = False

findSmallest :: (Ord a) => Tree a -> a
findSmallest Empty = error "Empty list"
findSmallest (Node a left _)
  | empty left = a
  | otherwise = findSmallest left

delete :: (Ord a) => Tree a -> a -> Tree a
delete Empty _ = Empty
delete (Node a left right) x
  | a == x && empty right = left
  | a == x && empty left = right
  | a == x = Node smaller left newRight
  | a >= x = Node a (delete left x) right
  | otherwise = Node a left (delete right x)
  where smaller = findSmallest right
        newRight = delete right smaller

A função delete usa duas helper functions para remover um nodo que são empty e findSmallest. A função delete funciona recursivamente. Vamos ver como é o processo para remover o valor 10:

  • O primeiro valor é 8 e ele é comparado com o valor 10 que queremos remover. O número 8 é menor que 10 e o valor 10 deve estar no lado direito da árvore.
  • Move para o próximo elemento no lado direito que é 12. O número 12 é maior que 10 e a avaliação deve continuar para o lado esquerdo do nodo 12.
  • Move para o próximo elemento no lado esquerdo que é 11. O número 11 é maior que 10 e a avaliação deve continuar para o lado esquerdo do nodo 11.
  • Move para o próximo elemento no lado esquerdo que é 10. O número 10 é o número que estamos procurando. A primeira avaliação que a nossa função faz nesse caso é se o lado direito é vazio. E ele é. Então retornamos o lado esquerdo que neste caso é vazio também.

E temos a mesma árvore sem o nodo com valor 10.

Agora vamos tentar remover o valor 12 da nossa árvore original. Mas o que faremos com os lados direito e esquerdo deste nodo se eles não são vazio? O resultado será:

A função delete remove o nodo com valor 12 e move o menor nodo da direita, que neste caso é o nodo com valor 18, para o lugar do nodo removido. Vamos ver como isso funciona:

  • O primeiro valor é 8 e ele é comparado com o valor 10 que queremos remover. O número 8 é menor que 10 e o valor 10 deve estar no lado direito da árvore.
  • Move para o próximo elemento no lado direito que é 12. O número 12 é o número que estamos procurando. Então nós temos que procurar o menor valor no lado direito desta sub-tree.
  • Usando a helper function findSmallest nós descobrimos que o menor valor à direita é 18.
  • Recriamos a árvore colocando o nodo com o valor 18 onde o nodo com valor 12 está.

Ambos o processo e a função não são simples de entender, mas é um bom exercício para treinar Haskell.


O código para a nossa BST esta nesse repositório.

Written on January 8, 2017