first commit
This commit is contained in:
@@ -0,0 +1,520 @@
|
||||
package binary_tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user