Anda di halaman 1dari 93

UNIDAD 1:

ESTRUCTURAS DE
DATOS

1.1. Definicin
En la prctica, la mayor parte de informacin til no aparece aislada en forma de datos simples, sino que lo hace de
forma organizada y estructurada. Los diccionarios, guas, enciclopedias, etc., son colecciones de datos que seran
intiles si no estuvieran organizadas de acuerdo con unas determinadas reglas. Adems, tener estructurada la
informacin supone ventajas adicionales, al facilitar el acceso y el manejo de los datos. Por ello parece razonable
desarrollar la idea de la agrupacin de datos, que tengan un cierto tipo de estructura y organizacin interna.
La seleccin de una estructura de datos frente a otra, a la hora de programar es una decisin importante, ya que ello
influye decisivamente en el algoritmo que vaya a usarse para resolver un determinado problema. De hecho, se trata de
dar una idea, acerca de los pros y contras de cada una de ellas con el propsito final de justificar la citada ecuacin de:

PROGRAMACIN = ESTRUCTURAS DE DATOS + ALGORITMOS

Una estructura de datos es, a grandes rasgos, una coleccin de datos (normalmente de tipo simple) que se
caracterizan por su organizacin y las operaciones que se definen en ellos. Por tanto, una estructura de datos vendr
caracterizada tanto por unas ciertas relaciones entre los datos que la constituyen (por ejemplo el orden de las
componentes de un vector de nmeros reales), como por las operaciones posibles en ella. Esto supone que podamos
expresar formalmente, mediante un conjunto de reglas, las relaciones y operaciones posibles (tales como insertar nuevos
elementos o como eliminar los ya existentes).
En programacin, el trmino estructura de datos se utiliza para referirse a una forma de organizar un conjunto de datos
que se relacionan entre s, sean estos simples o estructurados, con el objetivo de facilitar su manipulacin y de operarlo
como un todo.
En otras palabras es una forma particular de organizar datos en una computadora para que pueda ser utilizado de
manera eficiente.
En un lenguaje de programacin, un tipo de dato est definido por el conjunto de valores que representa y por el
conjunto de operaciones que se pueden realizar con dicho tipo de dato. Por ejemplo, el tipo de dato entero puede
representar nmeros en el rango de -2^31 a 2^31-1 y cuenta con operaciones como suma, resta, multiplicacin, divisin,
etc.
Por otro lado, podemos decir que en la solucin de un problema a ser procesado por una computadora podemos
encontrar dos grandes tipos de datos: datos simples y datos estructurados. Los datos simples son aquellos que no
estn compuestos de otras estructuras, que no sean los bits, al ser representados por la computadora en forma directa,
ocupan solo una casilla de memoria, sin embargo existen unas operaciones propias de cada tipo que en cierta manera
los caracterizan. Debido a esto, una variable de un tipo de dato simple hace referencia a un nico valor a la vez. Ejemplo
de estos tipos de datos son los enteros, reales, caracteres y booleanos.
As mismo, los datos estructurados se caracterizan porque su definicin est compuesta de otros tipos de datos
simples, as como de otros datos estructurados. En este caso, un nombre (identificador de la variable estructurada) hace
referencia no solo a una casilla de memoria, sino a un grupo de casillas. Por ejemplo: una cadena est formada por una
sucesin de caracteres; una matriz por datos simples organizados en forma de filas y columnas; y un archivo est
constituido por registros, stos por campos, que se componen, a su vez, de datos de tipo simple. Por un abuso de
lenguaje, se tiende a hacer sinnimos, el dato estructurado con su estructura correspondiente, aunque ello
evidentemente no es as.
Para muchos propsitos es conveniente tratar una estructura de datos como si fuera un objeto individual y
afortunadamente, muchos lenguajes de programacin permiten manipular estructuras completas como si se trataran de
datos individuales, de forma que los datos estructurados y simples se consideran a menudo por el programador de la
misma manera. As a partir de ahora un dato puede ser tanto un entero como una matriz, por nombrar dos ejemplos.
Las estructuras de datos son necesarias tanto en la memoria principal como en la secundaria, de forma que primero nos
centraremos en las correspondientes a la memoria principal, y posteriormente nos centraremos en las estructuras ms
adecuadas para el almacenamiento masivo de datos.

1.2. Clasificacin
Una clasificacin de estructuras de datos es segn dnde residan: Internas y externas. Si una estructura de datos reside
en la memoria central del computador se denomina estructura de datos interna. Recprocamente, si reside en un soporte
externo, se denomina estructura de datos externa.
Las estructuras de datos internas pueden ser de dos tipos:

Estructuras de Datos Estticas.


Estructuras de Datos Dinmicas.

Estructuras de Datos Estticas


Tienen un nmero fijo de elementos que queda determinado desde la declaracin de la estructura en el comienzo del
programa. Ejemplo los arreglos. Las estructuras de datos estticas, presentan dos inconvenientes:
La reorganizacin de sus elementos, si sta implica mucho movimiento puede ser muy costosa. Ejemplo: insertar un
dato en un arreglo ordenado.
Son estructuras de datos estticas, es decir, el tamao ocupado en memoria es fijo, el arreglo podra llenarse y si se
crea un arreglo de tamao grande se estara desperdiciando memoria.

Estructuras de Datos Dinmicas


Las estructuras de datos dinmicas nos permiten lograr un importante objetivo de la programacin orientada a objetos: la
reutilizacin de objetos. Al contrario de un arreglo, que contiene espacio para almacenar un nmero fijo de elementos,
una estructura dinmica de datos se ampla y contrae durante la ejecucin del programa.
A su vez, este tipo de estructuras se pueden dividir en dos grandes grupos segn la forma en la cual se ordenan sus
elementos.

Lineales.
No lineales.

Estructuras de Datos Lineales


En este tipo de estructuras los elementos se encuentran ubicados secuencialmente. Al ser dinmica, su composicin
vara a lo largo de la ejecucin del programa que lo utiliza a travs de operaciones de insercin y eliminacin.
Dependiendo del tipo de acceso a la secuencia, haremos la siguiente distincin:

Pilas: slo tienen un nico punto de acceso fijo a travs del cual se aaden, se eliminan o se consultan
elementos.
Colas: tienen dos puntos de acceso, uno para aadir y el otro para consultar o eliminar elementos.
Listas: podemos acceder (insertar y eliminar) por cualquier lado.

Estructuras de Datos No Lineales


Dentro de las estructuras de datos no lineales tenemos los rboles y grafos. Este tipo de estructuras los datos no se
encuentran ubicados secuencialmente. Permiten resolver problemas computacionales complejos.

1.3. Estructuras lineales


Las estructuras lineales de datos se caracterizan porque sus elementos estn en secuencia, relacionados en forma
lineal, uno luego del otro. Cada elemento de la estructura puede estar conformado por uno o varios sub-elementos o
campos que pueden pertenecer a cualquier tipo de dato, pero que normalmente son tipos bsicos.
Una estructura lineal de datos est conformada por ninguno, uno o varios elementos que tienen una relacin dnde
existe un primer elemento, seguido de un segundo elemento y as sucesivamente hasta llegar al ltimo.

PILAS.
Una pila se caracteriza por el hecho que el ltimo elemento en entrar es el primero en salir. En ingls una pila se suele
denominar con las siglas LIFO (last in first out). La definicin de pila nos indica que todas las operaciones trabajan sobre
el mismo extremo de la secuencia. En otras palabras, los elementos se sacan de la estructura en el orden inverso al
orden en que se han insertado, ya que el nico elemento de la secuencia que se puede obtener es el ltimo (figura 1).

Ejemplos de pilas
En nuestra vida diaria podemos ver este comportamiento muy a menudo. Por ejemplo, si apilamos los platos de una
vajilla, nicamente podremos coger el ltimo plato aadido a la pila, porque cualquier intento de coger un plato del medio
de la pila (como el plato oscuro de la figura 2) acabar en un destrozo. Otro ejemplo lo tenemos en los juegos de cartas,
en los que generalmente robamos las cartas (de una en una) de la parte superior del mazo (figura 2).

En el mundo informtico tambin encontramos pilas, como por ejemplo, en los navegadores web. Cada vez que
accedemos a una nueva pgina, el navegador la aade a una pila de pginas visitadas, de manera que cuando
seleccionamos la opcin Anterior, el navegador coge la pgina que se encuentra en la cima de la pila, porque es
justamente la ltima pgina visitada. Otro ejemplo lo tenemos en los procesadores de textos, en los que los cambios
introducidos en el texto tambin se almacenan en una pila. Cada vez que apretamos la combinacin de teclas Ctrl+Z
deshacemos el ltimo cambio introducido, mientras que cada vez que apretamos la combinacin Ctrl+Y volvemos a
aadir a la pila el ltimo cambio deshecho.

Operaciones
En la tabla 1 se encuentran las operaciones bsicas para trabajar con pilas.

Las operaciones del TAD pila se clasifican en:

Operaciones constructoras: crear, apilar y desapilar.


Operaciones consultoras: cima y vaca.

Las operaciones constructoras son las operaciones que modifican el estado de la pila, mientras que las operaciones
consultoras son las que consultan el estado de la pila sin modificarla.
El estado de una pila est definido por los elementos que contiene la pila y por el orden en que estn almacenados. Todo
estado es el resultado de una secuencia de llamadas a operaciones constructoras.
Igualmente, las operaciones constructoras se clasifican en:

Operaciones generadoras: crear y apilar, porque son imprescindibles para conseguir cualquier estado de la
pila.
Operaciones modificadoras: desapilar, porque modifica el estado de la pila extrayendo el elemento de la cima,
pero no es una operacin imprescindible para construir una pila.

Dado un estado de la pila, se puede llegar a l a travs de diversas secuencias de llamadas a operaciones constructoras,
pero de entre todas las secuencias slo habr una que est formada exclusivamente por operaciones generadoras
(figura 3).

Observar que la tercera secuencia de llamadas de la figura 3 es la mnima para llegar al estado deseado, ya que no
podemos eliminar ninguna de las llamadas que la forman. Por lo tanto, tal como se ha mencionado anteriormente, apilar
y crear son las operaciones generadoras.

COLAS.
Una cola est caracterizada por el hecho que el primer elemento en entrar es el primero en salir. Las colas se diferencian
de las pilas en la extraccin de los datos. En ingls, una cola suele denominarse con las siglas FIFO (first in first out).
La definicin de cola nos indica que las operaciones trabajan sobre ambos extremos de la secuencia: un extremo para
aadir los elementos y el otro para consultarlos o extraerlos. En otras palabras, los elementos se extraen en el mismo
orden en que se han insertado previamente, ya que se insertan por el final de la secuencia y se extraen por la cabecera
(figura 6).

Ejemplos de colas
Las colas aparecen a menudo en nuestra vida diaria... Sin ir ms lejos, podemos afirmar que pasamos una parte de
nuestra vida haciendo colas: para comprar la entrada en un cine, para pagar en la caja de un supermercado, para
visitarnos por el mdico, etc. La idea siempre es la misma: se atiende la primera persona de la cola, que es la que hace
ms rato que espera, y una vez atendida sale de la cola y la persona siguiente pasa a ser la primera de la cola (figura 7).

Si en el mundo real es habitual ver colas, en el mundo informtico todava lo es ms. Cuando el sistema operativo ha de
gestionar el acceso a un recurso compartido (procesos que quieren ejecutarse en la CPU, trabajos que se envan a una
impresora, descarga de ficheros, etc.), una de las estrategias ms utilizadas es organizar las peticiones por medio de
colas. Por ejemplo, la figura 8 nos muestra una captura de una cola de impresin en un instante dado. En este caso, la
tarea 321 se est imprimiendo porque es la primera en la cola, mientras que la tarea 326 ser la ltima en imprimirse
porque ha sido la ltima en llegar.

Operaciones
Dada la representacin de una cola, en la tabla 2 definimos las operaciones para trabajar con ella.

Las operaciones de una cola se clasifican segn su comportamiento en generadoras, modificadoras y consultoras:

Operaciones constructoras:
o Operaciones generadoras: crear y encolar.
o Operaciones modificadoras: desencolar.
Operaciones consultoras: cabeza y vaca.

El comportamiento de las operaciones desencolar y cabeza se resume en tres casos:


1) sobre una cola vaca (crear),
2) sobre una cola con un nico elemento (encolar(crear,e)) y
3) sobre una cola con ms de un elemento (encolar(c,e), donde c no es una cola vaca).

LISTAS.
Una lista est caracterizada por el hecho de que permite aadir, borrar o consultar cualquier elemento de la secuencia.
Es la estructura lineal ms flexible, hasta el punto de considerar que las pilas y colas son casos particulares de las listas.
Mientras que en las estructuras lineales anteriores hemos visto cmo las operaciones trabajaban solo con los extremos
de la secuencia, las listas nos ofrecern la posibilidad de acceder a cualquier punto de esta.
Ejemplos de listas
Tambin en este caso encontramos listas en nuestra vida cotidiana. Por ejemplo, la lista de la compra. Cuando estamos
en el supermercado generalmente eliminamos los artculos a medida que los encontramos en el recorrido que seguimos
con el carro, que no tiene porqu coincidir con el orden en que los hemos escrito en nuestra lista (figura 14).
Desde el punto de vista informtico tambin encontramos ejemplos, como los
editores de textos. Cuando escribimos un cdigo, en el fondo editamos una lista
de palabras dentro de una lista de lneas. Hablamos de listas, porque en
cualquier momento nos podemos desplazar sobre cualquier palabra del fichero
para modificarla o para insertar nuevas palabras. Hay diferentes modelos de
listas, uno de los ms habituales es el llamado lista con punto de inters. Esta
lista contiene un elemento distinguido que sirve de referencia para aplicar las
operaciones. El elemento distinguido es apuntado por el llamado punto de
inters, que puede desplazarse.
El punto de inters divide una secuencia en dos fragmentos, que a su vez tambin son secuencias. Por lo tanto, dada
una lista l cualquiera, se puede dividir en una secuencia situada a la izquierda del punto de inters (s) y otra secuencia
que va del punto de inters (elemento distinguido, e) hacia la derecha (et). Podemos representar esta unin de dos
secuencias para formar la lista l de la manera siguiente: l =< s,et >. La secuencia vaca (es decir, sin ningn elemento) se
representar con la letra l (lambda).

Operaciones
En la tabla 3 se encuentran las operaciones para trabajar con las listas.

Podemos ver que adems de las operaciones tpicas para trabajar con secuencias, esta vez se han aadido operaciones
para cambiar el elemento distinguido y as poder desplazar el punto de inters: principio y avanzar. En este caso, el
conjunto de operaciones constructoras generadoras est formado por crear, insertar y principio. Por tanto, la mnima
secuencia de llamadas que necesitaremos para obtener la lista <ho,la> es una combinacin de estas operaciones:
{insertar(insertar(principio(insertar(insertar(crear, l), a)), h), o)}
La implementacin de las listas con punto de inters se puede hacer de varias maneras:

Implementacin secuencial. Los elementos de la lista se almacenan en un vector respetando la norma que los
elementos consecutivos ocupan posiciones consecutivas. La representacin requiere tres elementos: un vector
(A), un indicador del nmero de elementos almacenados (nelem) y un indicador del punto de inters o elemento
distinguido (act).

El indicador nelem nos sirve para controlar que no sobrepasemos el nmero mximo de elementos (MAX) que
puede almacenar el vector, mientras que el indicador act apunta al elemento distinguido de la lista.

Implementacin encadenada. Los elementos se almacenan sin seguir la norma anterior, ya que cada
elemento del vector guarda la posicin en que se halla el siguiente elemento de la lista. Para romper el concepto
de secuencial, en que el sucesor de un elemento es el que ocupa la siguiente posicin del vector, introducimos
a continuacin el concepto de encadenamiento, en que cada elemento guarda la posicin en que se halla su
sucesor.

Como se puede observar en la representacin, se almacena para cada posicin del vector: el elemento (e) y un
indicador de la posicin en que se halla el siguiente elemento (suc). Este indicador se denominar, a partir de
este momento, encadenamiento.

EJERCICIOS DE APRENDIZAJE NO. 1

I.

Completa la siguiente tabla en base a las operaciones que se pueden realizar para cada una de las
estructuras lineales.
Nombre

Pilas

Colas

Listas

Operaciones

Crear

II.

Describe cada una de las operaciones que se pueden realizar en las listas.
Operacin
Crear
Insertar
Borrar
Actual
Vaca
Principio
Avanzar
Fin

III.

Descripcin

Coloca en la lnea que se encuentra debajo de cada dibujo a que estructura de datos lineal corresponden
cada una de las representaciones.

1.4. Estructuras no lineales


A las estructuras de datos no lineales se les llama tambin estructuras de datos multienlazadas en donde cada elemento
puede estar enlazado a cualquier otro componente. Se trata de estructuras de datos en las que cada elemento puede
tener varios sucesores y/o varios predecesores.
Tambin se pueden definir como aquellas estructuras que ocupan bloques de memoria no continuos/lineales. Para lidiar
con el problema de la fragmentacin y, sobre todo del crecimiento dinmico. Los bloques deben estar enlazados unos
con otros para poder navegar por la estructura, es decir, tener acceso a otro(s) dato(s) a partir del actual.

ARBOLES.
Un rbol es una estructura de datos no lineal que organiza sus elementos siguiendo una jerarqua de niveles entre sus
elementos (nodos). Cada elemento tiene un nico antecesor y puede tener varios sucesores. Existe un nico camino
entre el primer nodo de la estructura y cualquier otro nodo. Se utilizan para representar todo tipo de jerarquas.
Ejemplos de rboles:

rbol genealgico
Taxonomas
Diagramas de organizacin
rbol de directorios en una unidad de disco
Aplicaciones algortmicas (ordenacin, bsqueda)
Compilacin (rboles sintcticos, rboles de expresiones)

Terminologa

Nodo: los vrtices o elementos de un rbol.


Enlace/arco/arista: Conexin entre dos nodos consecutivos.
Los nodos pueden ser:
Nodo raz: nodo superior de la jerarqua.
Nodo terminal u hoja: nodo que no contienen
ningn subrbol.
Nodos interiores: nodos con uno o ms
subrboles; nodos que no son hojas.
Descendientes o hijos: cada uno de los
subrboles de un nodo.
Ascendiente, antecesor o padre: nodo de
jerarqua superior a uno dado.
Nodos hermanos: nodos del mismo padre.
Bosque: coleccin de rboles.
Camino: enlace entre dos nodos. No existe un
camino entre todos los nodos.
Rama: camino que termina en una hoja.
Grado de un nodo: nmero de subrboles que
tiene.
Nivel de un nodo o longitud del camino:
nmero de arcos o enlaces que hay desde el nodo raz hasta un nodo dado.
Altura o profundidad de un rbol: nmero mximo de nodos de una rama; el nivel ms alto de un rbol ms
uno.
Peso de un rbol: nmero de nodos terminales.
Ejemplo de un bosque.

Tipos de rboles.

Un rbol general sera un rbol en el que cada nodo puede tener un nmero ilimitado de subrboles.
Un rbol binario sera un conjunto de 0 o ms nodos en el cual existe un nodo raz y cada uno de los nodos,
incluido la raz podrn tener 0, 1 o dos subrboles:
o Subrbol izquierdo y subrbol derecho.
o Cada nodo es como mximo de grado 2.

Tipos de rboles binarios.

rboles similares: rboles con la misma estructura.


rboles equivalentes: rboles con la misma estructura y contienen la misma informacin.
rboles completos o rboles perfectos: todos los nodos, excepto las hojas, tienen grado 2. Un rbol binario de
nivel n tiene 2n-1 nodos.
rbol equilibrado: un rbol en el que las alturas de los dos subrboles de cada uno de los nodos tiene como
mximo una diferencia de una unidad.
rbol degenerado: todos sus nodos slo tienen un subrbol.

Recorrido de un rbol (Definiciones)

Se denomina recorrido de un rbol al proceso que permite acceder de una sola vez a cada uno de los nodos del rbol.
Cuando un rbol se recorre, el conjunto completo de nodos se examina.
En ciencias de la computacin, el recorrido de rboles refiere al proceso de visitar de una manera sistemtica,
exactamente una vez, cada nodo en una estructura de datos de rbol (examinando y/o actualizando los datos en los
nodos). Tales recorridos estn clasificados por el orden en el cual son visitados los nodos. Los siguientes algoritmos son
descritos para un rbol binario, pero tambin pueden ser generalizados a otros rboles.
Recorrido en profundidad
Los algoritmos de recorrido de un rbol binario presentan tres tipos de actividades comunes:

Visitar el nodo raz.


Recorrer el subrbol izquierdo.
Recorrer el subrbol derecho.

