Free Palestine and Lebanon 🍉 Stop the Genocide
Haskell Logo

Programação Funcional

Ficha 6: Árvores binárias com conteúdo nos nós

Voltar

1) Considere o seguinte tipo para representar árvores binárias.

data BTree a = Empty
    | Node a (BTree a) (BTree a)
deriving Show

Árvore de exemplo para testar as funções:

arvore = (Node 5 (Node 2 (Node 1 Empty
                                 Empty) 
                         (Node 3 Empty 
                                 Empty)) 
                 (Node 9 (Node 7 (Node 6 Empty 
                                         Empty) 
                                 (Node 8 Empty 
                                         Empty)) 
                         Empty))

Defina as seguintes funções:

a) altura :: BTree a -> Int que calcula a altura da árvore.

altura :: BTree a -> Int
altura Empty = 0
altura (Node _ l r) = 1 + max (altura l) (altura r)

b) contaNodos :: BTree a -> Int que calcula o número de nodos da árvore.

contaNodos :: BTree a -> Int
contaNodos Empty = 0
contaNodos (Node _ l r) = 1 + contaNodos l + contaNodos r

c) folhas :: BTree a -> Int que calcula o número de folhas (i.e., nodos sem descendentes) da árvore.

folhas :: BTree a -> Int
folhas Empty = 0
folhas (Node _ Empty Empty) = 1
folhas (Node _ l r) = folhas l + folhas r

d) prune :: Int -> BTree a -> BTree a que remove de uma árvore todos os elementos a partir de uma determinada profundidade.

prune :: Int -> BTree a -> BTree a
prune _ Empty = Empty
prune 0 _ = Empty
prune x (Node e l r) = Node e (prune (x - 1) l) (prune (x - 1) r)

e) path :: [Bool] -> BTree a -> [a] que dado um caminho (False corresponde a esquerda e True a direita) e uma árvore, dá a lista com a informação dos nodos por onde esse caminho passa.

path :: [Bool] -> BTree a -> [a]
path _ Empty = []
path [] (Node e _ _) = [e]
path (h:t) (Node e l r) = e : path t (if h then r else l)  

f) mirror :: BTree a -> BTree a que dá a árvore simétrica.

mirror :: BTree a -> BTree a
mirror Empty = Empty
mirror (Node e l r) = Node e (mirror r) (mirror l)

g) zipWithBT :: (a -> b -> c) -> BTree a -> BTree b -> BTree c que generaliza a função zipWith para árvores binárias.

zipWithBT :: (a -> b -> c) -> BTree a -> BTree b -> BTree c
zipWithBT f (Node e l r) (Node e' l' r') = Node (f e e') (zipWithBT f l l') (zipWithBT f r r')
zipWithBT _ _ _ = Empty

h) unzipBT :: BTree (a,b,c) -> (BTree a,BTree b,BTree c) que generaliza a função unzip (neste caso de triplos) para árvores binárias.

unzipBT :: BTree (a,b,c) -> (BTree a,BTree b,BTree c)
unzipBT Empty = (Empty, Empty, Empty)
unzipBT (Node (a,b,c) l r) = (Node a unzipL1 unzipR1, Node b unzipL2 unzipR2, Node c unzipL3 unzipR3)
    where (unzipL1,unzipL2,unzipL3) = unzipBT l
          (unzipR1,unzipR2,unzipR3) = unzipBT r

2) Defina as seguintes funções, assumindo agora que as árvores são binárias de procura:

a) Defina uma função minimo :: Ord a => BTree a -> a que calcula o menor elemento de uma árvore binária de procura não vazia.

minimo :: (Ord a) => BTree a -> a
minimo (Node e Empty _) = e
minimo (Node e l r) = minimo l

b) Defina uma função semMinimo :: Ord a => BTree a -> BTree a que remove o menor elemento de uma árvore binária de procura não vazia.

semMinimo :: Ord a => BTree a -> BTree a
semMinimo (Node _ Empty r) = r
semMinimo (Node e l r) = Node e (semMinimo l) r

c) Defina uma função minSmin :: Ord a => BTree a -> (a,BTree a) que calcula, com uma única travessia da árvore, o resultado das duas funções anteriores.

minSmin :: Ord a => BTree a -> (a,BTree a)
minSmin (Node e Empty r) = (e,r)
minSmin (Node e l r) = (a,Node e b r)
    where (a,b) = minSmin l

d) Defina uma função remove :: Ord a => a -> BTree a -> BTree a que remove um elemento de uma árvore binária de procura, usando a função anterior.

remove :: Ord a => a -> BTree a -> BTree a
remove _ Empty = Empty
remove x (Node e l r) 
    | x < e = Node e (remove x l) r
    | x > e = Node e l (remove x r)
    | otherwise = case r of Empty -> l
                            _ -> let (g,h) = minSmin r in Node g l h

