Estructuras de Datos y Algoritmos (ITIS). TAD Tree Estructuras de Datos y Algoritmos (ITIS) Ingeniería Técnica en Informática de Sistemas, Curso 2º PRÁCTICA 3 TAD TREE Árbol binario de búsqueda. Tabla de frecuencias. Uno de los mecanismos más sencillos que se utilizan en los sistemas de recuperación de información textual consiste en asociar a cada documento de texto una Tabla de Frecuencias, tabla en la que aparecen de forma ordenada las palabras encontradas en el texto junto con su frecuencia de aparición (número de ocurrencias). Uno de los objetivos de esta práctica será el utilizar un ABB para establecer un mecanismo eficiente de asociación entre un conjunto de datos de entrada (cadenas de caracteres y su correspondiente tabla de frecuencias) y hace eficiente el proceso de búsqueda sobre dicha árbol. Por ejemplo: En este caso, los objetos son del tipo Cstring, clase que heredará de Ccomparable y que encapsulará la funcionalidad del tipo string {“uno”, “uno”, “dos”, “tres”, “uno”, “uno”, “dos”,...} PALABRA “dos” “tres” “uno” … FRECUENCIA 2 1 4 … Listado ordenado En esta práctica vamos a utilizar un ABB “genérico” (utilizando herencia) para establecer un mecanismo eficiente de asociación entre un conjunto de datos de entrada (conjunto de objetos comparables generados de forma aleatoria) y su correspondiente tabla de frecuencias. • Utilizando Cstring (hereda de Comparable, que es una clase abstracta con todas sus funciones virtuales puras), generar n objetos Cstring comparables aleatorios e insertarlos en un ABB. • Mostrar en pantalla el contenido de la estructura realizando los distintos recorridos vistos clase. Como mínimo: anchura y profundidad (Preorden, Inorden y Postorden). • Mostrar estructura de ABB resultante, si n es relativamente pequeño. Página 1 Estructuras de Datos y Algoritmos (ITIS). TAD Tree • Generar una cadena de caracteres (string) que represente el objeto árbol (método string toString()). Introducir la posibilidad de construir un árbol a partir de una cadena de caracteres, resultado del método toString() de otro árbol (método void fromString(string)) • Mostrar por pantalla la tabla de frecuencias asociada. A continuación se muestran las cabeceras de las clases (declaración) a implementar, junto con la implementación (definición) de algunas de las funciones miembro declaradas. // Comparable.hpp #ifndef __CCOMPARABLE_HPP__ #define __CCOMPARABLE_HPP__ #include <string> using namespace std; class Ccomparable { public: virtual bool operator==(Ccomparable*) = 0; virtual bool operator<(Ccomparable*) = 0; virtual bool operator>(Ccomparable*) = 0; virtual string toString() = 0; virtual void fromString(string) = 0; }; #endif // Cstring.hpp #ifndef __CSTRING_HPP__ #define __CSTRING_HPP__ #include <string> #include <sstream> #include "Ccomparable.hpp" using namespace std; class Cstring : public Ccomparable{ string cad; public: Cstring(string); Cstring(); bool operator==(Ccomparable*); bool operator<(Ccomparable*); bool operator>(Ccomparable*); string getCad(); void setCad(string); string toString(); void fromString(string); }; #endif // Cstring.cpp #include "Cstring.hpp" Cstring::Cstring(string cadena) { cad = cadena; } Página 2 Estructuras de Datos y Algoritmos (ITIS). TAD Tree bool Cstring::operator<(Ccomparable* ele) { return( cad < ((Cstring*)ele)->getCad() ); } string Cstring::toString() { ostringstream os; os << cad; return os.str(); } void Cstring::fromString(string cadena) { cad = cadena; } // ***** Implementar restantes funciones miembro de la clase Cstring // Cnodo.hpp #ifndef __CNODO_HPP__ #define __CNODO_HPP__ #include "Ccomparable.hpp" using namespace std; class Cnodo { Ccomparable* info; Cnodo* izq,* dch; int frecuencia; public: Cnodo(); ~Cnodo(); Cnodo(Comparable*); Ccomparable* getInfo(); Cnodo* getIzq(); Cnodo* getDch(); string toString(); void setIzq(Cnodo*); void setDch(Cnodo*); void setFrecuencia(int); int getFrecuencia(); void setInfo(Ccomparable*); }; #endif // Cnodo.cpp #include "Cnodo.hpp" Cnodo::Cnodo(Ccomparable* valor) { info = valor; izq = NULL; dch = NULL; frecuencia = 0; } Página 3 Estructuras de Datos y Algoritmos (ITIS). TAD Tree Cnodo::Cnodo() { info=NULL; izq=NULL; dch=NULL; frecuencia=0; } Cnodo::~Cnodo() { delete info; } string Cnodo::toString() { return info->toString(); } // ***** Implementar restantes funciones miembro de la clase Cnodo // CarbolBB.hpp #ifndef __CARBOLBB_HPP__ #define __CARBOLBB_HPP__ #include "Cnodo.hpp" #include "Cstring.hpp" #include <iostream> #include <cstdlib> #include <cstdio> #include <queue> #include <math.h> using namespace std; class CarbolBB { Cnodo* raiz; void preOrden(Cnodo*); void inOrden(Cnodo*); void postOrden(Cnodo*); void eliminarArbol(Cnodo*); void mostrarFrecuencias(Cnodo*); Cnodo* eliminar(Ccomparable*, Cnodo*); void mostrarArbol(Cnodo*, int); int altura(Cnodo*); Cnodo* buscarMinimo(Cnodo*); Cnodo* eliminarMinimo(Cnodo*); Cnodo* buscar(Ccomparable*, Cnodo*); Cnodo* insertar(Ccomparable*, Cnodo*); string toString(string, Cnodo*); public: CarbolBB(); ~CarbolBB(); int altura(); void preOrden(); void inOrden(); void postOrden(); void anchura(); void insertar(Ccomparable*); void mostrarArbol(); void mostrarFrecuencias(); Página 4 Estructuras de Datos y Algoritmos (ITIS). TAD Tree void eliminar(Ccomparable*); Cnodo* buscar(Ccomparable*); string toString(); void fromString(string); }; #endif // CarbolBB.cpp #include "CarbolBB.hpp" CarbolBB::CarbolBB() { raiz = NULL; } CarbolBB::~CarbolBB() { eliminarArbol(raiz); } void CarbolBB::eliminarArbol(Cnodo* r) { if (!r) return; eliminarArbol(r->getIzq()); eliminarArbol(r->getDch()); delete r; } void CarbolBB::preOrden() { preOrden( raiz ); } void CarbolBB::preOrden(Cnodo* r) { if( r ) { cout << r->toString() <<" "; preOrden(r->getIzq()); preOrden(r->getDch()); } } Cnodo* CarbolBB::buscar(Ccomparable* x) { return buscar(x, raiz); } Cnodo* CarbolBB::buscar(Ccomparable* x, Cnodo* a) { if(!a) return NULL; if(*x == a->getInfo()) return a; if(*x > a->getInfo()) return buscar(x, a->getDch()); else return buscar(x, a->getIzq()); } Página 5 Estructuras de Datos y Algoritmos (ITIS). TAD Tree void CarbolBB::insertar(Ccomparable* x) { raiz = insertar(x, raiz); } Cnodo* CarbolBB::insertar(Ccomparable* x, Cnodo* nb) { if(!nb) nb = new Cnodo(x); if(*x < nb->getInfo()) nb->setIzq(insertar(x, nb->getIzq())); if(*x > nb->getInfo()) nb->setDch(insertar(x, nb->getDch())); if(*x == nb->getInfo()) nb->setFrecuencia(nb->getFrecuencia() + 1); return nb; } string CarbolBB::toString() { string cadena = ""; return toString(cadena, raiz); } string CarbolBB::toString(string cad, Cnodo* nb) { if(!nb) return cad; cad += (nb->getInfo())->toString() + " "; cad = toString(cad, nb->getIzq()); cad = toString(cad, nb->getDch()); return cad; } // ***** Implementar restantes funciones miembro de la clase CarbolBB Página 6 Estructuras de Datos y Algoritmos (ITIS). TAD Tree #include <cstdlib> #include <iostream> #include <cstdio> #include <string> #include "CarbolB.hpp" #include "Cstring.hpp" using namespace std; int main() { int n; CarbolB* arbol = NULL; Ccomparable* nuevoElemento = NULL; // Generar n objetos Cstring comparables aleatorios e insertarlos en un ABB cout << "INTRODUZCA EL NUMERO DE ELEMENTOS A INSERTAR: "; cin >> n; arbol = new CarbolB(); for(int i = 0; (i < n); i++) { int num = rand() % 10; string palabra = ""; char p; for (int j = 0; (j < num); j++) { p = (rand() % 25) + 65; palabra = palabra + p; } nuevoElemento = new Cstring(palabra); arbol->insertar(nuevoElemento); } if (arbol->altura() < 15) arbol->mostrarArbol(); else cout <<"ERROR -> Demasiados alturar para mostrar." << endl; // Realizar las pruebas de las funciones que se piden sobre el ABB delete arbol; } Página 7 Estructuras de Datos y Algoritmos (ITIS). TAD Tree Árbol binario de búsqueda. Validación de accesos a un ordenador (login). Planteamiento del problema. Consideremos el problema de organizar una colección de identificadores de usuario de un ordenador (nombre de usuario o username) y sus claves de acceso (palabras clave o password). Cada vez que un usuario accede al sistema introduciendo su username y su password (secreto), el sistema debe comprobar la validez de ambos para verificar que es un usuario legítimo. Puesto que dicha validación debe hacerse en un gran número de veces al día, es necesario estructurar esa información de forma que se pueda encontrar rápidamente. Además, debe ser una estructura dinámica porque se pueden ir añadiendo sistemáticamente usuarios al sistema. Diseño. Uno de los objetos de este problema es la información de usuario (username y password) que van a ser cadenas de caracteres. Para ello construiremos una clase InfoUsuario. Necesitamos también una colección de objetos InfoUsuario. Usaremos un ABB para ello, porque en él se pueden buscar rápidamente y también se trata de una estructura dinámica. Por tanto, las principales operaciones que necesitamos nos la proporciona la clase ABB: • Construir un ABB de objetos InfoUsuario • Buscar en el ABB un objeto InfoUsuario introducido desde teclado • Mostrar un mensaje indicando si el usuario es válido La clase InfoUsuario tendrá como atributos el username y el password del usuario (ambos cadenas de caracteres). Puesto que la búsqueda y la inserción en un ABB requiere poder comparar los valores almacenados en él con < y ==, debemos sobrecargar dichos operadores para InfoUsuario. Y también necesitamos una operación de entrada, por lo que también sobrecargaremos >> para este caso. class InfoUsuario { public: string id() const { return username; } void leer(istream & in) { in >> username >> password; } bool operator==(const InfoUsuario & user) const { return username == user.username && password == user.password; } bool operator<(const InfoUsuario & user) const { return username < user.username || username == user.username && password < user.password; } private: string username, password; }; istream & operator>>(istream & in, InfoUsuario & user) { user.leer(in); } El algoritmo que usa estos objetos y operaciones para la validación de acceso a un ordenador (login) es el siguiente (Algoritmo para leer username y password, comprobando su validez): // Inicialmente crea el ABB de objetos InfoUsuario 1. Abrir un stream a un archivo que contiene la información válida sobre username-password de usuarios Página 8 Estructuras de Datos y Algoritmos (ITIS). TAD Tree 2. Crear un ABB con elementos del tipo InfoUsuario 3. Leer los objetos InfoUsuario del archivo e insertarlos en el ABB // Proceso de verificación 4. Repetir lo siguiente hasta que se detenga el sistema a. Leer un objeto InfoUsuario b. Buscar en el ABB dicho objeto c. Si se encuentra, mostrar el mensaje “acceso permitido”, si no, mostrar el mensaje “acceso denegado” Fin del bucle repetir #include <iostream> #include <fstream> #include <string> using namespace std; int main() { // Abrir un stream a un archivo con los pares username-password de usuarios string archivoUsuario; cout << "Introduzca el nombre completo de archivo (con username-password): "; getline(cin, archivoUsuario); ifstream canalEntrada (archivoUsuario.data()); if (!canalEntrada.is_open()) { cerr << "No se puede abrir " << archivoUsuario << "\n"; exit(1); } // Crear un ABB con elementos del tipo InfoUsuario // ABB<InfoUsuario> abbUsuarios; // Leer los objetos InfoUsuario del archivo e insertarlos en el ABB InfoUsuario usuario; for(;;) { canalEntrada >> usuario; if (canalEntrada.eof()) break; // abbUsuarios.insertar(usuario); } canalEntrada.close(); // Proceso de verificación: validación de accesos (logins) cout << "Introduzca S S para salir (detener el procesamiento).\n"; for (;;) { cout << "\nUsuario username & password: "; cin >> usuario; if (usuario.id() == "S") break; //if (abbUsuarios.buscar(usuario)) // cout << "Acceso permitido\n"; //else // cout << "Acceso degenado\n"; } } Página 9 Estructuras de Datos y Algoritmos (ITIS). TAD Tree Ejercicios prácticos relacionados con los árboles vistos en clase. Ejercicio 1. Escribir una función que a partir de un ABB y dos números enteros n1 y n2, tal que n1 ≤ n2, visualice en orden todos los elementos e del árbol tal que n1 ≤ e ≤ n2. Ejercicio 2. Realizar una función que devuelva la posición de un elemento en un ABB (desde 1 hasta el número de nodos del árbol) según un recorrido en inorden sobre el mismo. Si el elemento no está en dicho árbol, devolverá 0. Ejercicio 3. Dado un heap mínimo, implementar una función que devuelva ordenado (de menor a mayor) el contenido de dicho heap (i.e. implementar el heapsort). Ejercicio 4. Para hacer uso de la implementación de árboles AVL estudiada. Inserte en un árbol AVL vacío los siguientes valores clave (por orden): 3, 8, 9, 2 1, 5, 4, 6, 10 y 7. Indique las rotaciones y los cambios en los factores de equilibrio que aplica el algoritmo de inserción en cada caso. Muestre el árbol anterior y siguiente a cada inserción. Después de haber dejado cerrado el árbol AVL tras las sucesivas inserciones, extraiga los siguientes valores de clave de dicho árbol: 8, 10 y 1, indicando también las rotaciones y los cambios en los factores de equilibrio que aplica el algoritmo de extracción en cada caso. Muestre el árbol anterior y siguiente a cada extracción. Ejercicio 5. Para hacer uso de la implementación de árboles Roji-Negros estudiada. Inserte en un árbol Roji-Negro vacío los siguientes valores clave (por orden): 8, 4, 6, 2, 14, 12 y 10. Indique las rotaciones y las recolocaciones que aplica el algoritmo de inserción en cada caso. Muestre el árbol anterior y siguiente a cada inserción. Después de haber dejado cerrado el árbol Roji-Negro tras las sucesivas inserciones, extraiga los siguientes valores de clave de dicho árbol: 6, 14 y 8, indicando también las reestructuraciones que aplica el algoritmo de extracción en cada caso. Muestre el árbol anterior y siguiente a cada extracción. Ejercicio 6 (Opcional). Estudiar detenidamente la implementación en C++ de un contenedor usando un árbol B (ArbolB) que se muestra en el libro “Fundamentos de Estructuras de Datos” (paginas 394405), sabiendo que éste extiende de Multirrama, y éste a su vez de ContenedorPersistente. Implementar un pequeño programa prueba que inserte una serie de elementos y elimine alguno de ellos, comprobando cómo quedaría el árbol después de todas las inserciones y extracciones. Puede elegir el orden del árbol B, y todas las decisiones que sean necesarias para la prueba, ... pero justificándolo todo. Página 10