Estas tres acciones repartidas en diferentes rdenes proporcionan los diferentes recorridos del rbol. Los ms frecuentes
tienen siempre en comn recorrer primero el subrbol izquierdo y luego el subrbol derecho. Los algoritmos anteriores se
llaman pre-orden, post-orden, in-orden, y su nombre refleja el momento en que se visita el nodo raz.
Preorden: (raz, izquierdo, derecho). Para recorrer un rbol binario no vaco en preorden, hay que realizar las
siguientes operaciones recursivamente en cada nodo, comenzando con el nodo de raz:
1. Visite la raz
2. Atraviese el sub-rbol izquierdo
3. Atraviese el sub-rbol derecho
Inorden: (izquierdo, raz, derecho). Para recorrer un rbol binario no vaco en inorden (simtrico), hay que realizar las
siguientes operaciones recursivamente en cada nodo:
1. Atraviese el sub-rbol izquierdo
2. Visite la raz
3. Atraviese el sub-rbol derecho
Postorden: (izquierdo, derecho, raz). Para recorrer un rbol binario no vaco en postorden, hay que realizar las
siguientes operaciones recursivamente en cada nodo:
1. Atraviese el sub-rbol izquierdo
2. Atraviese el sub-rbol derecho
3. Visite la raz
En general, la diferencia entre preorden, inorden y postorden es cundo se recorre la raz. En los tres, se recorre primero
el sub-rbol izquierdo y luego el derecho.

En preorden, la raz se recorre antes que los recorridos de los subrboles izquierdo y derecho.
En inorden, la raz se recorre entre los recorridos de los rboles izquierdo y derecho.
En postorden, la raz se recorre despus de los recorridos por el subrbol izquierdo y el derecho.
Preorden (antes), inorden (en medio), postorden (despus).

Recorrido en anchura
Los rboles tambin pueden ser recorridos en orden por nivel (de nivel en nivel), donde visitamos cada nodo en un nivel
antes de ir a un nivel inferior. Esto tambin es llamado recorrido en anchura-primero o recorrido en anchura.
Ejemplo

Se tiene el siguiente rbol binario ordenado, realizar los recorridos y obtener sus resultados.

Recorrido de Profundidad
Secuencia de recorrido de preorden: F, B, A, D, C, E, G, I, H (raz, izquierda, derecha)
Secuencia de recorrido de inorden: A, B, C, D, E, F, G, H, I (izquierda, raz, derecha); note cmo esto produce una
secuencia ordenada
Secuencia de recorrido de postorden: A, C, E, D, B, H, I, G, F (izquierda, derecha, raz)

Recorrido de Anchura
Secuencia de recorrido de orden por nivel: F, B, G, A, D, I, C, E, H

EJERCICIOS DE APRENDIZAJE NO. 2

I.

Realiza los recorridos y obtn los elementos que se te solicitan de los siguientes rboles binarios.

Pre-orden (raz, izquierda,


derecha)

In-orden (izquierda, raz,


derecha)

Post-orden (izquierda, derecha,


raz)
Anchura (amplitud o por
niveles)
Nodo raz:
Hojas:
Hijos del nodo raz:
Ejemplo de una rama:
Grado del nodo raz:
Profundidad del rbol:
Peso del rbol:
Nivel del rbol:

Pre-orden (raz, izquierda,


derecha)

In-orden (izquierda, raz,


derecha)

Post-orden (izquierda, derecha,


raz)
Anchura (amplitud o por
niveles)
Nodo raz:
Hojas:
Hijos del nodo raz:
Ejemplo de una rama:
Grado del nodo raz:
Profundidad del rbol:
Peso del rbol:
Nivel del rbol:

Pre-orden (raz, izquierda,


derecha)
In-orden (izquierda, raz,
derecha)
Post-orden (izquierda, derecha,
raz)
Anchura (amplitud o por
niveles)
Nodo raz:
Hojas:
Hijos del nodo raz:
Ejemplo de una rama:
Grado del nodo raz:
Profundidad del rbol:
Peso del rbol:
Nivel del rbol:
Pre-orden (raz, izquierda,
derecha)
In-orden (izquierda, raz,
derecha)
Post-orden (izquierda, derecha,
raz)
Anchura (amplitud o por
niveles)
Nodo raz:
Hojas:
Hijos del nodo raz:
Ejemplo de una rama:
Grado del nodo raz:
Profundidad del rbol:
Peso del rbol:
Nivel del rbol:
Pre-orden (raz, izquierda,
derecha)
In-orden (izquierda, raz,
derecha)
Post-orden (izquierda, derecha,
raz)
Anchura (amplitud o por
niveles)
Nodo raz:
Hojas:
Hijos del nodo raz:
Ejemplo de una rama:
Grado del nodo raz:
Profundidad del rbol:
Peso del rbol:
Nivel del rbol:

GRAFOS.
Un grafo (del griego grafos: dibujo, imagen) es un conjunto de objetos llamados vrtices o nodos unidos por enlaces
llamados aristas o arcos, que permiten representar relaciones binarias entre elementos de un conjunto.
Tpicamente, un grafo se representa grficamente como un conjunto de puntos (vrtices o nodos) unidos por lneas
(aristas). Desde un punto de vista prctico, los grafos permiten estudiar las interrelaciones entre unidades que
interactan unas con otras. Por ejemplo, una red de computadoras puede representarse y estudiarse mediante un grafo,
en el cual los vrtices representan terminales y las aristas representan conexiones (las cuales, a su vez, pueden ser
cables o conexiones inalmbricas).
Prcticamente cualquier problema puede representarse mediante un grafo, y su estudio trasciende a las diversas reas
de las ciencias exactas y las ciencias sociales.
Un grafo G es un par ordenado G=(V,E), donde:
V es un conjunto de vrtices o nodos, y
E es un conjunto de aristas o arcos, que relacionan estos nodos.
Normalmente V suele ser finito. Muchos resultados importantes sobre grafos no son aplicables para grafos infinitos. Se
llama orden del grafo G a su nmero de vrtices, |V|. El grado de un vrtice o nodo
es igual al nmero de arcos
que lo tienen como extremo. Un bucle es una arista que relaciona al mismo nodo; es decir, una arista donde el nodo
inicial y el nodo final coinciden. Dos o ms aristas son paralelas si relacionan el mismo par de vrtices.
Grafo no dirigido
Un grafo no dirigido o grafo propiamente dicho es un grafo G = (V, E) donde:
es un conjunto de pares no ordenados de elementos de V.

Un par no ordenado es un conjunto de la forma


, de manera que
conjuntos pertenecen al conjunto potencia de V, denotado P(V), y son de cardinalidad.

. Para los grafos, estos

Grafo dirigido
Un grafo dirigido o digrafo es un grafo G = (V, E) donde:

, es un conjunto de pares ordenados de elementos de V.

Dada una arista (a,b), a es su nodo inicial y b su nodo final.


Por definicin, los grafos dirigidos no contienen bucles.

Grafo mixto
Un grafo mixto es aquel que se define con la capacidad de poder contener aristas dirigidas y no dirigidas. Tanto los grafos
dirigidos como los no dirigidos son casos particulares de este.

Propiedades

Adyacencia: dos aristas son adyacentes si tienen un vrtice en comn, y dos vrtices son adyacentes si una
arista los une.
Incidencia: una arista es incidente a un vrtice si sta lo une a otro.
Ponderacin: corresponde a una funcin que a cada arista le asocia un valor (costo, peso, longitud, etc.), para
aumentar la expresividad del modelo. Esto se usa mucho para problemas de optimizacin, como el del vendedor
viajero o del camino ms corto.
Etiquetado: distincin que se hace a los vrtices y/o aristas mediante una marca que los hace unvocamente
distinguibles del resto.

Ejemplos

La imagen es una representacin del siguiente grafo:


V:={1,2,3,4,5,6}
E:={{1,2},{1,5},{2,3},{2,5},{3,4},{4,5},{4,6}}
En ciencias de la computacin los grafos dirigidos son usados para representar mquinas de estado finito y algunas otras
estructuras discretas.
Grafos particulares
Existen grafos que poseen propiedades destacables. Algunos ejemplos bsicos son:
Grafo nulo: aquel que no tiene vrtices ni aristas. Ntese que algunas personas exigen que el conjunto de
vrtices no sea vaco en la definicin de grafo.
Grafo vaco: aquel que no tiene aristas.
Grafo trivial: aquel que tiene un vrtice y ninguna arista.
Grafo simple: aquel que no posee bucles ni aristas paralelas. Consultar variantes en esta definicin.
Multigrafo (o pseudografo): G es multigrafo si y solo si no es simple. Consultar variantes en esta definicin.
Grafo completo: grafo simple en el que cada par de vrtices estn unidos por una arista, es decir, contiene
todas las posibles aristas.
Grafo bipartito: sea (W, X) una particin del conjunto de vrtices V, es aquel donde cada arista tiene un vrtice
en W y otro en X.
Grafo bipartito completo: sea (W, X) una particin del conjunto de vrtices V, es aquel donde cada vrtice en W
es adyacente slo a cada vrtice en X, y viceversa.
Grafo plano: aquel que puede ser dibujado en el plano cartesiano sin cruce de aristas.
Grafo rueda: grafo con n vrtices que se forma conectando un nico vrtice a todos los vrtices de un ciclo-(n1).
Grafo perfecto: aquel que el nmero cromtico de cada subgrafo inducido es igual al tamao del mayor clique
de ese subgrafo.
Una generalizacin de los grafos son los llamados hipergrafos. Los hipergrafos es una generalizacin de un grafo,
cuyas aristas aqu se llaman hiperaristas, y pueden relacionar a cualquier cantidad de vrtices, en lugar de slo un
mximo de dos como en el caso particular.

1.5. Estructuras estticas


Son aquellas en las que el tamao ocupado en memoria se define antes de que el programa se ejecute y no puede
modificarse dicho tamao durante la ejecucin del programa.
Estas estructuras estn implementadas en casi todos los lenguajes. Su principal caracterstica es que ocupan solo una
casilla de memoria, por lo tanto una variable simple hace referencia a un nico valor a la vez, dentro de este grupo de
datos se encuentra:
a) Enteros
b) Reales
c) Caracteres
d) Bolanos
e) Enumerados
f) Subrangos
Nota: Los ltimos no existen en algunos lenguajes de programacin.

Clasificacin de las estructuras de datos estticas:


1.- Simples o primitivas
a) Boolean
b) Char
c) Integer
d) Real
2.- Compuestas
a) Arreglos
b) Conjuntos
c) Strings
d) Registros
e) Archivos

1.6. Estructuras dinmicas


No tienen las limitaciones o restricciones en el tamao de memoria ocupada que son propias de las estructuras estticas.
Mediante el uso de un tipo de datos especifico, denominado puntero, es posible construir estructuras de datos dinmicas
que no son soportadas por la mayora de los lenguajes, pero que en aquellos que si tienen estas caractersticas ofrecen
soluciones eficaces y efectivas en la solucin de problemas complejos.
Se caracteriza por el hecho de que con un nombre se hace referencia a un grupo de casillas de memoria. Es decir un
dato estructurado tiene varios componentes.

Clasificacin de las estructuras de datos dinmicas:


1.- Lineales
a) Pila
b) Cola
c) Lista
2.- No lineales
a) rboles
b) Grafos

UNIDAD 2:
ESTRUCTURAS
LINEALES DE
DATOS

2.1. Introduccin a los tipos de datos abstractos


Un tipo de datos abstractos (TDA) es un tipo de dato definido por el programador que se puede manipular de un modo
similar a los tipos de datos definidos por el sistema. Est formado por un conjunto vlido de elementos y un nmero de
operaciones primitivas que se pueden realizar sobre ellos.
Ejemplo:
- Definicin del tipo
Numero racional: Conjunto de pares de elementos (a,b) de tipo entero, con b<>0.
- Operaciones:
CrearRacional: a, b = (a,b)
Suma: (a,b) + (c,d) = (a*d+b*c , b*d)
Resta: (a,b) - (c,d) = (a*d-b*c , b*d)
Producto: (a,b) * (c,d) = (a*c , b*d)
Divisin: (a,b) / (c,d) = (a*d , b*c)
Numerador: (a,b) = a
Denominador: (a,b) = b
ValorReal: (a,b) = a/b
MCD: (a,b) ...
Potencia: (a,b)^c = (a^c , b^c)
Simplifica: (a,b) = ( a/mcd(a,b) , b/mcd(a,b) )
Una vez definido se podrn declarar variables de ese tipo y operar con ellas utilizando las operaciones que aporta el tipo.
Ejemplo:
TRacional r1,r2, rsuma;
CrearRacional(4,7, &r1);
CrearRacional(5,8,&r2);
Suma(r1, r2, &rsuma);
printf(El valor real es %f, ValorReal(rsuma) );
Un TDA es el elemento bsico de la abstraccin de datos. Su desarrollo es independiente del lenguaje de programacin
utilizado, aunque este puede aportar mecanismos que faciliten su realizacin. Debe verse como una caja negra.

En un TDA existen dos elementos diferenciados:

La Interfaz de utilizacin
La representacin

A la hora de utilizar el TDA, la representacin debe permanecer oculta. Solo podremos utilizar las operaciones del tipo
para trabajar con sus elementos.
Interfaz
Para construir un tipo abstracto debemos (Definicin):
1. Exponer una definicin del tipo.
2. Definir las operaciones (funciones y procedimientos) que permitan operar con instancias de ese tipo.
3. Ocultar la representacin de los elementos del tipo de modo que slo se pueda actuar sobre ellos con las
operaciones proporcionadas.
4. Poder hacer instancias mltiples del tipo.

Tipos bsicos de operaciones en un TDA (Operaciones):

Constructores: Crean una nueva instancia del tipo.


Transformacin: Cambian el valor de uno o ms elementos de una instancia del tipo.
Observacin: Nos permiten observar el valor de uno o varios elementos de una instancia sin modificarlos.
Iteradores: Nos permiten procesar todos los componentes en un TDA de forma secuencial.

Implementacin

Una vez definido el TAD se escoge una representacin interna utilizando los tipos que proporciona el lenguaje y/o
otros TAD ya definidos previamente.
La representacin deber ocultarse utilizando los mecanismos que nos proporcione el lenguaje. Ocultamiento de
Informacin.
Normalmente la implementacin del tipo se realiza en un mdulo aparte que ser enlazado al programa principal.
Se necesitar un fichero cabecera que contenga la definicin de las operaciones y la declaracin del tipo
(representacin). Con esta informacin se podrn definir elementos del tipo y acceder a sus operaciones.

Ejemplo: Fichero cabecera


struct _TRacional { int a,b};
typedef struct _TRacional TRacional;
void CreaRacional (int a, int b, TRacional *r );
void SumaRacional(TRacional r1, TRacional r2, TRacional *rsum);

2.2. Estructuras de datos dinmicas


Las estructuras dinmicas de datos son estructuras que crecen a medida que ejecuta un programa. Una estructura
dinmica de datos es una coleccin de elementos llamadas nodos - que son normalmente registros. Al contrario de un
arreglo que contiene espacio para almacenar un nmero fijo de elementos, una estructura dinmica de datos se ampla y
contrae durante la ejecucin del programa, basada en los registros de almacenamiento de datos del programa.
Las estructuras dinmicas de datos son de gran utilidad para almacenar datos del mundo real, que estn cambiando
constantemente.
Un ejemplo tpico, es la lista de pasajeros de una lnea area. Si esta lista se mantuviera en orden alfabtico en un
arreglo, sera necesario hacer espacio para insertar un nuevo pasajero por orden alfabtico. Esto requiere utilizar un ciclo
para copiar los datos del registro de cada pasajero al siguiente elemento del arreglo. Si en su lugar se utilizar una
estructura dinmica de datos, los nuevos datos del pasajero se pueden insertar simplemente entre dos registros
existentes con un mnimo de esfuerzo.
Otro ejemplo es si tenemos almacenados en un array (tambin llamados vectores o arreglos) los datos de los alumnos de
un curso, los cuales estn ordenados de acuerdo al promedio, para insertar un nuevo alumno sera necesario correr cada
elemento un espacio: Si en su lugar se utilizara una estructura dinmica de datos, los nuevos datos del alumno se
pueden insertar fcilmente.

2.3. Estructuras de datos lineales


Las estructuras de datos lineales se caracterizan porque sus elementos estn en secuencia, relacionados en forma
lineal, uno luego del otro. Cada elemento de la estructura puede estar conformado por uno o varios subelementos o
campos que pueden pertenecer a cualquier tipo de dato, pero que normalmente son tipos bsicos.
Una estructura lineal de datos est conformada por ninguno, uno o varios elementos que tienen una relacin dnde
existe un primer elemento, seguido de un segundo elemento y as sucesivamente hasta llegar al ltimo.
Las estructuras lineales son importantes porque aparecen con mucha frecuencia en situaciones de la vida, tambin se
utilizan muy frecuente en los esquemas algortmicos. Ejemplos: Una cola de clientes de un banco, las instrucciones de un
programa, los caracteres de una cadena o las pginas de un libro.
Caractersticas:

Existe un nico elemento, llamado primero,


Existe un nico elemento, llamado ltimo,
Cada elemento, excepto el primero, tiene un nico predecesor y
Cada elemento, excepto el ltimo, tiene un nico sucesor

Existen tres estructuras lineales especialmente importantes:


1. Las pilas
2. Las colas
3. Las listas
Las operaciones bsicas para dichas estructuras son:

Crear la secuencia vaca


Aadir un elemento a la secuencia
Borrar un elemento a la secuencia
Consultar un elemento de la secuencia
Comprobar si la secuencia est vaca

El valor contenido en los elementos pueden ser el mismo o diferente. En estas estructuras se realizan operaciones de
agregar y/o eliminar elementos a la lista segn un criterio particular. Para definir claramente el comportamiento de la
estructura es necesario determinar en qu posicin se inserta un elemento nuevo y qu elemento se borra o se obtiene.
La diferencia entre las tres estructuras vendr dada por la posicin del elemento a aadir, borrar y consultar:

Pilas: Las tres operaciones actan sobre el final de la secuencia.


Colas: Se aade por el final y se borra y consulta por el principio.
Listas: Las tres operaciones se realizan sobre una posicin privilegiada de la secuencia, la cual puede
desplazarse.

2.4. Listas contiguas


Una lista lineal es un conjunto de elementos de un tipo dado que se encuentran ordenados y pueden variar en nmero.
Los elementos de una lista lineal se almacenan normalmente contiguos en posiciones consecutivas de la memoria. Las
sucesivas entradas en una gua o directorio telefnico, por ejemplo, estn en lneas sucesivas, excepto en las partes
superior e inferior de cada columna. Una lista lineal se almacena en la memoria principal de una computadora en
posiciones sucesivas de memoria; cuando se almacenan en cinta magntica, los elementos sucesivos se presentan en
sucesin en la cinta. Esta sucesin se denomina almacenamiento secuencial.
Las lneas as definidas se denominan contiguas. Las operaciones que se pueden realizar con listas lineales contiguas
son:
1.
2.
3.
4.
5.
6.
7.
8.

Insertar, eliminar o localizar un elemento.


Determinar el tamao de la lista (nmero de elementos).
Recorrer la lista para localizar un determinado elemento.
Clasificar los elementos de la lista en orden ascendente o descendente.
Unir dos o ms listas en una sola.
Dividir una lista en varias sublistas.
Copiar una lista.
Borrar una lista.

Una lista lineal se almacena en la memoria de la computadora en posiciones sucesivas o adyacentes y se procesa como
un arreglo unidimensional. En este caso, el acceso a cualquier elemento de la lista y la adicin de nuevos elementos es
fcil; Sin embargo la insercin o borrado requiere un desplazamiento de lugar de los elementos que le siguen y, en
consecuencia un diseo de algoritmo especifico.
Se implementan a travs de arreglos, donde la insercin o eliminacin de un elemento excepto en la cabecera o al final
de la lista necesitar una traslacin de los elementos de la lista.

La declaracin en C de una lista implementada por arreglos es la siguiente:


struct lista{
int elem[long_max];
int ultimo;
}
Creacin de lista contigua:
struct lista* Crear(){
struct lista* L = (struct lista*) malloc(sizeof(struct lista));
L-> ultimo=0;
return L;
}

2.5. Listas enlazadas

Una lista enlazada es un conjunto de elementos llamados nodos en los que cada uno de ellos contiene un dato y
tambin la direccin del siguiente nodo, donde el orden de los mismos se establece mediante punteros.
La idea bsica es que cada componente de la lista incluya un puntero que indique donde puede encontrarse el siguiente
componente por lo que el orden relativo de estos puede ser fcilmente alterado modificando los punteros lo que permite,
a su vez, aadir o suprimir elementos de la lista. El primer elemento de la lista es la cabecera, que slo contiene un
puntero que seala el primer elemento de la lista.
El ltimo nodo de la lista apunta a NULL (nulo) porque no hay ms nodos en la lista. Se usar el trmino NULL para
designar el final de la lista.
Listas Enlazadas frente a Arrays
Las listas enlazadas tienen las siguientes ventajas sobre los arrays:

No requieren memoria extra para soportar la expansin. Por el contrario, los arrays requieren memoria extra si se
necesita expandirlo (una vez que todos los elementos tienen datos no se pueden aadir datos nuevos a un
array).
Ofrecen una insercin/borrado de elementos ms rpida que sus operaciones equivalentes en los arrays. Slo se
tienen que actualizar los enlaces despus de identificar la posicin de insercin/borrado. Desde la perspectiva de
los arrays, la insercin de datos requiere el movimiento de todos los otros datos del array para crear un elemento
vaco. De forma similar, el borrado de un dato existente requiere el movimiento de todos los otros datos para
eliminar el elemento vaco.

En contraste, los arrays ofrecen las siguientes ventajas sobre las listas enlazadas:

Los elementos de los arrays ocupan menos memoria que los nodos porque no requieren campos de enlace.
Los arrays ofrecen un acceso ms rpido a los datos, mediante ndices basados en enteros.

