Facoltà di Ingegneria 2 Alberi Ogni nodo ha un unico arco entrante, tranne un nodo particolare, chiamato radice, che non ha archi entranti; Ogni nodo può avere zero o più archi uscenti I nodi senza archi uscenti sono detti foglie Un arco nell’albero induce una relazione padre-figlio L’ordine dei figli di un nodo non ha importanza Strutture dati: Alberi Strutture gerarchiche di dati Esempi – Il file system di un sistema operativo – L’organigramma di un’azienda – Alberi generali, alberi n-ari, alberi binari, … radice sottoalbero cammino foglia 3 4 Alberi: definizione ricorsiva Alberi: Alcuni concetti Grado di un nodo: numero di figli del nodo Cammino: sequenza di nodi <n0, n1, …, nk> dove il nodo ni è padre del nodo ni+1, per 0 i < k Un albero può essere definito come una coppia (r, s) dove r è un nodo e s è una lista di sottoalberi – La lista di sottoalberi può essere eventualmente vuota e ciò ferma la ricorsione – La lunghezza del cammino è k – Dato un nodo, esiste un unico cammino dalla radice dell’albero al nodo Livello di un nodo: lunghezza del cammino dalla radice al nodo Perché non si ammette l’albero vuoto ? – Definizione ricorsiva: il livello della radice è 0, il livello di un nodo non radice è 1 + il livello del padre – Quale è la differenza tra una lista vuota di sottoalberi e una lista non vuota di sottoalberi vuoti ? Altezza dell’albero: la lunghezza del più lungo cammino nell’albero – Parte dalla radice e termina in una foglia 5 6 Alberi: Una prima realizzazione Un albero dovrebbe fornire metodi per accedere – all’informazione contenuta nella radice dell’albero – alla lista dei sottoalberi (una SimpleList) Alberi: prima Realizzazione Ogni nodo ha un reference all’oggetto che costituisce l’informazione e un reference alla lista di sottoalberi Costruzione dell’albero 2 – una prima strategia: bottom-up – Dato una informazione e una lista di alberi, costruire un nuovo albero che ha come radice e come lista di sottoalberi l’informazione e la lista in ingresso – In questo caso definiamo il set di operatori minimali e si parla di SimpleTree Per attraversare un albero e per costruire è necessario usare i metodi della classe SimpleList Programmazione Object-Oriented in Java 2 1 4 6 8 4 5 9 5 6 1 8 9 7 7 1 Facoltà di Ingegneria 7 Classe SimpleTree SimpleTree: Costruttore import java.io.*; Import java.util.*; class SimpleTree { class Node { public Object elem; public SimpleList subTrees; // classe già nota } // definizione dei metodi private Node rootNode; } 8 public SimpleTree(Object rootInfo) { rootNode = new Node(); rootNode.elem = rootInfo; rootNode.subTrees = null; // per default } public SimpleTree(Object rootInfo, SimpleList subTrees) { rootNode = new Node(); rootNode.elem = rootInfo; rootNode.subTrees = subTrees; } // radice dell’albero 9 10 Visite di alberi SimpleTree: Altri metodi Un albero può essere visitato (attraversato) secondo le seguenti modalità public Object root() { return rootNode.elem; } – Preordine: visita prima la radice e poi i sottoalberi (completamente uno alla volta) – Postordine: visita prima i sottoalberi (completamente uno alla volta) e poi la radice – Per livelli: visita prima la radice, poi i nodi di livello 1 (figli della radice), poi quelli di livello 2, e così via public SimpleList subTreeList() { return rootNode.subTrees; } Poiché non è importante l’ordine di visita, ogni metodo di visita restituisce una Enumeration – La classe Tree include le classi PreOrderTraversal, PostOrderTraversal, BreadthFirstTraversal che implementano delle Enumeration 11 Visite 7 4 3 8 2 11 6 9 9 21 14 13 16 18 14 18 17 17 16 Visite di alberi: Metodi 5 5 2 public Enumeration preOrderElements() { return new PreOrderTraversal(); } 7 21 11 13 6 Preordine: 5, 7, 4, 3, 8, 6, 11, 21, 2, 9, 14, 13, 16, 17, 18 Postordine: 4, 8, 6, 3, 11, 21, 7, 2, 13, 16, 17, 14, 18, 9, 5 Per Livelli: 5, 7, 2, 9, 4, 3, 11, 21, 14, 18, 8, 6, 13, 16, 17 Programmazione Object-Oriented in Java 12 3 4 8 public Enumeration postOrderElements() { return new PostOrderTraversal(); } public Enumeration BreadthFirstElements () { return new BreadthFirstTraversal (); } 2 Facoltà di Ingegneria 13 PreOrderTraversal 14 PreOrderTraversal class PreOrderTraversal implements Enumeration { public PreOrderTraversal () { … } public boolean hasMoreElements() { … } public Object nextElement() { … } private void preOrderBuild(Node n) { … } private Queue queueElements; public PreOrderTraversal () { queueElements = new Queue(); if (rootNode != null) preOrderBuild(rootNode); } } Usiamo una coda, in cui il costruttore memorizza tutte le informazioni dell’albero in pre-order – Il costruttore usa il metodo ricorsivo preOrderBuild per memorizzare le informazioni dell’albero nella coda 15 PreOrderTraversal 16 PreOrderTraversal private void preOrderBuild(Node n) { // metodo ricorsivo if (n != null) { queueElements.addElement(n.elem); // inserisce la radice SimpleList list = n.subTrees; if(list != null) while(! list.isEmpty()) { // visita dei sottoalberi SimpleTree t = (SimpleTree) list.head(); preOrderBuild(t.rootNode); // ricorsione list.removeHead(); } } } public boolean hasMoreElements() { return queueElements.isEmpty(); } public Object nextElement() { Object el = queueElements.firstElement(); queueElements.removeElement(); return el; } Semplice scansione della coda 17 PostOrderTraversal class PostOrderTraversal implements Enumeration { public PostOrderTraversal () { … } public boolean hasMoreElements() { … } public Object nextElement() { … } private void postOrderBuild(Node n) { … } private Queue queueElements; } Usiamo una coda, in cui il costruttore memorizza tutte le informazioni dell’albero in post-order – Il costruttore usa il metodo ricorsivo postOrderBuild per memorizzare le informazioni dell’albero nella coda Programmazione Object-Oriented in Java 18 PostOrderTraversal public PostOrderTraversal () { queueElements = new Queue(); if (rootNode != null) postOrderBuild(rootNode); } La realizzazione di hasMoreElements e di nextElements è identica a quanto vista per la classe PreOrderTraversal (prima realizzazione) 3 Facoltà di Ingegneria 19 20 PostOrderTraversal BreadthFirstTraversal private void postOrderBuild(Node n) { // metodo ricorsivo if (n != null) { SimpleList list = n.subTrees; if(list != null) while(! list.isEmpty()) { // visita dei sottoalberi SimpleTree t = (SimpleTree) list.head(); postOrderBuild(t.rootNode); // ricorsione list.removeHead(); } queueElements.addElement(n.elem); // inserisce la radice } } class BreadthFirstTraversal implements Enumeration { public BreadthFirstTraversal () { … } public boolean hasMoreElements() { … } public Object nextElement() { … } private Queue nodes; } Molto simile alla seconda realizzazione di PreOrderTraversal, solo che usa una coda invece di uno stack – Il metodo nextElement() elimina il nodo dal front della coda, inserisce i sottoalberi (eventualmente) in coda e restituisce il nodo 21 22 BreadthFirstTraversal BreadthFirstTraversal public Object nextElement() { if (nodes.isEmpty()) return null; Node currentNode = (Node) nodes.firstElement(); nodes.removeElement(); SimpleList list = currentNode.subTrees; if(list != null) while(! list.isEmpty()) { SimpleTree t = (SimpleTree) list.head(); nodes.addElement(t.rootNode); list.removeHead(); } return currentNode.elem; } public BreadthFirstTraversal () { nodes = new Queue(); if (rootNode != null) nodes.addElement(rootNode); } public boolean hasMoreElements() { if (nodes.isEmpty()) return false; return true; } 23 24 Alberi: Una diversa realizzazione Costruzione dell’albero – strategia: top-down – aggiungere un figlio ad un nodo dell’albero In questo caso la classe Node non può essere incapsulata (nascosta) all’interno della classe Tree, ma occorre esplicitare la dicotomia dei concetti di nodo e informazione associata, presenti nell’albero Classe Node class Node { public Node() { } public Node(Object data) { this.data = data; } public void setData(Object data) { this.data = data; } public Object getData() { return data; } public List getChildren() { return children; } // altri metodi – La gestione dell’albero avviene usando la classe Node, ossia aggiungendo e/o rimuovendo figli a nodi – La classe Tree serve solo a mantenere il nodo radice e a realizzare le visite dell’albero private Object data; // informazione associata al nodo private List children = new List(); // in alternativa usare un Vector // inizializzazione all’atto dell’instanziazione } Programmazione Object-Oriented in Java 4 Facoltà di Ingegneria 25 26 Classe Node: altri metodi Classe Tree public Node addChild(Object data) { Node tempNode = new Node(data); children.insertHead(tempNode); return tempNode; } import java.io.*; Import java.util.*; class Tree { public Tree() {rootNode = new Node(); } public Tree(Object rootInfo) { rootNode = new Node(rootInfo); } public Node root() { return rootNode; } public void deleteSubTree(Node child) { int pos = children.indexOf(child); if (pos >=0) children.removeElementAt(pos); // si può migliorare } // realizzazioni delle Traversal … farlo come esercizio private Node rootNode; // radice dell’albero } 27 Esercizo 28 Alberi n-ary Realizzare la gestione di un file system Un nodo può avere al più n figli Ogni figlio e’ individuato da una posizione – Struttura gerarchica delle directory – Uno stack per mantenere il path della directory corrente, oppure un reference da ogni nodo al nodo padre – Implementazione delle operazioni – Ciascun figlio può essere presente o meno (in questo caso è ammesso l’albero vuoto) – Un albero è vuoto o è dato da una radice e una n-pla di sottoalberi – La lista dei figli è realizzata come un Vector o un array Costruzione dell’albero bottom-up • Cancellazione, creazione di una directory – Realizzazione semplificata – Nodo incapsulato nella definizione della classe albero Costruzione dell’albero top-down – Dicotomia Nodo-Informazione 29 Alberi binari Particolari alberi n-ari con caratteristiche molto importanti Ogni nodo può avere al più due figli – sottoalbero sinistro e sottoalbero destro Definizione ricorsiva: – un albero binario è vuoto – oppure è una terna (s, r, d), dove r è un nodo (la radice), s e d sono alberi binari Alberi binari semplificati – Costruttore bottom-up – Operatori di selezione – Operatori di visita Programmazione Object-Oriented in Java 30 Alberi Binari import java.io.*; import java.util.*; class SimpleBTree { class Node { public Object elem; // informazione associata al nodo public Node left; // sottoalbero sinistro public Node right; // sottoalbero destro } // definizione dei metodi private Node rootNode = null; } // radice dell’albero 5 Facoltà di Ingegneria 31 Esempio SimpleBTree: Costruttore 4 7 32 public SimpleBTree() { } public SimpleBTree(Object rootInfo, SimpleBTree left, SimpleBTree right) { rootNode = new Node(); rootNode.elem = rootInfo; if (left == null) rootNode.left = null; else rootNode.left = left.rootNode; if (right == null) rootNode.right = null; else rootNode.right = right.rootNode; } 12 3 1 4 9 33 SimpleBTree: Altri metodi 34 SimpleBTree: Altri metodi public SimpleBTree leftBTree() { if (rootNode == null) return null; SimpleBTree tmp = new SimpleBTree(); tmp.rootNode = rootNode.left; return tmp; } public boolean isEmpty() { return rootNode == null; } public Object root() { if (rootNode == null) return null; return rootNode.elem; } public SimpleBTree rightBTree() { if (rootNode == null) return null; SimpleBTree tmp = new SimpleBTree(); tmp.rootNode = rootNode.right; return tmp; } 35 Visite di alberi binari Oltre alle visite classiche di un albero – Preordine – Postordine – Per livelli Un ulteriore algoritmo di visita applicabile ad un albero binario è la visita simmetrica (In-Order) – visita prima il sottoalbero sinistro, poi la radice, e infine il sottoalbero destro Realizzazioni di classi Traversal che implementano Enumeration Programmazione Object-Oriented in Java 36 Visite di alberi binari: Metodi public Enumeration preOrderElements() { return new PreOrderTraversal(); } public Enumeration postOrderElements() { return new PostOrderTraversal(); } public Enumeration InOrderElements () { return new InOrderTraversal (); } 6 Facoltà di Ingegneria 37 Esercizi 38 Rappresentazione binaria di alberi Realizzare le tre visite dell’albero binario in maniera ricorsiva, in maniera simile a quanto visto per le visite in preorder e postorder dell’albero generale Ai soli fini della equivalenza della visita in preordine un albero può essere rappresentato mediante un albero binario equivalente – per ogni nodo dell’albero, Nalb, esiste un corrispondente nell’albero binario, Nbin – al primo figlio di Nalb corrisponde il figlio sinistro di Nbin – al fratello successivo di Nalb corrisponde il figlio destro di Nbin 39 5 Esempio 40 Rappresentazione binaria di alberi 7 Ogni nodo contiene 5 2 4 7 2 9 11 8 4 3 8 21 11 6 14 13 18 14 21 13 6 18 class Node { public Object elem; // informazione associata al nodo public Tree firstSubTree; // primo sottoalbero public Tree next; // prossimo sottoalbero fratello } 16 17 16 – la radice – reference al primo figlio – reference al fratello successivo 9 3 17 La visita in preordine dell’albero binario a sinistra è equivalente alla visita in preordine dell’albero a destra 41 1 Esercizi Esempio 2 5 1 10 2 5 10 3 6 4 7 11 8 9 Realizzare la classe SimpleTree utilizzando la rappresentazione binaria dell’albero 12 4 7 11 3 6 42 8 9 12 Programmazione Object-Oriented in Java 7