Laboratorio di Algoritmi e Strutture Dati M

Strutture dati: Alberi
Operazioni basilari su Alberi
Laboratorio di Algoritmi e Strutture Dati M-Z
Ingegneria e Scienze Informatiche - Cesena
A.A. 2015-2016
Pietro Di Lena
[email protected]
Formato Newick
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Definizione
I
I
Definizione di Albero: struttura rappresentata come un grafo completamente connesso in cui ogni coppia di nodi è connessa da un unico
percorso.
Alcuni esempi di applicazione:
I Teoria dei giochi: rappresentazione di tutte le possibili partite (o di
un sottoinsieme) di un gioco
I Genalogia: alberi genealogici
I Biologia: alberi filogenetici (ex. tree of life)
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Struttura dati Albero: rappresentazione grafica
A
\
\
A
2
1
3
B
C
B
2
D
C
3
D
1
\
E
F
G
1
\
2
2
\
\
\
\
\
1
2
E
I
I
I
F
2
G
Vincoli: l’ordine dei figli di un nodo non è rilevante.
Come definiamo in C la struttura nodo per alberi generici?
La definizione cambia di molto rispetto a quella vista per alberi binari?
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Definizione della struttura dati nodo per un albero in C
1
# include < stdlib .h >
2
3
4
5
6
7
8
struct node {
char
nlabel ;
int
elabel ;
struct node * brother ;
struct node * children ;
};
9
10
11
struct node * node_alloc ( char nlabel , int elabel ) {
struct node * tmp = ( struct node *) malloc ( sizeof ( struct node ));
12
if ( tmp != NULL ) {
tmp - > nlabel
= nlabel ;
tmp - > elabel
= elabel ;
tmp - > brother = NULL ;
tmp - > children = NULL ;
}
return tmp ;
13
14
15
16
17
18
19
20
I
I
}
Assumiamo che un nodo abbia un identificativo (nome) di tipo char,
nlabel.
Il campo elabel viene utilizzato per assegnare un peso all’arco che
collega il nodo con il rispettivo padre.
Strutture dati: Alberi
Operazioni basilari su Alberi
Generazione di un albero random
1
# define BASE 65
2
3
4
5
6
7
8
9
10
11
/*
* G e n e r a z i o n e random di un albero random di altezza massima
* height . Ogni nodo ha un numero di figli c o m p r e s o tra 0 e
* c hildren . I nodi sono marcati con lettere m a i u s c o l e A - Z
* ( i nomi sono ripe t u t i se ci sono piu di 26 nodi ).
* I pesi degli archi sono interi random tra 0 e 9.
*/
struct node * randtree ( int height , int children ) {
static int X = 0;
12
if ( height > 0) {
int i , n ;
struct node *T , * tmp ;
13
14
15
16
T = node_alloc ( BASE +X , rand ()%10);
X = ( X +1) % 26;
n = random ()%( children +1);
for ( i = 0; i < n ; i ++)
if (( tmp = randtree ( height -1 , children )) != NULL ) {
tmp - > brother = T - > children ;
T - > children = tmp ;
}
return T ;
} else return NULL ;
17
18
19
20
21
22
23
24
25
26
27
}
Formato Newick
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Operazioni basilari su Alberi: altezza e distruzione di un Albero
1: function Height(T )
2:
if T = NIL then
3:
return 0
4:
else
5:
n←0
6:
for c ∈ Children(T ) do
7:
n ← Max(n, Heigth(c))
8:
end for
9:
return n + 1
10:
end if
11: end function
I
I
1: function DeleteTree(T )
2:
if T =
6 NIL then
3:
for c ∈ Children(T ) do
4:
DeleteTree(c)
5:
end for
6:
Delete(T)
7:
end if
8: end function
ATTENZIONE: Le istruzioni di visita dei figli di un nodo dipendono dalla struttura utilizzata. L’implementazione in C potrebbe essere un po’ differente da quella presentata
nello pseudocodice.
I In particolare, nella DeleteTree , se si decide di utilizzare la struttura node
suggerita, il ciclo for non è necessario (tutto può essere calcolato in modo
ricorsivo).
Qual è il costo computazionale di queste funzioni?
I Entrambe le procedure visitano ogni nodo dell’albero esattamente una volta:
Θ(n), n = numero di nodi
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Operazioni basilari su Alberi: numero di nodi e numero di foglie
1: function CountNodes(T )
2:
if T = NIL then
3:
return 0
4:
else
5:
n←0
6:
for c ∈ Children(T ) do
7:
n ← n + CountNodes(c)
8:
end for
9:
return n + 1
10:
end if
11: end function
I
I
1: function CountLeaves(T )
2:
if T = NIL then
3:
return 0
4:
else
5:
n←0
6:
if Children(T ) = NIL then
7:
n←1
8:
else
9:
for c ∈ Children(T ) do
10:
n ← n + CountLeaves(c)
11:
end for
12:
end if
13:
return n
14:
end if
15: end function
Valgono le stesse considerazioni fatte in precedenza per la visita dei figli.
Qual è il costo computazionale di queste funzioni?
I Entrambe le funzioni visitano ogni nodo dell’albero esattamente una volta: Θ(n),
n = numero di nodi
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Esempio di stampa in ASCII di un Albero
Come possiamo stampare su terminale ed in modo graficamente chiaro un Albero?
A
\
\
A
2
1
3
B
C
B
2
D
C
3
D
1
\
E
1
\
F
2
G
2
\
\
\
\
\
1
2
E
2
F
G
1
2
3
4
5
6
7
A
+ - B :2
+ - C :3
| + - E :1
+ - D :1
+ - F :2
+ - G :2
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Stampa di un albero in ascii 1/2
Procedura di stampa principale. Richiede l’implementazione della funzione che calcola
l’altezza dell’albero Height(T).
1
2
# include < stdio .h >
# include < stdlib .h >
3
4
5
6
7
void print_tree ( struct node * T ) {
if ( T != NULL ) {
char * indent = ( char *) calloc (2* height ( T ) , sizeof ( char ));
struct node * tmp ;
8
printf ( " % c :% d \ n " ,T - > nlabel ,T - > elabel );
for ( tmp = T - > children ; tmp != NULL ; tmp = tmp - > brother )
print_tree_rec ( tmp , indent ,0);
free ( indent );
9
10
11
12
}
13
14
}
Strutture dati: Alberi
Operazioni basilari su Alberi
Stampa di un albero in ascii 2/2
1
2
3
4
5
static void print_indent ( char * indent , int n ) {
int i ;
for ( i = 0; i < n ; i ++)
printf ( " % c " , indent [ i ]);
}
6
7
8
9
static void print_tree_rec ( struct node *T , char * indent , int n ) {
if ( T != NULL ) {
struct node * tmp ;
10
print_indent ( indent , n );
printf ( " + - " );
if (T - > brother == NULL ) // Last c h i l d r e n
indent [ n ++] = ’ ’;
else
// More b r o t h e r s
indent [ n ++] = ’| ’;
indent [ n ++] = ’ ’;
printf ( " % c :% d \ n " ,T - > nlabel ,T - > elabel );
11
12
13
14
15
16
17
18
19
for ( tmp = T - > children ; tmp != NULL ; tmp = tmp - > brother )
prin t_tree_rec ( tmp , indent , n );
20
21
}
22
23
}
Formato Newick
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Formato Newick
I
I
I
Newick tree: notazione matematica standard e machine-readable per la
rappresentazione di alberi.
Sfrutta la corrispondenza tra struttura ad albero e parentesi annidate .
Regole grammaticali:
tree ⇒ descendant list [ root label ] [: root edge label ] ;
descendant list ⇒ ( subtree {, subtree } )
subtree ⇒ descendant list [ node label ] [: node edge label ]
⇒ [ leaf label ] [: leaf edge label ]
Convenzioni:
I Gli elementi in { } possono apparire zero o più volte.
I Gi elementi in [ ] sono opzionali, possono apparire una sola volta o
mai.
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Formato Newick: esempi
A
2
1
3
B
C
1
D
2
E
F
2
G
I
Solo struttura (Type 0): (,(),(,));
I
Solo label foglie (Type 1): (B,(E),(F,G));
I
Label foglie e label di tutti gli archi (Type 2): (B:2,(E:1):3,(F:2,G:2):1);
I
Label di tutti i nodi (Type 3): (B,(E)C,(F,G)D)A;
I
Completo (Type 4): (B:2,(E:1)C:3,(F:2,G:2)D:1)A;
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Salvataggio in formato Newick: pseudocodice
La funzione Tree2Newick salva un albero in formato Newick in una stringa.
1: function Tree2Newick(T, type)
2:
S ← ””
. Emptystring
3:
if T 6= NIL then
4:
S ← S + Subtree2Newick(T , type)
5:
end if
6:
S ← S+";"
7:
return S
8: end function
1: function Subtree2Newick(T , type)
2:
S ← ””
3:
if HasChildren(T) then
4:
S ← S+"("
5:
for c ∈ Children(T ) do
6:
S ← S + Subtree2Newick(c, type)
7:
if NotLastChildren(c) then
8:
S ← S+","
9:
end if
10:
end for
11:
S ← S+")"
12:
end if
13:
return S + Data2Newick(T , type)
14: end function
I
I
I
1: function Data2Newick(T, type)
2:
S ← ””
3:
if type = 1 or type = 2 then
4:
if IsLeaf(T) then
5:
S ← S + NLbl(T )
6:
end if
7:
if type = 2 then
8:
S ← S+":"+ELbl(T )
9:
end if
10:
else if type = 3 then
11:
S ← S + NLbl(T )
12:
else if type = 4 then
13:
S ← S +NLbl(T )+":"+ELbl(T )
14:
end if
15:
return S
16: end function
I vari tipi di stampa sono specificati nella slide precedente.
NLbl(T) e ELbl(T) indicano le label del nodo e dell’arco, rispettivamente.
L’operatore + indica l’operazione di append tra stringhe.
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Lettura del formato Newick: pseudocodice
La funzione Newick2Tree legge un albero da una stringa in formato Newick.
1: function Newick2Tree(S)
2:
if S[curr ] = ";" then
3:
return NIL
4:
else
5:
T ← NodeAlloc(DefaultName, DefaultLabel)
6:
if S[curr ] = "(" then
7:
curr ← curr + 1
8:
repeat
9:
tmp ← Newick2Tree(S)
10:
AddChildren(T , tmp)
11:
if A[curr ] = "," then
12:
curr ← curr + 1
13:
end if
14:
until S[curr ] 6= ")"
15:
end if
16:
Newick2Data(S, T )
17:
return T
18:
end if
19: end function
I
I
I
I
La funzione AddChildren aggiunge un figlio al nodo T.
La variabile curr mantiene la posizione attuale da valutare nella stringa.
La funzione Newick2Data legge i dati relativi ad un nodo, relativamente al formato
adottato .Aggiorna l’indice curr.
DefaultName e DefalutLabel sono costanti utilizzate di default nel caso in cui in nome
del nodo e la label dell’arco non siano specificate nella stringa newick.
Strutture dati: Alberi
Operazioni basilari su Alberi
Formato Newick
Salvataggio e lettura del formato Newick: costo computazionale
I
I
I
Quanto costa la funzione Tree2Newick?
I Visita ogni nodo dell’albero esattamente una volta: Θ(n)
I n = numero di nodi nell’albero.
Quanto costa la funzione Newick2Tree?
I Valuta ogni carattere della stringa in input: Θ(n)
I n = numero di caratteri nella stringa.
Entrambe le trasformazioni hanno un costo lineare nella dimensione dell’input.