-- Nesta função, depois de remover o elemento, temos de formar uma nova árvore, pois não podemos ter um nodo vazio. Para isso, removemos o menor elemento do ramo da direita e colocamos esse elemento onde estava o elemento removido. Desta forma, a árvore mantém a sua ordem, já que todos os elementos à esquerda continuam a ser mais pequenos e todos os elementos à direita continuam a ser maiores do que o elemento no nodo.

3) Considere agora que guardamos a informação sobre uma turma de alunos na seguinte estrutura de dados:

type Aluno = (Numero,Nome,Regime,Classificacao)
type Numero = Int
type Nome = String
data Regime = ORD | TE | MEL deriving Show
data Classificacao = Aprov Int
                   | Rep
                   | Faltou
    deriving Show

type Turma = BTree Aluno -- árvore binária de procura (ordenada por número)

Turma de exemplo para testar as funções:

turma :: Turma
turma = (Node (15,"Luís",ORD,Aprov 14) (Node (12,"Joana",MEL,Faltou) (Node (7,"Diogo",TE,Rep) Empty Empty) (Node (14,"Lara",ORD,Aprov 19) Empty Empty)) (Node (20,"Pedro",TE,Aprov 10) Empty (Node (25,"Sofia",ORD,Aprov 20) (Node (23,"Rita",ORD,Aprov 17) Empty Empty) (Node (28,"Vasco",MEL,Rep) Empty Empty))))

Defina as seguintes funções:

a) inscNum :: Numero -> Turma -> Bool que verifica se um aluno, com um dado número, está inscrito.

inscNum :: Numero -> Turma -> Bool
inscNum _ Empty = False
inscNum n (Node (num,_,_,_) l r) = n == num || inscNum n (if n < num then l else r)

b) inscNome :: Nome -> Turma -> Bool que verifica se um aluno, com um dado nome, está inscrito.

inscNome :: Nome -> Turma -> Bool
inscNome _ Empty = False
inscNome n (Node (_,nom,_,_) l r) = n == nom || inscNome n l || inscNome n r

c) trabEst :: Turma -> [(Numero,Nome)] que lista o número e nome dos alunos trabalhadores-estudantes (ordenados por número).

trabEst :: Turma -> [(Numero,Nome)]
trabEst Empty = []
trabEst (Node (num,nom,TE,_) l r) = trabEst l ++ [(num,nom)] ++ trabEst r
trabEst (Node _ l r) = trabEst l ++ trabEst r

d) nota :: Numero -> Turma -> Maybe Classificacao que calcula a classificação de um aluno (se o aluno não estiver inscrito a função deve retornar Nothing).

nota :: Numero -> Turma -> Maybe Classificacao
nota n (Node (num,_,_,clas) l r) 
    | n == num = Just clas
    | n < num = nota n l
    | otherwise = nota n r
nota _ _ = Nothing

e) percFaltas :: Turma -> Float que calcula a percentagem de alunos que faltaram à avaliação.

percFaltas :: Turma -> Float
percFaltas Empty = 0
percFaltas turma = sumFaltas turma / numAlunos turma * 100
    where sumFaltas :: Turma -> Float
          sumFaltas Empty = 0
          sumFaltas (Node (_,_,_,Faltou) l r) = 1 + sumFaltas l + sumFaltas r
          sumFaltas (Node _ l r) = sumFaltas l + sumFaltas r
          numAlunos = fromIntegral . contaNodos

f) mediaAprov :: Turma -> Float que calcula a média das notas dos alunos que passaram.

mediaAprov :: Turma -> Float
mediaAprov Empty = 0
mediaAprov turma = uncurry (/) (sumNumNotas turma)
    where sumNumNotas :: Turma -> (Float, Float)
          sumNumNotas Empty = (0,0)
          sumNumNotas (Node (_,_,_,Aprov nota) l r) = addPairs (fromIntegral nota, 1) (addPairs (sumNumNotas l) (sumNumNotas r))
          sumNumNotas (Node _ l r) = addPairs (sumNumNotas l) (sumNumNotas r)
          addPairs (a,b) (c,d) = (a+c,b+d)

g) aprovAv :: Turma -> Float que calcula o rácio de alunos aprovados por avaliados. Implemente esta função fazendo apenas uma travessia da árvore.

aprovAv :: Turma -> Float
aprovAv Empty = 0
aprovAv turma = uncurry (/) (sumAprovAv turma)
          
sumAprovAv :: Turma -> (Float, Float)
sumAprovAv Empty = (0,0)
sumAprovAv (Node (_,_,_,clas) l r) = case clas of Aprov nota -> (ap+1,av+1) 
                                                  Rep -> (ap,av+1)
                                                  _ -> (ap,av)
    where (ap,av) = addPairs (sumAprovAv l) (sumAprovAv r)
          addPairs (a,b) (c,d) = (a+c,b+d)