Las listas enlazadas son ms apropiadas cuando se trabaja con datos dinmicos. En otras palabras, inserciones y
borrados con frecuencia. Por el contrario, los arrays son ms apropiados cuando los datos son estticos (las inserciones
y borrados son raras). De todas formas, no olvide que si se queda sin espacio cuando aade tems a un array, debe crear
un array ms grande, copiar los datos del array original el nuevo array mayor y eliminar el original. Esto cuesta tiempo, lo
que afecta especialmente al rendimiento si se hace repetidamente.
Mezclando una lista de enlace simple con un array uni-dimensional para acceder a los nodos mediante los ndices del
array no se consigue nada. Gastar ms memoria, porque necesitar los elementos del array ms los nodos, y tiempo,
porque necesitar mover los tems del array siempre que inserte o borre un nodo. Sin embargo, si es posible integrar el
array con una lista enlazada para crear una estructura de datos til.
Existen diferentes tipos de listas enlazadas:

Lista Enlazadas Simples.


Listas Doblemente Enlazadas.
Listas Enlazadas Circulares.

Las listas enlazadas pueden ser implementadas en muchos lenguajes. Lenguajes tales como Lisp y Scheme tiene
estructuras de datos ya construidas, junto con operaciones para acceder a las listas enlazadas. Lenguajes imperativos u
orientados a objetos tales como C, C++ o Java disponen de referencias para crear listas enlazadas.
Operaciones con listas enlazadas
Las operaciones que podemos realizar sobre una lista enlazada son las siguientes:

Recorrido. Esta operacin consiste en visitar cada uno de los nodos que forman la lista. Para recorrer todos los
nodos de la lista, se comienza con el primero, se toma el valor del campo liga para avanzar al segundo nodo, el
campo liga de este nodo nos dar la direccin del tercer nodo, y as sucesivamente.
Insercin. Esta operacin consiste en agregar un nuevo nodo a la lista. Para esta operacin se pueden
considerar tres casos:
o Insertar un nodo al inicio.
o Insertar un nodo antes o despus de cierto nodo.
o Insertar un nodo al final.

Borrado. La operacin de borrado consiste en quitar un nodo de la lista, redefiniendo las ligas que correspondan.
Se pueden presentar cuatro casos:
o Eliminar el primer nodo.
o Eliminar el ltimo nodo.
o Eliminar un nodo con cierta informacin.
o Eliminar el nodo anterior o posterior al nodo cierta con informacin.
Bsqueda. Esta operacin consiste en visitar cada uno de los nodos, tomando al campo liga como puntero al
siguiente nodo a visitar.

Figura 1. Esquema de un nodo y una lista enlazada.


LISTA ENLAZADA SIMPLE
La lista enlazada bsica es la lista enlazada simple la cual tiene un enlace por nodo. Este enlace apunta al siguiente
nodo en la lista, o al valor NULL o a la lista vaca, si es el ltimo nodo.

LISTAS ENLAZADAS DOBLES


Cada nodo tiene dos enlaces: uno apunta al nodo anterior, o apunta al valor NULL o a la lista vaca si es el primer nodo; y
otro que apunta al siguiente nodo siguiente, o apunta al valor NULL o a la lista vaca si es el ltimo nodo.

LISTAS ENLAZADAS CIRCULARES


En una lista enlazada circular, el primer y el ltimo nodo estn unidos juntos. Esto se puede hacer tanto para listas
enlazadas simples como para las doblemente enlazadas. Para recorrer un lista enlazada circular podemos empezar por
cualquier nodo y seguir la lista en cualquier direccin hasta que se regrese hasta el nodo original. Desde otro punto de
vista, las listas enlazadas circulares pueden ser vistas como listas sin comienzo ni fin. Este tipo de listas es el ms usado
para dirigir buffers para ingerir datos, y para visitar todos los nodos de una lista a partir de uno dado.

Cada nodo tiene un enlace, similar al de las listas enlazadas simples, excepto que el siguiente nodo del ltimo apunta al
primero. Como en una lista enlazada simple, los nuevos nodos pueden ser solo eficientemente insertados despus de
uno que ya tengamos referenciado. Por esta razn, es usual quedarse con una referencia solamente al ltimo elemento

en una lista enlazada circular simple, esto nos permite rpidas inserciones al principio, y tambin permite accesos al
primer nodo desde el puntero del ltimo nodo.
LISTAS ENLAZADAS DOBLEMENTE CIRCULAR
En una lista enlazada doblemente circular, cada nodo tiene dos enlaces, similares a los de la lista doblemente enlazada,
excepto que el enlace anterior del primer nodo apunta al ltimo y el enlace siguiente del ltimo nodo, apunta al primero.
Como en una lista doblemente enlazada, las inserciones y eliminaciones pueden ser hechas desde cualquier punto con
acceso a algn nodo cercano. Aunque estructuralmente una lista circular doblemente enlazada no tiene ni principio ni fin,
un puntero de acceso externo puede establecer el nodo apuntado que est en la cabeza o al nodo cola, y as mantener el
orden tan bien como una lista doblemente enlazada con falsos nodos.

Implementacin
Cdigo con explicacin para implementar una lista enlazada:
Lo primero que debemos hacer es declarar las dos libreras necesarias.
#include<iostream>
#include<stdlib.h>
Luego declaramos la estructura o el estruct donde vamos a guardar los nodos
struct nodo{
int info;
// En la variable info guardaremos el contenido de los nodos.
struct nodo *sgt;
// El puntero siguiente sera para crear los nodos de la lista.
};
Seguimos como el main(), en el main declaramos los dos punteros que nos van a servir en la creacin de los nodos:
struct nodo *cabe;
// este para la cabeza del nodo
struct nodo *nuevo;
//este para los nuevos nodos que se creen.
Procedemos a declarar la cabeza como NULL
cabe=NULL;
Importante si no declaras la cabeza del nodo como NULL posiblemente obtendrs un error en tiempo de ejecucin.
Ahora vamos a declarar tres variables tipo entero que nos ayudaran en la creacin de nodos.
int dato;
// Esta variable para almacenar los datos que se le vallan a introducir a los nodos.
int i=1;
//Esta es la variable del contador para crear los nodos.
int cant;
//Esta para la cantidad de nodos que el usuario desee.
Ahora imprimimos un mensaje para pedir la cantidad de nodos que desea el usuario.
cout<<"Entrar cantidad de nodos=";
cin>>cant;
Como viste ya se haba declarado la variable "cant" que es la que nos permite saber cuntos nodos desea el usuario.
Ahora vamos a realizar el proceso ms engorroso y que requiere la mayor atencin posible, por favor pon atencin en
cada paso de esto porque son lo ms vitales. Abrimos un ciclo while que llegar hasta donde lo especifico el usuario con
la sentencia anterior escrita. Este ciclo while depender de dos variables las variables i y cant anteriormente declaradas.
La variable cant es el final del ciclo por ejemplo si la variable cant contiene el valor de 5 entonces este ser el nmero de

repeticiones del ciclo o mejor dicho ser la cantidad de vueltas que dar el ciclo. Con esta variable controlamos los nodos
que vamos a crear.
while(i<=cant){ //Aqui se inicia el ciclo while.
nuevo=(struct nodo *)malloc(sizeof(struct nodo)); //Crea un nodo nuevo
nuevo->sgt=cabe;
cout<<"Entre dato=";
cin>>dato;
nuevo->info=dato;
cabe=nuevo;
i++;
}
Se especificaran las lneas que se han creado en el ciclo:
nuevo=(struct nodo *)malloc(sizeof(struct nodo)); esta linease usa para crear el nuevo nodo que vamos a
usar en la primera vuelta del ciclo.
La parte de (struct nodo *)malloc(sizeof(struct nodo)) se usa para asignar memoria dinmicamente cuando
se est ejecutando el programa.
nuevo->sgt=cabe; Esta lnea es para que el nuevo nodo se le el valor de cabeza. Y as ir creando los dems
nodos de forma ordenada. Importante ver video para mirar grficamente este proceso donde el enlace del nodo
original pasa al enlace del nodo nuevo.
Entonces el nodo original pasa a ser el nodo de atrs y
el nuevo nodo pasa a ser el primero, y as
sucesivamente en la repeticin del ciclo.

cout<<"Entre dato=";

cin>>dato; Aqu solicitamos los datos que vamos a


entrar en el nuevo nodo.
nuevo->info=dato; Entonces ahora hacemos que la
informacin pedida anteriormente pase a la variable
"info" que ser el contenido del nodo nuevo.
cabe=nuevo; aqu damos al apuntador cabeza la direccin de nuevo quedando as el apuntador cabeza que
apuntaba a el nodo original, apuntando al nodo nuevo.

Ahora procedemos a imprimir los nodos con las siguientes lneas.


while(nuevo!=NULL){ //El apuntador nodo ira siguiendo los nodos ya para hasta encontrar NULL
cout<<"\nDATO="<<nuevo->puntos; //Imprimimos los datos de cada nodo
nuevo=nuevo->sgt; //Importante y no olivadar con esta sentencia corremos el apuntador al siguiente
}
//nodo
system("pause>>null");
}

Cdigo para implementar una lista enlazada.

Cdigo para implementar una lista enlazada con


Operaciones.

#include<iostream>
#include<stdlib.h>
using namespace std;
//*******************************
struct nodo{
int info;
struct nodo *sgt;
};
//************************************
main(){
struct nodo *cabe;
struct nodo *nuevo;
struct nodo *aux;
cabe=NULL;
int dato;
int cant, i=1, cont;
cout<<"Ejemplo de Listas en C++\n\n";
cout<<"Entrar cantidad de nodos=";cin>>cant;
while(i<=cant){
nuevo=(struct nodo *)malloc(sizeof(struct
nodo));
nuevo->sgt=cabe;
cout<<"Ingrese dato numerico=";
cin>>dato;
nuevo->info=dato;
cabe=nuevo;
i++;
}
while(nuevo!=NULL){
cout<<"\nDATO="<<nuevo->info;
nuevo=nuevo->sgt;
}
cout<<"\n\nRealizado por: David Ortiz \n";
system("pause>>null");
}

#include <iostream>
#include <stdlib.h>
#include <conio.h>
using namespace std;
struct nodo{
int nro;
nodo *sgte;
};
struct nodo *pi, *pa, *pf;
void menu();
void insertar(int);
void mostrar();
void buscar();
void eliminar();
void modificar();
void clrscr();
void clrscr(){
system("cls");
}
void insertar(int numero){
if(pi==NULL){
pi=new(nodo);
pi->nro=numero;
pf=pi;
}
else{
pa=new(nodo);
pf->sgte=pa;
pa->nro=numero;
pf=pa;
}
pf->sgte=NULL;
}
void mostrar(){
pa=pi;
if(pi==NULL)
{
cout<<"\n\t lista vacia";
}
while(pa!=NULL){
cout<<endl<<" \t-> "<<pa->nro;
pa=pa->sgte;
}
cout<<"\n\n\n\t Presione 'Enter' para Regresar";
}
void buscar(int numero){
int flag=0;
pa=pi;
if(pi==NULL)
{
cout<<"\n\t lista vacia";
}

else
{

while(pa!=NULL){
if(numero==pa->nro){
cout<<endl<<"\n\t Valor encontrado : "<<pa->nro;
flag=1;
}
pa=pa->sgte;
}
if(flag==0){
cout<<endl<<"\n\t El numero no fue encontrado";
}
}
cout<<"\n\n\n\t Presione 'Enter' para Regresar";
}
void modificar(int numero){
pa=pi;
if(pi==NULL)
{
cout<<"\n\t lista vacia";
}
else
{
int nro_mod,flag=0;
while(pa!=NULL){
if(numero==pa->nro && flag==0){
cout<<endl<<"\n\t Valor Encontrado : "<<pa->nro;
cout<<endl<<"\n\t Ingrese nuevo valor :";
cin>>pa->nro;
flag=1;
}
pa=pa->sgte;
}
if(flag==0){
cout<<endl<<"\n\t El numero no fue encontrado";
}
}
pa=pi;
cout<<"\n\n\n\t Presione 'Enter' para Regresar";
}
void eliminar(int numero){
struct nodo *pant=NULL;
if(pi==NULL){
cout<<"\n\t lista vacia";
}
else{
if(pi->nro==numero){
pa=pi;
pi=pi->sgte;
cout<<"\n\t numero eliminado : "<<pa->nro;

}
cout<<"\n\n\n\t Presione 'Enter' para Regresar";
}
void menu(){
int opc,numero;
do{
cout<<endl<<"\n\t Listas Enlazadas ";
cout<<endl<<"\n\t Menu de Opciones "<<endl;
cout<<"\n\t 1. Insertar";
cout<<"\n\t 2. Mostrar";
cout<<"\n\t 3. Buscar";
cout<<"\n\t 4. Modificar";
cout<<"\n\t 5. Eliminar";
cout<<"\n\t 6. Salir";
cout<<endl<<"\n\n\tIngrese opcion : ";
cin>>opc;
clrscr();
switch(opc){
case 1: cout<<"\n\t Ingrese Numero : ";
cin>>numero;
insertar(numero);
clrscr();
break;
case 2:mostrar();getch();clrscr();break;
case 3:cout<<"\n\t Ingrese Numero a Buscar : ";
cin>>numero;
buscar(numero);getch();clrscr();break;
case 4:cout<<"\n\t Ingrese Numero a Modificar : ";
cin>>numero;
modificar(numero);getch();clrscr();break;
case 5:cout<<"\n\t Ingrese Numero a Eliminar : ";
cin>>numero;
eliminar(numero);getch();clrscr();break;
}
}while(opc!=6);
cout<<"\n\n Realizado por: David Ortiz \n";
cout<<"\n\n\n\t Presione 'Enter' para Salir";getch();
}
int main() {
menu();
}

}
else{
pa=pi->sgte;
pant=pi;
while(pa!=NULL &&pa->nro!=numero){
pant=pa;
pa=pa->sgte;
}
if(pa!=NULL){
pant->sgte=pa->sgte;
cout<<"\n\t numero eliminado : "<<pa->nro;
}
}
delete(pa);

2.6.

Pilas estticas

Una Pila o Stack es una estructura en donde cada elemento es insertado y retirado del tope de la misma, y debido a
esto el comportamiento de una pila se conoce como LIFO (ltimo en entrar, primero en salir).
Un ejemplo de pila o stack se puede observar en el mismo procesador, es decir, cada vez que en los programas aparece
una llamada a una funcin el microprocesador guarda el estado de ciertos registros en un segmento de memoria
conocido como Stack Segment, mismos que sern recuperados al regreso de la funcin.

Pila en arreglo esttico


En el programa que se ver en seguida, se simula el comportamiento de una estructura de pila. Aunque en el mismo se
usa un arreglo esttico de tamao fijo se debe mencionar que normalmente las implementaciones hechas por fabricantes
y/o terceras personas se basan en listas dinmicas o enlazadas.
Para la implementacin de la clase Stack se han elegido los mtodos:

put(), poner un elemento en la pila


get(), retirar un elemento de la pila
empty(), regresa 1 (TRUE) si la pila est vaca
size(), nmero de elementos en la pila

El atributo SP de la clase Stack es el puntero de lectura/escritura, es decir, el SP indica la posicin dentro de la pila en
donde la funcin put() insertar el siguiente dato, y la posicin dentro de la pila de donde la funcin get() leer el
siguiente dato.

Cada vez que put() inserta un elemento el SP se decrementa.


Cada vez que get() retira un elemento el SP se incrementa.

En el siguiente ejemplo se analiza lo que sucede con el SP (puntero de pila) cuando se guardan en la pila uno por uno los
caracteres 'A', 'B', 'C' y 'D'. Observe que al principio el SP es igual al tamao de la pila.
Llenando la pila.

SP
|
+---+---+---+---+---+
|

+---+---+---+---+---+
SP

Al principio (lista vaca)

|
+---+---+---+---+---+

push('A');

despus de haber agregado el primer elemento

| A |

+---+---+---+---+---+
SP
|
+---+---+---+---+---+
|

| D | C | B | A |

despus de haber agregado cuatro elementos

+---+---+---+---+---+

Vaciando la pila.

SP
|
+---+---+---+---+---+

pop();

despus de haber retirado un elemento

| D | C | B | A |

+---+---+---+---+---+
SP
|
+---+---+---+---+---+
|

| D | C | B | A |

despus de haber retirado todos los elementos

+---+---+---+---+---+

Nota: observe que al final la lista est vaca, y que dicho estado se debe a que el puntero est al final de la pila y no al
hecho de borrar fsicamente cada elemento de la pila.

Implementacin en C++
Ejemplo: Pila basada en un arreglo esttico.
#include <iostream>
using namespace std;
#define STACK_SIZE 256 /* capacidad mxima */
typedef char arreglo[STACK_SIZE];
class Stack {
int sp; /* puntero de lectura/escritura */
int items; /* nmero de elementos en lista */
int itemsize; /* tamao del elemento */
arreglo pila;
/* el arreglo */

public:
// constructor
Stack() {
sp = STACK_SIZE-1;
items = 0;
itemsize = 1;
}
// destructor
~Stack() {};
/* regresa el nmero de elementos en lista */
int size() { return items; }
/* regresa 1 si no hay elementos en la lista, o sea, si la lista est vacia */
int empty() { return items == 0; }
/* insertar elemento a la lista */
int put(char d)
{
if ( sp >= 0) {
pila[sp] = d;
sp --;
items ++;
}
return d;
}
/* retirar elemento de la lista */
int get()
{
if ( ! empty() ) {
sp ++;
items --;
}
return pila[sp];
}
}; // fin de clase Stack

// probando la pila.
// Nota: obseve cmo los elementos se ingresan en orden desde la A hasta la Z,
// y como los mismos se recupern en orden inverso.
int main()
{
int d;
Stack s; // s es un objeto (instancia) de la clase Stack
cout<<"\n\nLlenando la pila... presiona <Enter> \n"; cin.get();// llenando la pila
for (d='A'; d<='Z'; d++) s.put(d);
cout << "Items =" << s.size() << endl;
cout<<"\n\nVaciando la pila... presiona <Enter>\n"; cin.get();// vaciando la pila
while ( s.size() ) cout << (char)s.get() << " ";
cout << "\n\nPara terminar presiona <Enter>...";
cout<<"\nRealizado por: David Ortiz \n";
cin.get();

return 0;
}

2.7. Pilas dinmicas


Es importante hacer notar que, a diferencia de una pila basada en un arreglo esttico, una pila enlazada dinmicamente
no posee de forma natural el mecanismo de acceso por ndices, en ese sentido, el programador puede crear los
algoritmos necesarios para permitir tal comportamiento. En la clase que se presenta en el ejemplo no se ha
implementado el mecanismo de acceso por ndices, ya que la misma se presenta como una alternativa para la simulacin
de una pila.
Uno de los puntos ms destacables en cuando al uso de listas enlazadas dinmicamente es el hecho de crear
estructuras conocidas como nodos. Por ejemplo, para crear una estructura de nodo para almacenar enteros y a la vez
para apuntar a otro posible nodo podemos emplear la sintaxis:
struct nodo {
int data;
nodo *siguiente;
};
Observe que con la declaracin anterior estamos creando el tipo estructurado nodo, mismo que posee a los miembros:
data para guardar valores enteros, y siguiente para apuntar o enlazar a un supuesto siguiente nodo.
Ya que las pilas dinmicas inicialmente se encuentran vacas, y ms an, una pila dinmica no posee una direccin
establecida en tiempo de compilacin ya que la direccin de memoria que ocupar cada uno de los elementos se
establecer en tiempo de ejecucin, entonces cmo determinar la condicin de vaco. En nuestro ejemplo usaremos un
contador (ITEMS) que dicho sea de paso, si ITEMS = 0, entonces la pila est vaca (la condicin de vaco tambin podra
determinarse al verificar el SP, es decir, si el SP = NULL, significa que la lista no posee elementos).
Al hacer un anlisis previo de los eventos que acontecern en la pila y su puntero de lectura y escritura (SP, que en esta
ocasin es una estructura tipo nodo), se tiene lo siguiente:
1) Al principio la pila est vaca, en ese caso el SP es igual a NULL y, en consecuencia, el puntero next tambin es
NULL.
SP = NULL
+------+------+
| ???? | next |--> NULL
+------+------+
2) Despus de agregar el primer elemento la situacin se vera as:
SP = asignado
1
+------+------+
| data | next |--> NULL
+------+------+
3) Despus de agregar otro elemento la situacin se vera as:
SP = asignado
2

+------+------+

+------+------+

| data | next |--> | data | next |--> NULL


+------+------+

+------+------+

Implementacin en C++
Ejemplo: Pila basada en un arreglo esttico
#include <iostream>
using namespace std;
/* tipo de dato que contendr la lista */
typedef char DATA_TYPE;
// declaracin de estructura nodo
struct nodo {
DATA_TYPE data;
nodo *next;
};
class StackDin {
// atributos
int ITEMS; /* nmero de elementos en la lista */
int ITEMSIZE; /* tamao de cada elemento */
nodo *SP; /* puntero de lectura/escritura */
public:
// constructor
StackDin() : SP(NULL), ITEMS(0), ITEMSIZE(sizeof(DATA_TYPE)) {}
// destructor
~StackDin() {}
// regresa el numero de elementos de la pila
int size() { return ITEMS; }
/* agregar componente a la lista */
DATA_TYPE put(DATA_TYPE valor)
{
nodo *temp;
temp = new nodo;
if (temp == NULL) return -1;
temp->data = valor;
temp->next = SP;
SP = temp;
ITEMS ++;
return valor;
}
int empty() { return ITEMS == 0; }
/* retirar elemento de la lista */
DATA_TYPE get()
{
nodo *temp;
DATA_TYPE d;
if ( empty() ) return -1;
d = SP->data;
temp = SP->next;
if (SP) delete SP;
SP = temp;
ITEMS --;
return d;
}
}; // fin de la clase StackDin
/* punto de prueba para la clase StackDin */
int main()

