package binary_tree; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Stack; public class LinkedBinaryTree implements BinaryTree{ // VARIABILI D'INSTANZA private BinaryNode root; private int size; // Metodi costruttori public LinkedBinaryTree() { root = null; size = 0; } public LinkedBinaryTree(E data) { root = new BinaryNode(data); size = 1; } public LinkedBinaryTree(LinkedBinaryTree left, E data, LinkedBinaryTree right) { root = new BinaryNode(left.root, data, right.root); size = 1 + left.size + right.size; } // METODI public int size() { return size; } @Override public boolean isEmpty() { return root == null; } public E getRoot() { if (isEmpty()) return null; return root.getData(); } public BinaryNode getRootNode() { if (isEmpty()) return null; return root; } public void clear() { root = null; size = 0; } private int getSize(BinaryNode node) { if (node == null) return 0; int nLeft = (node.getLeft() == null) ? 0 : getSize(node.getLeft()); int nRight = (node.getRight() == null) ? 0 : getSize(node.getRight()); return 1 + nLeft + nRight; } public LinkedBinaryTree removeLeft() { LinkedBinaryTree leftTree = null; if (root.getLeft() == null) return leftTree; leftTree = new LinkedBinaryTree(); leftTree.root = root.getLeft(); leftTree.size = getSize(root.getLeft()); leftTree.root.setAsRoot(); size = size - leftTree.size; return leftTree; } public LinkedBinaryTree removeRight() { LinkedBinaryTree rightTree = null; if (root.getRight() == null) return rightTree; rightTree = new LinkedBinaryTree(); rightTree.root = root.getRight(); rightTree.size = getSize(root.getRight()); rightTree.root.setAsRoot(); size = size - rightTree.size; return rightTree; } protected BinaryNode find(E targetElement, BinaryNode root) { if (root == null) return null; if (root.getData().equals(targetElement)) return root; BinaryNode resNode; resNode = find(targetElement, root.getLeft()); if (resNode == null) resNode = find(targetElement, root.getRight()); return resNode; } public boolean remove(E targetElement) { if (targetElement == null) return false; BinaryNode temp = find(targetElement, root); if (temp != null) { temp.setData(null); return true; } return false; } public boolean contains(E targetElement) { return find(targetElement, root) != null; } // ITERATORE PREORDER /* * Qui si utilizza il metodo del preorder. * Le regole per il preorder sono queste: * - Visita prima se stesso * - Poi visita tutto ciò che si trova sinistra * - Infine visita tutto ciò che si trova a destra */ protected void preorder (BinaryNode node, List temporaryList) { if (node == null) return; if (node.getData() != null) temporaryList.add(node.getData()); preorder(node.getLeft(), temporaryList); preorder(node.getRight(), temporaryList); } public Iterator iteratorPreOrder() { ArrayList temporaryList = new ArrayList(); preorder(root, temporaryList); return temporaryList.iterator(); } // ITERATORE INORDER /* * Qui si utilizza il metodo del inorder. * Le regole per l'inorder sono queste: * - Visita prima tutto ciò che si trova a sinistra * - Poi visita se stesso * - Infine visita tutto ciò che si trova a destra */ protected void inorder(BinaryNode node, List temporaryList) { if (node == null) return; inorder(node.getLeft(), temporaryList); if (node.getData() != null) temporaryList.add(node.getData()); inorder(node.getRight(), temporaryList); } public Iterator iteratorInOrder() { ArrayList temporaryList = new ArrayList(); inorder(root, temporaryList); return temporaryList.iterator(); } // ITERATORE POSTORDER /* * Qui si utilizza il metodo postorder. * Le regole per il postorder sono queste: * - Visita prima di tutto ciò che si trova a sinistra * - Poi visita tutto ciò che si trova a destra * - Infine visita il sestesso */ protected void postorder(BinaryNode node, List temporaryList) { if (node == null) return; postorder(node.getLeft(), temporaryList); postorder(node.getRight(), temporaryList); if (node.getData() != null) temporaryList.add(node.getData()); } public Iterator iteratorPostOrder() { ArrayList temporaryList = new ArrayList(); postorder(root, temporaryList); return temporaryList.iterator(); } // ITERATORE LEVEL ORDER /* * Qui si usa il metodo levelorder. * Si analizza tutto il livello dell'albero. * Per chiarire di seguito un esempio: * * A * / \ * B C * / \ \ * D E F * * La lettura avviene quindi in questo modo: * A - B - C - D - E - F * * Si adopera una coda (Queue) di tipo FI-FO (First In - First Out) */ protected void levelorder(BinaryNode node, List temporaryList) { Queue> queueOfNodes = new LinkedList>(); BinaryNode current; queueOfNodes.add(node); while(!queueOfNodes.isEmpty()) { current = queueOfNodes.remove(); if (current.getData() != null) temporaryList.add(current.getData()); if (current.getLeft() != null) queueOfNodes.add(current.getLeft()); if (current.getRight() != null) queueOfNodes.add(current.getRight()); } } public Iterator iteratorLevelOrder() { ArrayList temporaryList = new ArrayList(); levelorder(root, temporaryList); return temporaryList.iterator(); } // ITERATORE public Iterator iterator() { return iteratorPreOrder(); } // ITERATORE PREORDER ITERATIVO (NON RICORSIVO) /* * In questo caso stiamo usando un tipo stack. * Dobbiamo di fatto simulare uno stack se vogliamo * iterativamente fare questa cosa. * * Perchè se prima dobbiamo visitare il nodo centrale, * poi il nodo a sinistra e poi il nodo a destra inseriamo * prima quello di destra e poi quello di sinistra? * * Lo stack di fatto è una coda LIFO, ovvero last in - first out * e quindi l'ultimo che entra è il primo che esce. * * dal momento che vale questa cosa, se facciamo entrare prima quello * di destra e poi quello di sinistra, il primo nodo che verrà * prelevato è ovviamente quello a sinistra. */ protected void itpreorder (BinaryNode node, List temporaryList) { Stack> queueOfNodes = new Stack>(); BinaryNode current; queueOfNodes.add(node); while (!queueOfNodes.isEmpty()) { current = queueOfNodes.pop(); if (current.getData() != null) temporaryList.add(current.getData()); // Come già spiegato pushamo prima il nodo di destra if (current.getRight() != null) queueOfNodes.push(current.getRight()); // Infine aggiungiamo il nodo di sinistra if (current.getLeft() != null) queueOfNodes.push(current.getLeft()); } } public Iterator ititeratorPreOrder() { ArrayList temporaryList = new ArrayList(); itpreorder(root, temporaryList); return temporaryList.iterator(); } // ITERATOR INORDER ITERATIVO (NON RICORSIVO) /* * Questo metodo riguarda lo stesso di quello superiore. * Il metodo inorder richiede che si visiti prima sinistra, * poi il nodo stesso, infine il nodo di destra. * * Possiamo quindi sempre adoperare uno Stack per siumlare la * ricorsione. * * Come detto prima, se il primo nodo ad essere prelevato è * l'ultimo che si inserisce (coda LIFO, Stack) allora per simulare * sinistra -> centro -> destra. * * Nel metodo sono state inserite delle check con uno stack di * flag che permette di capire quando il nodo centrale è visitare * oppure no. */ private void itinorder(BinaryNode node, List temporaryList) { Stack> queueOfNodes = new Stack>(); Stack flags = new Stack(); BinaryNode current; Boolean flag; queueOfNodes.add(node); flags.push(false); while (!queueOfNodes.isEmpty()) { current = queueOfNodes.pop(); flag = flags.pop(); if (flag) { // il nodo è da visitare if (current.getData() != null) temporaryList.add(current.getData()); } else { // il nodo non è da visitare // prima si aggiunge il nodo di destra if (current.getRight() != null) { queueOfNodes.add(current.getRight()); flags.add(false); } queueOfNodes.push(current); flags.push(true); if (current.getLeft() != null) { queueOfNodes.add(current.getLeft()); flags.push(false); } } } } public Iterator ititeratorInOrder() { ArrayList temporaryList = new ArrayList(); itinorder(root, temporaryList); return temporaryList.iterator(); } // ITERATOR POST ORDER ITERATIVO (NON RICORSIVO) /* * In questo caso la logica è la seguente. si ha la * necessità di fare il seguente ciclo: SINISTRA -> DESTRA -> NODO CORRENTE * * Per simulare lo stack dobbiamo quindi ripercorrerlo al contrario, * dunque NODO CORRENTE -> DESTRA -> SINISTRA * * Possiamo dunque rifare lo stesso, di ciò che abbiamo fatto sopra. */ protected void itpostorder(BinaryNode node, List temporaryList) { Stack> queueOfNodes = new Stack>(); Stack flags = new Stack(); BinaryNode current; Boolean flag; queueOfNodes.push(node); flags.push(false); while (!queueOfNodes.isEmpty()) { current = queueOfNodes.pop(); flag = flags.pop(); if (flag) { // il nodo è da visitare if (current.getData() != null) temporaryList.add(current.getData()); } else { // il nodo non è ancora da visitare // Visitiamo il nodo corrente queueOfNodes.push(current); flags.push(true); // visitiamo il nodo di destra if (current.getRight() != null) { queueOfNodes.push(current.getRight()); flags.push(false); } // visitiamo il nodo di sinistra infine if (current.getLeft() != null) { queueOfNodes.push(current.getLeft()); flags.push(false); } } } } public Iterator ititeratorPostOrder() { ArrayList temporaryList = new ArrayList(); itpostorder(root, temporaryList); return temporaryList.iterator(); } /* * II parziale 2022/2023 * Realizzare un metodo costruttore della classe LinkedBinaryTree che * prende in input una lista di oggetti di tipo E e costruisce una catena * casuale tale che l'iesimo elemento della lista è posizionato al livello iesimo * della catena e ogni nodo ha probabilità 1/2 di avere un figlio sinistro/destro. */ /* * Spiegazione intuitiva (visto che la melideo intende complicare la consegna): * Data ad esempio una lista del genere: * [A, B, C, D] * * Si ottiene quindi una catena del genere: * A * \ * B * / * C * \ * D */ public LinkedBinaryTree(List objectList) { if (objectList == null) throw new NullPointerException(); Iterator iterator = objectList.iterator(); if (!iterator.hasNext()) return; E currentObject = iterator.next(); root = new BinaryNode(currentObject); BinaryNode currentNode = root; currentNode.setData(currentObject); size = 1; while (iterator.hasNext()) { currentObject = iterator.next(); int wing = (int) (Math.random() * 2); BinaryNode newNode = new BinaryNode(currentObject); if (wing == 0) { // Left newNode.setParentAsLeftChild(currentNode); } else { // Right newNode.setParentAsRightChild(currentNode); } currentNode = newNode; size++; } } /* * II parziale 2022 * Si aggiunga alla classe LinkedBinaryTree un metodo * che stampa le foglie dall'albero corrente (da sinistra verso destra) */ public void printLeaf() { printLeaf(root); } public void printLeaf(BinaryNode node) { if (node == null) return; if (node.getLeft() == null && node.getRight() == null) System.out.println(node.getData().toString()); else { printLeaf(node.getLeft()); printLeaf(node.getRight()); } } /* * Si aggiunga alla classe LinkedBinaryTree un metodo che conta il numero * di foglie dell'albero corrente */ public int numberLeaf() { return numberLeaf(root); } public int numberLeaf(BinaryNode node) { if (node == null) return 0; if (node.getLeft() == null && node.getRight() == null) return 1; int nLeft = (node.getLeft() == null) ? 0 : numberLeaf(node.getLeft()); int nRight = (node.getRight() == null) ? 0 : numberLeaf(node.getRight()); return nLeft + nRight; } /* * Si aggiunga alla classe LinkedBinaryTree un metodo che trasforma l'albero corrente * in modo che ogni suo nodo interno abbia esattamente due figli. Se un nodo ha solo un * figlio, si aggiunga l'altro nodo figlio come copia del figlio esistente */ public void makeFull() { makeFull(root); } public void makeFull(BinaryNode node) { if (node == null) return; // Se il nodo è nullo, non serve fare operazioni if (!node.hasLeft() && !node.hasRight()) return; // Se il nodo è una foglia non ha bisogno di fare operazioni makeFull(node.getLeft()); makeFull(node.getRight()); BinaryNode copy; if (!node.hasLeft()) { // Si copia il figlio destro in quello sinistro copy = new BinaryNode(node.getRight().getData()); node.setLeft(copy); copy.setParentAsLeftChild(node); size++; } if (!node.hasRight()) { copy = new BinaryNode(node.getLeft().getData()); node.setRight(copy); copy.setParentAsRightChild(node); size++; } } /* * TOTALE 2024-01-16 * Realizzare un metodo ricorsivo interno alla classe LinkedBinaryTree * che dato l'albero binario corrente restituisca la distanza della foglia * più vicina alla radice. */ public int shortestLeaf() { if (root == null) throw new NullPointerException(); return shortestLeaf(root); } protected int shortestLeaf(BinaryNode node) { // Se questa è una foglia if (node.getLeft() == null && node.getRight() == null) return 1; // Recuperiamo l'altezza di sinistra e di destra /* * Nota BENE: Non si arriverà mai nella situazione in cui vi sono 2 MAX_VALUE assegnati al * nodo corrente poichè il caso foglia è gestito sopra. */ int altezzaMinimaSinistra = (node.getLeft() != null) ? shortestLeaf(node.getLeft()) : Integer.MAX_VALUE; int altezzaMinimaDestra = (node.getRight() != null) ? shortestLeaf(node.getRight()) : Integer.MAX_VALUE; // Ritorno del valore foglia minimo return Integer.min(altezzaMinimaSinistra, altezzaMinimaDestra) + 1; } /* * TOTALE 2024-01-30 * Realizzare un metodo interno alla classe LinkedBinaryTree che dato l'albero binario * corrente restituisca il numero di occorrenze di ciascun oggetto contenuto nei nodi dell'albero */ public Map getElementsOccurrency() { // Creazione Map Map mappa = new HashMap(); if (root == null) return mappa; getElementsOccurrency(root, mappa); return mappa; } protected void getElementsOccurrency(BinaryNode node, Map mappa) { // Se è un nodo nullo if (node == null) return; // Si proceda a sx e dx if (node.getLeft() != null) getElementsOccurrency(node.getLeft(), mappa); if (node.getRight() != null) getElementsOccurrency(node.getRight(), mappa); // Si legge l'elemento corrente E currentData = node.getData(); if (currentData == null) return; if (mappa.get(currentData) == null) mappa.put(currentData, 1); else mappa.put(currentData, 1 + mappa.get(currentData)); } }