UNIVERSITÀ DEGLI STUDI ROMA 3 FACOLTÀ DI SCIENZE MATEMATICHE, FISICHE E NATURALI CORSO DI LAUREA MAGISTRALE IN MATEMATICA Tesi di Laurea Magistrale in Matematica Analisi comparativa di strutture dati per la rappresentazione di alberi Candidato Relatore Roberta Tirocchi Prof. Roberto Di Pietro Correlatore Prof. Alessandro Colantonio Anno Accademico 2011/2012 2 CONCETTI BASE SUGLI ALBERI 1 Introduzione La struttura ad albero, in inglese Tree, è una struttura dati non lineare che segue un modello gerarchico. La sua caratteristica principale è che ciascun elemento ha molti successori ed un unico predecessore. L’albero è molto usato nell’ambito informatico perchè consente di modellare aspetti della vita reale, come ad esempio l’organigramma di un’azienda, la tassonomia degli organismi animali e vegetali, le gerarchie militari e la genealogia di una persona. Nella letteratura esistono diverse strutture dati per rappresentare l’albero nel calcolatore. Le principali distinzioni tra queste strutture si possono individuare attraverso la valutazione delle prestazioni, in termini di memoria occupata, e del tempo impiegato nell’esecuzione delle operazioni. Anche se la letteratura è molto ricca per quanto riguarda la descrizione di queste strutture dati, tuttavia è molto difficile trovare delle analisi comparative che mostrino l’efficienza di ogni forma rispetto alle altre. La tesi si occuperà di fare un’analisi comparativa delle metodologie note in letteratura usate per rappresentare l’albero attraverso gli array e le matrici. Saranno inoltre descritti due nuovi approcci, uno basato sull’uso di una matrice binaria, l’altro composto da un insieme di array di dimensione variabile. In entrambi i casi l’idea principale è quella di memorizzare tutti gli antenati di ciascun elemento dell’albero. Nella tesi esamineremo pro e contro dei nuovi approcci rispetto alle metodologie classiche. Inoltre analizzeremo il legame tra la teoria spettrale della forma matriciale, già nota in letteratura, e la teoria dei grafi ed esamineremo anche le caratteristiche spettrali della nuova matrice binaria. Nel corso del lavoro saranno comparate tra loro tutte le implementazioni delle rappresentazioni descritte, la complessità computazionale delle principali operazioni sugli alberi, il tempo di esecuzione di ogni operazione ed il numero di celle usate per allocare nel calcolatore l’intero albero. Quest’analisi comparativa sarà effettuata sia in maniera formale che mediante implementazione nel linguaggio di programmazione Java di tutti gli algoritmi, eseguendo test numerici su alberi con un numero variabile di nodi. Da quest’analisi saranno evidenziati gli aspetti positivi di ogni forma di rappresentazione e saranno messi in luce i miglioramenti che la nuova forma apporterà nella gestione degli alberi, come la riduzione del tempo di esecuzione di alcune operazioni. 2 Concetti base sugli alberi In questo capitolo definiamo in modo rigoroso la struttura dati ad albero. 2 2 CONCETTI BASE SUGLI ALBERI 2.1 Definizioni Nella teoria dei grafi, per grafo G intendiamo una coppia G = (V, E) dove V è un insieme non vuoto, discreto e finito, i cui elementi sono i vertici del grafo ed E è l’insieme degli spigoli del grafo ed è un sottoinsieme del prodotto cartesiano V ×V [1]. Definizione 1. Un albero puó essere definito ricorsivamente come segue: • G = ({v}, ∅) è un albero; • se G1 e G2 sono alberi, allora G = (V, E) definito ponendo V = V (G1 ) ∪ V (G2 ), E = E(G1 ) ∪ E(G2 ) ∪ {(v1 , v2 )}, con v1 ∈ V (G1 ) e v2 ∈ V (G2 ) Da questa definizione ricorsiva ricaviamo che Definizione 2. Un albero è un grafo T = (V, E) connesso ed aciclico. Ciò vuol dire che presi due vertici distinti sono sempre connessi da uno ed un solo cammino e che non devono esistere cicli. La connessione e l’aciclicità di un albero T sono due proprietà legate alla cardinalità dell’insieme E(T ). Infatti se si aggiunge uno spigolo a T si perde l’aciclità, invece se si elimina uno spigolo si perde la connessione. Osserviamo inoltre preso un albero T abbiamo che se |V (T )|= n; allora |E(T )| =n−1. Questa relazione caratterizza in modo forte un albero; infatti dato un grafo G = (V, E) con |E| = |V | − 1 e connesso allora è un albero. Tutte queste proprietà sono riassunte nel seguente teorema di caratterizzazione degli alberi. Teorema 1. Sia G = (V, E), un grafo non orientato.Le seguenti affermazioni sono equivalenti : 1. G è un albero; 2. due vertici qualsiasi di G sono connessi da unico cammino semplice; 3. G è connesso, ma se qualunque spigolo di G venisse rimosso, allora G diventerebbe non connesso; 4. G è connesso e |E| = |V | − 1; 5. G è aciclico e |E| = |V | − 1; 6. G è aciclico, ma se venisse aggiunto uno spigolo qualsiasi il grafo risultante non sarebbe più aciclico. 3 3 METODI DI RAPPRESENTAZIONE CLASSICI Dato un albero T = (V, E) è possibile selezionare un vertice r ∈ V (T ) qualsiasi e a partire da esso orientare tutti gli spigoli in modo naturale scegliendo i cammini che da r portano a tutti i vertici dell’albero. Otteniamo in questo modo un albero orientato e radicato. Il vertice r è detto radice dell’albero, unico vertice privo di spigoli entranti ma può possedere un numero arbitrario di spigoli uscenti. Ogni nodo dell’albero, diverso dalla radice, possiede diversi spigoli uscenti ma un solo spigolo entrante. I vertici con solo spigoli entranti sono detti foglie. In un albero orientato T = (V, E) se (u, v) ∈ E(T ), il vertice u si dice padre di v ed al tempo stesso v si dice figlio di u. Se due nodi hanno lo stesso padre, sono fratelli. Le foglie sono quindi vertici privi di figli, invece la radice è l’unico vertice che non ha padre. Definiamo ora un albero ordinato, un albero radicato i cui figli di ciascun nodo sono ordinati, cioè se un nodo ha k-figli abbiamo un primo, un secondo,. . . , un k-simo figlio. Se abbiamo un albero dove ogni nodo possiede solo due figli, definiamo questi con il nome di figlio destro e sinistro. Un nodo non foglia è interno. Il grado di un nodo x ∈ V (T ), in un albero T radicato, è il numero dei suoi figli. L’ordine è il grado massimo di quasi tutti i nodi dell’albero. Preso un nodo x di un albero radicato T con radice r; un qualunque nodo y compreso nell’unico cammino da r a x è chiamato antenato di x mentre x è detto discendente di y. La profondità di un nodo x, è la lunghezza del cammino dalla radice al nodo in questione. La profondità massima di un qualsiasi nodo dell’albero determina l’altezza dell’albero. Infine diciamo che un albero è bilanciato se la profondità di ogni foglia coincide con l’altezza dell’albero oppure a questa stessa altezza meno uno. Per concludere questo discorso introduttivo sulle proprietà della struttura dati ad albero enunciamo il seguente teorema con il suo corollario. Teorema 2. Un albero completo di ordine d ed altezza h possiede dh+1 −1 d−1 nodi. Corollario 1. L’altezza di un albero completo di ordine d e avente n nodi è H = logd (nd − n + 1) − 1. 3 Metodi di rappresentazione classici In questo capitolo viene offerta una descrizione ad alto livello delle principali strutture dati utilizzate per rappresentare alberi. Ci sono vari metodi per rappresentare la struttura ad albero. Nella letteratura, tale struttura viene implementata in due modi: con gli array e con le liste concatenate, ovvero oggetti (i nodi) che puntano ad altri oggetti. Per semplicità di esposizione ci soffermeremo a descrivere tutti i metodi noti che ricorrono all’uso di uno ma anche più array e non quelli che ricorrono ai puntatori. Non 4 3 METODI DI RAPPRESENTAZIONE CLASSICI descriveremo le liste concatenate perchè nella maggior parte dei casi la complessità dell’esecuzione delle operazioni è la stessa, gli array sono più compatti e possiamo avere accesso diretto ai dati mediante indici. Inoltre alcune tipologie di alberi come ad esempio gli heap ricorrono proprio per definizione agli array. Gli alberi che andremo a rappresentare saranno alberi k-ari, ovvero alberi dove ogni nodo possiede al più k figli. Preso un albero T, memorizziamo il contenuto dell’etichetta di ciascun nodo all’interno di un array L ed i nodi vengono rinominati 0 ,1 ,..., n-1. In questo modo modelliamo i nodi dell’albero con dei numeri naturali così da rendere più semplice l’esecuzione delle operazioni sull’albero per qualsiasi tipo di dato contenuto nell’etichette dei nodi. 3.1 Parent Representation (PR) La Parent Representation(PR) è la forma di rappresentazione più semplice di un albero. Il seguente metodo sfrutta la caratteristica della struttura dati ad albero che evidenzia che ogni nodo ha un solo padre. La Parent Representation(PR) dell’albero T è un array lineare A nel quale nella cella A[i] viene memorizzato il padre del nodo i, ovvero A[i] = j se il nodo j è il padre del nodo i e A[i] = −1 se il nodo i è la root. Con questa rappresentazione sarà semplice l’operazione di ricerca del padre di un nodo ma risulterà più costosa eseguire l’operazione di calcolo dei figli e molte altre operazioni. Inoltre in questo caso non viene specificato l’ordine dei figli di un nodo. Nell’inserimento dei nodi è possibile imporre un ordine artificiale di inserimento dei nodi nell’array. I nodi dell’albero possono essere letti, livello dopo livello, partendo da sinistra a destra a partire dalla radice. Assegniamo alla root la prima cella dell’array L (quella di posto 0), quindi avremo che A[0] = −1. È giusto notare che nel seguente modo possiamo rappresentare qualsiasi tipo di albero non sono quello binario. 3.2 Adjacency Matrix Representation(AMR) In teoria dei grafi, un albero è un tipo particolare di grafo, ovvero è un grafo connesso ed aciclico. Un grafo può essere sempre rappresentato utilizzando una matrice particolare detta matrice di adiacenza . Definizione 3. La matrice di adiacenza è una matrice A di dimensione |V | × |V | definita in questo modo : aij = 1 se (i , j) ∈ E(G) 0 altrimenti . 5 3 METODI DI RAPPRESENTAZIONE CLASSICI Se m è il numero di spigoli del grafo, avremmo 2m celle con il valore 1. La trasposta di una matrice A = (aij ) è definita come la matrice AT = (aTij ), dove aTij = aji . Poichè in un grafo non orientato (u, v) e (v, u) rappresentano lo stesso arco, la matrice di adiacenza di un grafo non orientato è identica alla trasposta : A = AT . In alcune applicazioni è conveniente memorizzare solo i dati che compaiono sopra la diagonale principale (diagonale inclusa) della matrice di adiacenza, riducendo quindi la memoria necessaria per memorizzare il grafo quasi della metà. L’albero è un grafo quindi potremmo usare la matrice di adiacenza definita in questo modo per rappresentarlo : 1 se i è padre di j aij = 1 se i è figlio di j 0 altrimenti Quindi avremmo che ai,j = aj,i perchè se i è padre di j allora sicuramente j è figlio di i. Come nel caso del grafo non orientato A = AT quindi per convenienza possiamo memorizzare solo le celle al di sopra della diagonale e quindi definiamo la matrice di adiacenza dell’albero nel seguente modo : 1 se i è padre di j aij = 0 altrimenti In questa rappresentazione abbiamo bisogno di memorizzare dove si trova la root altrimenti è difficile distinguerla dagli altri nodi. Possiamo rappresentare ogni albero k-ario con questo metodo. 3.3 List of Children Representation(LCR) Una tecnica per la rappresentazione degli alberi molto usata in informatica prevede l’utilizzo di una famiglia di n liste L1 , ...., Ln di vertici denominate liste di adiacenza del grafo G. La rappresentazione mediante liste di adiacenza avviene nel seguente modo: ∀ vi ∈ V (G) memorizziamo nella lista Li tutti i vertici ad esso adiacenti. Due vertici u e v si dicono adiacenti se esiste uno spigolo che li collega. Come nel paragrafo precedente sapendo che l’albero T = (V, E) è un grafo possiamo sfruttare questo metodo per rappresentarlo. Sia T un albero, le liste sono array A di lunghezza n. Come per le altre forme questa può essere usata per ogni tipo di albero non solo quello binario, nel caso di alberi k-ari avremo k array. In ciascuna cella A[i] sono contenuti i figli del nodo i. Alla root assegniamo sempre la prima posizione e per inserire gli altri nodi usiamo sempre un ordine convenzionale partendo dall’alto e proseguendo verso il basso. Nel caso dell’albero binario avremo solo due array L[i] e R[i] , nelle quali entrate memorizziamo rispettivamente il figlio sinistro e il figlio destro del nodo i. Per questo motivo, nel caso nell’albero binario, oltre al metodo 6 3 METODI DI RAPPRESENTAZIONE CLASSICI d’inserimento con gli input standard (nodo e padre del nodo) implementeremo anche un altro metodo per inserire i nodi , per input avremo un ulteriore informazione ovvero se il nodo in questione è un figlio sinistro o destro del padre. Chiamiamo questa forma di rappresentazione List of Children Representation(LCR) . 3.4 Binary Tree Representation(BTR) Binary Tree Representation(BTR) è una rappresentazione posizionale perchè la posizione del nodo nell’array corrisponde alla sua posizione nell’albero. Assegniamo la cella 0 alla root, il nodo nella cella 1 è il figlio sinistro della radice e quello nella cella 2 è il figlio destro, e così via, proseguendo da sinistra a destra lungo ogni livello dell’albero. Aggiungere un nodo in una posizione nell’albero vuol dire collocare il nodo nella corrispettiva cella dell’array, se un nodo nell’albero non esiste nella sua cella viene memorizzato il valore −1. Sia T un albero binario, i figli ed il padre di un nodo possono essere trovati applicando semplici operazioni aritmetiche agli indici di locazione del nodo nell’ array. Sia index l’indice del nodo, il figlio sinistro si troverà nella cella 2index + 1 mentre il figlio destro invece nella cella 2index+2 ed infine il padre si trova nella posizione index−1 . Quando cancelliamo 2 un nodo dall’albero avremo uno spreco di memoria perchè nell’array si creeranno dei buchi ovvero rimarranno vuote quelle celle precedentemente occupate dai nodi eliminati. Questa rappresentazione non è solo usata per gli alberi binari ma potrà essere sfruttata per rappresentare qualsiasi tipo di albero k-ario. 3.5 Left Child - Right Sibling Representation (LCRSR) Dato un albero k-ario esiste un algoritmo per trasformarlo in albero binario, questo procedimento non è reversibile senza l’ aggiunta di ulteriori informazioni. Se non eseguiamo l’ultimo passo dell’algoritmo abbiamo la rappresentazione di qualsiasi albero k-ario. Sia T un albero, prendiamo due array Left e Right di lunghezza pari al numero n di nodi dell’albero. Memorizziamo all’interno della cella Left [i] il figlio sinistro del nodo i, mentre nella cella Right[i] memorizziamo il fratello destro del nodo i. Specifichiamo che con il termine figlio sinistro del nodo i, intendiamo il primo nodo che abbiamo con padre il nodo i. L’ultimo passo dell’algoritmo consiste nel prendere un nuovo albero T’ con gli stessi array Left e Right però cambierà il significato del contenuto di questi array. Infatti Left[i] e Right[i] rappresentano il figlio sinistro e destro del nodo i. Nel nostro caso non siamo interessati alla trasformazione ma solo alla rappresentazione dell’albero quindi ci fermeremo al passo precedente, ove in Left[i] abbiamo il figlio più sinistro del nodo i mentre in Right[i] il fratello destro di i. Se abbiamo un albero non completo segnaliamo la mancanza di un nodo 7 4 NUOVO METODO DI RAPPRESENTAZIONE BASATA SU MATRICI BINARIE memorizzando -1 nell’entrata corrispettiva. Alla radice come in LCR assegniamo sempre la prima cella dei due array quindi in Left[0] è contenuto il figlio più sinistro della root e Right[0] = -1. Questa forma di rappresentazione si chiama Left Child Right Sibling Representation (LCRSR). 4 Nuovo metodo di rappresentazione basata su matrici binarie Presentiamo ora la nuova forma di rappresentazione, nella sua forma estesa e nella sua forma contratta. Descriviamo queste due rappresentazioni della struttura dati ad albero, la prima che ricorre all’uso di una matrice binaria, la seconda è sempre una matrice ma costituita di array di dimensioni variabili. 4.1 Rappresentazione nella forma estesa Dato un albero T con n nodi, i quali vengono memorizzati in un array L e li denominiamo 0,1,. . . ,n-1 come per le classi note in letteratura. Rappresentiamo l’albero attraverso una matrice binaria A di dimensione n × n. La matrice A viene riempita seguendo il criterio che segue: A[i][j] = 1 se j è un antenato del nodo i ed A[i][i] = 1, cioè ogni nodo è antenato di se stesso. Questa forma matriciale di rappresentazione dell’albero ricalca un legame specifico tra i nodi, essere antenati, per questo rende efficiente l’esecuzione di alcune operazioni. Come vedremo in avanti questo metodo porta dei benefici in diverse operazioni, per il fatto che siamo in grado di trovare in tempi costanti tutti gli antenati di ogni nodo dell’albero senza dover iterare il metodo di ricerca del padre. Lo svantaggio è chiaramente rappresentato da una maggiore occupazione di memoria rispetto alla memorizzazione del solo antenato diretto (cioè il padre), ma come vedremo in seguito è possibile ridurre moltissimo tale overhead di memoria con tecniche di compressione dei dati. Per ogni nodo dell’albero la i-esima riga contiene gli antenati del nodo i. Tale metodo segue, come nel caso di PR, un criterio d’inserimento dei nodi nell’albero. La matrice binaria che otteniamo da tale rappresentazione è una matrice sparsa. Questa matrice si distingue dalla matrice di adiacenza descritta nel paragrafo precedente, perchè in questa sono riportati tutti gli antenati del nodo ed il nodo stesso, mentre nella matrice di adiacenza riportiamo solamente il padre diretto di ogni nodo. 4.2 Rappresentazione nella forma compressa Per non avere una matrice sparsa, possiamo definire una versione compressa del metodo. C è un array n - dimensionale. Ogni cella è un array di dimensione 8 4 NUOVO METODO DI RAPPRESENTAZIONE BASATA SU MATRICI BINARIE variabile, all’interno di ogni cella C[i] memorizziamo il nodo i ed il valore di tutti i suoi antenati. Utilizziamo, data la dimensione variabile di ogni cella dell’array C, un altro array N. Ogni cella N [i] contiene il numero di elementi dell’array C[i], tale valore è pari al numero di antenati del nodo i. In questo caso risulterà semplicissimo ricercare gli antenati di un nodo dato. Definiamo quanto detto in maniera rigorosa. C[i]= {j : A[i][j] = 1} ed con N [i]=|{j : A[i][j] = 1}|. Questa matrice vista per colonne ricorda le liste di adiacenza, ma a differenza di questa vengono memorizzate in ogni riga gli antenati di ogni nodo ed il nodo stesso, mentre all’interno delle liste di adiacenza in ogni colonna troviamo i figli di ogni nodo. 4.3 Legame tra la nuova matrice binaria e la matrice di adiacenza Sia T un albero radicato, rappresentato nella memoria del calcolatore mediante la matrice di adiacenza A definita nel seguente modo: 1 se i è padre di j aij = 0 altrimenti Conosciuta h, l’altezza dell’albero, dalla matrice di adiacenza A possiamo ottenere la matrice binaria della nuova forma di rappresentazione mediante i seguenti passi: • Calcoliamo la trasposta della matrice di adiacenza AT = A0 . • Sommiamo ad essa la matrice identità In quindi (A0 + In ) • Calcoliamo (A0 + In )h ed otteniamo la nuova forma di rappresentazione dell’albero mediante matrice binaria. Osservazione 1. Il prodotto e la somma tra le matrici segue in questo caso le regole dell’algebra booleana. Sia A ∈ Mm,n (K) e B ∈ Mn,p (K) il loro prodotto righe per colonne è la matrice AB ∈ Mmp (K) il cui elemento di posto (i, k) è il prodotto della i-esima riga di A per k-esima colonna di B. In formule si ha AB = (A(i) B (k) ) = ai1 b1k + · · · + ain bnk . Il prodotto è il prodotto logico (AND) che restituisce 1 se tutti gli operandi valgono 1, mentre restituisce 0 in tutti gli altri casi. Invece la somma è la somma logica (OR) che restituisce 1 se almeno uno degli elementi è 1 altrimenti vale 0. 9 5 IMPLEMENTAZIONE DELLE PRINCIPALI OPERAZIONI TIPICHE DEGLI ALBERI 4.4 Pro e Contro dell’uso della nuova di rappresentazione degli alberi Nell’analizzare questa forma di rappresentazione rispetto alle altre note in letteratura evidenzieremo vantaggi ma anche svantaggi. Rispetto agli altri metodi sarà semplice e veloce ricercare gli antenati di un nodo rispetto alle lunghe iterazioni ai cui ricorrono le altre metodologie. Questa riduzione del tempo di calcolo degli antenati influenzerà in positivo i tempi di ricerca del cammino minimo di un nodo, di calcolo della profondità di un nodo e calcolo dell’altezza dell’albero. Questa sua particolarità ci permetterà di usare questa rappresentazione ad esempio nella gestione dell’organigramma di un’azienda basata sui ruoli. Grazie al modo in cui vengono memorizzati i nodi rispetto alle altre forme di rappresentazione saremo in grado di risalire in breve tempo al ruolo root di ogni utente attraverso una semplice lettura. Avremo però degli svantaggi per quanto riguarda la memoria occupata, dato che essendo entrambe delle matrici occupano sicuramente più celle degli array ma anche della stessa matrice di adiacenza (dato che come abbiamo detto memorizziamo tutti gli antenati ed il nodo stesso, non solo il padre). Si possono sfruttare tecniche di compressione degli insieme come ad esempio Concise: Compressed ’n’ Composable Integer Set [11].Concise è un algoritmo di compressione di matrici di bit. La compressione avviene senza compromettere le prestazioni delle operazioni bit per bit. Concise è in grado di rappresentare 32 interi in una cella di memoria solamente se gli interi sono consecutivi (i, i + 1, i + 2, . . . , i + 31). [11] . Nel caso degli alberi quindi se gli antenati fossero tutti interi consecutivi sarebbe possibile applicare la compressione dei dati con Concise e quindi l’occupazione di memoria della matrice binaria nella forma estesa diviene pari all’occupazione di memoria della matrice di adiacenza. I vantaggi e gli svantaggi di questa nuova metodologia saranno esaminate nei dettagli nei capitoli successi della tesi, attraverso l’analisi degli algoritmi, lo studio della complessità computazionale e dell’occupazione di memoria 5 Implementazione delle principali operazioni tipiche degli alberi Per ogni struttura dati proposta nella tesi verranno analizzate in dettaglio le seguenti operazioni sugli alberi ed implementate in java: • aumento dell’arietà, cioè aumento del numero massimo di figli per ogni nodo. 10 5 IMPLEMENTAZIONE DELLE PRINCIPALI OPERAZIONI TIPICHE DEGLI ALBERI • inserimento di un nodo in una data posizione, cioè inserimento all’interno della struttura dati del nodo in una determinata posizione. • ricerca del padre di un dato nodo, cioè ricerca del padre diretto di un nodo. • ricerca dei figli diretti di un dato nodo, cioè ricerca dei figli diretti di un nodo. • ricerca dei fratelli di un dato nodo, cioè ricerca di tutti i nodi che hanno lo stesso padre del nodo dato. • rimozione di un nodo in una data posizione, cioè eliminazione di un nodo dalla sua posizione all’interno della struttura dati. • spostamento di un nodo da una posizione ad un’altra, cioè cambiamento della posizione del nodo all’interno della struttura dati. • ricerca del cammino più breve tra due nodi dati e calcolo della sua lunghezza, cioè ricerca del cammino di lunghezza minima che connette due nodi dell’albero. • calcolo antenati di un dato nodo. • calcolo discendenti di un dato nodo. • calcolo profondità di dato nodo. • calcolo altezza dell’albero. • calcolo radice dell’albero. • verificare se un dato nodo è una foglia. • verificare se dati due nodi, uno è figlio diretto dell’altro. • verificare se dati due nodi, uno è padre dell’altro. • verificare se dati due nodi, uno è discendente dell’altro. • verificare se dato un nodo, è padre diretto di almeno un nodo contenuto in un insieme generico di nodi. • verificare se dato un nodo, è padre diretto di tutti i nodi contenuti in un insieme generico di nodi. 11 6 ANALISI MEMORIA E COMPLESSITÀ COMPUTAZIONALE 6 Analisi memoria e complessità computazionale 6.1 Vantaggi e Svantaggi in termini di complessità computazionale della nuova forma di rappresentazione: Attraverso la nozione O grande, con cui si definisce l’insieme delle funzioni la cui crescita asintotica non è maggiore, a meno di fattori moltiplicativi costanti, a quella di una determinata funzione f (n). Descriviamo ora una scala per magnitudine crescente, con c indichiamo una costante arbitraria . Notazione 0(1) 0((log) O(n) O(n2 ) O(nc ) O(n!) Nome costante logaritmica lineare quadratica polinomiale fattoriale Tabella 1: Ordini di funzioni comuni Riassumiamo nelle seguenti la complessità computazionali calcolate in precedendenza. Memory Father PR n O(1) n BTR O(k ) O(1) (n−1)n AMR O(n) 2 LCR kn O(kn) LCRRSR kn O(nk) (n+1)n NEW O(n) 2 (n−1)n NEWC O(1) 2 Children O(n) O(1) O(n) O(1) O(1) O(n2 ) O(n) Siblings O(n) O(1) O(n2 ) O(kn) O(1) O(n2 ) O(n2 ) Tabella 2: Tabella riassuntiva 1 12 7 RISULTATI SPERIMENTALI Insert PR O(1) BTR O(1) AMR O(1) LCR O(k) LCRRSR O(k) NEW O(n) NEWC O(n) Remove O(n2 ) O(n) O (n2 ) O(n2 ) O(n2 ) O (n2 ) O(n2 ) Shortest Path O( h) O(n) O(n2 ) O(kn2 ) O(kn2 ) O(h) O(h) Move O(1) O(n) O(1) O(kn) O(kn) O(n3 ) O(n2 ) Tabella 3: Tabella riassuntiva 2 Con il termine h, indichiamo l’altezza dell’albero. Da questa tabelle possiamo notare come la nuova forma di rappresentazione nella forma estesa (NEW) e compressa(NEWC) per quanto riguarda le operazione in tabella 2 non vanno peggio di altri metodi. Infatti la forma compresse nel calcolo del padre di un nodo ha una complessità bassa pari a quella di PR. Invece nelle altre operazioni non risultano essere efficienti ma neanche hanno tempi più alti delle altre classi. Mentre dalla tabella 3 NEW e NEWC vediamo che in alcune operazioni come ShortestPath vadano meglio di altre. Infatti tale operazione che ha complessità O(h) poichè al più abbiamo h antenati, ma nel caso peggiore h=n. 7 Risultati sperimentali Per verificare i risultati teorici precedentemente esposti effettuiamo ora test numerici per vedere l’andamento medio dei tempi di esecuzione di alcune operazioni. Eseguiamo i test su un albero binario con nodi random ed un albero binario bilanciato. • Remove: Eseguiamo anche in questo caso i test su entrambi i tipi di alberi. Prendiamo in questo caso molti alberi con un numero sempre diverso di nodi e calcoliamo il tempo medio di eliminazione da ogni albero dello stesso nodo. Nel caso del metodo remove, come abbiamo visto dobbiamo sempre calcolarci i discendenti e poi eliminare il nodo in questione con a seguito ogni discendente. Siccome il calcolo dei discendenti nella nuova forma si riduce a semplice lettura di una colonna ciò si ripercuote nell’esecuzione della remove poichè usando la nuova forma di rappresentazione nella forma estesa si svolge con un tempo medio più basso rispetto alle altre forme. 13 7 RISULTATI SPERIMENTALI Figura 1: Tempo medio remove albero nodi random Figura 2: Tempo medio remove alberi nodi random grafico 3D 14 7 RISULTATI SPERIMENTALI Figura 3: Tempo medio remove alberi bilanciati Figura 4: Tempo medio remove alberi bilanciati 3D • ParentOf : Anche in questo caso conduciamo il test su alberi con nodi random ed alberi bilanciati. Ogni test ricerchiamo su uno stesso albero il padre di ogni nodo dell’albero a partire dalla radice fino alle foglie. Calcoliamo il tempo medio per svolgere tale ricerca. In entrambe le tipologie di alberi possiamo notare come PR e BTR risultano essere le forme note più convenienti per condurre tale ricerca invece come la forma matriciale della nuova rappresentazione nella versione contratta risulta essere migliore di AMR. 15 7 RISULTATI SPERIMENTALI Figura 5: Tempo medio parentOf albero nodi random Figura 6: Tempo medio parentOf alberi nodi random grafico 3D 16 7 RISULTATI SPERIMENTALI Figura 7: Tempo medio parentOf alberi bilanciati Figura 8: Tempo medio parentOf alberi bilanciati 3D • allParentOf : In questo caso per testare i tempi d’esecuzione dell’algoritmo di calcolo degli antenati di un nodo usiamo sempre uno stesso albero e cerchiamo gli antenati di ogni nodo a partire dalla radice fino alle foglie. Calcoliamo il tempo medio per condurre tale operazione. Possiamo vedere come nello svolgimento di quest’operazione è conveniente usare un array sfruttando la forma rappresentazione PR e BTR ma grazie alle due nuove forme di rappresentazione diviene utile anche l’uso della matrice. Infatti entrambi le forme risultano essere efficienti per ricercare gli antenati di un nodo. 17 7 RISULTATI SPERIMENTALI Figura 9: Tempo medio AllParentOf albero nodi random Figura 10: Tempo medio AllParentOf alberi nodi random grafico 3D 18 7 RISULTATI SPERIMENTALI Figura 11: Tempo medio AllParentOfOf alberi bilanciati Figura 12: Tempo medio AllParentOfOf alberi bilanciati 3D • Height : In questo caso testo su più alberi e calcolo il tempo medio per il calcolo dell’altezza di ogni albero. Il calcolo dell’altezza dell’albero viene influenzato dal calcolo degli antenati e dalla ricerca della foglia. Dalla combinazione di questi due fattori negli alberi con nodi random la nuova forma nella versione estesa risulta essere efficiente. 19 7 RISULTATI SPERIMENTALI Figura 13: Tempo medio Height alberi nodi random Figura 14: Tempo medio Height alberi nodi random grafico 3D 20 8 ANALISI DELLE RAPPRESENTAZIONI DEGLI ALBERI BASATI SU MATRICE Figura 15: Tempo medio Height alberi bilanciati Figura 16: Tempo medio Height alberi bilanciati 3D 8 Analisi delle rappresentazioni degli alberi basati su matrice Dopo aver descritto tutte le forme di rappresentazione degli alberi, tramite array e matrici ed introdotte le nuove forme di rappresentazione matriciali degli alberi, in questo capitolo studieremo ulteriore proprietà della matrice ed il legame che c’è tra essa ed i grafi. Lo studio verrà effettuato sulla matrice di adiacenza, unica forma di 21 8 ANALISI DELLE RAPPRESENTAZIONI DEGLI ALBERI BASATI SU MATRICE rappresentazione matriciale presente nella letteratura. Possiamo usare lo spettro di una matrice(autovalori di una matrice) per avere informazioni su un determinato tipo di grafo. In questo modo creiamo una relazione tra gli autovalori di una matrice e le proprietà di un grafo. Lo studio della relazione tra questi due oggetti si chiama Teoria Spettrale dei grafi[10]. 8.1 La matrice di adiacenza di un grafo Come abbiamo già detto nella presentazione delle varie metodologie di presentazione dell’albero abbiamo che : Definizione 4. La matrice di adiacenza è una matrice A di dimensione |V | × |V | definita in questo modo : 1 se (i , j)∈ E(G) aij = 0 altrimenti . Definiamo lo spettro del grafo G, assumendo spec(G) = spec(A) ed estendiamo a G la terminologia usata per la matrice A (autovalori,autovettori, polinomio caratteristico ecc...).Vediamo ora come dallo studio spettrale della matrice siamo in grado di cogliere le caratteristiche di un grafo. Nel caso di alberi e grafi non orientati la matrice è simmetrica e reale e tutti gli autovalori sono reali e mg (λi ) = ma (λi ) per ogni autovalore di A. Proposizione 1. Sia G un grafo con n vertici, sia A la sua matrice di adiacenza e sia pG (λ):= pA (λ) il polinomio caratteristico di G. Se il grafo G ha t ≥ 1 componenti connesse G1 . . . Gt allora pG = pG1 . . . pGt . Pertanto lo spettro di G è l’unione degli spettri delle sue componenti, e la molteplicità di ciascun autovalore di G è la somma delle sue molteplicità come autovalore delle componenti Gi 8.2 La matrice di adiacenza e i cammini tra i vertici Proposizione 2. Sia G=(V, E) un grafo e sia A la sua matrice di adiacenza. Allora per ogni x,y ∈ V e per ogni s ≥ 1 : (As )xy = numero di cammini tra x e y di lunghezza s. Osservazione 2. Sia G = (V, E) un grafo ed A la sua matrice di adiacenza. Dalla proposizione precedente segue che per ogni x ∈ V : (A2 )xx = dG (x). e quindi la traccia P P di A2 è tr(A2 ) := x∈V (A)2xx = x∈V dG (x) = 2|E|. Proposizione 3. Sia M ∈ C n×n . La traccia ed il determinate di M sono rispettivaP P mente la somma e il prodotto dei suoi autovalori : tr(M) := mij = λi ∈Spec(M ) λi Q det(M ) := λi ∈Spec(M ) λi 22 9 CONCLUSIONI Lemma 1. Siano M ∈ C ,λsn con (s= 1,2,3,. . . ) n×n e Spec(M) = λ1 , . . . , λn . Allora Spec(M s ) = λs1 ,. . . Proposizione 4. Sia G = (V, E) un grafo con n vertici, sia A la matrice di adiacenza e sia Spec(G) = {λ1 ,. . . ,λn } i suoi autovalori. Allora : 1. P 2. P 2 P λi = x∈V (A)2xx = P λi = 2= 3. P x∈V (A)xx = x∈V P 3 λ tr(A) = 0; tr(A2 ) = numero delle passeggiate chiuse di G lunghezza dG (x) = 2|E| = x∈V (A)3xx = tr(A3 ) = numero delle passeggiate chiuse di G lunghezza 3 = 6 volte il numero dei triangoli del grafo i P 4. Conclusione : tr(As )= numero delle passeggiate chiuse di G lunghezza s = P s λi = numero delle passeggiate chiuse di G di lunghezza s 8.3 La matrice binaria della nuova forma La nuova matrice viene definita nel seguente modo : 1 se j è antenato i oppure i=j Aij = 0 altrimenti Osservazione 3. Da tale definizione deriva che la traccia della nostra matrice è pari sempre ad n ove con n intendiamo il numero di nodi dell’albero. Rispetto alla matrice di adiacenza per come è definita dipendendo dagli antenati di un nodo, è conveniente principalmente per la rappresentazione di un albero. Per rappresentare un grafo qualsiasi con questa metodologia dovremmo prima trasformarlo in albero. Calcolando gli autovalori di tale matrice (attraverso l’ analisi numerica e anche con il calcolo delle radici del polinomio caratteristico) possiamo dire che P P in questo caso λi = x∈V (A)xx = tr(A) = n =|V |. Attraverso lo studio degli autovalori della matrice binaria nella nuova forma si potrebbero trovare particolari caratteristiche di un albero. 9 Conclusioni L’ albero è una struttura dati non lineare, che segue un modello gerarchico. La sua struttura non sequenziale permette di stabilire dei rapporti di parentela tra i nodi, per cui è possibile definire un nodo padre, un nodo figlio ed un nodo fratello per questo viene impiegato in molti campi dell’informatica. Attraverso la struttura 23 9 CONCLUSIONI dati ad albero siamo in grado di modellare aspetti della vita reale, come ad esempio l’organigramma di un’azienda, la tassonomia degli organismi animali e vegetali, le gerarchie militari e la genealogia di una persona. La letteratura è molto ricca per quanto riguarda la descrizione di queste strutture dati che rappresentano l’albero nella memoria del calcolatore, tuttavia è molto difficile trovare delle analisi comparative che mostrino l’efficienza di ogni forma rispetto alle altre. Nel corso della tesi sono state presentate le metodologie che utilizzano uno o più array. Tutte le forme di rappresentazione note in letteratura sono state presentate nel secondo capitolo della tesi, nel quale abbiamo visto che ogni metodologia ricalca un legame di parentela specifico che si sviluppa tra i nodi dell’albero. Nel terzo capitolo è stata presentata la nuova metodologia di rappresentazione nella forma estesa e compressa. Questo nuovo metodo rispecchia il legame tra due nodi di essere antenati. Sono entrambe due matrici, nella forma estesa è binaria e quadrata mentre in quella compressa è una matrice dove le righe sono array di dimensioni variabili. In ogni riga sono contenuti gli antenati del nodo associato a tale riga ed il nodo stesso. Questa forma usa le matrici e quindi nel sesto capitolo della tesi abbiamo evidenziato l’importanza dell’uso della matrice nella rappresentazione di grafi grazie al legame che nasce tra le proprietà algebriche della matrice e la teoria dei grafi. Infatti attraverso lo studio dello spettro della matrice di adiacenza siamo in grado di conoscere diverse caratteristiche di un grafo, uno studio spettrale è stato condotto anche sulla matrice della nuova rappresentazione nella forma estesa. Siamo passati alla comparazione delle varie forme di rappresentazione, abbiamo descritto ed implementato in Java gli algoritmi di esecuzione nelle principiali operazioni, abbiamo calcolato l’occupazione di memoria all’interno del calcolatore ed infine la complessità computazionale di ogni operazione per ogni forma. Nel quinto capitolo sono stati condotti test numerici da quali è possibile dedurre come la nuova forma di rappresentazione nella forma estesa e compressa ha portato dei miglioramenti sull’esecuzione di alcune operazioni e dove ogni struttura dati risulta essere valida nell’esecuzione di un operazione. La nuova forma risulta essere efficiente nel calcolo degli antenati, nel calcolo dell’altezza dell’albero, nel calcolo del cammino minimo tra due nodi , nella semplice ricerca del padre del nodo e nella rimozione di un nodo. Tali miglioramenti specialmente apportati in queste operazione rendono utilizzabile l’albero mediante questa nuova forma di rappresentazione nella gestione dell’organigramma di un’azienda, di siti web o anche della rete di comunicazione tra gli enti di una stessa azienda. Come sviluppi futuri si potrebbero analizzare con maggior dettaglio i vantaggi 24 RIFERIMENTI BIBLIOGRAFICI che un metodo di compressione dei dati apporta nell’uso di questa forma. Riferimenti bibliografici [1] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Introduzione agli algoritmi, seconda edizione, McGraw - Hill,1999. [2] John R. Hubbard, Schaum’s Outline of Data Structures with Java, McGraw Hill, 2000 [3] Robert Lafore, Data Structures e Algorithms in Java, Sams ,1998. [4] Alfred V.Aho,John E. Hopcroft, and Jeffrey Ullman. Data Structures e Algorithms .Addison-Wesley Longman Publishing Co. , Inc.,Boston , MA, USA, 1st edition, 1983. [5] J.A. Bondy and U.S.R. Murty.Graph Theory. Springer Publishing Company, Incorporated, 1st edition, 2008 [6] P.S. Deshpande and O.G. Kakde. C e Data Structures (Eletrical and Computer Engineering Series). Charles River Media, Inc, Rockland, MA,USA,2004. [7] Alan Gibbons, Algorithmic graph theory, Cambridge University Press, 19948 [8] Richard J. Trudeau, Introduction to Graph Theory, Dover Publiactions Ins., New York,1993. [9] L.W. Beineke, R.J. Wilson, Topics in algebraic graph theory. Edited by L.W. Beineke and R.J. Wilson. Cambridge University Press, 2004. [10] N. Biggs ,Algebraic graph theory. Second edition. Cambridge University Press,1993. [11] Alessandro Colantonio and Roberto Di Pietro, CONCISE: COmpressed ’N’ Composable Integer SEt, Information Processing Letters, Elsevier, 2010, 110 ,16, 644–650. 25