{
StackDin s;
DATA_TYPE d;
cout<<"\n\nLlenando la pila... presiona <Enter> \n"; cin.get();// llenando la pila
for (d='A'; d<='Z'; d++) s.put(d);
cout << "Items =" << s.size() << endl;
cout<<"\n\nVaciando la pila... presiona <Enter>\n"; cin.get();// vaciando la pila
while ( ! s.empty() )
cout << (DATA_TYPE)s.get() << " ";
cout << "\n\nPara terminar presione <Enter>...";
cout<<"\nRealizado por: David Ortiz \n";
cin.get();
return 0;
}

2.8. Colas estticas


Una cola sencilla (Queue) es una estructura en donde cada elemento es insertado inmediatamente despus del ltimo
elemento insertado; y donde los elementos se retiran siempre por el frente de la misma, debido a esto el comportamiento
de un una cola se conoce como FIFO (primero en entrar, primero en salir).
Un ejemplo a citar de cola es el comportamiento del buffer del teclado. Cuando en el teclado se oprime una tecla, el
cdigo del carcter ingresado es trasladado y depositado en un rea de memoria intermedia conocida como "el buffer del
teclado", para esto el microprocesador llama a una rutina especfica. Luego, para leer el carcter depositado en el buffer
existe otra funcin, es decir, hay una rutina para escribir y otra para leer los caracteres del buffer cada una de las cuales
posee un puntero; uno para saber en dnde dentro del buffer se escribir el siguiente cdigo y otro para saber de dnde
dentro del buffer se leer el siguiente cdigo.
Cola en un arreglo esttico.
En el programa que se ve en seguida, se simula el comportamiento de una estructura de cola simple. Aunque en el
mismo se usa un arreglo esttico de tamao fijo se debe mencionar que normalmente las implementaciones hechas por
fabricantes y/o terceras personas se basan en listas dinmicas o dinmicamente enlazadas.
Para la implementacin de la clase Queue se han elegido los mtodos:
put(), poner un elemento en la cola
get(), retirar un elemento de la cola
empty(), regresa 1 (TRUE) si la cola est vaca
size(), nmero de elementos en la cola
El atributo cabeza de la clase Queue es el puntero de lectura.
El atributo cola de la clase Queue es el puntero de escritura.
Es decir, la cola indica la posicin dentro de la lista en donde la funcin put() insertar el siguiente dato, y la cabeza
indica la posicin dentro de la lista de donde la funcin get() leer el siguiente dato.
Cada vez que put() inserta un elemento la cola se incrementa.
Cada vez que get() retira un elemento la cabeza se incrementa.
En el siguiente ejemplo se analiza lo que sucede con la cola y la cabeza (punteros de escritura y de lectura de la Cola)
cuando se guardan en la cola uno por uno los caracteres 'A', 'B', 'C' y 'D'. Observe que al principio: cola = cabeza = cero.
Llenando la cola.
cola
|
+---+---+---+---+---+
|
|
|
|
|
|
+---+---+---+---+---+
|
cabeza

al principio

cola
|
+---+---+---+---+---+
| A |
|
|
|
|
+---+---+---+---+---+
|
cabeza

...

put('A');
despus de haber agregado el primer elemento

cola
|
+---+---+---+---+---+
| A | B | C | D |
|
+---+---+---+---+---+
|
cabeza

despus de haber agregado cuatro elementos

Vaciando la cola.
cabeza
|
+---+---+---+---+---+
| A | B | C | D |
|
+---+---+---+---+---+
cabeza
|
+---+---+---+---+---+
| A | B | C | D |
|
+---+---+---+---+---+

antes de haber retirado elementos

get();
despus de haber retirado un elemento

...
cabeza
|
+---+---+---+---+---+
al final
| A | B | C | D |
|
despus de haber retirado todos los elementos
+---+---+---+---+---+
|
cola

Obsrvese que al final el cabeza apunta hacia el mismo elemento que la cola, es decir, la cola vuelve a estar vaca.
Puesto que la cola que estamos proyectando reside en un arreglo esttico los componentes del arreglo an estn dentro
de la misma, salvo que para su recuperacin se debera escribir otro mtodo. En una cola dinmica (como se demostrar
ms adelante) los elementos retirados de la misma se eliminan de la memoria y podra no ser posible su recuperacin
posterior.
Nota: En el programa que aparece en seguida, el comportamiento de sus punteros a travs de los mtodos para escribir
o leer si detectan que el puntero correspondiente ha sobrepasado el tamao mximo de elementos permitidos dentro de
la cola, ste es puesto a cero.
Implementacin en C++
Ejemplo: cola en un arreglo esttico.
#include <iostream.h>
#define MAX_SIZE 256 /* capacidad mxima */
typedef char almacen[MAX_SIZE];
class Queue {
int cabeza; /* puntero de lectura */
int cola; /* puntero de escritura */
int ITEMS; /* nmero de elementos en la lista */
int ITEMSIZE; /* tamao de cada elemento */
almacen alma; /* el almacen */
public:
// constructor
Queue() {

cabeza = 0;
cola = 0;
ITEMS = 0;
ITEMSIZE = 1;
}
// destructor
~Queue() {}
// regresa 1 (true) si la lista est vacia
int empty() { return ITEMS == 0; }
// insertar elemento a la lista
int put(int d)
{
if ( ITEMS == MAX_SIZE) return -1;
if ( cola >= MAX_SIZE) { cola = 0; }
alma[cola] = d;
cola ++;
ITEMS ++;
return d;
}
// retirar elemento de la lista
int get()
{
char d;
if ( empty() ) return -1;
if ( cabeza >= MAX_SIZE ) { cabeza = 0; }
d = alma[cabeza];
cabeza ++;
ITEMS --;
return d;
}
// regresa el nmero de elementos en lista
int size() { return ITEMS; }
}; // fin de la clase Queue
// probando la cola
int main()
{
int d;
Queue q;
for (d='A'; d<='Z'; d++) q.put(d);
cout << "Items = " << q.size() << endl;
while ( q.size() ) {
cout << (char)q.get() << " ";
}
cout<<"\n\nRealizado:por David Ortiz\n";
cout << "\nPara terminar oprima <Enter> ...";
cin .get();
return 0;
}

2.9. Colas dinmicas


Una cola dinmica no tendr un tamao fijo, sino que ste ir variando segn se aadan o eliminen objetos de la cola.
Para poder realizar la cola dinmica, emplearemos un objeto Nodo, que contendr el dato a almacenar en cada lugar de
la cola y la referencia al siguiente nodo que le sigue.
Implementacin en C++
Ejemplo: cola en un arreglo dinmico.
#include <iostream>
using namespace std;
typedef char DATA_TYPE;
struct nodo {
DATA_TYPE data;
nodo *next;
};
class QueueDin {
// atributos
int ITEMS, ITEMSIZE;
nodo *cola, *cabeza;
public:
// constructor
QueueDin() : cola(NULL), cabeza(NULL), ITEMS(0), ITEMSIZE(sizeof(DATA_TYPE)) {}
// destructor
~QueueDin() {}
/* agregar componente a la lista */
DATA_TYPE put(DATA_TYPE valor)
{
nodo *temp;
temp = new nodo;
if (temp == NULL) return -1;
ITEMS ++;
temp->data = valor;
temp->next = NULL;
if (cabeza == NULL)
{
cabeza = temp;
cola = temp;
} else
{
cola->next = temp;
cola = temp;
}
return valor;
}
// regresa 1 (true) si la lista est vacia
int empty() { return ITEMS == 0; }
/* retirar elemento de la lista */
DATA_TYPE get()
{

nodo *temp;
DATA_TYPE d;
if ( empty() ) return -1;
d = cabeza->data;
temp = cabeza->next;
if (cabeza) delete cabeza;
cabeza = temp;
ITEMS --;
return d;
}
// regresa el nmero de elementos en lista
int size() { return ITEMS; }
}; // fin de la clase QueueDin
/* punto de prueba */
int main()
{
QueueDin s;
DATA_TYPE d;
// llenando la cola
for (d='A'; d<='Z'; d++)s.put(d);
cout << "Items = " << s.size() << endl;
// vaciando la cola
while ( ! s.empty() )
cout << (DATA_TYPE)s.get() << " ";
cout<<"\n\nRealizado:por David Ortiz\n";
cout << "\nPara terminar presione <Enter>...";
cin.get();
return 0;
}

2.10.Aplicaciones
LISTAS
Un sistema operativo es un programa que gestiona y asigna los recursos de un sistema informtico. Los sistemas
operativos utilizan listas enlazadas de muchas formas. La asignacin de espacio de memoria (uno de los recursos del
sistema) puede gestionarse usando una lista doblemente enlazada de bloques de tamao variables de memoria. El
enlace doble de las listas facilita la sustitucin de bloques de la mitad de la lista. En un sistema multiusuario, el sistema
operativo puede llevar los trabajos en espera del usuario para que se ejecuten mediante colas enlazadas de bloques de
control (registros que contienen informacin sobre la identificacin del usuario, el programa a ejecutar los archivos
asociados con el programa, etc.).
Aunque normalmente una representacin enlazada o secuencial de una lista puede usarse con buenos resultados, hay
veces en las que la enlazada es una eleccin mucho mejor. Por ejemplo, las listas enlazadas se utilizan frecuentemente
para implementar una matriz rala. Una matriz, al ser tan grande de tamao, no es posible (o es intil) definirla como un
array de dos dimensiones, porque la mayor parte de sus elementos son nulos, y estaramos ocupando memoria esttica
demasiado grande sin sentido alguno. De esta forma, una matriz rala puede especificarse de una forma ms eficiente
mediante una implementacin dinmica, con listas enlazadas, ya que podemos almacenar en el campo info los ndices
de la matriz donde se encuentra el elemento no nulo (fila y columna), y obviamente el coeficiente correspondiente.
PILAS
Un ejemplo natural de la aplicacin de la estructura pila aparece durante la ejecucin de un programa de ordenador, en la
forma en que la mquina procesa las llamadas a los procedimientos. Cada llamada a un procedimiento (o funcin) hace
que el sistema almacene toda la informacin asociada con ese procedimiento (parmetros, variables, constantes,
direccin de retorno, etc...) de forma independiente a otros procedimientos y permitiendo que unos procedimientos
puedan invocar a otros distintos (o a s mismos) y que toda esa informacin almacenada pueda ser recuperada
convenientemente cuando corresponda. Como en un procesador slo se puede estar ejecutando un procedimiento, esto
quiere decir que slo es necesario que sean accesibles los datos de un procedimiento (el ltimo activado, el que est en
la cima). De ah que una estructura muy apropiada para este fin sea la estructura pila.

Algunos de los casos ms representativos de aplicacin de las mismas pilas son:


Llamadas a subprogramas
Recursividad
Tratamiento de expresiones aritmticas
Ordenacin
Expresiones aritmticas

COLAS
Las Colas tambin se utilizan en muchas maneras en los sistemas operativos para planificar el uso de los distintos
recursos de la computadora. Uno de estos recursos es la propia CPU (Unidad Central de Procesamiento).
Si est trabajando en un sistema multiusuario, cuando le dice a la computadora que ejecute un programa concreto, el
sistema operativo aade su peticin a su "cola de trabajo".
Cuando su peticin llega al frente de la cola, el programa solicitado pasa a ejecutarse. Igualmente, las colas se utilizan
para asignar tiempo a los distintos usuarios de los dispositivos de entrada/salida (E/S), impresoras, discos, cintas y
dems. El sistema operativo mantiene colas para peticiones de imprimir, leer o escribir en cada uno de estos dispositivos.
Otro ejemplo se refiere a las redes de computadores. Hay muchas redes de computadores personales en las que el disco
est conectado a una mquina, conocida como servidor de archivos. Los usuarios en otras mquinas obtienen acceso a
los archivos sobre la base de que el primero en llegar es el primero atendido, as que la estructura de datos es una cola.

Entre otros ejemplos podemos mencionar:

Por lo general, las llamadas telefnicas a compaas grandes se colocan en una cola, cuando todas las
operadoras estn ocupadas.
En universidades grandes, cuando los recursos son limitados, los estudiantes deben firmar una lista de espera si
todas las terminales estn ocupadas. Aquel estudiante que haya estado ms tiempo en una terminal es el
primero que debe desocuparla, y el que haya estado esperando ms tiempo ser el que tenga acceso primero.
En un supermercado, intentar simulas el funcionamiento de una cola para saber cuntas cajas son necesarias
dependiendo de varias condiciones, el nmero de clientes, y el tiempo medio de clientes.

IMPLEMENTACION APLICANDO UNA LISTA SIMPLE EN C++


#include <iostream>
#include <stdlib.h>
using namespace std;
struct nodo{
int nro;
struct nodo *sgte;
};
typedef struct nodo *Tlista;

/*-------------------- Insertar siguiente Elemento-------------------------*/


void insertarSgte(Tlista &lista, int valor)
{
Tlista t, q = new(struct nodo);
q->nro = valor;
q->sgte = NULL;
if(lista==NULL)
{
lista = q;
}
else
{
t = lista;
while(t->sgte!=NULL)
{
t = t->sgte;
}
t->sgte = q;
}
}
/*----------------------Mostrar Lista--------------------------------------*/
void reportarLista(Tlista lista)
{
int i = 0;
while(lista != NULL)
{
cout <<' '<< i+1 <<") " << lista->nro << endl;
lista = lista->sgte;
i++;
}
}

void calcularMayMenProm(Tlista lista, int mayor, int menor, int promedio, int n){
while(lista!=NULL){
if(mayor<(lista->nro))
mayor=lista->nro;
if(menor>(lista->nro))
menor=lista->nro;
promedio+=lista->nro;
lista=lista->sgte;
}
promedio=promedio/n;
cout<<endl<<"mayor:"<<mayor<<endl;
cout<<endl<<"menor:"<<menor<<endl;
cout<<endl<<"promedio:"<<promedio<<endl<<endl;
}
/*------------------------- Funcion Principal ---------------------------*/
int main(void)
{
Tlista lista = NULL;
int n, mayor, menor, promedio;
system("color 0a");
cout<<"\n\n\t\t[ EJERCICIO UTILIZANDO LISTAS SIMPLES ]\n";
cout<<"\t\t-----------------------------\n\n";
cout<<" EJERCICIO: Calcular mayor, menor y promedio de una lista"<<endl<<endl;
cout<<"\n Ingrese tamanio de lista: ";
cin>>n;
for(int i=1;i<=n;i++){
insertarSgte(lista,i);
}
cout<<endl<<"Elementos de lista"<<endl;
reportarLista(lista);
mayor=lista->nro;
menor=lista->nro;
promedio=lista->nro;
lista=lista->sgte;
calcularMayMenProm(lista, mayor, menor, promedio, n);
cout<<"\n\nRealizado:por David Ortiz\n";
cout << "\nPara terminar presione <Enter>...";
system("pause");
return 0;
}

UNIDAD 3:
ESTRUCTURAS DE
DATOS NO
LINEALES

D a to s n o
lin e a le s

3.1. Estructuras de datos no lineales


Dentro de las estructuras de datos no lineales tenemos los rboles y grafos. Este tipo de estructuras los datos no se
encuentran ubicados secuencialmente. Nos permiten resolver problemas computacionales complejos. Las estructuras de
datos no lineales se caracterizan por no existir una relacin de sus elementos es decir que un elemento puede estar con
cero o ms elementos. Pueden estar enlazados con ms de un elemento anterior y posterior.
Caractersticas:

Superan las desventajas de las listas.


Sus elementos se pueden recorrer en distintas formas, no necesariamente uno detrs de otro.
Son muy tiles para la bsqueda y recuperacin de informacin.

rboles
Grafos

Figura 1: Ejemplo de un rbol.

Figura 2: Ejemplo de un grafo.

3.2. Concepto de recursividad


La recursividad no es una estructura de datos, sino que es una tcnica de programacin que nos permite que un bloque
de instrucciones se ejecute n veces. Es una alternativa diferente para implementar estructuras de repeticin (ciclos) ya
que remplaza en ocasiones a estructuras repetitivas. Los mdulos se hacen llamadas recursivas. Se puede usar en toda
situacin en la cual la solucin pueda ser expresada como una secuencia de movimientos, pasos o transformaciones
gobernadas por un conjunto de reglas no ambiguas.
La recursividad es un concepto difcil de entender en principio, pero luego de analizar diferentes problemas aparecen
puntos comunes. En C++ los mtodos pueden llamarse a s mismos. Si dentro de un mtodo existe la llamada a s
mismo decimos que el mtodo es recursivo. Cuando un mtodo se llama a s mismo, se asigna espacio en la pila para
las nuevas variables locales y parmetros. Al volver de una llamada recursiva, se recuperan de la pila las variables
locales y los parmetros antiguos y la ejecucin se reanuda en el punto de la llamada al mtodo.
La recursividad es un mtodo que lo podemos usar ya que se llama a s mismo. En otras palabras, la recursividad nos
ayuda a que los procesos se repitan as mismos y sea ms fcil su codificacin. La recursividad la podemos usar cuando
tenemos problemas matemticos como la derivada de cualquier funcin.

Figura 3: Imgenes con recursividad.

3.3. Naturaleza de la recursividad


Los clculos se realizan entonces recursivamente donde la solucin ptima de un subproblema se utiliza como dato de
entrada para el siguiente problema. La solucin para todo el problema est disponible cuando se soluciona el ltimo
problema. La forma en que se realizan los clculos recursivos depende de cmo se descomponga el problema original.
En particular, normalmente los subproblemas estn vinculados por restricciones comunes.
Factible de utilizar recursividad:
Para simplificar el cdigo.
Cuando la estructura de datos es recursiva.
No factible utilizar recursividad:

Cuando los mtodos usen arreglos largos.


Cuando el mtodo cambia de manera impredecible de campos.
Cuando las iteraciones sean la mejor opcin.

Recursin vs iteracin
Repeticin
Iteracin: ciclo explcito (se expresa claramente).
Recursin: repetidas invocaciones a mtodo.
Terminacin
Iteracin: el ciclo termina o la condicin del ciclo falla.
Recursin: se reconoce el caso base.
En ambos casos podemos tener ciclos infinitos. La recursividad se debe usar cuando sea realmente necesaria, es decir,
cuando no exista una solucin iterativa simple.

Figura 4: Ejemplo de recursividad.

3.4. Procedimientos y funciones recursivas


Procedimiento
Un procedimiento es un fragmento de cdigo cuya funcin es la de realizar una tarea especfica independientemente del
programa en el que se encuentre. Con los procedimientos se pueden crear algoritmos de ordenacin de arrays, de
modificacin de datos, clculos paralelos a la aplicacin, activacin de servicios, etc. Prcticamente cualquier cosa puede
realizarse desde procedimientos independientes.
Funcin
Una funcin es exactamente lo mismo que un procedimiento salvo por un detalle, una funcin puede devolver un valor al
programa principal y un procedimiento no. Con las funciones podemos realizar todas las tareas que se hacen con los
procedimientos pero adems pudiendo devolver valores (como por ejemplo el rea de un tringulo).
Estructura y uso
Como se puede ver los conceptos de procedimiento y funcin son bastante similares, incluso podramos definir un
procedimiento como una funcin que no retorna ningn valor. Ambos poseen la misma estructura:
Tipo_Dato_Retorno Nombre (Parmetros){
sentencias;
retorno; (slo en el caso de las funciones)
}
Para un procedimiento el Tipo_Dato_Retorno es siempre void, o lo que es lo mismo, nada. En el caso de las funciones
se puede poner cualquier tipo de dato, int, float, char, etc.
Es muy recomendable definir todos los procedimientos y funciones antes de la funcin principal main, el motivo de esto
es que en C todas las funciones y procedimientos deben definirse antes de la primera lnea de cdigo en que se usa. En
caso de que incumplamos sta norma a la hora de compilar tendremos errores.
Para llamar a un procedimiento o funcin debemos hacerlo de la siguiente forma:

En un procedimiento escribimos su nombre seguido de los parmetros entre parntesis:


areatriangulo();

En una funcin debemos declarar antes una variable que almacene el dato que la funcin va a devolver, la
variable debe ser del mismo tipo que el que retorna la funcin. Seguidamente llamamos a la funcin escribiendo
la variable seguido del operador de asignacin, el nombre de la funcin y los parmetros entre parntesis:
float area;
area=areatriangulo();

Implementar funciones y procedimientos en nuestros programas es una muy buena prctica que se debe hacer siempre
que se pueda, ayuda a mantener organizado el cdigo y nos permite reutilizar funcionalidades en otras secciones del
programa.
Parmetros.
A la hora de crear funciones y procedimientos puede que sea necesario que trabajemos con datos que se encuentran
alojados en el programa que los llama, para eso existen los parmetros. Los parmetros son valores que se mandan a un
procedimiento o funcin para que stos puedan trabajar con ellos.

