578 lines
16 KiB
Java
578 lines
16 KiB
Java
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<E> implements BinaryTree<E>{
|
|
|
|
// VARIABILI D'INSTANZA
|
|
private BinaryNode<E> root;
|
|
private int size;
|
|
|
|
// Metodi costruttori
|
|
public LinkedBinaryTree() {
|
|
root = null;
|
|
size = 0;
|
|
}
|
|
|
|
public LinkedBinaryTree(E data) {
|
|
root = new BinaryNode<E>(data);
|
|
size = 1;
|
|
}
|
|
|
|
public LinkedBinaryTree(LinkedBinaryTree<E> left, E data, LinkedBinaryTree<E> right) {
|
|
root = new BinaryNode<E>(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<E> getRootNode() {
|
|
if (isEmpty()) return null;
|
|
return root;
|
|
}
|
|
|
|
public void clear() {
|
|
root = null;
|
|
size = 0;
|
|
}
|
|
|
|
private int getSize(BinaryNode<E> 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<E> removeLeft() {
|
|
LinkedBinaryTree<E> leftTree = null;
|
|
if (root.getLeft() == null) return leftTree;
|
|
|
|
leftTree = new LinkedBinaryTree<E>();
|
|
leftTree.root = root.getLeft();
|
|
leftTree.size = getSize(root.getLeft());
|
|
leftTree.root.setAsRoot();
|
|
|
|
size = size - leftTree.size;
|
|
|
|
return leftTree;
|
|
}
|
|
|
|
public LinkedBinaryTree<E> removeRight() {
|
|
LinkedBinaryTree<E> rightTree = null;
|
|
if (root.getRight() == null) return rightTree;
|
|
|
|
rightTree = new LinkedBinaryTree<E>();
|
|
rightTree.root = root.getRight();
|
|
rightTree.size = getSize(root.getRight());
|
|
rightTree.root.setAsRoot();
|
|
|
|
size = size - rightTree.size;
|
|
|
|
return rightTree;
|
|
}
|
|
|
|
protected BinaryNode<E> find(E targetElement, BinaryNode<E> root) {
|
|
if (root == null) return null;
|
|
if (root.getData().equals(targetElement)) return root;
|
|
BinaryNode<E> 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<E> 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<E> node, List<E> temporaryList) {
|
|
if (node == null) return;
|
|
|
|
if (node.getData() != null) temporaryList.add(node.getData());
|
|
|
|
preorder(node.getLeft(), temporaryList);
|
|
preorder(node.getRight(), temporaryList);
|
|
}
|
|
|
|
public Iterator<E> iteratorPreOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
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<E> node, List<E> temporaryList) {
|
|
if (node == null) return;
|
|
|
|
inorder(node.getLeft(), temporaryList);
|
|
|
|
if (node.getData() != null) temporaryList.add(node.getData());
|
|
|
|
inorder(node.getRight(), temporaryList);
|
|
}
|
|
|
|
public Iterator<E> iteratorInOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
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<E> node, List<E> temporaryList) {
|
|
if (node == null) return;
|
|
|
|
postorder(node.getLeft(), temporaryList);
|
|
postorder(node.getRight(), temporaryList);
|
|
|
|
if (node.getData() != null) temporaryList.add(node.getData());
|
|
}
|
|
|
|
public Iterator<E> iteratorPostOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
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<E> node, List<E> temporaryList) {
|
|
Queue<BinaryNode<E>> queueOfNodes = new LinkedList<BinaryNode<E>>();
|
|
BinaryNode<E> 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<E> iteratorLevelOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
levelorder(root, temporaryList);
|
|
return temporaryList.iterator();
|
|
}
|
|
|
|
// ITERATORE
|
|
|
|
public Iterator<E> 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<E> node, List<E> temporaryList) {
|
|
Stack<BinaryNode<E>> queueOfNodes = new Stack<BinaryNode<E>>();
|
|
BinaryNode<E> 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<E> ititeratorPreOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
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<E> node, List<E> temporaryList) {
|
|
Stack<BinaryNode<E>> queueOfNodes = new Stack<BinaryNode<E>>();
|
|
Stack<Boolean> flags = new Stack<Boolean>();
|
|
|
|
BinaryNode<E> 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<E> ititeratorInOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
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<E> node, List<E> temporaryList) {
|
|
Stack<BinaryNode<E>> queueOfNodes = new Stack<BinaryNode<E>>();
|
|
Stack<Boolean> flags = new Stack<Boolean>();
|
|
|
|
BinaryNode<E> 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<E> ititeratorPostOrder() {
|
|
ArrayList<E> temporaryList = new ArrayList<E>();
|
|
itpostorder(root, temporaryList);
|
|
return temporaryList.iterator();
|
|
}
|
|
|
|
/*
|
|
* II parziale 2022/2023
|
|
* Realizzare un metodo costruttore della classe LinkedBinaryTree<E> 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<E> objectList) {
|
|
if (objectList == null) throw new NullPointerException();
|
|
|
|
Iterator<E> iterator = objectList.iterator();
|
|
if (!iterator.hasNext()) return;
|
|
|
|
E currentObject = iterator.next();
|
|
root = new BinaryNode<E>(currentObject);
|
|
BinaryNode<E> currentNode = root;
|
|
currentNode.setData(currentObject);
|
|
size = 1;
|
|
|
|
while (iterator.hasNext()) {
|
|
currentObject = iterator.next();
|
|
int wing = (int) (Math.random() * 2);
|
|
|
|
BinaryNode<E> newNode = new BinaryNode<E>(currentObject);
|
|
|
|
if (wing == 0) {
|
|
// Left
|
|
newNode.setParentAsLeftChild(currentNode);
|
|
} else {
|
|
// Right
|
|
newNode.setParentAsRightChild(currentNode);
|
|
}
|
|
|
|
currentNode = newNode;
|
|
size++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* II parziale 2022
|
|
* Si aggiunga alla classe LinkedBinaryTree<E> un metodo
|
|
* che stampa le foglie dall'albero corrente (da sinistra verso destra)
|
|
*/
|
|
public void printLeaf() {
|
|
printLeaf(root);
|
|
}
|
|
|
|
public void printLeaf(BinaryNode<E> 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<E> un metodo che conta il numero
|
|
* di foglie dell'albero corrente
|
|
*/
|
|
public int numberLeaf() {
|
|
return numberLeaf(root);
|
|
}
|
|
|
|
public int numberLeaf(BinaryNode<E> 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<E> 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<E> 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<E> copy;
|
|
|
|
if (!node.hasLeft()) {
|
|
// Si copia il figlio destro in quello sinistro
|
|
copy = new BinaryNode<E>(node.getRight().getData());
|
|
node.setLeft(copy);
|
|
copy.setParentAsLeftChild(node);
|
|
size++;
|
|
}
|
|
if (!node.hasRight()) {
|
|
copy = new BinaryNode<E>(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<E> 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<E, Integer> getElementsOccurrency() {
|
|
// Creazione Map
|
|
Map<E, Integer> mappa = new HashMap<E, Integer>();
|
|
if (root == null) return mappa;
|
|
getElementsOccurrency(root, mappa);
|
|
return mappa;
|
|
}
|
|
|
|
protected void getElementsOccurrency(BinaryNode<E> node, Map<E, Integer> 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));
|
|
}
|
|
|
|
}
|