El Código Fuente En Programación: ¿Qué es una Pila? ¿Una Lista? ¿Una Cola? ¿Un Árbol?
ESTRUCTURAS DE DATOS
Por: Juan Chirinos, Yordy Jiménez y Alejandro Giménez
El C贸digo Fuente PUNTEROS
MEMORIA DINAMICA
OPERADORES NEW Y DELETE
ESTRUCTURAS DE DATOS
ESTRUCTURAS DINAMICAS DE DATOS
ARBOLES BINARIOS DE BUSQUEDA (A.B.B)
LISTAS
PILAS
COLAS
ARBOLES
EJEMPLO DE UN (A.B.B.) EN C++
SITIOS WEB DE APOYO
EJEMPLO DE RECURSIVIDAD
VIDEOS DE APOYO POR TEMA
GLOSARIO DE PROGRAMACION EN C++
CONTENIDOS - ESTRUCTURAS DE DATOS EN C++
ESTRUCTURAS DE DATOS EN C++ En programación, una estructura de datos es una forma de organizar un conjunto de datos elementales con el objetivo de facilitar su manipulación.
Un dato elemental es la mínima información que se tiene en un sistema. Una estructura de datos define la organización e interrelación de éstos y un conjunto de operaciones que se pueden realizar sobre ellos.
ESTRUCTURAS DE DATOS EN C++
¿QUE SON LOS PUNTEROS? Las variables de tipo puntero son las que nos permiten referenciar datos dinámicos. Tenemos que diferenciar claramente entre: •
la variable referencia o apuntadora, de tipo puntero;
• la variable anónima referenciada o apuntada, de cualquier tipo, tipo que estará asociado siempre al puntero. Físicamente, un puntero no es mas que una direccion de memoria.
typedef enum luces_semaforo {Rojo, Amarillo, Verde} estado_luces; estado_luces semaforo; typedef int vector_de_20[20]; vector_de_20 mivector; Definiremos un tipo puntero con el carácter asterisco (*) y especificando siempre el tipo de la variable referenciada. Ejemplo: typedef int *PtrInt; // puntero a enteros PtrInt p;
Typedef:
// puntero a enteros O bien directamente:
Antes de definir los punteros, vamos a recordar como puede darse nombre a tipos de datos propios utilizando la palabra reservada typedef. El uso de typedef (definición de tipo) en C++ permite definir un nombre par un tipo de datos en C++. La declaración de typedef es similar a la declaración de una variable.
int *p;
La forma es: typedef tipo nuevo-tipo; EJEMPLOS: typedef char LETRA; LETRA caracter;
// puntero a enteros Cuando p este apuntando a un entero de valor -13. , gráficamente lo representaremos así:
Para acceder a la variable apuntada hay que hacerlo a través de la variable puntero, ya que aquella no tiene nombre (por eso es anónima). La forma de denotarla es *p. En el ejemplo *p = -13 (y p = dirección de memoria de la celda con el valor -13, dirección que no necesitamos tratar directamente).
P
-13
ESTRUCTURAS DE DATOS EN C++
¿QUE ES MEMORIA DINAMICA?
Datos estáticos y dinámicos Datos estáticos: su tamaño y forma es constante durante la ejecución de un programa y por tanto se determinan en tiempo de compilación. El ejemplo típico son los arrays. Tienen el problema de que hay que dimensionar la estructura de antemano, lo que puede conllevar desperdicio o falta de memoria. Datos dinámicos: su tamaño y forma es variable (o puede serlo) a lo largo de un programa, por lo que se crean y destruyen en tiempo de ejecución. Esto permite dimensionar la estructura de datos de una forma precisa: se va asignando memoria en tiempo de ejecución según se va necesitando. Cuando el sistema operativo carga un programa para ejecutarlo y lo convierte en proceso, le asigna cuatro partes lógicas en memoria principal: TEXTO, DATOS (ESTÁTICOS), PILA Y UNA ZONA LIBRE, Esta zona libre (o heap) es la que va a contener los datos dinámicos, la cual, a su vez, en cada instante de la ejecución tendrá partes asignadas a los mismos y partes libres que fragmentaran esta zona, siendo posible que se agote si no se liberan las partes utilizadas ya inservibles. Hasta ahora hemos visto que cada vez que queremos usar una variable debemos reservarle un lugar de la memoria al comenzar el programa. Debemos indicar al compilador cuánta memoria Vamos a usar.
Por ejemplo, si hacemos la declaración: disco mis_cds[40]; El compilador reserva espacio para 40 cds. Si en realidad solo queremos guardar datos para 2 cds, se desperdicia mucha memoria. Si me encanta la música, dicho array se me quedará pequeño. Por tanto, hay ocasiones en las que no sabemos cuánta memoria vamos a necesitar hasta que no se ejecuta el programa. C++ permite poder reservar memoria según se va necesitando, es decir, en tiempo de ejecución. Podremos reservar memoria para almacenar 2 cds, o reservar memoria para 100 cds si el usuario que ejecuta la aplicación es un adicto a la música: esto lo sabremos en el momento en que se ejecuta el programa, no antes. VENTAJA DE UTILIZAR MEMORIA DINÁMICA: Los programas aprovecharán mejor la memoria del ordenador en el que se ejecuten, usando sólo lo necesario
ESTRUCTURAS DE DATOS EN C++
NEW Y DELETE OPERADORES NEW Y DELETE :
Operador DELETE:
Operador NEW:
El operador delete se usa para liberar la memoria dinámica reservada con new.
El operador new sirve para reservar memoria dinámica. Sintaxis: [::]new [<emplazamiento>] <tipo> [(<inicialización>)] [::]new [<emplazamiento>] (<tipo>) [(<inicialización>)] [::]new [<emplazamiento>] <tipo>[<número_elementos>] [::]new [<emplazamiento>] (<tipo>)[<número_elementos>]
Sintaxis:
La inicialización, si aparece, se usará para asignar valores iniciales a la memoria reservada con new, pero no puede ser usada con arrays.
[::]delete [<expresión>] [::]delete[] [<expresión>] La expresión será normalmente un puntero, el operador delete[] se usa para liberar memoria de arraysdinámicos. Es importante liberar siempre usando delete la memoria reservada con new. Existe el peligro de pérdida de memoria si se ignora esta regla. Cuando se usa el operador delete con un puntero nulo, no se realiza ninguna acción. Esto permite usar el operador delete con punteros sin necesidad de preguntar si es nulo antes.
Las formas tercera y cuarta se usan para reservar memoria para arrays dinámicos. La memoria reservada con new será válida hasta que se libere con delete o hasta el fin del programa, aunque es aconsejable liberar siempre la memoria reservada con new usando delete.
De todos modos, es buena idea asignar el valor 0 a los punteros que no han sido inicializados y a los que han sido liberados. También es bueno preguntar si un puntero es nulo antes de intentar liberar la memoria dinámica que le fue asignada.
Se considera una práctica muy sospechosa no hacerlo. Si la reserva de memoria no tuvo éxito, new devuelve un puntero nulo, NULL.
Nota: los operadores new y delete son propios de C++. En C se usan funciones, como malloc y free para reservar y liberar memoria dinámica y liberar un puntero nulo con free suele tener consecuencias desastrosas.
El operador opcional :: está relacionado con la sobrecarga de operadores, de momento no lo usaremos. Lo mismo se aplica a emplazamiento.
ESTRUCTURAS DE DATOS EN C++
NEW Y DELETE NOTA: VEAMOS ALGUNOS EJEMPLOS: int main() { char *c; int *i = NULL; float **f; int n;
}
// Cadena de 122 más el nulo: c = new char[123]; // Array de 10 punteros a float: f = new float *[10]; (1) // Cada elemento del array es un array de 10 float for(n = 0; n < 10; n++) f[n] = new float[10]; (2) // f es un array de 10*10 f[0][0] = 10.32; f[9][9] = 21.39; c[0] = 'a'; c[1] = 0; // liberar memoria dinámica for(n = 0; n < 10; n++) delete[] f[n]; delete[] f; delete[] c; delete i; return 0;
f es un puntero que apunta a un puntero que a su vez apunta a un float. Un puntero puede apuntar a cualquier tipo de variable, incluidos otros punteros. Este ejemplo nos permite crear arrays dinámicos de dos dimensiones. La línea (1) crea un array de 10 punteros a float. La (2) crea 10 arrays de float. El comportamiento final de f es el mismo que si lo hubiéramos declarado como: float f[10][10]; OTRO EJEMPLO: #include <iostream> using namespace std; int main() { int *x;
}
x = new int(67); cout << *x << endl; delete x;
En este caso, reservamos memoria para un entero, se asigna la dirección de la memoria obtenida al puntero x, y además, se asigna el valor 67 al contenido de esa memoria.
ESTRUCTURAS DE DATOS EN C++
ESTRUCTURAS DE DATOS Una estructura básica de un nodo para crear listas de datos seria: ESTRUCTURAS DE DATOS Las estructuras de datos en C++ se pueden entender como un tipo de dato compuesto (no complejo). Las estructuras de datos permiten almacenar de manera ordenada una serie de valores dados en una misma variable. Las estructuras de datos más comunes son los vectores o arreglos y las matrices, aunque hay otras un poco más diferentes como son el struct y las enumeraciones. Las estructuras de datos están compuestas de otras pequeñas estructuras a las que llamaremos nodos o elementos, que agrupan los datos con los que trabajará nuestro programa y además uno o más punteros auto referenciales, es decir, punteros a objetos del mismo tipo nodo.
struct nodo { int dato; struct nodo *otronodo; }; El campo "otronodo" puede apuntar a un objeto del tipo nodo. De este modo, cada nodo puede usarse como un ladrillo para construir listas de datos, y cada uno mantendrá ciertas relaciones con otros nodos. Para acceder a un nodo de la estructura sólo necesitaremos un puntero a un nodo. EL NODO ANTERIOR SE REPRESENTARÁ ASÍ:
DATO
ESTRUCTURAS DE DATOS EN C++
ESTRUCTURAS DINAMICAS ESTRUCTURAS DINAMICAS Las estructuras dinámicas nos permiten crear estructuras de datos que se adapten a las necesidades reales a las que suelen enfrentarse nuestros programas. Pero no sólo eso, como veremos, también nos permitirán crear estructuras de datos muy flexibles, ya sea en cuanto al orden, la estructura interna o las relaciones entre los elementos que las componen.
COLAS: otro tipo de listas, conocidas como listas FIFO (First In, First Out: El primero en entrar es el primero en salir). Los elementos se almacenan en fila, pero sólo pueden añadirse por un extremo y leerse por el otro. LISTAS CIRCULARES: o listas cerradas, son parecidas a las listas abiertas, pero el último elemento apunta al primero. De hecho, en las listas circulares no puede hablarse de "primero" ni de "último". Cualquier nodo puede ser el nodo de entrada y salida.
Las estructuras dinámicas son una implementación de TDAs o TADs (Tipos Abstractos de Datos). En estos tipos el interés se centra más en la estructura de los datos que en el tipo concreto de información que almacenan.
LISTAS DOBLEMENTE ENLAZADAS: cada elemento dispone de dos punteros, uno a punta al siguiente elemento y el otro al elemento anterior. Al contrario que las listas abiertas anteriores, estas listas pueden recorrerse en los dos sentidos.
Dependiendo del número de punteros y de las relaciones entre nodos, podemos distinguir varios tipos de estructuras dinámicas.
ARBOLES: cada elemento dispone de dos o más punteros, pero las referencias nunca son a elementos anteriores, de modo que la estructura se ramifica y crece igual que un árbol. Arboles binarios: son árboles donde cada nodo sólo puede apuntar a dos nodos.
ENUMERAREMOS AHORA SÓLO DE LOS TIPOS BÁSICOS: LISTAS ABIERTAS: cada elemento sólo dispone de un puntero, que apuntará al siguiente elemento de la lista o valdrá NULL si es el último elemento. PILAS: son un tipo especial de lista, conocidas como listas LIFO (Last In, First Out: el último en entrar es el primero en salir). Los elementos se "amontonan" o apilan, de modo que sólo el elemento que está encima de la pila puede ser leído, y sólo pueden añadirse elementos encima de la pila.
ARBOLES BINARIOS DE BÚSQUEDA (ABB): son árboles binarios ordenados. Desde cada nodo todos los nodos de una rama serán mayores, según la norma que se haya seguido para ordenar el árbol, y los de la otra rama serán menores. ARBOLES AVL: son también árboles de búsqueda, pero su estructura está más optimizada para reducir los tiempos de búsqueda. ARBOLES B: son estructuras más complejas, aunque también se trata de árboles de búsqueda, están mucho más optimizados que los anteriores.
ESTRUCTURAS DE DATOS EN C++
LISTAS LISTA: La forma más simple de estructura dinámica es la lista abierta. En esta forma los nodos se organizan de modo que cada uno apunta al siguiente, y el último no apunta a nada, es decir, el puntero del nodo siguiente vale NULL. En las listas abiertas existe un nodo especial: el primero. Normalmente diremos que nuestra lista es un puntero a ese primer nodo y llamaremos a ese nodo la cabeza de la lista. Eso es porque mediante ese único puntero podemos acceder a toda la lista. Cuando el puntero que usamos para acceder a la lista vale NULL, diremos que la lista está vacía. El nodo típico para construir listas tiene esta forma: struct nodo { int dato; struct nodo *siguiente; };
ESTRUCTURAS DE DATOS EN C++
PILAS PILA: Las pilas son un tipo de estructura de datos de tipo lineal condicionadas donde las inserciones y eliminaciones se realizan por un mismo extremo. Se tratan los temas relacionados con los conceptos básicos de las pilas, las operaciones que se pueden realizar con las pilas, todo conjugado en programas de aplicación, implementados con apuntadores, al igual que en los anteriores capítulos, cada uno de los programas aquí presentados, están previamente compilados y depurados de tal manera que se muestra la salida en pantalla de cada uno.
Estas operaciones se conocen como "push" y "pop", respectivamente "empujar" y "tirar". Además, las escrituras de datos siempre son inserciones de nodos, y las lecturas siempre eliminan el nodo leído.
Estas características implican un comportamiento de lista LIFO (Last In First Out), el último en entrar es el primero en salir. El símil del que deriva el nombre de la estructura es una pila de platos. Sólo es posible añadir platos en la parte superior de la pila, y sólo pueden tomarse del mismo extremo.
Lo anterior garantiza al estudiante que puede guiarse en el código fuente para hacerle modificaciones y proponer soluciones a entornos reales. Una pila es un tipo especial de lista abierta en la que sólo se pueden insertar y eliminar nodos en uno de los extremos de la lista.
ESTRUCTURAS DE DATOS EN C++
COLAS COLA: Una cola es un tipo especial de lista abierta en la que sólo se pueden insertar nodos en uno de los extremos de la lista y sólo se pueden eliminar nodos en el otro. Además, como sucede con las pilas, las escrituras de datos siempre son inserciones de nodos, y las lecturas siempre eliminan el nodo leído. Este tipo de lista es conocido como lista FIFO (First In First Out), el primero en entrar es el primero en salir. El símil cotidiano es una cola para comprar, por ejemplo, las entradas del cine. Los nuevos compradores sólo pueden colocarse al final de la cola, y sólo el primero de la cola puede comprar la entrada. El nodo típico para construir pilas es el mismo que vimos en los capítulos anteriores para la construcción de listas y pilas: struct nodo { int dato; struct nodo *siguiente; };
DECLARACIONES DE TIPOS PARA MANEJAR COLAS EN C: Los tipos que definiremos normalmente para manejar colas serán casi los mismos que para manejar listas y pilas, tan sólo cambiaremos algunos nombres: typedef struct _nodo { int dato; struct _nodo *siguiente; } tipoNodo; typedef tipoNodo *pNodo; typedef tipoNodo *Cola; tipoNodo es el tipo para declarar nodos, evidentemente. pNodo es el tipo para declarar punteros a un nodo. Cola es el tipo para declarar colas.
ESTRUCTURAS DE DATOS EN C++
COLAS Es evidente, a la vista del gráfico, que una cola es una lista abierta. Así que sigue siendo muy importante que nuestro programa nunca pierda el valor del puntero al primer elemento, igual que pasa con las listas abiertas. Además, debido al funcionamiento de las colas, también deberemos mantener un puntero para el último elemento de la cola, que será el punto donde insertemos nuevos nodos. Teniendo en cuenta que las lecturas y escrituras en una cola se hacen siempre en extremos distintos, lo más fácil será insertar nodos por el final, a continuación del nodo que no tiene nodo siguiente, y leerlos desde el principio, hay que recordar que leer un nodo implica eliminarlo de la cola.
ESTRUCTURAS DE DATOS EN C++
ARBOLES ARBOL: Un árbol es una estructura no lineal en la que cada nodo puede apuntar a uno o varios nodos. También se suele dar una definición recursiva: un árbol es una estructura en compuesta por un dato y varios árboles. Esto son definiciones simples. Pero las características que implican no lo son tanto.
NODO PADRE: nodo que contiene un puntero al nodo actual. En el ejemplo, el nodo 'A' es padre de 'B', 'C' y 'D'. LOS ÁRBOLES CON LOS QUE TRABAJAREMOS TIENEN OTRA CARACTERÍSTICA IMPORTANTE: cada nodo sólo puede ser apuntado por otro nodo, es decir, cada nodo sólo tendrá un padre. Esto hace que estos árboles estén fuertemente jerarquizados, y es lo que en realidad les da la apariencia de árboles. EN CUANTO A LA POSICIÓN DENTRO DEL ÁRBOL: NODO RAÍZ: nodo que no tiene padre. Este es el nodo que usaremos para referirnos al árbol. En el ejemplo, ese nodo es el 'A'. NODO HOJA: nodo que no tiene hijos. En el ejemplo hay varios: 'F', 'H', 'I', 'K', 'L', 'M', 'N' y 'O'. NODO RAMA: aunque esta definición apenas la usaremos, estos son los nodos que no pertenecen a ninguna de las dos categorías anteriores. En el ejemplo: 'B', 'C', 'D', 'E', 'G' y 'J'.
DEFINIREMOS VARIOS CONCEPTOS. EN RELACIÓN CON OTROS NODOS: NODO HIJO: cualquiera de los nodos apuntados por uno de los nodos del árbol. En el ejemplo, 'L' y 'M' son hijos de 'G'.
Otra característica que normalmente tendrán nuestros árboles es que todos los nodos contengan el mismo número de punteros, es decir, usaremos la misma estructura para todos los nodos del árbol. Esto hace que la estructura sea más sencilla, y por lo tanto también los programas para trabajar con ellos.
ESTRUCTURAS DE DATOS EN C++
ARBOLES Tampoco es necesario que todos los nodos hijos de un nodo concreto existan. Es decir, que pueden usarse todos, algunos o ninguno de los punteros de cada nodo. Un árbol en el que en cada nodo o bien todos o ninguno de los hijos existe, se llama árbol completo. En una cosa, los árboles se parecen al resto de las estructuras que hemos visto: dado un nodo cualquiera de la estructura, podemos considerarlo como una estructura independiente. Es decir, un nodo cualquiera puede ser considerado como la raíz de un árbol Completo. EXISTEN OTROS CONCEPTOS QUE DEFINEN LAS CARACTERÍSTICAS DEL ÁRBOL, EN RELACIÓN A SU TAMAÑO: Orden: es el número potencial de hijos que puede tener cada elemento de árbol. De este modo, diremos que un árbol en el que cada nodo puede apuntar a otros dos es de orden dos, si puede apuntar a tres será de orden tres, etc. Grado: el número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol del ejemplo, el grado es tres, ya que tanto 'A' como 'D' tienen tres hijos, y no existen elementos con más de tres hijos.
Altura: la altura de un árbol se define como el nivel del nodo de mayor nivel. Como cada nodo de un árbol puede considerarse a su vez como la raíz de un árbol, también podemos hablar de altura de ramas. El árbol del ejemplo tiene altura 3, la rama 'B' tiene altura 2, la rama 'G' tiene altura 1, la 'H' cero, etc. Los árboles de orden dos son bastante especiales, de hecho les dedicaremos varios capítulos. Estos árboles se conocen también como árboles binarios. Frecuentemente, aunque tampoco es estrictamente necesario, para hacer más fácil moverse a través del árbol, añadiremos un puntero a cada nodo que apunte al nodo padre. De este modo podremos avanzar en dirección a la raíz, y no sólo hacia las hojas. Es importante conservar siempre el nodo raíz ya que es el nodo a partir del cual se desarrolla el árbol, si perdemos este nodo, perderemos el acceso a todo el árbol.
Nivel: se define para cada elemento del árbol como la distancia a la raíz, medida en nodos. El nivel de la raíz es cero y el de sus hijos uno. Así sucesivamente. En el ejemplo, el nodo 'D' tiene nivel 1, el nodo 'G' tiene nivel 2, y el nodo 'N', nivel 3.
ESTRUCTURAS DE DATOS EN C++
ARBOLES El nodo típico de un árbol difiere de los nodos que hemos visto hasta ahora para listas, aunque sólo en el número de nodos. Veamos un ejemplo de nodo para crear árboles de orden tres: struct nodo { int dato; struct nodo *rama1; struct nodo *rama2; struct nodo *rama3; };
O generalizando más:
typedef struct _nodo { int dato; struct _nodo *rama[ORDEN]; } tipoNodo; typedef tipoNodo *pNodo; typedef tipoNodo *Arbol; Al igual que hicimos con las listas que hemos visto hasta ahora, declaramos un tipo tipoNodo para declarar nodos, y un tipo pNodo para es el tipo para declarar punteros a un nodo. Árbol es el tipo para declarar árboles de orden ORDEN.
#define ORDEN 5 struct nodo { int dato; struct nodo *rama[ORDEN]; }; DECLARACIONES DE TIPOS PARA MANEJAR ÁRBOLES EN C: Para C, y basándonos en la declaración de nodo que hemos visto más arriba, trabajaremos con los siguientes tipos:
ESTRUCTURAS DE DATOS EN C++
ARBOLES BINARIOS DE BUSQUEDA El movimiento a través de árboles, salvo que implementemos punteros al nodo padre, será siempre partiendo del nodo raíz hacia un nodo hoja. Cada vez que lleguemos a un nuevo nodo podremos optar por cualquiera de los nodos a los que apunta para avanzar al siguiente nodo. En general, intentaremos que exista algún significado asociado a cada uno de los punteros dentro de cada nodo, los árboles que estamos viendo son abstractos, pero las aplicaciones no tienen por qué serlo. Un ejemplo de estructura en árbol es el sistema de directorios y ficheros de un sistema operativo. Aunque en este caso se trata de árboles con nodos de dos tipos, nodos directorio y nodos fichero, podríamos considerar que los nodos hoja son ficheros y los nodos rama son directorios.
ÁRBOLES BINARIOS DE BÚSQUEDA (ABB): Definición Se trata de árboles de orden 2 en los que se cumple que para cada nodo, el valor de la clave de la raíz del subárbol izquierdo es menor que el valor de la clave del nodo y que el valor de la clave raíz del subárbol derecho es mayor que el valor de la clave del nodo.
Otro ejemplo podría ser la tabla de contenido de un libro, por ejemplo de este mismo curso, dividido en capítulos, y cada uno de ellos en subcapítulos. Aunque el libro sea algo lineal, como una lista, en el que cada capítulo sigue al anterior, también es posible acceder a cualquier punto de él a través de la tabla de contenido. También se suelen organizar en forma de árbol los organigramas de mando en empresas o en el ejército, y los árboles genealógicos.
ESTRUCTURAS DE DATOS EN C++
ARBOLES BINARIOS DE BUSQUEDA OPERACIONES EN ABB El repertorio de operaciones que se pueden realizar sobre un ABB es parecido al que realizábamos sobre otras estructuras de datos, más alguna otra propia de árboles: • BUSCAR UN ELEMENTO. • INSERTAR UN ELEMENTO. • BORRAR UN ELEMENTO.
• MOVIMIENTOS A TRAVÉS DEL ÁRBOL: IZQUIERDA. DERECHA. RAÍZ.
• INFORMACIÓN: COMPROBAR SI UN ÁRBOL ESTÁ VACÍO. CALCULAR EL NÚMERO DE NODOS. COMPROBAR SI EL NODO ES HOJA.
BUSCAR UN ELEMENTO: Partiendo siempre del nodo raíz, el modo de buscar un elemento se define de forma recursiva. Si el árbol está vacío, terminamos la búsqueda: el elemento no está en el árbol. Si el valor del nodo raíz es igual que el del elemento que buscamos, terminamos la búsqueda con éxito. Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la búsqueda en el árbol izquierdo. Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la búsqueda en el árbol derecho. El valor de retorno de una función de búsqueda en un ABB puede ser un puntero al nodo encontrado, o NULL, si no se ha encontrado.
CALCULAR LA ALTURA DE UN NODO. CALCULAR LA ALTURA DE UN ÁRBOL.
ESTRUCTURAS DE DATOS EN C++
ARBOLES BINARIOS DE BUSQUEDA INSERTAR UN ELEMENTO: Para insertar un elemento nos basamos en el algoritmo de búsqueda. Si el elemento está en el árbol no lo insertaremos. Si no lo está, lo insertaremos a continuación del último nodo visitado.
Si nodo no es NULL, el elemento está en el árbol, por lo tanto salimos.
Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raíz actual. El valor inicial para ese puntero es NULL.
Si Padre es NULL, el árbol estaba vacío, por lo tanto, el nuevo árbol sólo contendrá el nuevo elemento, que será la raíz del árbol.
Padre = NULL; nodo = Raiz; Bucle: mientras actual no sea un árbol vacío o hasta que se encuentre el elemento.
Si el elemento es menor que el Padre, entonces insertamos el nuevo elemento como un nuevo árbol izquierdo de Padre. Si el elemento es mayor que el Padre, entonces insertamos el nuevo elemento como un nuevo árbol derecho de Padre.
Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la búsqueda en el árbol izquierdo: Padre=nodo, nodo=nodo->izquierdo. Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la búsqueda en el árbol derecho: Padre=nodo, nodo=nodo->derecho.
ESTRUCTURAS DE DATOS EN C++
ARBOLES BINARIOS DE BUSQUEDA EL NODO RAÍZ ES UN NODO HOJA: BORRAR UN ELEMENTO: PARA BORRAR UN ELEMENTO TAMBIÉN NOS BASAMOS EN EL ALGORITMO DE BÚSQUEDA. SI EL ELEMENTO NO ESTÁ EN EL ÁRBOL NO LO PODREMOS BORRAR. SI ESTÁ, HAY DOS CASOS POSIBLES: • SE TRATA DE UN NODO HOJA: en ese caso lo borraremos directamente. • SE TRATA DE UN NODO RAMA: en ese caso no podemos eliminarlo, puesto que perderíamos todos los elementos del árbol de que el nodo actual es padre. En su lugar buscamos el nodo más a la izquierda del subárbol derecho, o el más a la derecha del subárbol izquierdo e intercambiamos sus valores. A continuación eliminamos el nodo hoja. Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raíz actual. El valor inicial para ese puntero es NULL. Padre = NULL • SI EL ÁRBOL ESTÁ VACÍO: el elemento no está en el árbol, por lo tanto salimos sin eliminar ningún elemento. SI EL VALOR DEL NODO RAÍZ ES IGUAL QUE EL DEL ELEMENTO QUE BUSCAMOS, ESTAMOS ANTE UNO DE LOS SIGUIENTES CASOS:
• Si 'Padre' es NULL, el nodo raíz es el único del árbol, por lo tanto el puntero al árbol debe ser NULL. • Si raíz es la rama derecha de 'Padre', hacemos que esa rama apunte a NULL. • Si raíz es la rama izquierda de 'Padre', hacemos que esa rama apunte a NULL. • Eliminamos el nodo, y salimos. EL NODO NO ES UN NODO HOJA: • Buscamos el 'nodo' más a la izquierda del árbol derecho de raíz o el más a la derecha del árbol izquierdo. Hay que tener en cuenta que puede que sólo exista uno de esos árboles. Al mismo tiempo, actualizamos 'Padre' para que apunte al padre de 'nodo'. • Intercambiamos los elementos de los nodos raíz y 'nodo'.
• Borramos el nodo 'nodo'. Esto significa volver a (1), ya que puede suceder que 'nodo' no sea un nodo hoja. (Ver ejemplo 3) • Si el valor del nodo raíz es mayor que el elemento que buscamos, continuaremos la búsqueda en el árbol izquierdo. • Si el valor del nodo raíz es menor que el elemento que buscamos, continuaremos la búsqueda en el árbol derecho.
ESTRUCTURAS DE DATOS EN C++
ARBOLES BINARIOS DE BUSQUEDA EJEMPLO 1: BORRAR UN NODO HOJA: En el árbol de ejemplo, borrar el nodo 3. • Localizamos el nodo a borrar, al tiempo que mantenemos un puntero a 'Padre'. • Hacemos que el puntero de 'Padre' que apuntaba a 'nodo', ahora apunte a NULL. • Borramos el 'nodo'. EJEMPLO 2: BORRAR UN NODO RAMA CON INTERCAMBIO DE UN NODO HOJA. En el árbol de ejemplo, borrar el nodo 4. • Localizamos el nodo a borrar ('raíz'). • Buscamos el nodo más a la derecha del árbol izquierdo de 'raíz', en este caso el 3, al tiempo que mantenemos un puntero a 'Padre' a 'nodo'. • Intercambiamos los elementos 3 y 4. • Hacemos que el puntero de 'Padre' que apuntaba a 'nodo', ahora apunte a NULL. • Borramos el 'nodo'.
ESTE MODO DE ACTUAR ASEGURA QUE EL ÁRBOL SIGUE SIENDO ABB
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ #include <iostream> #include <stdlib.h> using namespace std;
/*--------- Estructura del arbol -------*/ typedef struct nodo{ int nro;
/* ---------- Estructura de la cola --------*/ struct nodoCola{ ABB ptr; struct nodoCola *sgte; }; struct cola{ struct nodoCola *delante;
struct nodo *izq, *der; }*ABB;
struct nodoCola *atras; };
int numNodos = 0; // nummero de nodos del arbol ABB int numK = 0, k; // nodos menores que un numero k ingresado
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ void inicializaCola(struct cola &q) {
ABB desencola(struct cola &q) { struct nodoCola *p;
q.delante = NULL;
p = q.delante;
q.atras = NULL;
ABB n = p->ptr;
}
q.delante = (q.delante)->sgte;
void encola(struct cola &q, ABB n)
delete(p);
{
struct nodoCola *p; p = new(struct nodoCola); p->ptr = n; p->sgte = NULL;
return n; } ABB crearNodo(int x) { ABB nuevoNodo = new(struct nodo);
if(q.delante==NULL)
nuevoNodo->nro = x;
q.delante = p;
nuevoNodo->izq = NULL;
else
nuevoNodo->der = NULL;
(q.atras)->sgte = p;
q.atras = p; }
return nuevoNodo; }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ void preOrden(ABB arbol) { if(arbol!=NULL)
void insertar(ABB &arbol, int x)
{
{
cout << arbol->nro <<" ";
if(arbol==NULL)
preOrden(arbol->izq);
{
preOrden(arbol->der);
arbol = crearNodo(x); cout<<"\n\t Insertado..!"<<endl<<endl;
} else if(x < arbol->nro) insertar(arbol->izq, x);
} } void inOrden(ABB arbol) { if(arbol!=NULL)
else if(x > arbol->nro)
{
insertar(arbol->der, x);
inOrden(arbol->izq);
}
cout << arbol->nro << " "; inOrden(arbol->der); } }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ void verArbol(ABB arbol, int n) { if(arbol==NULL)
void postOrden(ABB arbol)
return;
{
verArbol(arbol->der, n+1);
if(arbol!=NULL) {
for(int i=0; i<n; i++)
inOrden(arbol->izq);
cout<<"
inOrden(arbol->der); cout << arbol->nro << " ";
";
numNodos++;
}
cout<< arbol->nro <<endl;
}
verArbol(arbol->izq, n+1); }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ bool busquedaRec(ABB arbol, int dato)
int alturaAB(ABB arbol)
{
{ int r=0;
// 0 indica que lo encontre
int AltIzq, AltDer;
if(arbol==NULL)
if(arbol==NULL)
return r;
return -1; else
if(dato<arbol->nro)
{
r = busquedaRec(arbol->izq, dato);
AltIzq = alturaAB(arbol->izq); AltDer = alturaAB(arbol->der);
else if(dato> arbol->nro) r = busquedaRec(arbol->der, dato);
if(AltIzq>AltDer) return AltIzq+1;
else r = 1;
else // son iguales, lo encontre
return AltDer+1; }
return r;
}
}
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ void recorrerxNivel(ABB arbol)
ABB arbolEspejo(ABB arbol)
{
{
struct cola q; inicializaCola(q);
ABB temp;
cout << "\t"; if(arbol!=NULL)
if(arbol!=NULL)
{
{ encola(q, arbol);
temp = arbol->izq;
while(q.delante!=NULL)
arbol->izq = arbolEspejo(arbol->der);
{
arbol->der = arbolEspejo(temp); arbol = desencola(q);
}
cout << arbol->nro << ' ';
return arbol;
if(arbol->izq!=NULL)
}
encola(q, arbol->izq); if(arbol->der!=NULL) encola(q, arbol->der); } } }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++
void menu()
cout << "\t [6] Recorrido por nieveles (Amplitud) \n";
{ //system("cls"); cout << "\n\t\t ..[ ARBOL BINARIO DE BUSQUEDA ].. \n\n"; cout << "\t [1] Insertar elemento cout << "\t [2] Mostrar arbol cout << "\t [3] Recorridos de profundiad
\n"; \n";
cout << "\t [7] Altura del arbol
\n";
cout << "\t [8] Construir arbol reflejo
\n";
cout << "\t [9] Contar nodos
\n";
cout << "\t [x] Contar hojas
\n";
cout << "\t [10] SALIR
\n";
\n";
cout << "\t [4] Buscar elemento
\n";
cout << "\t [5] Eliminar elemento
\n";
cout << "\n\t Ingrese opcion : "; }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ int contarHojas(ABB arbol) { if (arbol==NULL) { return 0;
void menu2() { //system("cls");
}
cout << endl;
if ((arbol->der ==NULL)&&(arbol->izq ==NULL)) { return 1; }
cout << "\t [1] En Orden
\n";
cout << "\t [2] Pre Orden
\n";
cout << "\t [3] Post Orden
\n";
cout << "\n\t
else
// para limpiar pantalla
Opcion : ";
}
{ return contarHojas(arbol->izq) + contarHojas(arbol-
>der); }
}
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ int main()
switch(op)
{
{ ABB arbol = NULL;
case 1:
int x;
cin>> x;
int op, op2;
cout << " Ingrese valor : "; insertar( arbol, x); break;
//system("color f9"); do
// poner color a la consola
case 2: verArbol(arbol, 0);
{ menu(); cin>> op;
break;
cout << endl;
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ case 3:
case 4: menu2(); cin>> op2; if(arbol!=NULL) { switch(op2) { case 1: inOrden(arbol); break; case 2: preOrden(arbol); break; case 3: postOrden(arbol); break; }
bool band;
cout<<" Valor a buscar: "; cin>> x;
band = busquedaRec(arbol,x);
if(band==1) cout << "\n\tEncontrado..."; else cout << "\n\tNo encontrado..."; break;
} else cout << "\n\t Arbol vacio..!"; break;
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ case 5:
cout<<" Valor a eliminar: "; cin>> x; elimina(arbol, x); cout << "\n\tEliminado..!"; break;
case 6: amplitud\n\n";
cout<<"\n\n Mostrando recorrido por
case 8: ABB espejo; espejo = NULL;
cout << "\n\n Arbol incial \n\n";
verArbol(arbol, 0);
recorrerxNivel(arbol); break;
case 7:
cout << "\n\n Arbol espejo \n\n";
espejo = arbolEspejo(arbol);
int h; h = alturaAB(arbol);
verArbol(espejo, 0);
cout << " La altura es : "<< h << endl;
break;
break;
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ case 9: verArbol(arbol, 0); cout << "\n\n El numero de nodos es : "; cout << numNodos; break;
case 10: exit(0); }
cout<<"\n\n\n"; //system("pause"); // hacer pausa y presionar una tecla para continuar }while(op!=11); }
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA INSERTAR UN NUEVO NODO AL ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA VISUALIZAR LOS NODOS DE UN ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA REALIZAR RECORRIDOS DE PROFUNDIDAD AL ARBOL EN PRE-ORDEN:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA REALIZAR RECORRIDOS DE PROFUNDIDAD AL ARBOL EN IN-ORDEN :
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA REALIZAR RECORRIDOS DE PROFUNDIDAD AL ARBOL EN POST-ORDEN:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA REALIZAR LA BUSQUEDA DE UN NUMERO DADO EN EL ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA ELIMINAR UN NODO DEL ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA CALCULAR LA ALTURA DEL ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA CONSTRUIR UN ARBOL REFLEJO DEL ARBOL ORIGINAL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ COMPILANDO Y EJECUTANDO EL PROGRAMA APRECIAMOS ESTO: PARA CONTAR LOS NODOS QUE HAY EN EL ARBOL:
ESTRUCTURAS DE DATOS EN C++
EJEMPLO DE UN A.B.B. - EN C++ PARA DESCARGAR EL CODIGO COMPLETO DEL EJEMPLO ANTERIOR DAR CLICK AQUI
PARA DESCARGAR EL ARCHIVO EJECUTABLE (.EXE) DEL EJEMPLO ANTERIOR DAR CLICK AQUI
ESTRUCTURAS DE DATOS EN C++
ARBOLES – EJEMPLO DE RECURSIVIDAD /** Crea un arbol binario y lo recorre en * preorden, inorden, y en postOrden utilizando recursividad en sus funciones*/ #include <iostream>
/* inserta un nodo dentro del árbol */
#include <stdlib.h>
P *crear(int valor){
#include <stdio.h> #include <TIME.h>
P *nuevo;
using namespace std;
nuevo= new(struct nodo);
/* ESTRUCTURA DEL ARBOL */
nuevo->dato=valor;
struct nodo{
nuevo->derecho=NULL; nuevo->izquierdo=NULL; int dato; struct nodo *derecho; struct nodo *izquierdo;
return nuevo; }
}; /* con typedef le asignamos un sinónimo a el puntero de tipo struct nodo : P */
typedef struct nodo P;
ESTRUCTURAS DE DATOS EN C++
ARBOLES – EJEMPLO DE RECURSIVIDAD /* ESTA FUNCION ES PARA IMPRIMIR LO QUE ESTA DENTRO DEL NODO*/ P *adderecho(P *nodonuevo, P* raiz)
void imprimirNodo(P *nodo)
{
{ raiz->derecho=nodonuevo;
/* SI EL NODO ES DIFERENTE DE NULL O SI NO ESTA VACIO IMPRIME DATO, DE LO CONTRARIO DIRA “ARBOL VACIO”*/ if(nodo!=NULL)
return raiz;
{
}
P *adizquierdo(P *nodonuevo, P* raiz)
>dato<<endl;
{
}
raiz->izquierdo=nodonuevo;
else {
return raiz;
}
cout<<"dato: "<<nodo-
cout<<"arbol vacio! "<<endl; } }
ESTRUCTURAS DE DATOS EN C++
ARBOLES – EJEMPLO DE RECURSIVIDAD /* comienza el recorrido preorden del árbol */
/* comienza el recorrido inorden del árbol */
void preorden(P *raiz)
void inorden(P *raiz)
{
{
/* si el árbol no está vacío, entonces recórrelo */
/* si el árbol no está vacío, entonces recórrelo */
if (raiz!=NULL)
if (raiz!=NULL)
{
{ imprimirNodo(raiz); preorden(raiz->izquierdo);
inorden(raiz->izquierdo);
preorden(raiz->derecho);
imprimirNodo(raiz); inorden(raiz->derecho);
} }
} }
ESTRUCTURAS DE DATOS EN C++
ARBOLES – EJEMPLO DE RECURSIVIDAD /* comienza el recorrido postOrden del árbol */
/* la función main comienza la ejecución del programa */ int main() {
void postorden(P *raiz)
P *raiz;
{
raiz=crear(1); raiz->izquierdo=crear(2); raiz->derecho=crear(3);
/* si el árbol no está vacío, entonces recórrelo */ if (raiz!=NULL) {
postorden(raiz->izquierdo); postorden(raiz->derecho); imprimirNodo(raiz); }
raiz->izquierdo->izquierdo=crear(4); raiz->derecho->izquierdo=crear(5); raiz->derecho->derecho=crear(6); /* recorre el árbol en preorden */ cout<<"preorden "<<endl; preorden(raiz); /* recorre el árbol en in inorden */ cout<<"inorden "<<endl; inorden(raiz); /* recorre el árbol en postOrden */ cout<<"postorden "<<endl; postorden(raiz);
}
system("pause"); return 0;
}
ESTRUCTURAS DE DATOS EN C++
ARBOLES – EJEMPLO DE RECURSIVIDAD La salida para el programa que acabamos de construir será la siguiente:
PARA DESCARGAR EL EJMPLO DE RECURSIVIDAD DAR CLICK AQUI
ESTRUCTURAS DE DATOS EN C++
SITIOS WEB DE APOYO PUNTEROS
LISTAS
PILAS
ARBOLES
COLAS
MEMORIA DINAMICA
EJERCICIO COMPLETO DE PILAS ARBOL BINARIO DE BUSQUEDA PAGINA CON MAS DE 20 EJEMPLOS DE LISTAS, PILAS, COLAS Y ARBOL EN C++ PARA DESCARGAR EJEMPLO DE ARBOL BINARIO DE BUSQUEDA EN C++ ESTRUCTURAS DE DATOS EN C++
VIDEOS DE APOYO POR TEMA PUNTEROS
LISTAS
PILAS
ARBOLES
COLAS
MEMORIA DINAMICA
LISTAS SIMPLEMENTE ENLAZADAS IMPLEMENTACIÓN ARBOL BINARIO EN C CON SUS RECORRIDOS
ESTRUCTURAS DE DATOS EN C++
GLOSARIO DE PROGRAMACION EN C++ Declaraci贸n de Variables Tipo
Declaraci贸n
Entero
Int A;
Entero Corto
Short Int A;
Entero Largo
Long Int A;
Entero sin Signo
Unsigned Int A;
Entero con Signo
Signed Int A;
Real
Float A;
Real Doble
Double A;
Real Largo
Long DoubleA;
Caracter
Char A;
Caracter sin signo
Unsigned Char A;
Caracter con signo
Signed Char A;
Palabra
Char[ ] A;
Valor Nulo
Void
Arreglo
Int A[N]
Texto
Text A;
ante Apuntador
A; *A
ESTRUCTURAS DE DATOS EN C++
GLOSARIO DE PROGRAMACION EN C++
Operandos Aritm茅ticos
Operandos Relacionales
Operandos Logicos
- Resta + Suma * Multiplicacion / Division real % Residuo = Asignacion
< Menor que > Mayor que <= Menor igual que >= Mayor igual que != Desigualdad = = Igualdad
&& Y || O Xor Bicondicional ! Negaci贸n ++ Incremento -- Decremento
ESTRUCTURAS DE DATOS EN C++
GLOSARIO DE PROGRAMACION EN C++
for(Contador = 1;Contador <= N;Contador++) { Sentencia; }
De Contador=1 hasta Contador<=N Incremento
If(Condicion = = 1) { Sentencia; } Else { Sentencia; }
Si Condicion = 1 Entonces
While(Condicion= =1) { Sentencia; }
Mientras Condicion = 1 haz
Do{ Sentencia; }(Condicion = = 1);
HazHasta Condicion = 1
Switch(Opcion) { 1: Sentencia1; break; 2: Sentencia2; break; Default: Sentencia3; break; }
En caso de OpcionSi Opcion = 1 Sentencia1 rompe Si Opcion = 2 Sentencia2 rompe Si no Sentencia3 rompe
Si no
ESTRUCTURAS DE DATOS EN C++
El Código Fuente INICIO
MUCHAS GRACIAS POR LEERNOS… ESTRUCTURAS DE DATOS EN C++