Para utilizar los parmetros en un procedimiento o funcin debemos declarar el tipo de dato y su identificador,
exactamente igual que si declaramos una variable.
Paso de parmetros por valor y por referencia.
El paso de parmetros a procedimientos y funciones es un recurso bastante potente de cara a trabajar con datos,
adems tenemos la posibilidad de pasar estos parmetros de varias formas, por valor o por referencia, esto lo
especificamos cuando definimos el procedimiento o funcin.
El paso por valor es el que acabamos de ver, mediante el paso por valor el procedimiento o funcin reciben los valores
indicados para poder trabajar con ellos, su declaracin es la siguiente:

Procedimiento - void areatriangulo (float base, float altura)


Funcin - float areatriangulo (float base, float altura)

A veces necesitaremos modificar varios de estos datos que hemos pasado en los parmetros, para ello es posible
declarar los parmetros de tal forma que se puedan modificar, a esto se le llama paso por referencia.
Gracias al paso por referencia un procedimiento o funcin no slo realiza unas operaciones y devuelve (en el caso de las
funciones) un valor, sino que tambin puede modificar los valores que se pasan como parmetros.
Para poder realizar el paso de parmetros por referencia es necesario que en la declaracin del procedimiento o funcin
se especifique el espacio de memoria donde se encuentra alojado el dato, esto se realiza as:

Procedimiento - void areatriangulo (float *base, float *altura)


Funcin - float areatriangulo (float *base, float *altura)

Lo que acabamos de definir como parmetros no son las variables en s, sino un puntero que apunta a la direccin de
memoria donde se encuentra alojado el valor, as podremos modificarlo.
Para llamar a un procedimiento o funcin pasando los parmetros por valor se hace de la forma que hemos visto:

Procedimiento - areatriangulo(base, altura);


Funcin - area=areatriangulo(base, altura);

Para llamar a un procedimiento o funcin pasando los parmetros por referencia se hace de la siguiente forma:

Procedimiento - areatriangulo(&base, &altura);


Funcin - area=areatriangulo(&base, &altura);

