diff --git a/.gitignore b/.gitignore index 17a4914..dc72967 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ cmake-build-debug/ .idea/ build/ + +# Excalidraw files +*.excalidraw diff --git a/include/avltree.hpp b/include/avltree.hpp new file mode 100644 index 0000000..191ecc2 --- /dev/null +++ b/include/avltree.hpp @@ -0,0 +1,59 @@ +#include + +/** + * El nodo representa un puesto de avanzada. Los puestos de avanzada existen en el mundo postapocaliptico del Refugio 33, y se + * encuentran distribuidos a lo largo del mapa. + * + * outpostId: Es el identificador único del puesto de avanzada. + * prority: Cada puesto de avanzada tiene una prioridad estratégica basada en diversos factores. + */ +struct Nodo { + int outpostId; + int priority; + Nodo* izq; + Nodo* der; + + Nodo(int id) : outpostId(id), priority(1), izq(nullptr), der(nullptr) {} +}; + +class AVLTree { + public: + AVLTree(); + ~AVLTree(); + + void insert(int outpostId); + bool contains(int outpostId); + void remove(int outpostId); + + private: + Nodo* raiz; // Cuando se instancia, raiz = nullptr (declarado en el constructor) + + /** + * @brief Este método se encarga de insertar un nuevo puesto de avanzada (nodo) al Arbol AVL, estructura donde + * se hace el control de los puestos de avanzada. + * + * @param nodo Para insertar un nuevo nodo al arbol se le debe pasar cual será su nodo padre. Normalmente se le pasará + * la raíz como este parámetro, aunque si se está usando en un caso de recursión nodo puedo ser algun sub-hijo. + * + * @param outpostId Es el identificador único del puesto de avanzada que se va a insertar al Arbol AVL. + * + * */ + Nodo* insert(Nodo*& nodo, int outpostId); + void liberar(Nodo* nodo); + bool contains(Nodo* raiz, int outpostId); + void remove(Nodo* raiz, int outpostId); + + // Métodos auxiliares + int max(int n1, int n2) { return n1 > n2 ? n1 : n2; } + void actualizarAltura(Nodo* nodo); + int priority(Nodo* nodo); + int balance(Nodo* nodo); + Nodo* rotarDerecha(Nodo* nodo); + Nodo* rotarIzquierda(Nodo* nodo); + + +}; + + + + diff --git a/include/decisionTree.hpp b/include/decisionTree.hpp new file mode 100644 index 0000000..56e3d14 --- /dev/null +++ b/include/decisionTree.hpp @@ -0,0 +1,32 @@ +#include + +struct Nodo { + std::string decision; + Nodo* izquierda; + Nodo* derecha; + + Nodo(const std::string& d) + : decision(d), izquierda(nullptr), derecha(nullptr) {} +}; + +class DecisionTree { +public: + DecisionTree(); + ~DecisionTree(); + + void insertar(const std::string& decision); + bool buscar(const std::string& decision) const; + void eliminar(const std::string& decision); + bool estaVacio() const; + void recorrerPreorden() const; + +private: + Nodo* raiz; + + void insertar(Nodo*& nodo, const std::string& decision); + bool buscar(Nodo* nodo, const std::string& decision) const; + void eliminarNodo(Nodo*& nodo, const std::string& decision); + void recorrerPreorden(Nodo* nodo) const; + void destruir(Nodo* nodo); + Nodo* encontrarMinimo(Nodo* nodo); +}; diff --git a/src/avltree.cpp b/src/avltree.cpp new file mode 100644 index 0000000..4c9ba33 --- /dev/null +++ b/src/avltree.cpp @@ -0,0 +1,190 @@ +#include "avltree.hpp" + +// Constructor y destructor +AVLTree::AVLTree() { + raiz = nullptr; +} + +AVLTree::~AVLTree() { + liberar(raiz); +} + +// Para implementar el destructor primero se debe implementar un metodo que elimine Nodos del Arbol AVL + + +// Métodos públicos +void AVLTree::insert(int outpostId) { + // Llamamos al método privado, se le pasa la raiz. + raiz = insert(raiz, outpostId); +} + +bool AVLTree::contains(int outpostId) { + return contains(raiz, outpostId); +} + +void AVLTree::remove(int outpostId) { + if(!contains(raiz, outpostId)) { + std::cout << "El puesto de avanzada con id " << outpostId << " no se encuetra registrado." << std::endl; + } else { + remove(raiz, outpostId); + } +} + + +// Métodos privados +void AVLTree::remove(Nodo* raiz, int outpostId) { + // El chequeo de si la raiz es null ya se hace en contains, por lo tanto no se vendrá a este método sin verificar que el + // puesto de avanzada existen en el arbol. + + if(raiz->outpostId < outpostId) { + remove(raiz->izq, outpostId); + } else if(raiz->outpostId > outpostId) { + remove(raiz->der, outpostId); + } else { + // El sucesor, o nueva raíz o padre, será el más chico del sub arbol derecho + + + } +} +bool AVLTree::contains(Nodo* raiz, int outpostId) { + if(!raiz) return false; + + if(raiz->outpostId == outpostId) { + return true; + } + + return contains(raiz->izq, outpostId) || contains(raiz->der, outpostId); +} + +void AVLTree::liberar(Nodo* nodo) { + if(!nodo) return; + + liberar(nodo->izq); + liberar(nodo->der); + delete nodo; +} +Nodo* AVLTree::insert(Nodo*& nodo, int outpostId) { + // Este if se ejecutará en el caso de que sea el primer nodo a insertar (raiz == null), o en algún llamado de recursión + // cuando se llega a la posición de un nodo hijo que sea null y que en el se pueda insertar el nuevo nodo. + if(!nodo) { + return new Nodo(outpostId); + } + + // Sino, se evalúa el valor del ID para insertarlo en el sub-arbol izq. o der. + if(outpostId < nodo->outpostId) { + // Si es menor, se agrega a la izquierda, en este caso, el parámetro nodo es el puntero al nodo izquierdo de la raíz + nodo->izq = insert(nodo->izq, outpostId); + } else if(outpostId > nodo->outpostId) { + // Si es mayor, se agrega a la derecha, en este caso, el parámetro nodo es el puntero al nodo der. de la raíz + nodo->der = insert(nodo->der, outpostId); + } else { + // Sino es ni mayor ni meno quiere decir que es igual, pero no puede haber identificadores + // repetidos, entonces no se agrega. Se retorna el mismo valor de nodo que vino por parametro, dando a entender + // que si era la raiz, esta permanece igual, o si era un nodo padre de algun sub arbol, el nodo padre tampoco + // cambiara + return nodo; + } + + // Una vez agregado un nuevo nodo al arbol AVL se debe calcular la nueva altura de este, y si esta fuera del + // rango [-1, 1] se debe balancear el arbol... + // La altura en este arbol es representada por la priority + actualizarAltura(nodo); + + // Ahora verificaremos si se encuentra en el rango [-1, 1], y si está fuera se debe balancear + + int balanceDelNodo = balance(nodo); + + // Desbalanceo Left-Left: Hay un desbalance hacia la izquierda "doble". + if (balanceDelNodo > 1 && outpostId < nodo->izq->outpostId) { + // Retorna la nueva raiz que queda al hacer la rotacion + return rotarDerecha(nodo); + } + + // Desbalanceo Right-Right: Hay un desbalance hacia la derecha "doble". + if(balanceDelNodo < -1 && outpostId > nodo->der->outpostId) { + return rotarIzquierda(nodo); + } + + // Desbalanceo Left-Right: La raiz esta desbalanceada a la izquierda, y el sub arbol izq esta desbalanceado a la derecha + if(balanceDelNodo > 1 && outpostId > nodo->izq->outpostId) { + nodo->izq = rotarIzquierda(nodo->izq); + return rotarDerecha(nodo); + } + + // Desbalanceo Right-Left: La raíz está desbalanceada a la derecha, y el sub arbol der está desbalanceado a la izquierda + if(balanceDelNodo < -1 && outpostId < nodo->der->outpostId) { + nodo->der = rotarDerecha(nodo->der); + return rotarIzquierda(nodo); + } + + // Si el balance esta dentro de [-1, 1] no hay que hacer ninguna rotacion, se devuelve el nodo que llego como parametro + // que en ocasiones sera la raiz o un nodo padre en algun caso de recursion + return nodo; +} + + +// Métodos auxiliares +Nodo* AVLTree::rotarDerecha(Nodo* nodo) { + // Para razonar y comprender el metodo supongamos que el nodo es la raiz, es el caso de una raiz con un sub-arbol izquierdo + // y se agrego otro dato menor que el sub-arbol izquierdo, por ejemplo, raiz 50, sub nodo 40 y se agrego el 20 + + // El nodo que llega como parametro tiene que convertirse en el sub nodo der de su sub nodo izq + // Y el sub nodo izquierdo del nodo que llego va a ser la nueva raiz o nodo padre + + Nodo* nuevoPR = nodo->izq; // nuevoPR = nuevoPadreRaiz para indicar que es el nuevo padre o raiz + + // El sub nodo derecho, del sub nodo izquierdo del nodo que llego como parametro (es decir, el sub nodo derecho + // de la nueva raiz o nodo padre) va a ser el sub nodo izquierdo del nodo que llego, que el nodo que llego ahora es + // el sub arbol derecho de su nodo izquierdo (que ahora es el nuevo nodo padre o raiz) + Nodo* nuevoIzqDer = nuevoPR->der; // nuevoIzqDer = nuevo nodo izquierdo del nuevo nodo derecho (que es el nodo que llego como parametro) + + // A la derecha de la nueva raiz va el nodo que llego... + nuevoPR->der = nodo; + + // ... pero hay que quitarle los nodos hijos al que llego, pq sino estariamos como copiando una rama del arbol... + nodo->izq = nuevoIzqDer; // A la izquierda se quitan el 40 y 20 (en este ejemplo) y se agrega lo que estaba a la derecha + // del nuevo nodo padre o raiz + + actualizarAltura(nodo); + actualizarAltura(nuevoPR); + + return nuevoPR; + +} + +Nodo* AVLTree::rotarIzquierda(Nodo* nodo) { + Nodo* nuevoPR = nodo->der; + Nodo* nuevoDerIzq = nuevoPR->izq; + + nuevoPR->izq = nodo; + nodo->der = nuevoDerIzq; + + actualizarAltura(nodo); + actualizarAltura(nuevoPR); + + return nuevoPR; +} + +void AVLTree::actualizarAltura(Nodo* nodo) { + if(!nodo) return; + + nodo->priority = 1 + max(priority(nodo->izq), priority(nodo->der)); + + // Siempre que se agregue un nodo, este va a tener altura 1. Entonces el valor de altura que se actuliza es el de + // la raiz o algún sub-nodo si estamos en un caso de recursión. + // Por ejemplo luego de agregar un segundo elemento (ya tenemos la raíz con priority 1), la altura (o priority) de + // la raiz va a ser 1 + la altura del nodo recien agregado que tambien es 1, por lo tanto la raiz queda con una priority + // (o altura) de 2. Y el nodo recién agregado queda con altura 1. +} + +int AVLTree::priority(Nodo* nodo) { + if(!nodo) return 0; + + return nodo->priority; +} + +int AVLTree::balance(Nodo* nodo) { + if(!nodo) return 0; + + return priority(nodo->izq) - priority(nodo->der); +} diff --git a/src/decisionTree.cpp b/src/decisionTree.cpp new file mode 100644 index 0000000..cb99f18 --- /dev/null +++ b/src/decisionTree.cpp @@ -0,0 +1,118 @@ +#include "decisionTree.hpp" + +DecisionTree::DecisionTree() { + raiz = nullptr; +} + +DecisionTree::~DecisionTree() { + destruir(raiz); +} + + +// Metodos publicos +void DecisionTree::insertar(const std::string& decision) { + insertar(raiz, decision); +} + +void DecisionTree::eliminar(const std::string& decision) { + eliminarNodo(raiz, decision); +} + +bool DecisionTree::buscar(const std::string& decision) const { + return buscar(raiz, decision); +} + +void DecisionTree::recorrerPreorden() const { + recorrerPreorden(raiz); +} + +bool DecisionTree::estaVacio() const { + if(!raiz) return true; + + return false; +} + + +// Metodos privados +void DecisionTree::insertar(Nodo*& nodo, const std::string& decision) { + // nodo va a ser la raiz del arbol + + if(nodo == nullptr) { + nodo = new Nodo(decision); + } + + if(decision < nodo->decision) insertar(nodo->izquierda, decision); + if(decision > nodo->decision) insertar(nodo->derecha, decision); +} + + +void DecisionTree::destruir(Nodo* nodo) { + if(!nodo) return; + destruir(nodo->izquierda); + destruir(nodo->derecha); + delete nodo; +} + +void DecisionTree::eliminarNodo(Nodo*& nodo, const std::string& decision) { + if(!nodo) return; + + if(decision < nodo->decision) { + eliminarNodo(nodo->izquierda, decision); + } else if (decision > nodo->decision) { + eliminarNodo(nodo->derecha, decision); + } else { + // Caso 1: El nodo a eliminar NO tiene hijos + if(!nodo->izquierda && !nodo->derecha) { + delete nodo; + nodo = nullptr; + } + + // Caso 2: El nodo a eliminar tiene UN SOLO hijo + else if(!nodo->izquierda) { + Nodo* temp = nodo; + nodo = nodo->derecha; + delete temp; + } else if(!nodo->derecha) { + Nodo* temp = nodo; + nodo = nodo->izquierda; + delete temp; + } + + // Caso 3: El nodo a eliminar tiene DOS hijos + else { + Nodo* sucesor = encontrarMinimo(nodo->derecha); + nodo->decision = sucesor->decision; + eliminarNodo(nodo->derecha, sucesor->decision); + } + + } +} + +Nodo* DecisionTree::encontrarMinimo(Nodo* nodo) { + while(nodo && nodo->izquierda) { + nodo = nodo->izquierda; + } + return nodo; +} + +bool DecisionTree::buscar(Nodo* nodo, const std::string& decision) const { + + if(!nodo) { + return false; + } + + if(nodo->decision == decision) { + return true; + } + + return buscar(nodo->izquierda, decision) || buscar(nodo->derecha, decision); +} + + +void DecisionTree::recorrerPreorden(Nodo* nodo) const { + if(!nodo) return; + + std::cout<decision<<" "; + recorrerPreorden(nodo->izquierda); + recorrerPreorden(nodo->derecha); +} \ No newline at end of file