As lo que pasamos como parmetros no es el valor de la variable, sino la direccin de memoria donde se encuentra el
dato, de sta forma el procedimiento o funcin podr modificar su valor directamente.
Ejemplos de cdigos en C++ implementado lo anterior:
1. rea de un tringulo utilizando procedimientos.
#include <iostream>
using namespace std;
void areatriangulo (void) { //empieza procedimiento
float base, altura;
cout<<"Introduce base: ";
cin>>base;
cout<<"Introduce altura: ";
cin>>altura;

cout<<"El Area es: "<<(base*altura)/2<<"\n";


} // termina procedimiento
int main(int argc, char *argv[])
{
areatriangulo(); // se llama al procedimiento
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

2. rea de un tringulo utilizando funciones.


#include <iostream>
using namespace std;
float areatriangulo (void) {// empieza la funcin
float base, altura;
cout<<"Introduce base: ";
cin>>base;
cout<<"Introduce altura: ";
cin>>altura;
return (base*altura)/2; // retorno de valor
}// termina la funcin
int main(int argc, char *argv[])
{
float area;
area=areatriangulo(); // se llama a la funcin
cout<<"El Area es: "<<area<<"\n";
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

3. rea de un tringulo utilizando funciones con parmetros.


#include <iostream>
using namespace std;
void areatriangulo (float base, float altura) { // empieza funcin
cout<<"El rea es: "<<(base*altura)/2<<"\n";
}// termina funcin
int main(int argc, char *argv[])
{
float base, altura;
cout<<"Introduce base: ";
cin>>base;
cout<<"Introduce altura: ";
cin>>altura;
areatriangulo(base,altura); // llamado a funcin con parametros
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

4. Intercambiar dos nmeros utilizando procedimientos con parmetros punteros.

#include <iostream>
using namespace std;
void intercambio(int *num1, int *num2){// empieza procedimiento
int aux;
aux=*num1;
*num1=*num2;
*num2=aux;
cout<<"El intercambio es: num1="<<*num1<<" num2="<<*num2<<"\n";
}// termina procedimiento
int main(int argc, char *argv[])
{
int num1=3, num2=5;
cout<<"Vamos a intercambiar: num1="<<num1<<" num2="<<num2<<"\n";
intercambio(&num1,&num2); // se llama a procedimiento con parametros por punteros
cout<<"El resultado tras el intercambio es: num1="<<num1<<" num2="<<num2<<"\n";
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

Procedimientos y Funciones Recursivas.


Procedimiento Recursivo: Un procedimiento recursivo es aquel que se llama as mismo, solo que no regresa valor.
Funcin Recursiva: Una funcin recursiva es aquella que se llama as misma y regresa un valor.
Cada mtodo (funcin o procedimiento), tiene ciertas reglas, las cuales se mencionan a continuacin:

La Funcin Recursiva Debe tener ciertos argumentos llamados valores base para que esta ya no se refiera a s
misma.
El Procedimiento Recursivo es donde Cada vez que la funcin se refiera a s misma debe estar ms cerca de los
valores base.

Propiedades de procedimientos recursivos

Debe existir criterio base para que este se llame a s mismo.


Cada vez que el procedimiento se llame a si mismo debe estar ms cerca del criterio base.

El Mtodo De Las Tres Preguntas


Se usa para verificar si hay dentro de un programa funciones recursivas, se debe responder a 3 preguntas.
1. La pregunta caso base:
Hay salida NO recursiva del procedimiento o funcin y la rutina funciona correctamente para este caso base?
2. La pregunta llamador ms pequeo
Cada llamada al procedimiento o funcin se refiere a un caso ms pequeo del problema original?
3. La pregunta caso general
Suponiendo que las llamadas recursivas funcionan correctamente funciona correctamente todo el
procedimiento o funcin?
Crear una subrutina recursiva requiere principalmente la definicin de un "caso base", y entonces definir reglas para
subdividir casos ms complejos en el caso base.

Para una subrutina recursiva es esencial que con cada llamada recursiva, el problema se reduzca de forma que al final
llegue al caso base.
Caso base: una solucin simple para un caso particular (puede haber ms de un caso base).
Escritura de procedimiento y funciones recursivas (pasos a seguir para hacer programas recursivos). Se puede utilizar el
siguiente mtodo para escribir cualquier rutina recursiva.

Primero obtener una funcin exacta del problema a resolver.


A continuacin, determinar el tamao del problema completo que hay que resolver, este tamao determina los
valores de los parmetros en la llamada inicial al procedimiento o funcin.
Resolver el caso base en el que el problema puede expresarse no recursivamente, esto asegura una respuesta
afirmativa a la pregunta base.
Por ltimo, resolver el caso general correctamente en trminos de un caso ms pequeo del mismo problema, es
decir una respuesta afirmativa a las preguntas 2 y 3 del mtodo de las 3 preguntas.

Tipos de Recursividad
En programacin existen dos tipos de recursividad:
1. La recursividad directa: es cuando un procedimiento o una funcin se llama a s mismas.
2. La recursividad indirecta: es cuando un procedimiento o funcin invoca a otro procedimiento o
funcin, y este ltimo vuelve a invocar al primero.
1.

3.5. Resolucin de problemas recursivos


El tema de la recursividad nos ofrece un abanico de posibilidades bastante amplio de cara a comenzar con desarrollos
ms grandes.
El haber visto el concepto de procedimientos y funciones nos facilitar enormemente la tarea de organizar el cdigo, esto
apoyado en el paso de parmetros nos asegura unas posibilidades casi ilimitadas.
Ejemplos de implementacin de procedimientos y funciones recursivas en C++.

1.

Implementacin de un procedimiento recursivo que imprime los dgitos de un nmero natural n en


orden inverso. Por ejemplo, para n=675 la salida debera ser 576.
#include <iostream>
using namespace std;
unsigned leerDato(){ // empieza funcin
unsigned res;
cout << "Introduce numero: ";
cin >> res;
return res;
} // termina funcin
void inverso (unsigned n){ //empieza procedimiento
if(n == 0){
cout << " ";
}else{
cout << n%10;
inverso(unsigned(n/10)); // recursividad
}
} // termina procedimiento
int main() {
cout << "Programa para Invertir un numero. \n\n\n" << endl;
unsigned n =leerDato(); // llama a funcin
cout << "El numero invertido es: ";
inverso(n); // llama a procedimiento
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

2. Implementacin de una funcin que calcule recursivamente el valor de un nmero elevado a la n


potencia xn.
#include <iostream>
using namespace std;
void leerDatos(unsigned& x, unsigned& n){ // empieza procedimiento
cout << "Introduce el numero(x): ";
cin >> x;
cout << "Introduce la potencia(n): ";
cin >> n;
} // termina procedimiento
unsigned potencia(unsigned x, unsigned n){ // empieza funcin
unsigned pot;
if(n == 0){
pot = 1;
}else{

pot = x * potencia(x, n-1); // recursividad


}
return pot; // retorna valor
} // termina funcin
int main() {
unsigned x, n;
cout << "Programa que calcula una potencia x^n \n\n\n" << endl;
leerDatos(x, n); // llama procedimiento
cout << "El resultado es: "<<potencia(x, n); // llama a la funcin
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

TAREA:
1. Realizar la implementacin para calcular el factorial de un nmero utilizando funciones o procedimientos
recursivos.
2. Realizar la implementacin de la sucesin de Fibonacci utilizando funciones o procedimientos recursivos
3.

3.6. rboles
Un rbol es una estructura no lineal en la que cada nodo puede apuntar a uno o varios nodos. Tambin se suele dar una
definicin recursiva: un rbol es una estructura en compuesta por un dato y varios rboles.

Se definen varios conceptos en relacin 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'.
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 se trabajaran tienen otra caracterstica importante: cada nodo slo puede ser apuntado por otro
nodo, es decir, cada nodo slo tendr un padre. Esto hace que estos rboles estn fuertemente jerarquizados, y es lo
que en realidad les da la apariencia de rboles. En cuanto a la posicin dentro del rbol:
Nodo raz: 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 definicin apenas la usaremos, estos son los nodos que no pertenecen a ninguna de
las dos categoras anteriores. En el ejemplo: 'B', 'C', 'D', 'E', 'G' y 'J'.
Otra caracterstica que normalmente tendrn los rboles es que todos los nodos contengan el mismo nmero de
punteros, es decir, se usar la misma estructura para todos los nodos del rbol. Esto hace que la estructura sea ms
sencilla, y por lo tanto tambin los programas para trabajar con ellos.
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 otras palabras un rbol completo se caracteriza porque todos sus nodos terminales
tienen la misma altura.
Los rboles se parecen al resto de las estructuras que se han visto dado que un nodo cualquiera de la estructura,
podemos considerarlo como una estructura independiente. Es decir, un nodo cualquiera puede ser considerado como la
raz de un rbol completo. Existen otros conceptos que definen las caractersticas del rbol, en relacin a su tamao:
Orden: es el nmero 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 nmero de hijos que tiene el elemento con ms 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 ms de tres hijos.
Nivel: se define para cada elemento del rbol como la distancia a la raz, medida en nodos. El nivel de la raz 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.
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 raz de un rbol, tambin 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, estos rboles se conocen tambin como rboles binarios.

Frecuentemente, aunque tampoco es estrictamente necesario, para hacer ms fcil moverse a travs del rbol,
aadiremos un puntero a cada nodo que apunte al nodo padre. De este modo podremos avanzar en direccin a la raz, y
no slo hacia las hojas. Es importante conservar siempre el nodo raz ya que es el nodo a partir del cual se desarrolla el
rbol, si perdemos este nodo, perderemos el acceso a todo el rbol. El nodo tpico de un rbol difiere de los nodos que
hemos visto hasta ahora para listas, aunque slo en el nmero de nodos. Veamos un ejemplo de nodo para crear rboles
de orden tres utilizando estructuras:
struct nodo {
int dato;
struct nodo *rama1;
struct nodo *rama2;
struct nodo *rama3;
};
O generalizando ms:
#define ORDEN 5
struct nodo {
int dato;
struct nodo *rama[ORDEN];
};
Declaracin de un rbol en C++ utilizando clases
class Nodo {
public:
// Constructor:
Nodo(const int dat, Nodo *izq=NULL, Nodo *der=NULL) :
dato(dat), izquierdo(izq), derecho(der) {}
// Miembros:
int dato;
Nodo *izquierdo;
Nodo *derecho;
};
El movimiento a travs de rboles, salvo que se implementen punteros al nodo padre, ser siempre partiendo del nodo
raz hacia un nodo hoja. Cada vez que se llegue a un nuevo nodo se podr optar por cualquiera de los nodos a los que
apunta para avanzar al siguiente nodo.

Operaciones bsicas con rboles


El repertorio de operaciones de las que disponen los rboles son las siguientes:

Aadir o insertar elementos.


Buscar o localizar elementos.
Borrar elementos.
Moverse a travs del rbol.
Recorrer el rbol completo.

Los algoritmos de insercin y borrado dependen en gran medida del tipo de rbol que estemos implementando, de modo
que por ahora los pasaremos por alto y nos centraremos ms en el modo de recorrer rboles.

Recorridos por rboles


El modo evidente de moverse a travs de las ramas de un rbol es siguiendo los punteros. Esos recorridos dependen en
gran medida del tipo y propsito del rbol, pero hay ciertos recorridos que se usan frecuentemente. Se trata de aquellos
recorridos que incluyen todo el rbol.
Hay tres formas de recorrer un rbol completo, y las tres se suelen implementar mediante recursividad. En los tres casos
se sigue siempre a partir de cada nodo todas las ramas una por una. Supongamos que tenemos un rbol de orden tres, y
queremos recorrerlo por completo.

Partiremos del nodo raz:


RecorrerArbol(raiz);
La funcin RecorrerArbol, aplicando recursividad, ser tan sencilla como invocar de nuevo a la funcin RecorrerArbol
para cada una de las ramas:
void RecorrerArbol(Arbol a) {
if(a == NULL) return;
RecorrerArbol(a->rama[0]);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
}
Lo que diferencia los distintos mtodos de recorrer el rbol no es el sistema de hacerlo, sino el momento que elegimos
para procesar el valor de cada nodo con relacin a los recorridos de cada una de las ramas. Los tres tipos son:
Pre-orden
En este tipo de recorrido, el valor del nodo se procesa antes de recorrer las ramas:
void PreOrden(Arbol a) {
if(a == NULL) return;
Procesar(dato);
RecorrerArbol(a->rama[0]);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
}
Si seguimos el rbol del ejemplo en pre-orden, y el proceso de los datos es sencillamente mostrarlos por pantalla,
obtendremos algo as:
ABEKFCGLMDHIJNO
In-orden
En este tipo de recorrido, el valor del nodo se procesa despus de recorrer la primera rama y antes de recorrer la ltima.
Esto tiene ms sentido en el caso de rboles binarios, y tambin cuando existen ORDEN-1 datos, en cuyo caso
procesaremos cada dato entre el recorrido de cada dos ramas (este es el caso de los rboles-b):
void InOrden(Arbol a) {
if(a == NULL) return;
RecorrerArbol(a->rama[0]);
Procesar(dato);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
}

Si seguimos el rbol del ejemplo en in-orden, y el proceso de los datos es sencillamente mostrarlos por pantalla,
obtendremos algo as:
KEBFALGMCHDINJO
Post-orden
En este tipo de recorrido, el valor del nodo se procesa despus de recorrer todas las ramas:
void PostOrden(Arbol a) {
if(a == NULL) return;
RecorrerArbol(a->rama[0]);
RecorrerArbol(a->rama[1]);
RecorrerArbol(a->rama[2]);
Procesar(dato);
}
Si seguimos el rbol del ejemplo en post-orden, y el proceso de los datos es sencillamente mostrarlos por pantalla,
obtendremos algo as:
KEFBLMGCHINOJDA
Eliminar nodos en un rbol
El proceso general es muy sencillo en este caso, pero con una importante limitacin, slo podemos borrar nodos hoja.
El proceso sera el siguiente:
1. Buscar el nodo padre del que queremos eliminar.
2. Buscar el puntero del nodo padre que apunta al nodo que queremos borrar.
3. Liberar el nodo.
4. padre->nodo[i] = NULL;.
Cuando el nodo a borrar no sea un nodo hoja, diremos que hacemos una "poda", y en ese caso eliminaremos el rbol
cuya raz es el nodo a borrar. Se trata de un procedimiento recursivo, aplicamos el recorrido PostOrden, y el proceso ser
borrar el nodo.
El procedimiento es similar al de borrado de un nodo:
1. Buscar el nodo padre del que queremos eliminar.
2. Buscar el puntero del nodo padre que apunta al nodo que queremos borrar.
3. Podar el rbol cuyo padre es nodo.
4. padre->nodo[i] = NULL;.
En el rbol del ejemplo, para podar la rama 'B', recorreremos el subrbol 'B' en postorden, eliminando cada nodo cuando
se procese, de este modo no perdemos los punteros a las ramas apuntadas por cada nodo, ya que esas ramas se
borrarn antes de eliminar el nodo. De modo que el orden en que se borrarn los nodos ser:
KEFyB
rboles ordenados
Slo se hablar de rboles ordenados, ya que son los que tienen ms inters desde el punto de vista de TDA, y los que
tienen ms aplicaciones genricas.
Un rbol ordenado, en general, es aquel a partir del cual se puede obtener una secuencia ordenada siguiendo uno de
los recorridos posibles del rbol: inorden, preorden o postorden. En estos rboles es importante que la secuencia se
mantenga ordenada aunque se aadan o se eliminen nodos. Existen varios tipos de rboles ordenados, que veremos a
continuacin:
rboles binarios de bsqueda (ABB): son rboles de orden 2 que mantienen una secuencia ordenada si se
recorren en inorden.
rboles AVL: son rboles binarios de bsqueda equilibrados, es decir, los niveles de cada rama para cualquier
nodo no difieren en ms de 1.
rboles perfectamente equilibrados: son rboles binarios de bsqueda en los que el nmero de nodos de
cada rama para cualquier nodo no difieren en ms de 1. Son por lo tanto rboles AVL tambin.
rboles 2-3: son rboles de orden 3, que contienen dos claves en cada nodo y que estn tambin equilibrados.
Tambin generan secuencias ordenadas al recorrerlos en inorden.
rboles-B: caso general de rboles 2-3, que para un orden M, contienen M-1 claves.

3.7. Arboles binarios


Un rbol binario es una estructura de datos en la cual cada nodo puede tener un hijo izquierdo y un hijo derecho. No
pueden tener ms de dos hijos (de ah el nombre "binario"). Si algn hijo tiene como referencia a null, es decir que no
almacena ningn dato, entonces este es llamado un nodo externo. En el caso contrario el hijo es llamado un nodo
interno. Usos comunes de los rboles binarios son los rboles binarios de bsqueda, los montculos binarios y
Codificacin de Huffman.
Un rbol binario es un rbol en el que ningn nodo puede tener ms de dos subrboles. En un rbol binario cada nodo
puede tener cero, uno o dos hijos (subrboles). Se conoce el nodo de la izquierda como hijo izquierdo y el nodo de la
derecha como hijo derecho.
rbol binario de bsqueda (ABB)
Se trata de rboles de orden 2 en los que se cumple que para cada nodo, el valor de la clave de la raz del subrbol
izquierdo es menor que el valor de la clave del nodo y que el valor de la clave raz del subrbol derecho es mayor que el
valor de la clave del nodo.
Un rbol binario de bsqueda tambin llamados BST (acrnimo del ingls Binary Search Tree) es un tipo particular de
rbol binario que presenta una estructura de datos en forma de rbol usada en informtica.
Un rbol binario no vaco, de raz R, es un rbol binario de bsqueda si:
En caso de tener subrbol izquierdo, la raz R debe ser mayor que el valor mximo almacenado en el subrbol
izquierdo, y que el subrbol izquierdo sea un rbol binario de bsqueda.
En caso de tener subrbol derecho, la raz R debe ser menor que el valor mnimo almacenado en el subrbol
derecho, y que el subrbol derecho sea un rbol binario de bsqueda.
Para una fcil comprensin queda resumido en que es un rbol binario que cumple que el subrbol izquierdo de
cualquier nodo (si no est vaco) contiene valores menores que el que contiene dicho nodo, y el subrbol derecho (si no
est vaco) contiene valores mayores.

Operaciones en ABB
Las operaciones que se pueden realizar sobre un ABB son parecidas a las que se realizan sobre otras estructuras de
datos, ms alguna otra propia de rboles:
Buscar un elemento.
Insertar un elemento.
Borrar un elemento.
Movimientos a travs del rbol:
o Izquierda.
o Derecha.
o Raz.
Informacin:
o Comprobar si un rbol est vaco.
o Calcular el nmero de nodos.
o Comprobar si el nodo es hoja.

o
o

Calcular la altura de un nodo.


Calcular la altura de un rbol.

Buscar un elemento
Partiendo siempre del nodo raz, el modo de buscar un elemento se define de forma recursiva.
Si el rbol est vaco, terminamos la bsqueda: el elemento no est en el rbol.
Si el valor del nodo raz es igual que el del elemento que buscamos, terminamos la bsqueda con xito.
Si el valor del nodo raz es mayor que el elemento que buscamos, continuaremos la bsqueda en el rbol
izquierdo.
Si el valor del nodo raz es menor que el elemento que buscamos, continuaremos la bsqueda en el rbol
derecho.
El valor de retorno de una funcin de bsqueda en un ABB puede ser un puntero al nodo encontrado, o NULL, si
no se ha encontrado.
Insertar un elemento
Para insertar un elemento nos basamos en el algoritmo de bsqueda. Si el elemento est en el rbol no lo insertaremos.
Si no lo est, lo insertaremos a continuacin del ltimo nodo visitado.
Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raz actual. El valor inicial para ese
puntero es NULL.
Padre = NULL
nodo = Raiz
Bucle: mientras actual no sea un rbol vaco o hasta que se encuentre el elemento.
o Si el valor del nodo raz es mayor que el elemento que buscamos, continuaremos la bsqueda en el rbol
izquierdo: Padre=nodo, nodo=nodo->izquierdo.
o Si el valor del nodo raz es menor que el elemento que buscamos, continuaremos la bsqueda en el
rbol derecho: Padre=nodo, nodo=nodo->derecho.
Si nodo no es NULL, el elemento est en el rbol, por lo tanto salimos.
Si Padre es NULL, el rbol estaba vaco, por lo tanto, el nuevo rbol slo contendr el nuevo elemento, que ser
la raz del rbol.
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.
Este modo de actuar asegura que el rbol sigue siendo ABB.
Borrar un elemento
Para borrar un elemento tambin nos basamos en el algoritmo de bsqueda. Si el elemento no est en el rbol no lo
podremos borrar. Si est, hay dos casos posibles:
1. Se trata de un nodo hoja: en ese caso lo borraremos directamente.
2. Se trata de un nodo rama: en ese caso no podemos eliminarlo, puesto que perderamos todos los elementos del
rbol de que el nodo actual es padre. En su lugar buscamos el nodo ms a la izquierda del subrbol derecho, o
el ms a la derecha del subrbol izquierdo e intercambiamos sus valores. A continuacin eliminamos el nodo
hoja.
Necesitamos un puntero auxiliar para conservar una referencia al padre del nodo raz actual. El valor inicial para ese
puntero es NULL.
Padre = NULL
Si el rbol est vaco: el elemento no est en el rbol, por lo tanto salimos sin eliminar ningn elemento.
Si el valor del nodo raz es igual que el del elemento que buscamos, estamos ante uno de los siguientes casos:
o El nodo raz es un nodo hoja:

Si 'Padre' es NULL, el nodo raz es el nico del rbol, por lo tanto el puntero al rbol debe ser
NULL.
Si raz es la rama derecha de 'Padre', hacemos que esa rama apunte a NULL.
Si raz es la rama izquierda de 'Padre', hacemos que esa rama apunte a NULL.
Eliminamos el nodo, y salimos.
o El nodo no es un nodo hoja:
Buscamos el 'nodo' ms a la izquierda del rbol derecho de raz o el ms a la derecha del rbol
izquierdo. Hay que tener en cuenta que puede que slo exista uno de esos rboles. Al mismo
tiempo, actualizamos 'Padre' para que apunte al padre de 'nodo'.
Intercambiamos los elementos de los nodos raz 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 raz es mayor que el elemento que buscamos, continuaremos la bsqueda en el rbol
izquierdo.
Si el valor del nodo raz es menor que el elemento que buscamos, continuaremos la bsqueda en el rbol
derecho.

Ejemplo 1: Borrar un nodo hoja


En el rbol de ejemplo, borrar el nodo 3.
1. Localizamos el nodo a borrar, al tiempo que mantenemos un puntero a 'Padre'.
2. Hacemos que el puntero de 'Padre' que apuntaba a 'nodo', ahora apunte a NULL.
3. Borramos el 'nodo'.

Ejemplo 2: Borrar un nodo rama con intercambio de un nodo hoja.


En el rbol de ejemplo, borrar el nodo 4.
1. Localizamos el nodo a borrar ('raz').
2. Buscamos el nodo ms a la derecha del rbol izquierdo de 'raz', en este caso el 3, al tiempo que mantenemos
un puntero a 'Padre' a 'nodo'.
3. Intercambiamos los elementos 3 y 4.
4. Hacemos que el puntero de 'Padre' que apuntaba a 'nodo', ahora apunte a NULL.
5. Borramos el 'nodo'.

Ejemplo 3: Borrar un nodo rama con intercambio de un nodo rama.


Para este ejemplo usaremos otro rbol. En ste borraremos el elemento 6.

1. Localizamos el nodo a borrar ('raz').


2. Buscamos el nodo ms a la izquierda del rbol derecho de 'raz', en este caso el 12, ya que el rbol derecho no
tiene nodos a su izquierda, si optamos por la rama izquierda, estaremos en un caso anlogo. Al mismo tiempo
que mantenemos un puntero a 'Padre' a 'nodo'.
3. Intercambiamos los elementos 6 y 12.
4. Ahora tenemos que repetir el bucle para el nodo 6 de nuevo, ya que no podemos eliminarlo.

5. Localizamos de nuevo el nodo a borrar ('raz').


6. Buscamos el nodo ms a la izquierda del rbol derecho de 'raz', en este caso el 16, al mismo tiempo que
mantenemos un puntero a 'Padre' a 'nodo'.
7. Intercambiamos los elementos 6 y 16.
8. Hacemos que el puntero de 'Padre' que apuntaba a 'nodo', ahora apunte a NULL.
9. Borramos el 'nodo'.

Este modo de actuar asegura que el rbol sigue siendo ABB.


Ejemplo de cdigo en C++ de un rbol binario de bsqueda con sus operaciones:
#include <iostream>
using namespace std;
class ArbolABB {
private:
//// Clase local de Lista para Nodo de ArbolBinario:
class Nodo {
public:
// Constructor:
Nodo(const int dat, Nodo *izq=NULL, Nodo *der=NULL) :
dato(dat), izquierdo(izq), derecho(der) {}
// Miembros:
int dato;

Nodo *izquierdo;
Nodo *derecho;
};
// Punteros de la lista, para cabeza y nodo actual:
Nodo *raiz;
Nodo *actual;
int contador;
int altura;
public:
// Constructor y destructor bsicos:
ArbolABB() : raiz(NULL), actual(NULL) {}
~ArbolABB() { Podar(raiz); }
// Insertar en rbol ordenado:
void Insertar(const int dat);
// Borrar un elemento del rbol:
void Borrar(const int dat);
// Funcin de bsqueda:
bool Buscar(const int dat);
// Comprobar si el rbol est vaco:
bool Vacio(Nodo *r) { return r==NULL; }
// Comprobar si es un nodo hoja:
bool EsHoja(Nodo *r) { return !r->derecho && !r->izquierdo; }
// Contar nmero de nodos:
const int NumeroNodos();
const int AlturaArbol();
// Calcular altura de un int:
int Altura(const int dat);
// Devolver referencia al int del nodo actual:
int &ValorActual() { return actual->dato; }
// Moverse al nodo raiz:
void Raiz() { actual = raiz; }
// Aplicar una funcin a cada elemento del rbol:
void InOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
void PreOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
void PostOrden(void (*func)(int&) , Nodo *nodo=NULL, bool r=true);
private:
// Funciones auxiliares
void Podar(Nodo* &);
void auxContador(Nodo*);
void auxAltura(Nodo*, int);
};
// Poda: borrar todos los nodos a partir de uno, incluido
void ArbolABB::Podar(Nodo* &nodo)
{
// Algoritmo recursivo, recorrido en postorden
if(nodo) {
Podar(nodo->izquierdo); // Podar izquierdo
Podar(nodo->derecho); // Podar derecho
delete nodo;
// Eliminar nodo
nodo = NULL;
}
}
// Insertar un int en el rbol ABB
void ArbolABB::Insertar(const int dat)
{
Nodo *padre = NULL;
actual = raiz;
// Buscar el int en el rbol, manteniendo un puntero al nodo padre
while(!Vacio(actual) && dat != actual->dato) {
padre = actual;
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}

// Si se ha encontrado el elemento, regresar sin insertar


if(!Vacio(actual)) return;
// Si padre es NULL, entonces el rbol estaba vaco, el nuevo nodo ser
// el nodo raiz
if(Vacio(padre)) raiz = new Nodo(dat);
// Si el int es menor que el que contiene el nodo padre, lo insertamos
// en la rama izquierda
else if(dat < padre->dato) padre->izquierdo = new Nodo(dat);
// Si el int es mayor que el que contiene el nodo padre, lo insertamos
// en la rama derecha
else if(dat > padre->dato) padre->derecho = new Nodo(dat);
}
// Eliminar un elemento de un rbol ABB
void ArbolABB::Borrar(const int dat)
{
Nodo *padre = NULL;
Nodo *nodo;
int aux;
actual = raiz;
// Mientras sea posible que el valor est en el rbol
while(!Vacio(actual)) {
if(dat == actual->dato) { // Si el valor est en el nodo actual
if(EsHoja(actual)) { // Y si adems es un nodo hoja: lo borramos
if(padre) // Si tiene padre (no es el nodo raiz)
// Anulamos el puntero que le hace referencia
if(padre->derecho == actual) padre->derecho = NULL;
else if(padre->izquierdo == actual) padre->izquierdo = NULL;
delete actual; // Borrar el nodo
actual = NULL;
return;
}
else { // Si el valor est en el nodo actual, pero no es hoja
// Buscar nodo
padre = actual;
// Buscar nodo ms izquierdo de rama derecha
if(actual->derecho) {
nodo = actual->derecho;
while(nodo->izquierdo) {
padre = nodo;
nodo = nodo->izquierdo;
}
}
// O buscar nodo ms derecho de rama izquierda
else {
nodo = actual->izquierdo;
while(nodo->derecho) {
padre = nodo;
nodo = nodo->derecho;
}
}
// Intercambiar valores de no. a borrar u nodo encontrado
// y continuar, cerrando el bucle. El nodo encontrado no tiene
// por qu ser un nodo hoja, cerrando el bucle nos aseguramos
// de que slo se eliminan nodos hoja.
aux = actual->dato;
actual->dato = nodo->dato;
nodo->dato = aux;
actual = nodo;
}
}
else { // Todava no hemos encontrado el valor, seguir buscndolo
padre = actual;
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}
}
}

// Recorrido de rbol en inorden, aplicamos la funcin func, que tiene


// el prototipo:
// void func(int&);
void ArbolABB::InOrden(void (*func)(int&) , Nodo *nodo, bool r)
{
if(r) nodo = raiz;
if(nodo->izquierdo) InOrden(func, nodo->izquierdo, false);
func(nodo->dato);
if(nodo->derecho) InOrden(func, nodo->derecho, false);
}
// Recorrido de rbol en preorden, aplicamos la funcin func, que tiene
// el prototipo:
// void func(int&);
void ArbolABB::PreOrden(void (*func)(int&), Nodo *nodo, bool r)
{
if(r) nodo = raiz;
func(nodo->dato);
if(nodo->izquierdo) PreOrden(func, nodo->izquierdo, false);
if(nodo->derecho) PreOrden(func, nodo->derecho, false);
}
// Recorrido de rbol en postorden, aplicamos la funcin func, que tiene
// el prototipo:
// void func(int&);
void ArbolABB::PostOrden(void (*func)(int&), Nodo *nodo, bool r)
{
if(r) nodo = raiz;
if(nodo->izquierdo) PostOrden(func, nodo->izquierdo, false);
if(nodo->derecho) PostOrden(func, nodo->derecho, false);
func(nodo->dato);
}
// Buscar un valor en el rbol
bool ArbolABB::Buscar(const int dat)
{
actual = raiz;
// Todava puede aparecer, ya que quedan nodos por mirar
while(!Vacio(actual)) {
if(dat == actual->dato) return true; // int encontrado
else if(dat > actual->dato) actual = actual->derecho; // Seguir
else if(dat < actual->dato) actual = actual->izquierdo;
}
return false; // No est en rbol
}
// Calcular la altura del nodo que contiene el int dat
int ArbolABB::Altura(const int dat)
{
int altura = 0;
actual = raiz;
// Todava puede aparecer, ya que quedan nodos por mirar
while(!Vacio(actual)) {
if(dat == actual->dato) return altura; // int encontrado
else {
altura++; // Incrementamos la altura, seguimos buscando
if(dat > actual->dato) actual = actual->derecho;
else if(dat < actual->dato) actual = actual->izquierdo;
}
}
return -1; // No est en rbol
}
// Contar el nmero de nodos
const int ArbolABB::NumeroNodos()
{

contador = 0;
auxContador(raiz); // FUncin auxiliar
return contador;
}
// Funcin auxiliar para contar nodos. Funcin recursiva de recorrido en
// preorden, el proceso es aumentar el contador
void ArbolABB::auxContador(Nodo *nodo)
{
contador++; // Otro nodo
// Continuar recorrido
if(nodo->izquierdo) auxContador(nodo->izquierdo);
if(nodo->derecho) auxContador(nodo->derecho);
}
// Calcular la altura del rbol, que es la altura del nodo de mayor altura.
const int ArbolABB::AlturaArbol()
{
altura = 0;
auxAltura(raiz, 0); // Funcin auxiliar
return altura;
}
// Funcin auxiliar para calcular altura. Funcin recursiva de recorrido en
// postorden, el proceso es actualizar la altura slo en nodos hojas de mayor
// altura de la mxima actual
void ArbolABB::auxAltura(Nodo *nodo, int a)
{
// Recorrido postorden
if(nodo->izquierdo) auxAltura(nodo->izquierdo, a+1);
if(nodo->derecho) auxAltura(nodo->derecho, a+1);
// Proceso, si es un nodo hoja, y su altura es mayor que la actual del
// rbol, actualizamos la altura actual del rbol
if(EsHoja(nodo) && a > altura) altura = a;
}
// Funcin de prueba para recorridos del rbol
void Mostrar(int &d)
{
cout << d << ",";
}
int main()
{
// Un rbol de enteros
ArbolABB ArbolInt;
// Insercin de nodos en rbol:
ArbolInt.Insertar(10);
ArbolInt.Insertar(5);
ArbolInt.Insertar(12);
ArbolInt.Insertar(4);
ArbolInt.Insertar(7);
ArbolInt.Insertar(3);
ArbolInt.Insertar(6);
ArbolInt.Insertar(9);
ArbolInt.Insertar(8);
ArbolInt.Insertar(11);
ArbolInt.Insertar(14);
ArbolInt.Insertar(13);
ArbolInt.Insertar(2);
ArbolInt.Insertar(1);
ArbolInt.Insertar(15);
ArbolInt.Insertar(10);
ArbolInt.Insertar(17);
ArbolInt.Insertar(18);
ArbolInt.Insertar(16);

cout << "Ejemplo de Arbol Binario de Bsqueda en C++ " << ArbolInt.AlturaArbol() << endl;
cout << "\n\nAltura de arbol: " << ArbolInt.AlturaArbol() << endl;
// Mostrar el rbol en tres ordenes distintos:
cout << "InOrden: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
cout << "PreOrden: ";
ArbolInt.PreOrden(Mostrar);
cout << endl;
cout << "PostOrden: ";
ArbolInt.PostOrden(Mostrar);
cout << endl;
// Borraremos algunos elementos:
cout << "Numero de nodos: " << ArbolInt.NumeroNodos() << endl;
ArbolInt.Borrar(5);
cout << "\nBorrar 5: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(8);
cout << "Borrar 8: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(15);
cout << "Borrar 15: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(245);
cout << "Borrar 245: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
ArbolInt.Borrar(4);
cout << "Borrar 4: ";
ArbolInt.InOrden(Mostrar);
ArbolInt.Borrar(17);
cout << endl;
cout << "Borrar 17: ";
ArbolInt.InOrden(Mostrar);
cout << endl;
// Veamos algunos parmetros
cout << "Numero nodos: " << ArbolInt.NumeroNodos() << endl;
cout << "Altura de 1: " << ArbolInt.Altura(1) << endl;
cout << "Altura de 10: " << ArbolInt.Altura(10) << endl;
cout << "Altura de arbol: " << ArbolInt.AlturaArbol() << endl;
cout<<"\n\nRealizado por: David Ortiz\n";
cin.get();
return 0;
}

UNIDAD 4:
MTODOS DE
ORDENAMIENTO Y
BSQUEDA

4.1. Algoritmos de ordenamiento


Uno de los procedimientos ms comunes y tiles en el procesamiento de datos, es la ordenacin de los mismos. Se
considera ordenar al proceso de reorganizar un conjunto dado de objetos en una secuencia determinada (patrn de
arreglo). El objetivo de este proceso generalmente es facilitar la bsqueda de uno o ms elementos pertenecientes a un
conjunto.
El ordenamiento es una labor comn que realizamos continuamente. Pero te has preguntado qu es ordenar? No? Es
que es algo tan corriente en nuestras vidas que no nos detenemos a pensar en ello. Ordenar es simplemente colocar
informacin de una manera especial basndonos en un criterio de ordenamiento.
En la computacin el ordenamiento de datos tambin cumple un rol muy importante, ya sea como un fin en s o como
parte de otros procedimientos ms complejos. Se han desarrollado muchas tcnicas en este mbito, cada una con
caractersticas especficas, y con ventajas y desventajas sobre las dems.
Como ejemplos de conjunto de datos ordenados tenemos:
Meses del ao (ordenados de 1 al 12).
Listado de estudiantes (ordenados alfabticamente).
Guas Telefnicas (ordenadas por Pas/por regin/por sector/por orden alfabtico)
La ordenacin, tanto numrica como alfanumrica, sigue las mismas reglas que empleamos nosotros en la vida normal.
Esto es, un dato numrico es mayor que otro cuando su valor es ms grande, y una cadena de caracteres es mayor que
otra cuando esta despus por orden alfabtico.
Antes de comenzar a ver cada algoritmo vamos estudiar algunos conceptos, para que no haya confusiones:
Clave: La parte de un registro por la cual se ordena la lista. Por ejemplo, una lista de registros con campos
nombre, direccin y telfono se puede ordenar alfabticamente de acuerdo a la clave nombre. En este caso los
campos direccin y telfono no se toman en cuenta en el ordenamiento.
Criterio de ordenamiento (o de comparacin): EL criterio que utilizamos para asignar valores a los registros
con base en una o ms claves. De esta manera decidimos si un registro es mayor o menor que otro. En el
pseudocdigo presentado ms adelante simplemente se utilizarn los smbolos < y >, para mayor simplicidad.
Registro: Un grupo de datos que forman la lista. Pueden ser datos atmicos (enteros, caracteres, reales, etc.) o
grupos de ellos, que en C equivalen a las estructuras.
Cuando se estudian algoritmos de todo tipo, no slo de ordenamiento, es bueno tener una forma de evaluarlos antes de
pasarlos a cdigo, que se base en aspectos independientes de la plataforma o el lenguaje. De esta manera podremos
decidir cul se adapta mejor a los requerimientos de nuestro programa. As que veamos estos aspectos:
Estabilidad: Cmo se comporta con registros que tienen claves iguales. Algunos algoritmos mantienen el orden
relativo entre stos y otros no. Veamos un ejemplo. Si tenemos la siguiente lista de datos (nombre, edad): "Pedro
19, Juan 23, Felipe 15, Marcela 20, Juan 18, Marcela 17", y la ordenamos alfabticamente por el nombre con un
algoritmo estable quedara as: "Felipe 15, Marcela 20, Marcela 17, Juan 23, Juan 18, Pedro 19". Un algoritmo no
estable podra dejar a Juan 18 antes de Juan 23, o a Marcela 20 despus de Marcela 17.
Tiempo de ejecucin: La complejidad del algoritmo, que no tiene que ver con dificultad, sino con rendimiento.
Es una funcin independiente de la implementacin. Se tiene que identificar una operacin fundamental que
realice nuestro algoritmo, que en este caso es comparar. Ahora contamos cuntas veces el algoritmo se necesita
comparar. Si en una lista de n trminos realiza n comparaciones la complejidad es O(n). Algunos ejemplos de
complejidades comunes son:
o O(1) : Complejidad constante.
o O(n) : Complejidad lineal.
o O(n2) : Complejidad cuadrtica.
o O(log(n)) : Complejidad logartmica.
Ahora podemos decir que un algoritmo de complejidad O(n) es ms rpido que uno de complejidad O(n 2). Otro
aspecto a considerar es la diferencia entre el peor y el mejor caso. Cada algoritmo se comporta de modo
diferente de acuerdo a cmo se le entregue la informacin; por eso es conveniente estudiar su comportamiento
en casos extremos, como cuando los datos estn prcticamente ordenados o muy desordenados.

Requerimientos de memoria: El algoritmo puede necesitar memoria adicional para realizar su labor. En general
es preferible que no sea as, pero es comn en la programacin tener que sacrificar memoria por rendimiento.
Hay bastantes otros aspectos que se pueden tener en cuenta. Por ltimo estableceremos algunas convenciones sobre
el pseudocdigo:
Vamos a ordenar la lista en forma ascendiente, es decir, de menor a mayor. Obviamente es esencialmente lo
mismo que hacerlo en forma inversa.
La forma de intercambiar los elementos depende de la estructura de datos: si es un arreglo (dinmico o
esttico) es necesario guardar una copia del primer elemento, asignarle el segundo al primero y el temporal al
segundo. La variable temporal es necesaria, porque de lo contrario se perdera uno de los elementos. Si la
estructura es una lista dinmica el procedimiento es parecido, pero se utilizan las direcciones de los elementos.
En el pseudocdigo se utilizar el primer mtodo.
La lista se manejar como un arreglo de C: si tiene TAM elementos, el primer elemento es lista[0] y el ltimo
es lista[TAM-1]. Esto ser as para todo el pseudocdigo presentado en este artculo.
Los mtodos de ordenacin, pueden agruparse en dos grandes grupos:
Los internos: Es cuando los datos estn disponibles de un rea de la memoria principal, como cuando se leen
un conjunto de datos desde el teclado.
Los externos: Los datos estn guardados en un medio externo, como puede ser un fichero, una base de datos,
etc. En donde los datos estn alojados en el disco duro u otro medio fsico.
Existen varios mtodos de ordenamiento pero solo se mencionaran los cuatro modos de ordenamiento ms usados
los cuales son:

Algoritmo de Ordenamiento Burbuja (Buble Sort).


Algoritmo de Ordenamiento por Seleccin.
Algoritmo de Ordenamiento por Insercin.
Algoritmo de Ordenamiento Rpido (Quick Sort).
Tabla comparativa de algoritmos

Eligiendo el ms adecuado
Cmo saber cul es el que necesitas? Cul es el algoritmo ms adecuado? Cada algoritmo se comporta de modo
diferente de acuerdo a la cantidad y la forma en que se le presenten los datos, entre otras cosas. No existe el mejor
algoritmo de ordenamiento. Slo existe el ms adecuado para cada caso particular. Debes conocer a fondo el problema
que quieres resolver, y aplicar el ms adecuado. Aunque hay algunas preguntas que te pueden ayudar a elegir:

Qu grado de orden tendr la informacin que vas a manejar? Si la informacin va a estar casi ordenada y
no quieres complicarte, un algoritmo sencillo como el ordenamiento burbuja ser suficiente. Si por el contrario los
datos van a estar muy desordenados, un algoritmo poderoso como Quicksort puede ser el ms indicado. Y si no
puedes hacer una presuncin sobre el grado de orden de la informacin, lo mejor ser elegir un algoritmo que se
comporte de manera similar en cualquiera de estos dos casos extremos.
Qu cantidad de datos vas a manipular? Si la cantidad es pequea, no es necesario utilizar un algoritmo
complejo, y es preferible uno de fcil implementacin. Una cantidad muy grande puede hacer prohibitivo utilizar
un algoritmo que requiera de mucha memoria adicional.
Qu tipo de datos quieres ordenar? Algunos algoritmos slo funcionan con un tipo especfico de datos
(enteros, enteros positivos, etc.) y otros son generales, es decir, aplicables a cualquier tipo de dato.
Qu tamao tienen los registros de tu lista? Algunos algoritmos realizan mltiples intercambios (burbuja,
insercin). Si los registros son de gran tamao estos intercambios son ms lentos.

ORDENAMIENTO BURBUJA (BUBBLESORT)


Este es el algoritmo ms sencillo, ideal para empezar a entender los ordenamientos. Consiste en ciclar repetidamente a
travs de la lista, comparando elementos adyacentes de dos en dos. Si un elemento es mayor que el que est en la
siguiente posicin se intercambian.
Pseudocdigo en C++.
Tabla de variables

1. for (i=1; i<TAM; i++)


2.
for j=0 ; j<TAM - 1; j++)
3.
if (lista[j] > lista[j+1])
4.
temp = lista[j];
5.
lista[j] = lista[j+1];
6.
lista[j+1] = temp;
Ejemplo
Vamos a ver un ejemplo. Esta es nuestra lista:
4-3-5-21
Tenemos 5 elementos. Es decir, TAM toma el valor 5. Comenzamos comparando el primero con el segundo elemento. 4
es mayor que 3, as que intercambiamos. Ahora tenemos:
3-4-5-21
Ahora comparamos el segundo con el tercero: 4 es menor que 5, as que no hacemos nada. Continuamos con el tercero
y el cuarto: 5 es mayor que 2. Intercambiamos y obtenemos:
3-4-2-51
Comparamos el cuarto y el quinto: 5 es mayor que 1. Intercambiamos nuevamente:
3-4-2-15
Repitiendo este proceso vamos obteniendo los siguientes resultados:
3-2-1-4-5
2-1-3-4-5
1-2-3-45
Optimizando.
Se pueden realizar algunos cambios en este algoritmo que pueden mejorar su rendimiento:
Si observas bien, te dars cuenta que en cada pasada a travs de la lista un elemento va quedando en su
posicin final. Si no te queda claro mira el ejemplo de arriba. En la primera pasada el 5 (elemento mayor) qued
en la ltima posicin, en la segunda el 4 (el segundo mayor elemento) qued en la penltima posicin. Podemos
evitar hacer comparaciones innecesarias si disminuimos el nmero de stas en cada pasada. Tan slo hay que
cambiar el ciclo interno de esta manera:
for (j=0; j<TAM - i; j++)
Puede ser que los datos queden ordenados antes de completar el ciclo externo. Podemos modificar el algoritmo
para que verifique si se han realizado intercambios. Si no se han hecho entonces terminamos con la ejecucin,

pues eso significa que los datos ya estn ordenados. Te dejo como tarea que modifiques el algoritmo para hacer
esto :-).
Otra forma es ir guardando la ltima posicin en que se hizo un intercambio, y en la siguiente pasada slo
comparar hasta antes de esa posicin.
Anlisis del algoritmo.
ste es el anlisis para la versin no optimizada del algoritmo:
Estabilidad: Este algoritmo nunca intercambia registros con claves iguales. Por lo tanto es estable.
Requerimientos de Memoria: Este algoritmo slo requiere de una variable adicional para realizar los
intercambios.
Tiempo de Ejecucin: El ciclo interno se ejecuta n veces para una lista de n elementos. El ciclo externo tambin
se ejecuta n veces. Es decir, la complejidad es n * n = O(n 2). El comportamiento del caso promedio depende del
orden de entrada de los datos, pero es slo un poco mejor que el del peor caso, y sigue siendo O(n 2).
Ventajas:
Fcil implementacin.
No requiere memoria adicional.
Desventajas:
Muy lento.
Realiza numerosas comparaciones.
Realiza numerosos intercambios.
En resumen este algoritmo es uno de los ms pobres en rendimiento y no es muy recomendable usarlo.
ORDENAMIENTO POR SELECCIN
Este algoritmo tambin es sencillo. Consiste en lo siguiente:

Buscas el elemento ms pequeo de la lista.


Lo intercambias con el elemento ubicado en la primera posicin de la lista.
Buscas el segundo elemento ms pequeo de la lista.
Lo intercambias con el elemento que ocupa la segunda posicin en la lista.
Repites este proceso hasta que hayas ordenado toda la lista.

Pseudocdigo en C++.
Tabla de variables

1. for (i=0; i<TAM - 1; i++)


2.
pos_men = Menor(lista, TAM, i);
3.
temp = lista[i];
4.
lista[i] = lista [pos_men];
5.
lista [pos_men] = temp;
Nota: Menor(lista, TAM, i) es una funcin que busca el menor elemento entre las posiciones i y TAM-1. La bsqueda es
lineal (elemento por elemento). No lo incluyo en el pseudocdigo porque es bastante simple.
Ejemplo.
Vamos a ordenar la siguiente lista (la misma del ejemplo anterior):

4-3-5-21
Comenzamos buscando el elemento menor entre la primera y ltima posicin. Es el 1. Lo intercambiamos con el 4 y la
lista queda as:
1-3-5-2-4
Ahora buscamos el menor elemento entre la segunda y la ltima posicin. Es el 2. Lo intercambiamos con el elemento en
la segunda posicin, es decir el 3. La lista queda as:
1-2-5-34
Buscamos el menor elemento entre la tercera posicin y la ltima. Es el 3, que intercambiamos con el 5:
1-2-3-54
El menor elemento entre la cuarta y quinta posicin es el 4, que intercambiamos con el 5:
1-2-3-45
Anlisis del algoritmo.

Estabilidad: Aqu discrepo con un libro de la bibliografa que dice que no es estable. Yo lo veo as: si tengo dos
registros con claves iguales, el que ocupe la posicin ms baja ser el primero que sea identificado como menor.
Es decir que ser el primero en ser desplazado. El segundo registro ser el menor en el siguiente ciclo y quedar
en la posicin adyacente. Por lo tanto se mantendr el orden relativo. Lo que podra hacerlo inestable sera que
el ciclo que busca el elemento menor revisara la lista desde la ltima posicin hacia atrs. Qu opinas t? Yo
digo que es estable, pero para hacerle caso al libro (el autor debe sabe ms que yo cierto?:-)) vamos a decir
que no es estable.
Requerimientos de Memoria: Al igual que el ordenamiento burbuja, este algoritmo slo necesita una variable
adicional para realizar los intercambios.
Tiempo de Ejecucin: El ciclo externo se ejecuta n veces para una lista de n elementos. Cada bsqueda
requiere comparar todos los elementos no clasificados. Luego la complejidad es O(n 2). Este algoritmo presenta
un comportamiento constante independiente del orden de los datos. Luego la complejidad promedio es tambin
O(n2).

Ventajas:
Fcil implementacin.
No requiere memoria adicional.
Realiza pocos intercambios.
Rendimiento constante: poca diferencia entre el peor y el mejor caso.
Desventajas:
Lento.
Realiza numerosas comparaciones.
Este es un algoritmo lento. No obstante, ya que slo realiza un intercambio en cada ejecucin del ciclo externo, puede
ser una buena opcin para listas con registros grandes y claves pequeas.
ORDENAMIENTO POR INSERCIN
Este algoritmo tambin es bastante sencillo. Has jugado cartas? Cmo las vas ordenando cuando las recibes? Yo lo
hago de esta manera: tomo la primera y la coloco en mi mano. Luego tomo la segunda y la comparo con la que tengo: si
es mayor, la pongo a la derecha, y si es menor a la izquierda. Despus tomo la tercera y la comparo con las que tengo en
la mano, desplazndola hasta que quede en su posicin final. Contino haciendo esto, insertando cada carta en la
posicin que le corresponde, hasta que las tengo todas en orden. Lo haces as t tambin? Bueno, pues si es as
entonces comprenders fcilmente este algoritmo, porque es el mismo concepto.
Para simular esto en un programa necesitamos tener en cuenta algo: no podemos desplazar los elementos as como as
o se perder un elemento. Lo que hacemos es guardar una copia del elemento actual y desplazar todos los elementos
mayores hacia la derecha. Luego copiamos el elemento guardado en la posicin del ltimo elemento que se desplaz.
Pseudocdigo en C.
Tabla de variables

1. for (i=1; i<TAM; i++)


2.
temp = lista[i];
3.
j = i - 1;
4.
while ( (lista[j] > temp) && (j >= 0) )
5.
lista[j+1] = lista[j];
6.
j--;
7.
lista[j+1] = temp;
Nota: Observa que en cada iteracin del ciclo externo los elementos 0 a i forman una lista ordenada.
Ejemplo.
Vamos a ordenar la siguiente lista (la misma del ejemplo anterior):
4-3-5-2-1
temp toma el valor del segundo elemento, 3. La primera carta es el 4. Ahora comparamos: 3 es menor que 4. Luego
desplazamos el 4 una posicin a la derecha y despus copiamos el 3 en su lugar.
4-4-5-2-1
3-4-5-2-1
El siguiente elemento es 5. Comparamos con 4. Es mayor que 4, as que no ocurren intercambios.
Continuamos con el 2. Es menor que cinco: desplazamos el 5 una posicin a la derecha:
3-4-5-5-1
Comparamos con 4: es menor, as que desplazamos el 4 una posicin a la derecha:
3-4-4-5-1
Comparamos con 3. Desplazamos el 3 una posicin a la derecha:
3-3-4-5-1
Finalmente copiamos el 2 en su posicin final:
2-3-4-5-1
El ltimo elemento a ordenar es el 1. Cinco es menor que 1, as que lo desplazamos una posicin a la derecha:
2-3-4-5-5
Continuando con el procedimiento la lista va quedando as:
2-3-4-4-5
2-3-3-4-5
2-2-3-4-5
1-2-3-4-5
Anlisis del algoritmo.

Estabilidad: Este algoritmo nunca intercambia registros con claves iguales. Por lo tanto es estable.
Requerimientos de Memoria: Una variable adicional para realizar los intercambios.
Tiempo de Ejecucin: Para una lista de n elementos el ciclo externo se ejecuta n-1 veces. El ciclo interno se
ejecuta como mximo una vez en la primera iteracin, 2 veces en la segunda, 3 veces en la tercera, etc. Esto
produce una complejidad O(n2).

Ventajas:
Fcil implementacin.

Requerimientos mnimos de memoria.

Desventajas:
Lento.
Realiza numerosas comparaciones.
Este tambin es un algoritmo lento, pero puede ser de utilidad para listas que estn ordenadas o semiordenadas, porque
en ese caso realiza muy pocos desplazamientos.
ORDENAMIENTO RPIDO (QUICK SORT)
Esta es probablemente la tcnica ms rpida conocida. Fue desarrollada por C.A.R. Hoare en 1960. El algoritmo original
es recursivo, pero se utilizan versiones iterativas para mejorar su rendimiento (los algoritmos recursivos son en general
ms lentos que los iterativos, y consumen ms recursos). El algoritmo fundamental es el siguiente:
Eliges un elemento de la lista. Puede ser cualquiera (en Optimizando veremos una forma ms efectiva). Lo
llamaremos elemento de divisin.
Buscas la posicin que le corresponde en la lista ordenada (explicado ms abajo).
Acomodas los elementos de la lista a cada lado del elemento de divisin, de manera que a un lado queden todos
los menores que l y al otro los mayores (explicado ms abajo tambin). En este momento el elemento de
divisin separa la lista en dos sublistas (de ah su nombre).
Realizas esto de forma recursiva para cada sublista mientras stas tengan un largo mayor que 1. Una vez
terminado este proceso todos los elementos estarn ordenados.
Una idea preliminar para ubicar el elemento de divisin en su posicin final sera contar la cantidad de elementos
menores y colocarlo un lugar ms arriba. Pero luego habra que mover todos estos elementos a la izquierda del
elemento, para que se cumpla la condicin y pueda aplicarse la recursividad. Reflexionando un poco ms se obtiene un
procedimiento mucho ms efectivo. Se utilizan dos ndices: i, al que llamaremos contador por la izquierda, y j, al que
llamaremos contador por la derecha. El algoritmo es ste:
Recorres la lista simultneamente con i y j: por la izquierda con i (desde el primer elemento), y por la derecha con
j (desde el ltimo elemento).
Cuando lista[i] sea mayor que el elemento de divisin y lista[j] sea menor los intercambias.
Repites esto hasta que se crucen los ndices.
El punto en que se cruzan los ndices es la posicin adecuada para colocar el elemento de divisin, porque
sabemos que a un lado los elementos son todos menores y al otro son todos mayores (o habran sido
intercambiados).
Al finalizar este procedimiento el elemento de divisin queda en una posicin en que todos los elementos a su izquierda
son menores que l, y los que estn a su derecha son mayores.
Tabla de variables

Nombre Procedimiento: OrdRap


Parmetros:
lista a ordenar (lista)
ndice inferior (inf)

ndice superior (sup)


// Inicializacin de variables
1. elem_div = lista[sup];
2. i = inf - 1;
3. j = sup;
4. cont = 1;
// Verificamos que no se crucen los lmites
5. if (inf >= sup)
6.
retornar;
// Clasificamos la sublista
7. while (cont)
8.
while (lista[++i] < elem_div);
9.
while (lista[--j] > elem_div);
10.
if (i < j)
11.
temp = lista[i];
12.
lista[i] = lista[j];
13.
lista[j] = temp;
14.
else
15.
cont = 0;
// Copiamos el elemento de divisin
// en su posicin final
16. temp = lista[i];
17. lista[i] = lista[sup];
18. lista[sup] = temp;
// Aplicamos el procedimiento
// recursivamente a cada sublista
19. OrdRap (lista, inf, i - 1);
20. OrdRap (lista, i + 1, sup);
Nota: La primera llamada debera ser con la lista, cero (0) y el tamao de la lista menos 1 como parmetros.
Ejemplo.
Esta la lista es la siguiente:
5-3-7-6-2-1-4
Comenzamos con la lista completa. El elemento divisor ser el 4:
5-3-7-6-2-1-4
Comparamos con el 5 por la izquierda y el 1 por la derecha.
5-3-7-6-2-1-4
5 es mayor que cuatro y 1 es menor. Intercambiamos:
1-3-7-6-2-5-4
Avanzamos por la izquierda y la derecha:
1-3-7-6-2-5-4
3 es menor que 4: avanzamos por la izquierda. 2 es menor que 4: nos mantenemos ah.
1-3-7-6-2-5-4
7 es mayor que 4 y 2 es menor: intercambiamos.
1-3-2-6-7-5-4
Avanzamos por ambos lados:
1-3-2-6-7-5-4
En este momento termina el ciclo principal, porque los ndices se cruzaron. Ahora intercambiamos lista[i] con lista[sup]
(pasos 16-18):
1-3-2-4-7-5-6
Aplicamos recursivamente a la sublista de la izquierda (ndices 0 - 2). Tenemos lo siguiente:
1-3-2
1 es menor que 2: avanzamos por la izquierda. 3 es mayor: avanzamos por la derecha. Como se intercambiaron los
ndices termina el ciclo. Se intercambia lista[i] con lista[sup]:

1-2-3
Al llamar recursivamente para cada nueva sublista (lista[0]-lista[0] y lista[2]-lista[2]) se retorna sin hacer cambios
(condicin 5.).Para resumir te muestro cmo va quedando la lista:
Segunda sublista: lista[4]-lista[6]
7-5-6
5-7-6
5-6-7
Para cada nueva sublista se retorna sin hacer cambios (se cruzan los ndices).
Finalmente, al retornar de la primera llamada se tiene el arreglo ordenado:
1-2-3-4-5-6-7
Optimizando.
Slo se van a mencionar algunas optimizaciones que pueden mejorar bastante el rendimiento de quicksort:
Hacer una versin iterativa: Para ello se utiliza una pila en que se van guardando los lmites superior e inferior de
cada sublista.
No clasificar todas las sublistas: Cuando el largo de las sublistas va disminuyendo, el proceso se va
encareciendo. Para solucionarlo slo se clasifican las listas que tengan un largo menor que n. Al terminar la
clasificacin se llama a otro algoritmo de ordenamiento que termine la labor. El indicado es uno que se comporte
bien con listas casi ordenadas, como el ordenamiento por insercin por ejemplo. La eleccin de n depende de
varios factores, pero un valor entre 10 y 25 es adecuado.
Eleccin del elemento de divisin: Se elige desde un conjunto de tres elementos: lista[inferior], lista[mitad] y
lista[superior]. El elemento elegido es el que tenga el valor medio segn el criterio de comparacin. Esto evita el
comportamiento degenerado cuando la lista est prcticamente ordenada.
Anlisis del algoritmo.

Estabilidad: No es estable.
Requerimientos de Memoria: No requiere memoria adicional en su forma recursiva. En su forma iterativa la
necesita para la pila.
Tiempo de Ejecucin:
o Caso promedio. La complejidad para dividir una lista de n es O(n). Cada sublista genera en promedio dos
sublistas ms de largo n/2. Por lo tanto la complejidad se define en forma recurrente como:
f(1) = 1
f(n) = n + 2 f(n/2)
La forma cerrada de esta expresin es:
f(n) = n log2n
Es decir, la complejidad es O(n log2n).
o El peor caso ocurre cuando la lista ya est ordenada, porque cada llamada genera slo una sublista
(todos los elementos son menores que el elemento de divisin). En este caso el rendimiento se degrada
a O(n2). Con las optimizaciones mencionadas arriba puede evitarse este comportamiento.
Ventajas:
Muy rpido
No requiere memoria adicional.
Desventajas:
Implementacin un poco ms complicada.
Recursividad (utiliza muchos recursos).
Mucha diferencia entre el peor y el mejor caso.
La mayora de los problemas de rendimiento se pueden solucionar con las optimizaciones mencionadas. Este es un
algoritmo que puedes utilizar en la vida real. Es muy eficiente. En general ser la mejor opcin.
Ejemplo de cdigo en C++ de los ordenamientos ms usados:
#include <iostream>
#include <time.h>
using namespace std;
/* Aprovechamos las funciones de consola si las trae el compilador.

EL programa no las necesita, pero queda mejor si se puede borrar


la pantalla y esperar a que se presione una tecla */
#if defined DJGPP || defined __TURBOC__ || \
defined __BORLAND__ || defined __LCC__
#define __CONIO_SOPORTADO__
#endif
#ifdef __CONIO_SOPORTADO__
#include <conio.h>
#define PAUSA() \
printf ("Presione una tecla para volver al men..."); getch()
#define LEER_TECLA() getch()
#define LIMPIAR_PANTALLA() clrscr()
#elif defined __MINGW32__
#define LEER_TECLA() getchar()
#define PAUSA() system("PAUSE")
#define LIMPIAR_PANTALLA() system("CLS")
#else
#define LEER_TECLA() getchar()
#define PAUSA() getchar()
#define LIMPIAR_PANTALLA()
#endif
/* El tamao del arreglo: slo 20 para que quepa en la pantalla */
#define TAM 20
/* La opcin para salir */
#define SALIR '7'
/* ---------------------------------------------------------------------Definicin de funciones.
---------------------------------------------------------------------- */
void menu(void);
/* Presenta el men
*/
int leer_opcion(void);
/* Lee la opcin del usuario
*/
int generar_elementos (int arreglo[]); /* Genera nmeros aleatorios para
llenar el arreglo
*/
void mostrar (int arreglo[]);
/* Imprime el arreglo
*/
void burbuja (int arreglo[]);
/* Ordenamiento burbuja
*/
void seleccion (int arreglo[]);
/* Ordenamiento por seleccin
*/
int menor (int arreglo[], int desde); /* Utilizada por seleccion(): busca
el elemento menor del arreglo */
void insercion (int arreglo[]);
/* Ordenamiento por insercin
*/
void quicksort (int arreglo[]);
/* Ordenamiento r pido
*/
void ord_rap (int arreglo[], int inf, int sup);
/* Parte recursiva de quicksort() */
void copiar_arr (int *desde, int *hacia); /* Copia desde en hasta
*/
/* ---------------------------------------------------------------------- */
int main()
{
int arreglo[TAM];
int copia_arreglo[TAM];
char opcion = 0;
/* Llenamos el arreglo con nmeros al azar por primera vez */
generar_elementos(arreglo);
copiar_arr(arreglo, copia_arreglo); /* Guardamos una copia */
while (opcion != SALIR)
{
menu();
opcion = leer_opcion();
/* Ejecutamos la funcin correspondiente a la opcin */
switch (opcion)
{
case '1':
/* Llenamos el arreglo con nmeros al azar */
generar_elementos(arreglo);
/* Guardamos una copia */
copiar_arr(arreglo, copia_arreglo);
LIMPIAR_PANTALLA();
cout<<"\n\n\tSe generaron los siguientes elementos:\n";
mostrar (arreglo);
break;

case '2':
LIMPIAR_PANTALLA();
cout<<"\n\n\tLos elementos del arreglo son:\n";
mostrar (arreglo);
break;
case '3':
burbuja (arreglo);
copiar_arr(copia_arreglo, arreglo); /* Restauramos */
break;
case '4':
seleccion (arreglo);
copiar_arr(copia_arreglo, arreglo); /* Restauramos */
break;
case '5':
insercion (arreglo);
copiar_arr(copia_arreglo, arreglo); /* Restauramos */
break;
case '6':
quicksort (arreglo);
copiar_arr(copia_arreglo, arreglo); /* Restauramos */
break;
}
}
return 0;
}
void menu(void)
{
LIMPIAR_PANTALLA();
cout<<"\n\n\t Men:\n";
cout<<"\n\t1) Generar los elementos en forma aleatoria.";
cout<<"\n\t2) Mostrar los elementos.";
cout<<"\n\t3) Ordenamiento burbuja.";
cout<<"\n\t4) Ordenamiento por seleccin.";
cout<<"\n\t5) Ordenamiento por insercin.";
cout<<"\n\t6) Ordenamiento r pido (QUICKSORT).";
cout<<"\n\t7) Salir.";
cout<<"\n\n\t Ingrese su opcin por favor: ";
}
int leer_opcion(void)
{
char opcion;
opcion = LEER_TECLA();
while (opcion < '1' || opcion > SALIR)
{
opcion = LEER_TECLA();
}
return opcion;
}
int generar_elementos (int arreglo[])
{
int i;
/* Introducimos una semilla para los nmeros aleatorios */
srand ((unsigned) time(NULL));
for (i=0; i<TAM; i++)
arreglo[i] = rand()%TAM; /* Slo nmeros pequeos... */
return TAM;
}
void mostrar (int arreglo[])
{
int i;
for (i=0; i<TAM; i++)
cout<<"\n\tElemento"<<i<<" = "<<arreglo[i];
cout<<"\n\n\t";
PAUSA();

}
void burbuja (int arreglo[])
{
int i, j;
int temp;
for (i=1; i<TAM; i++)
for (j=0; j<TAM - i; j++)
if (arreglo[j] > arreglo[j+1])
{
/* Intercambiamos */
temp = arreglo[j];
arreglo[j] = arreglo[j+1];
arreglo[j+1] = temp;
}
LIMPIAR_PANTALLA();
cout<<"\n\n\tOrdenamiento burbuja.";
cout<<"\n\tEl arreglo ordenado es:\n";
mostrar (arreglo);
}
void seleccion (int arreglo[])
{
int i;
int temp, pos_men;
for (i=0; i<TAM - 1; i++)
{
/* Buscamos el elemento menor */
pos_men = menor(arreglo, i);
/* Lo colocamos en el lugar que le corresponde */
temp = arreglo[i];
arreglo[i] = arreglo [pos_men];
arreglo [pos_men] = temp;
}
LIMPIAR_PANTALLA();
cout<<"\n\n\tOrdenamiento por seleccin.";
cout<<"\n\tEl arreglo ordenado es:\n";
mostrar (arreglo);
}
int menor (int arreglo[], int desde)
{
int i, menor;
menor = desde++;
for (i=desde; i<TAM; i++)
if (arreglo[i] < arreglo[menor])
menor = i;
return menor;
}
void insercion (int arreglo[])
{
int i, j, temp;
for (i=1; i<TAM; i++)
{
temp = arreglo[i];
j = i - 1;
/* Desplazamos los elementos mayores que arreglo[i] */
while ( (arreglo[j] > temp) && (j >= 0) )
{
arreglo[j+1] = arreglo[j];
j--;
}
/* Copiamos arreglo[i] en su posicin final */
arreglo[j+1] = temp;
}
LIMPIAR_PANTALLA();
cout<<"\n\n\tOrdenamiento por insercin.";
cout<<"\n\tEl arreglo ordenado es:\n";

mostrar (arreglo);
}
/* Esta funcin es recursiva. La separamos en dos partes: la
recursiva y la de impresin de resultados. Si no fuera as
cada vez que llamaramos a la funcin se imprimira
'ordenado'(aunque no est ordenado).
*/
void quicksort (int arreglo[])
{
ord_rap (arreglo, 0, TAM - 1);
LIMPIAR_PANTALLA();
cout<<"\n\n\tOrdenamiento r pido (QUICKSORT).";
cout<<"\n\tEl arreglo ordenado es:\n";
mostrar (arreglo);
}
void ord_rap (int arreglo[], int inf, int sup)
{
int elem_div = arreglo[sup];
int temp ;
int i = inf - 1, j = sup;
int cont = 1;
if (inf >= sup) /* Se cruzaron los ndices ? */
return;
while (cont)
{
while (arreglo[++i] < elem_div);
while (arreglo[--j] > elem_div);
/* Se cumple la condicin ? */
if (i < j)
{
temp = arreglo[i];
arreglo[i] = arreglo[j];
arreglo[j] = temp;
}
else
cont = 0;
}
/* Dejamos el elemento de divisin en su posicin final */
temp = arreglo[i];
arreglo[i] = arreglo[sup];
arreglo[sup] = temp;
/* Aplicamos recursivamente a los subarreglos generados */
ord_rap (arreglo, inf, i - 1);
ord_rap (arreglo, i + 1, sup);
}
void copiar_arr (int *desde, int *hacia)
{
int i;
if ( (desde == NULL) || (hacia == NULL) )
return;
for (i=0; i<TAM; i++)
hacia[i] =desde[i];
}

4.2. Mtodos de bsqueda


La bsqueda de informacin est relacionada con las tablas para consultas. Estas tablas contienen una cantidad de
informacin que se almacenan en forma de listas de parejas de datos. Por ejemplo un catlogo con una lista de libros de
matemticas, en donde es necesario buscar con frecuencia elementos en una lista.
Existen diferentes tipos de bsqueda, pero los ms utilizados son:
Mtodo de bsqueda Secuencial
Mtodo de bsqueda Binaria.
METODO DE BUSQUEDA SECUENCIAL
La bsqueda lineal probablemente es sencilla de implementar e intuitiva. Bsicamente consiste en buscar de manera
secuencial un elemento, es decir, preguntar si el elemento buscado es igual al primero, segundo, tercero y as
sucesivamente hasta encontrar el deseado.
Entonces este algoritmo tiene una complejidad de O(n).
Ejemplo de cdigo en C++
#include <iostream>
using namespace std;
int n,i,a[10],nb,b;
void ingresar(){
cout<<"Ingrese tamano del arreglo: ";
cin>>n;
cout<<"\n\n Ingresar elementos numericos"<<endl;
for(i=1;i<=n;i++){
cout<<"a["<<i<<"] : ";
cin>>a[i];
}
cout<<"\n\n Ingrese numero a buscar: ";
cin>>nb;
//inicia bsqueda lineal
for(i=1;i<=n;i++){
if(nb=a[i]){
b=1;
}
} //termina busqueda lineal
if(b){
cout<<"\n Numero encontrado"<<endl;
}
else
{
cout<<endl<<" Numero buscado "<<nb;
cout<<" no se encuentra en el arreglo."<<endl;
}
}
int main(int argc, char *argv[]) {
ingresar();
cout<<"\n\nRealizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

METODO DE BUSQUEDA BINARIA


La bsqueda binaria utiliza un mtodo de divide y vencers para localizar el valor deseado. Con este mtodo se
examina primero el elemento central de la lista; si ste es el elemento buscado, entonces la bsqueda ha terminado.
En caso contrario, se determina si el elemento buscado ser en la primera o la segunda mitad de la lista y a continuacin
se repite este proceso, utilizando el elemento central de esa sublista.
Se puede aplicar tanto a datos en listas lineales como en rboles binarios de bsqueda. Los pre-requisitos principales
para la bsqueda binaria son:

La lista debe estar ordenada en un orden especfico de acuerdo al valor de la llave.


Debe conocerse el nmero de registros.

Ejemplo de cdigo en C++


#include<iostream>
#include<conio.h>
using namespace std;
int main(){
int i,n,A[100],nbuscar,primero=0,ultimo,centro,encontrado=0;
cout<<" Ingrese dimension del arreglo: ";
cin>>n;
for(i=1;i<=n;i++){
cout<<" Ingrese Valor numerico en A["<<i<<"] = ";
cin>>A[i];
}
cout<<"\n\n Ingrese numero a buscar: ";
cin>>nbuscar;
ultimo=n;
// inicia busqueda binaria
while(primero<=ultimo && (!encontrado)){
centro=(primero+ultimo)/2;
if(nbuscar==A[centro]){
encontrado=1;
}
else{
if(nbuscar>A[centro])
primero=centro+1;
else
ultimo=centro-1;
}
}// termina busqueda binaria
if(encontrado){
cout<<"\n\n Numero encontrado ";
cout<<" esta en la posicion: "<<centro<<endl;
}
else
{
cout<<endl<<"\n\n Numero buscado "<<nbuscar;
cout<<" no se encuentra en el arreglo "<<endl;
}
cout<<"\n\n Realizado por: David Ortiz\n";
system("PAUSE");
return 0;
}

4.3. Recuperacin de datos


Recuperacin de datos es obtener los datos que se encuentran almacenados en un archivo el cual puede estar en un
dispositivo de almacenamiento primario (Memoria RAM) o secundario (Disco Duro, USB, etc).
Archivos de acceso aleatorio
Los archivos de acceso aleatorio son ms verstiles, permiten acceder a cualquier parte del fichero en cualquier
momento, como si fueran arrays en memoria. Las operaciones de lectura y/o escritura pueden hacerse en cualquier
punto del archivo.
En general se suelen establecer ciertas normas para la creacin, aunque no todas son obligatorias:
1. Abrir el archivo en un modo que te permita leer y escribir. Esto no es imprescindible, es posible usar archivos de
acceso aleatorio slo de lectura o de escritura.
2. Abrirlo en modo binario, ya que algunos o todos los campos de la estructura pueden no ser caracteres.
3. Usar funciones como fread y fwrite, que permiten leer y escribir registros de longitud constante desde y hacia un
fichero.
4. Usar la funcin fseek para situar el puntero de lectura/escritura en el lugar apropiado de tu archivo.
Por ejemplo, supongamos que nuestros registros tienen la siguiente estructura:
struct stRegistro {
char Nombre[34];
int dato;
int matriz[23];
} reg;
Teniendo en cuenta que los registros empiezan a contarse desde el cero, para hacer una lectura del registro nmero 6
usaremos:
fseek(fichero, 5*sizeof(stRegistro), SEEK_SET);
fread(&reg, sizeof(stRegistro), 1, fichero);
Para hacer una operacin de escritura, usaremos:
fseek(fichero, 5*sizeof(stRegistro), SEEK_SET);
fwrite(&reg, sizeof(stRegistro), 1, fichero);
Muy importante: despus de cada operacin de lectura o escritura, el cursor del fichero se actualiza automticamente a
la siguiente posicin, as que es buena idea hacer siempre un fseek antes de un fread o un fwrite.
Para calcular el tamao de un fichero, ya sea en bytes o en registros se suele usar el siguiente procedimiento:
long nRegistros;
long nBytes;
fseek(fichero, 0, SEEK_END); // Colocar el cursor al final del fichero
nBytes = ftell(fichero); // Tamao en bytes
nRegistros = ftell(fich)/sizeof(stRegistro); // Tamao en registros
Borrar registros
Borrar registros puede ser complicado, ya que no hay ninguna funcin de biblioteca estndar que lo haga.
Es su lugar se suele usar uno de estos dos mtodos:
1. Marcar el registro como borrado o no vlido, para ello hay que aadir un campo extra en la estructura del
registro:
struct stRegistro {
char Valido; // Campo que indica si el registro es vlido
char Nombre[34];
int dato;
int matriz[23];
};

Si el campo Valido tiene un valor prefijado, por ejemplo 'S' o ' ', el registro es vlido. Si tiene un valor prefijado,
por ejemplo 'N' o '*', el registro ser invlido o se considerar borrado.
De este modo, para borrar un registro slo tienes que cambiar el valor de ese campo.
Pero hay que tener en cuenta que ser el programa el encargado de tratar los registros del modo adecuado
dependiendo del valor del campo Valido, el hecho de marcar un registro no lo borra fsicamente.
Si se quiere elaborar ms, se puede mantener un fichero auxiliar con la lista de los registros borrados. Esto tiene
un doble propsito:
Que se pueda disear una funcin para sustituir a fseek() de modo que se tengan en cuenta los registros
marcados.
Que al insertar nuevos registros, se puedan sobrescribir los anteriormente marcados como borrados, si
existe alguno.
2. Hacer una copia del fichero en otro fichero, pero sin copiar el registro que se quiere borrar. Este sistema es ms
tedioso y lento, y requiere cerrar el fichero y borrarlo o renombrarlo, antes de poder usar de nuevo la versin con
el registro eliminado.
Lo normal es hacer una combinacin de ambos, durante la ejecucin normal del programa se borran registros con el
mtodo de marcarlos, y cuando se cierra la aplicacin, o se detecta que el porcentaje de registros borrados es alto o el
usuario as lo decide, se "empaqueta" el fichero usando el segundo mtodo.
Ejemplo de cdigo en C++
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
using namespace std;
// Funciones auxiliares:
int Menu();
long LeeNumero();
// Clase registro.
class Registro {
public:
Registro(char *n=NULL, int d1=0, int d2=0, int d3=0) : valido('S') {
if(n) strcpy(nombre, n); else strcpy(nombre, "");
dato[0] = d1;
dato[1] = d2;
dato[2] = d3;
}
void Leer();
void Mostrar();
void Listar(long n);
const bool Valido() { return valido == 'S'; }
const char *Nombre() { return nombre; }
private:
char valido; // Campo que indica si el registro es vlido
// S->Vlido, N->Invlido
char nombre[34];
int dato[4];
};
// Implementaciones de clase Registro:
// Permite que el usuario introduzca un registro por pantalla
void Registro::Leer() {
system("cls");
cout << "Leyendo registro..." << endl << endl;
valido = 'S';
cout << "Nombre: ";
cin.getline(nombre, 34);
for(int i = 0; i < 3; i++) {
cout << "Dato[" << i << "]: ";
dato[i] = LeeNumero();
}
}
// Muestra un registro en pantalla, si no est marcado como borrado
void Registro::Mostrar()
{
system("cls");
if(Valido()) {
cout << "Nombre: " << nombre << endl;
for(int i = 0; i < 3; i++)
cout << "Dato[" << i << "]: " << dato[i] << endl;

}
cout << "\n\n Pulsa una tecla";
cin.get();
}
// Muestra un registro por pantalla en forma de listado,
// si no est marcado como borrado
void Registro::Listar(long n) {
int i;
if(Valido()) {
cout << "[" << setw(6) << n << "] ";
cout << setw(34) << nombre;
for(i = 0; i < 3; i++)
cout << ", " << setw(4) << dato[i];
cout << endl;
}
}
// Clase Datos, almacena y trata los datos.
class Datos :public fstream {
public:
Datos() : fstream("aleatorio.dat", ios::in | ios::out | ios::binary) {
if(!good()) {
open("aleatorio.dat", ios::in | ios::out | ios::trunc | ios::binary);
cout << "Fichero creado" << endl;
system("PAUSE");
}
}
~Datos() {
Empaquetar();
}
void Guardar(Registro &reg);
bool Recupera(long n, Registro &reg);
void Borrar(long n);
private:
void Empaquetar();
};
// Implementacin de la clase Datos.
void Datos::Guardar(Registro &reg) {// Insertar al final:
clear();
seekg(0, ios::end);
write(reinterpret_cast<char *> (&reg), sizeof(Registro));
cout << reg.Nombre() << endl;
}
bool Datos::Recupera(long n, Registro &reg) {
clear();
seekg(n*sizeof(Registro), ios::beg);
read(reinterpret_cast<char *> (&reg), sizeof(Registro));
return gcount() > 0;
}
// Marca el registro como borrado:
void Datos::Borrar(long n) {
char marca;
clear();
marca = 'N';
seekg(n*sizeof(Registro), ios::beg);
write(&marca, 1);
}
// Elimina los registros marcados como borrados
void Datos::Empaquetar() {
ofstream ftemp("aleatorio.tmp", ios::out);
Registro reg;
clear();
seekg(0, ios::beg);
do {
read(reinterpret_cast<char *> (&reg), sizeof(Registro));
cout << reg.Nombre() << endl;
if(gcount() > 0 && reg.Valido())
ftemp.write(reinterpret_cast<char *> (&reg), sizeof(Registro));
} while (gcount() > 0);
ftemp.close();
close();
remove("aleatorio.bak");
rename("aleatorio.dat", "aleatorio.bak");
rename("aleatorio.tmp", "aleatorio.dat");

open("aleatorio.dat", ios::in | ios::out | ios::binary);


}
int main()
{
Registro reg;
Datos datos;
int opcion;
long numero;
do {
opcion = Menu();
switch(opcion) {
case '1': // Aadir registro
reg.Leer();
datos.Guardar(reg);
break;
case '2': // Mostrar registro
system("cls");
cout << "Coloca registro a Mostrar: ";
numero = LeeNumero();
if(datos.Recupera(numero, reg))
reg.Mostrar();
break;
case '3': // Eliminar registro
system("cls");
cout << "Coloca registro a Eliminar: ";
numero = LeeNumero();
datos.Borrar(numero);
break;
case '4': // Mostrar todo
numero = 0;
system("cls");
cout << "Nombre
Datos" << endl;
while(datos.Recupera(numero, reg)) reg.Listar(numero++);
cout << "\n\n Presiona <<enter>>";
cin.get();
break;
}
} while(opcion != '0');
}
// Muestra un men con las opciones disponibles y captura una opcin del usuario
int Menu()
{
char resp[20];
do {
system("cls");
cout << "MENU PRINCIPAL" << endl;
cout << "--------------" << endl << endl;
cout << "1- Insertar registro" << endl;
cout << "2- Mostrar registro" << endl;
cout << "3- Eliminar registro" << endl;
cout << "4- Mostrar todo" << endl;
cout << "0- Salir" << endl;
cout<<"\n Realizado por: David Ortiz\n";
cout<<"\n\n Ingrese opcion: ";
cin.getline(resp, 20);
} while(resp[0] < '0' && resp[0] > '4');
return resp[0];
}
// Lee un nmero suministrado por el usuario
long LeeNumero()
{
char numero[6];
fgets(numero, 6, stdin);
return atoi(numero);
}

BIBLIOGRAFA
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

Fundamentos de Informtica y Programacin.


Estructuras de datos bsicas, Xavier Sez Pous.
Estructuras no lineales de datos: arboles, Luis Rodrguez Baena.
http://www.monografias.com/trabajos14/estrucdinamicas/estrucdinamicas.shtml#ixzz40DHQcOgS
https://hhmosquera.wordpress.com/listasenlazadas/
https://dmmolina.wordpress.com/listas-enlazadas-y-ejemplos/
https://es.wikibooks.org/wiki/Programaci%C3%B3n_en_C%2B%2B/Estructuras_II
http://www.monografias.com/trabajos25/colas/colas.shtml#ixzz41n9704LH
http://wwwtemarioestructuradedatos.blogspot.mx/p/estructura-de-datos-dinamicas-y.html
http://programavideojuegos.blogspot.com/2013/05/curso-basico-de-c-710-procedimientos.html
https://es.wikibooks.org/wiki/Programaci%C3%B3n_en_C/Algoritmos_y_Estructuras_de_Datos
http://c.conclase.net/

Anda mungkin juga menyukai