Está en la página 1de 143

Auditoria II

ALGORITMOS II
INGENIERÍA DE SISTEMAS
Algoritmos II
sfs

© Corporación Universitaria
Remington

Medellín, Colombia
Derechos Reservados ©2011
Primera edición
2020
Algoritmos II
José Antonio Polo
Facultad de Ingenierías

Comité académico
Jorge Mauricio Sepúlveda Castaño
Decano de la Facultad de Ingenierías
jsepulveda@uniremington.edu.co

David Ernesto González Parra


Director de Educación a Distancia y Virtual
dgonzalez@unireminton.edu.co

Francisco Javier Álvarez Gómez


Coordinador CUR-Virtual
falvarez@uniremington.edu.co

Edición y Montaje
Dirección de Educación a Distancia y Virtual
Equipo de diseño gráfico

www.uniremington.edu.co
virtual@uniremington.edu.co

Derechos reservados: El módulo de estudio del curso de


ALGORITMOS II es propiedad de la Corporación Universitaria
Remington; las imágenes fueron tomadas de diferentes fuentes que
se relacionan en los derechos de autor y las citas en la bibliografía. El
contenido del módulo está protegido por las leyes de derechos de autor
que rigen al país. Este material tiene fines educativos y no puede
usarse con propósitos económicos o comerciales. El autor(es) certificó
(de manera verbal o escrita) No haber incurrido en fraude científico,
plagio o vicios de autoría; en caso contrario eximió de toda
responsabilidad a la Corporación Universitaria Remington y se declaró
como el único responsable.

Esta obra es publicada bajo la licencia Creative Commons.


Reconocimiento-No Comercial-Compartir Igual 2.5 Colombia
Algoritmos II
sfs

3
TABLA DE CONTENIDO
Pág.

1 UNIDAD 1 ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA 7


1.1.1 OBJETIVO GENERAL 7
1.1.2 OBJETIVOS ESPECÍFICOS 7
1.1.3 PRUEBA INICIAL 8

1.2 TEMA 1 BÚSQUEDA SECUENCIAL, BINARIA Y TRANSFORMACIÓN DE CLAVES 8


1.2.1 BÚSQUEDA SECUENCIA 8
1.2.2 BÚSQUEDA BINARIA 10
1.2.3 BÚSQUEDA POR TRANSFORMACIÓN DE CLAVES 14
1.2.4 EJERCICIO DE APRENDIZAJE 14
1.2.5 TALLER DE ENTRENAMIENTO 15

1.3 TEMA 2 ORDENAMIENTO POR BURBUJA, INSERCIÓN Y SELECCIÓN (MARGE SORT, QUICK SORT, COCKTAIL
SORT) 16
1.3.1 ORDENAMIENTO ASCENDENTE POR MÉTODO BURBUJA 16
1.3.2 PROCESO DE INSERCIÓN EN UN VECTOR ORDENADO ASCENDENTEMENTE 20
1.3.3 ORDENAMIENTO POR SELECCIÓN 23
1.3.4 MARGE SORT, QUICK SORT, COCKTAIL SORT 28
1.3.5 EJERCICIO DE APRENDIZAJE 31
1.3.6 TALLER DE ENTRENAMIENTO 32

1.4 TEMA 3 ANÁLISIS DE EFICIENCIA DE ORDENAMIENTO Y BÚSQUEDA 33


1.4.1 EJERCICIO DE APRENDIZAJE 35
1.4.2 TALLER DE ENTRENAMIENTO 36

2 UNIDAD 2 MANEJO DE ESTRUCTURAS DINÁMICAS EN MEMORIA (LISTAS LIGADAS) 38


2.1.1 OBJETIVO GENERAL 38
2.1.2 OBJETIVOS ESPECIFICOS 38
2.1.3 PRUEBA INICIAL 39

2.2 TEMA 1 CONCEPTOS BÁSICOS Y LISTAS SIMPLEMENTE LIGADAS 39


2.2.1 TEMA 1 PUNTEROS 39
2.2.2 CLASE NODOSIMPLE 40
2.2.3 LISTAS SIMPLEMENTE LIGADAS 45
2.2.4 DIFERENTES TIPOS DE LISTAS SIMPLEMENTE LIGADAS Y SUS CARACTERÍSTICAS 64
2.2.5 EJERCICIO DE APRENDIZAJE 65
2.2.6 TALLER DE ENTRENAMIENTO 65

2.3 TEMA 2 LISTAS DOBLEMENTE LIGADAS Y OTROS TIPOS DE TOPOLOGÍAS DE LISTAS 66


2.3.1 LISTA DOBLEMENTE LIGADAS 66
2.3.2 EJERCICIO DE APRENDIZAJE 70
2.3.3 TALLER DE ENTRENAMIENTO 72

2.4 TEMA 3 PILAS Y COLAS (Estáticas y Dinámicas) 73


Algoritmos II
sfs

4
2.4.1 RELACIÓN DE CONCEPTOS 73
2.4.2 DEFINICIÓN 75
2.4.3 PILAS CON VECTORES Y LISTAS 75
2.4.4 TEMA 3 COLAS CON VECTORES Y LISTAS 86
2.4.5 EJERCICIO DE APRENDIZAJE 95
2.4.6 TALLER DE ENTRENAMIENTO 95

3 UNIDAD 3 CONCEPTUALIZACIÓN DE RECURSIVIDAD, ARBOLES Y GRAFOS 97


3.1.1 OBJETIVO GENERAL 97
3.1.2 OBJETIVOS ESPECÍFICOS 98
3.1.3 PRUEBA INICIAL 98

3.2 TEMA 1 DEFINICIÓN Y APLICACIÓN DE LA RECURSIVIDAD 98


3.2.1 EJERCICIO DE APRENDIZAJE 99
3.2.2 TALLER DE ENTRENAMIENTO 100

3.3 TEMA 2 DEFINICIÓN, CONCEPTOS, REPRESENTACIÓN Y RECORRIDOS DE ARBOLES 101


3.3.1 RELACIÓN DE CONCEPTOS 101
3.3.2 ARBOLES 101
3.3.3 ARBOLES BINARIOS Y SU REPRESENTACIÓN 106

3.4 LISTAS GENERALIZADAS 115


3.4.1 DEFINICIÓN DE LISTAS GENERALIZADAS (ESTILO TÍTULO 3) 116
3.4.2 EJERCICIO DE APRENDIZAJE 119
3.4.3 TALLER DE ENTRENAMIENTO 121

3.5 TEMA 3 DEFINICIÓN, CONCEPTOS, REPRESENTACIÓN Y RECORRIDOS DE GRAFOS 122


3.5.1 RELACIÓN DE CONCEPTOS 122

3.6 GRAFOS 122


3.6.1 DEFINICIÓN Y TERMINOLOGÍA BÁSICA SOBRE GRAFOS 122
3.6.2 EJERCICIO DE APRENDIZAJE 136
3.6.3 TALLER DE ENTRENAMIENTO 136

4 PISTAS DE APRENDIZAJE 137

5 GLOSARIO 141

6 BIBLIOGRAFÍA 143
Algoritmos II
sfs

PROPÓSITO GENERAL

ALGORITMOS II

Que el estudiante pueda identificar las diferentes estructuras de almacenamiento y


aplicarlas de forma óptima en la solución de problemas.
Algoritmos II
sfs

ALGORITMOS II
a

OBJETIVO GENERAL
Entrenar a los estudiantes en las técnicas de programación para el
almacenamiento de datos tanto a nivel de memoria como en disco,
para el desarrollo de algoritmos aplicables a diversos retos, que
permita la generación de una capacidad analítica y creativa en la
solución e implementación.

OBJETIVOS ESPECÍFICOS
➢ Reconocer los diferentes algoritmos de ordenamiento y búsqueda
y realizar operaciones a nivel de programación.
➢ Identificar las estructuras de almacenamiento de información como
piolas y colas, representadas de forma estática y dinámica
➢ Aplicar soluciones a problemas a partir de árboles y grafos

UNIDAD 1 UNIDAD 2 UNIDA 3

Algoritmos de Manejo de estructuras Conceptualización de


ordenamiento y dinámicas en Memoria recursividad, árboles y
búsqueda (listas ligadas) grafos
Algoritmos II
sfs

7
1 UNIDAD 1 ALGORITMOS DE ORDENAMIENTO Y
BÚSQUEDA

1.1.1 OBJETIVO GENERAL


➢ Realizar operaciones de ordenamiento y búsqueda sobre grupos de datos, que pueden
estar representados en arreglos; permitiendo mejorar la eficiencia de los algoritmos.

1.1.2 OBJETIVOS ESPECÍFICOS


➢ Aplicar algoritmos de búsqueda con sus diferentes esquemas de representación y realizar
comparativos de eficiencia entre ellos.
Algoritmos II
sfs

8
➢ Aplicar los diferentes algoritmos de búsqueda que permitan encontrar un valor
determinado dentro de un grupo de valores dados que pueden estar almacenados en un
arreglo.

➢ Aplicar los diferentes algoritmos de ordenamiento que permitan a un grupo de datos


ordenarse de forma ascendente o descendente según la necesidad los datos pueden estar
almacenados en un arreglo.

➢ Aplicar en análisis de algoritmos para determinar la eficiencia de los procesos de


ordenamiento y búsqueda y efectuar la comparación para determinar cuál algoritmo es el
de mejor rendimiento.

1.1.3 PRUEBA INICIAL


• ¿Cuál es la diferencia entre arreglos unidimensionales y arreglos bidimensionales?

• ¿Porque es importante tener los datos ordenados en todo momento?

• ¿Qué es la eficiencia en un algoritmo?

1.2 TEMA 1 BÚSQUEDA SECUENCIAL, BINARIA Y


TRANSFORMACIÓN DE CLAVES
➢ Video: ” Búsqueda Lineal VS Búsqueda Binaria”
➢ ” Ver Video”: https://www.youtube.com/watch?v=HmUpRHn31FU ”

1.2.1 BÚSQUEDA SECUENCIA


Cuando tenemos un grupo de datos almacenados secuencialmente en un arreglo y se necesita
determinar si un dato D está dentro de eses grupo, debemos construir un algoritmo que nos
permita aplicar la búsqueda secuencial: se debe recorrer el vector con un ciclo hasta su tamaño
(cantidad de datos de la estructura), se debe pedir el dato a buscar fuera del ciclo (puede ser un
parámetro si es un método o una simple lectura por teclado. Se debe comparar ese dato con el
contenido de cada una de las posiciones del vector y el objetivo es encontrar el dato dentro de
una de ellas, si lo encuentra debe devolver el índice o la posición donde lo encontró, pero, sino lo
encuentra debe devolver un mensaje que diga que el dato no se encuentra en el vector. Algoritmo
que realiza este proceso.

Búsqueda Secuencial
Algoritmos II
sfs

9
1. i=1 1
2. While ((i <= n) and (V[j] <> x) do n+1
3. i=i-1 n
4. End (while) n
5. If (i <= n) then return i 1
6. Else write(“el dato” , x , “no existe”) 1
Contador de frecuencia = 3(n + 4)
Y el orden de magnitud es O(n)

Sea el siguiente vector de tamaño 10, que contiene los preciso de 10 artículos distintos (el vector
no se encuentra ordenado). Si el administrador necesita determinar si existe dentro del vector
un producto con un valor igual a 80.000 pesos como se podría ilustrar este proceso de búsqueda:

D=80000
Algoritmos II
sfs

10
Al comparar en este paso encuentra que el valor almacenado al dato con el se realiza la búsqueda,
el algoritmo inmediatamente debe parar la búsqueda y debe devolver el índice o la posición del
vector don se encuentra ese dato.

Un caso particular en esta búsqueda se da por ejemplo cunado busca el dato igual a 60000 dentro
del vector anterior

Como el dato no está en ninguna de las posiciones del vector el algoritmo de búsqueda debe
devolver un mensaje que indique que el dato no fue encontrado dentro de la estructura (se debe
notar que primero debe haber recorrido todo el vector buscando el dato). El orden de magnitud
de este algoritmo que tiene un ciclo para poder trabajar es normalmente O(n); puesto que el peor
de los casos el algoritmo recorre todo el vector.

1.2.2 BÚSQUEDA BINARIA


El proceso de búsqueda binaria utiliza el hecho de que los datos se encuentran ordenados en
forma ascendente o descendente. La primera comparación se hace con el dato de la mitad del
vector. Si se tiene suerte, ese es el dato que se está buscando y no hay que hacer más
comparaciones; si no lo es, es porque el dato que se está buscando es mayor o menor que el dato
de la mitad del vector. Si el dato que se está buscando es mayor que el dato de la mitad del vector,
significa que, si el dato se halla en el vector, está a la derecha del dato de la mitad, o sea que no
abra necesidad de comparar el dato que se está buscando con todos los datos que están a la
izquierda del dato de la mitad del vector. Luego de que hemos descartado la mitad de los datos,
la siguiente comparación se hará con el dato de la mitad, de la mitad en la cual posiblemente este
el dato a buscar. Así continuamos con este proceso hasta que se encuentre el dato, o se detecte
que el dato no está en el vector. Si tuviéramos un millón de datos, con una sola comparación
estamos ahorrando 500.000 comparaciones, con la segunda comparación se ahorran 250.000
comparaciones, con la tercera se ahorran 125.000 comparaciones y así sucesivamente. Como se
puede ver, la reducción del número de comparaciones es notable.

A continuación, se presenta el método con el cual se efectúa este proceso:


Algoritmos II
sfs

11
1. PUBLICO ESTATICO ENTERO BusquedaBinaria (V, n, d)
2. VARIABLES: li, ls, m (ENTEROS)
3. li = 1
4. ls = n
5. MQ (li <= ls)
6. m = (li + ls) / 2
7. SI (V[m] == d)
8. RETORNE (m)
9. SINO
10. SI (V[m] < d)
11. li = m + 1
12. SINO
13. ls = m – 1
14. FINSI
15. FINSI
16. FINMQ
17. RETORNE (n + 1)
18. FIN(Método)

En la instrucción 1 se define el método BúsquedaBinaria, cuyos parámetros son: V, en el cual hay


que efectuar la búsqueda; n, el tamaño del vector; y d, el dato a buscar.

En la instrucción 2 se definen las variables de trabajo: li, para guardar el límite inferior del rango
en el cual hay que efectuar la búsqueda; ls, para guardar el límite superior del rango en el que se
efectuará la búsqueda; y m, para guardar la posición de la mitad del rango entre li y ls.

En las instrucciones 3 y 4 se asignan los valores iníciales a las variables li y ls. Estos valores iníciales
son 1 y n, respectivamente, ya que la primera vez el rango sobre el cual hay que efectuar la
búsqueda es desde la primera posición hasta la última del vector.

En la instrucción 5 se plantea el ciclo MIENTRAS_QUE (MQ), con la condición de que li sea menor
o igual que ls. Es decir, si el límite inferior (li) es mayor o igual que el límite superior (ls), aún
existen posibilidades de que el dato que se está buscando se encuentre en el vector. Cuando li
sea mayor que ls significa que el dato no está en el vector y retornara n+1.

Cuando la condición de la instrucción 5 sea verdadera se ejecutan las instrucciones del ciclo.

En la instrucción 6 se calcula la posición de la mitad entre li y ls, llamamos a esta posición m.

En la instrucción 7 se compara el dato de la posición m con el dato d. si el dato de la posición m


es igual al dato d que se está buscando (V[m] == d), ejecuta la instrucción 8: termina el proceso
de búsqueda retornando m, la posición en la cual se halla el dato d en el vector.
Algoritmos II
sfs

12
Si el dato que se encuentra en la posición m no es el que buscamos, pasamos a la instrucción 10.
En caso de que la condición de la instrucción 10 sea verdadera, significa que, si el dato está en el
vector, se halla a la derecha de la posición m; por consiguiente, el límite inferior del rango en el
cual se debe efectuar la búsqueda es m+1, y por tanto ejecuta la instrucción 11 en el cual a la
variable li se le asigna m+1.

Si el resultado de la comparación de la instrucción 10 es falso, significa que, si el dato está en el


vector, se halla a la izquierda de la posición m; por consiguiente, el límite superior del rango en el
cual se debe efectuar en la búsqueda es m–1, y por tanto ejecuta la instrucción 13 en la cual a la
variable ls se le asigna m–1.

Habiendo actualizando el límite inferior o el límite superior, se llega a la instrucción 16, la cual
retorna la ejecución a la instrucción 5, donde se efectúa de nuevo la comparación entre li y ls.
Cuando la condición sea falsa se sale del ciclo y ejecuta la instrucción 17, la cual retorna n+1,
indicando que el dato no se halla en el vector.

Fíjese que la única manera de que se ejecute la instrucción 17 es que no haya encontrado el dato
que se está buscando, ya que si lo encuentra ejecuta la instrucción 8, la cual termina el proceso.

Consideremos, como ejemplo, el siguiente vector, y que se desea buscar el número 38 en dicho
vector:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V 3 6 7 10 12 18 22 28 31 37 45 52 60 69 73

Al ejecutar nuestro algoritmo de búsqueda binaria, cuando se esté en la instrucción 5 por primera
vez, li se situará en 1 y ls en n, como vemos en la siguiente figura:

li M Ls

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V 3 6 7 10 12 18 22 28 31 37 45 52 60 69 73

Al ejecutar la instrucción 7, la cual compara el dato de la posición m (28) con d (38), se determina
que el dato d, si está en el vector, se halla a la derecha de la posición m, por consiguiente, el valor
de li deberá ser 9. En constancia, al ejecutar la instrucción 11, el límite inferior del rango sobre el
Algoritmos II
sfs

13
cual hay que efectuar la búsqueda es 9, por lo tanto, el rango sobre el cual hay que efectuar dicha
búsqueda es entre 9 y 15.

Al ejecutar el ciclo por segunda vez el valor de m será 12, como muestra la figura siguiente:

li m ls

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V 3 6 7 10 12 18 22 28 31 37 45 52 60 69 73

Luego, al comparar d (38) con V[m] (52), se determina que el dato que se está buscando, si está
en el vector, debe hallarse a la izquierda de m, es decir, entre las posiciones 9 y 11, por
consiguiente, el valor de j será 11 (j = m – 1).

Al ejecutar el ciclo por tercera vez, las variables quedarán de la siguiente forma:

li m ls

n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V 3 6 7 10 12 18 22 28 31 37 45 52 60 69 73

En esta pasada se detecta que el dato debe estar a la derecha de m, por consiguiente el valor de
li deberá ser 11, y en consecuencia el valor de m también. La situación en el vector queda así:

li m Ls
n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
V 3 6 7 10 12 18 22 28 31 37 45 52 60 69 73

En este punto la comparación de d con V[m] indica que si el dato d está en el vector, debe estar
a la izquierda de m, por tanto el valor de ls será 10, y al regresar a la instrucción 5 y evaluar la
condición del ciclo (li <= ls), esta será falsa, por consiguiente termina el ciclo y retorna como
posición el valor de 16 (n+1), indicando que el 38 no se encuentra en dicho vector.
Algoritmos II
sfs

14
1.2.3 BÚSQUEDA POR TRANSFORMACIÓN DE CLAVES
Cuando se utiliza métodos de búsqueda por transformación de claves permiten aumentar la
velocidad de búsqueda, con una condición importante y es que los elementos no tienen que esta
ordenados, es decir, que la ventaja es que el tiempo de búsqueda es totalmente independiente
al número componentes del arreglo (vector).

Supongamos que se tiene una colección de datos donde cada uno de ellos identificado por una
clave donde resulta atractivo tener acceso a ellos de forma directa.

Se tiene que tener en cuenta que dos claves diferentes pueden apuntar a una misma dirección
dentro del arreglo a esto se le llama colisiones.

Cuando se quiere trabajar con este método de búsqueda se debe seleccionar previamente:

Una función Hash que sea fácil de calcular y que distribuya uniformemente las claves

Un método para resolver las colisiones, si estas se presentan, se contará con algún método que
genere posiciones alternativas.

1.2.3.1 FUNCIÓN HASH POR MODULO:


Consiste en tomar el residuo de la división de la clave entre el número de componentes del arreglo
ejemplo: se tiene un arreglo de N elementos y K es la clave del dato a buscar. La función hash
correspondiente es:

H(K)=(K*ModN)+1
Se observa en la fórmula que al residuo de la división se le suma 1, con el objetivo de tener un
valor comprendido entre 1 y N siendo N el tamaño del Arreglo.

Para lograr una mayor uniformidad en la distribución es importante que N sea un numero primo
o divisible entre muy pocos números, en caso de que N no sea un numero primo, se debe buscar
el número primo más cercano a N.

1.2.4 EJERCICIO DE APRENDIZAJE


Supongamos que N es igual a 100 es el tamaño del Arreglo y las direcciones que se le deben
asignar a los elementos (al guardarlo o recuperarlo) son los números del 1 al 100y además
Algoritmos II
sfs

15
contamos con las siguientes claves K1=7259 y K2=9359 son las que se les debe asignar posiciones
dentro el arreglo, las direcciones correspondientes para K1 y K2

H(K1)=(7259*Mod 100) +1= 60

H(K2)=(9359*Mod 100) +1= 60


Nota: se observa que dos claves distintas apuntan a una misma posición en el arreglo, lo que
generó una colisión y este problema se resuelve asignando el primo más cercan para N=100 que
es 97.

Aplicando las fórmulas con este valor observamos:

H(K1)=(7259*Mod 97) +1= 82

H(K2)=(9359*Mod 97) +1= 48

Con N=97 se ha eliminado la colisión.

1.2.5 TALLER DE ENTRENAMIENTO


Supongamos que N es igual a 200 es el tamaño del Arreglo y las direcciones que se le deben
asignar a los elementos (al guardarlo o recuperarlo) son los números del 1 al 200 y además
contamos con las siguientes claves K1=5526 y K2=2850 son las que se les debe asignar posiciones
dentro el arreglo, encontrar las direcciones asociadas a las direcciones K1 y K2 para la búsqueda
hashing y en caso de que se den colisiones resolverlas (recuerde que se debe tomar el primo más
cercano al valor de N).

TIPS
El hashing usa las claves para asociar y encontrar los datos de un arreglo
mayor velocidad.
Algoritmos II
sfs

16
1.3 TEMA 2 ORDENAMIENTO POR BURBUJA, INSERCIÓN Y
SELECCIÓN (MARGE SORT, QUICK SORT, COCKTAIL SORT)
➢ Video: ” ALGORITMOS - METODOS DE ORDENAMIENTO”
➢ ” Ver Video”: https://www.youtube.com/watch?v=VJ_EUuURRg4 ”

1.3.1 ORDENAMIENTO ASCENDENTE POR MÉTODO BURBUJA


El método de ordenamiento por burbuja consiste en comparar dos datos consecutivos y
ordenarlos ascendentemente, es decir, si el primer dato es mayor que el segundo se intercambia
dichos datos, de lo contrario se dejan tal cual están. Cualquiera de las dos situaciones que se
hubiera presentado se avanza en el vector para comparar los siguientes dos datos consecutivos.
En general, el proceso de comparaciones es: el primer dato con el segundo, el segundo dato con
el tercero, el tercer dato con el cuarto, el cuarto con el dato quinto, y así sucesivamente, hasta
comparar el penúltimo dato con el último. Como resultado de estas comparaciones, y los
intercambios que se hagan, el resultado es que en la última posición quedara el mayor dato en el
vector. La segunda vez se compara nuevamente los datos consecutivos, desde el primero con el
segundo, hasta que se comparan el antepenúltimo dato con el penúltimo, obteniendo como
resultado que el segundo dato mayor queda de penúltimo. Es decir, en cada pasada de
comparaciones, de los datos que faltan por ordenar, se ubica el mayor dato en la posición que le
corresponde, o sea de ultimo en el conjunto de datos que faltan por ordenar. Veamos el método
que efectúa dicha tarea

1. PUBLICO ESTATICO VOID OrdenamientoAscendenteBurbuja (V, n)


2. VARIABLES: i, j (ENTEROS
3. PARA (i= 1, n-1, 1)
4. PARA (j= 1, n-i, 1)
5. SI (V[j] > V[j+1])
6. INTERCAMBIAR (V, j, j+1)
7. FINSI
8. FINPARA
9. FINPARA
10. FIN(Método)

Como ejemplo vamos a considerar el siguiente vector:


Algoritmos II
sfs

17
1 2 3 4 5 6
V 3 1 6 2 8 4

Nuestro interés es ordenar los datos de dicho vector en forma ascendente, utilizando el método
que llamamos burbuja.

1 2 3 4 5 6
Parte 1 3 1 6 2 8 4

Parte 2 1 3 6 2 8 4
Primera pasada: cinco Parte 3 1 3 6 2 8 4 comparaciones
Parte 4 1 3 2 6 8 4

Parte 5 1 3 2 6 8 4

En la figura anterior se presentan los cincos comparaciones que se efectúan en la primera pasada.
La comparación correspondiente a cada pasada se ve resaltada en cada vector. A cada
comparación la llamaremos parte con el fin de evitar redundancias.

• En la primera parte se compara el dato de la posición 1 con el dato de la posición 2. Como


el dato de la posición 1 es mayor que el dato de la posición 2, se intercambian dichos
datos, quedando el vector como se ve en la parte 2.

• En la parte 2 se compara el dato de la posición 2 con el dato de la posición 3. Como el dato


de la posición 3 es mayor que el dato de la posición 2, los datos se dejan intactos.

• En la parte 3 se compara el dato de la posición 3 con el dato de la posición 4. Como el dato


de la posición 3 es mayor que el dato de la posición 4, se intercambian dichos datos,
quedando el vector como se ve en la parte 4.

• En la parte 4 se compara el dato de la posición 4 con el dato de la posición 5. Como el dato


de la posición 4 es menor que el dato de la posición 5, los datos permanecen intactos.

• En la parte 5 se compara el dato de la posición 5 con el dato de la posición 6. Como el dato


de la posición 5 es mayor que el dato de la posición 6, se intercambian dichos datos.
Algoritmos II
sfs

18
Luego de realizar estas comparaciones y estos intercambios, el vector queda de la siguiente
forma:

1 2 3 4 5 6
1 3 2 6 4 8

En esta figura se ha resaltado el dato de la posición 6, puesto que este ha quedado en el sitio que
le corresponde.

En la segunda pasada se harán nuevamente comparaciones de datos consecutivos desde la


posición 1 hasta la posición 5, ya que la posición 6 ya está en orden.

Las comparaciones e intercambios correspondientes a esta segunda pasada se presentan en la


figura 31.4

1 2 3 4 5 6
Parte 1 1 3 2 6 4 8

Parte 2 1 3 2 6 4 8
Segunda pasada: cuatro comparaciones
Parte 3 1 2 3 6 4 8
Parte 4 1 2 3 6 4 8

Como resultado final de esta segunda pasada el vector quedará de la siguiente forma:

1 2 3 4 5 6
1 2 3 4 6 8

En esta figura se resaltan las dos últimas posiciones, indicando que esta parte del vector ya está
ordenada.

En la siguiente figura se muestra como es el proceso de comparaciones e intercambios


correspondientes a la tercera pasada. Fíjese que las comparaciones solo se efectúan hasta la
posición 4, ya que los datos de las posiciones 5 y 6 ya están ordenados y ubicados en sus
correspondientes lugares.
Algoritmos II
sfs

19
1 2 3 4 5 6
1 2 3 4 6 8

1 2 3 4 6 8 Tercera pasada: tres comparaciones


1 2 3 4 6 8

Ahora veamos el estado actual de nuestro vector después de la tercera pasada:

1 2 3 4 5 6
1 2 3 4 6 8

En la siguiente figura se muestra las dos comparaciones correspondientes a la cuarta pasada:

1 2 3 4 5 6
1 2 3 4 6 8
Cuarta pasada: dos comparaciones
1 2 3 4 6 8

Luego de la cuarta pasada el vector quedará de la siguiente forma:

1 2 3 4 5 6
1 2 3 4 6 8

En la siguiente figura se muestra la comparación que se hará en la quinta pasada:

1 2 3 4 5 6
1 2 3 4 6 8 Quinta pasada: una comparación

Después de la quinta pasada nuestro vector quedará así:

1 2 3 4 5 6
1 2 3 4 6 8

Observe que al concluir la tercera pasada los datos del vector ya estaban ordenados; es decir, las
comparaciones correspondientes a las pasadas cuarta y quinta no eran necesarias. Esta situación
se puede detectar en el algoritmo si utilizamos una variable auxiliar y le asignamos un valor antes
de comenzar el ciclo interno. Si ocurrió algún intercambio, modificamos el valor inicial de dicha
Algoritmos II
sfs

20
variable auxiliar. Al terminar el ciclo interno averiguamos cual es el contenido de dicha variable
auxiliar. Si es el mismo que se le asigno inicialmente, significa que no hubo intercambios en el
ciclo interno, por consiguiente, los datos del vector ya están ordenados y no hay necesidad de
hacer más comparaciones. Para nuestro ejemplo llamaremos a esa variable auxiliar sw.

Nuestro algoritmo queda:

1. PUBLICO ESTATICO VOID OrdenamientoAscendenteBurbujaSW (V, n)


2. VARIABLES: i, j, sw (ENTEROS)
3. PARA (i= 1, n-1, 1)
4. Sw = 0
5. PARA (j= 1, n-i, 1)
6. SI (V[j] > V[j+1])
7. INTERCAMBIAR (V, j, j+1)
8. sw = 1
9. FINSI
10. FINPARA
11. SI (sw == 0)
12. i=n
13. FINSI
14. FINPARA
15. FIN(Método)

El algoritmo anterior es similar al primero que hicimos de ordenamiento ascendente con el


método de burbuja, la diferencia está en que agregamos una variable llamada sw con la cual
sabremos si el vector está completamente ordenado sin haber hecho todos los recorridos.
Cuando la variable sw no cambie de valor significa que el vector ya está ordenado y asignaremos
a la variable i el valor de n para salir del ciclo externo.

1.3.2 PROCESO DE INSERCIÓN EN UN VECTOR ORDENADO


ASCENDENTEMENTE
Si queremos insertar un dato en un vector ordenado ascendentemente, sin dañar su
ordenamiento, primero debemos buscar la posición en la que debemos insertar. Para buscar la
posición donde vamos a insertar un nuevo dato en el vector ordenado ascendentemente
debemos recorrer el vector e ir comparando el dato de cada posición con el dato a insertar. Como
los datos están ordenados ascendentemente, cuando se encuentre en el vector un dato mayor
que el que se va a insertar esa es la posición en la cual deberá quedar el nuevo dato.
Algoritmos II
sfs

21
Si el dato a insertar es mayor que todos los datos del vector, entonces el dato a insertar quedara
de último.

El método siguiente ejecuta esta tarea y retorna en la variable i La posición en la cual debe
quedar el dato a insertar:

1. PUBLICO ESTATICO ENTERO BuscarDondeInsertar (V, m, d)


2. VARIABLES: i (ENTERO)
3. i=1
4. MQ ((i <= m) ^ (V[i] < d))
5. i=i+1
6. FINMQ
7. RETORNE (i)
8. FIN(Método)

En la instrucción 1 se define el nombre del programa con sus parámetros: V, variable que contiene
el nombre del vector en el cual hay que efectuar el proceso de búsqueda; m, variable que contiene
el número de posiciones utilizadas en el vector; y d, variable que contiene el dato a insertar.

En la instrucción 2 se define la variable i, que es la variable con la que recorre el vector y se va


comparando el dato de la posición i del vector (V[i]) con el dato a insertar (d).

En la instrucción 3 se inicializa la variable i en 1, ya que esta es la posición a partir de la cual se


comienza a almacenar la posición donde se va a insertar el dato.

En la instrucción 4, la instrucción del ciclo, se controlan dos situaciones: una, que no se haya
terminado de comparar los datos del vector, (i <= m), y dos, que el dato de la posición i sea mayor
que d (V[i] < d).

Si ambas condiciones son verdaderas se ejecutan las construcciones 5 y 6, es decir, se incrementa


la i en 1 y se regresa a la instrucción 4 a evaluar nuevamente las condiciones.

Cuando una de las condiciones sea falsa (i>m o V[i]>=d), se sale del ciclo y ejecuta la instrucción
7, es decir, retorna al programa llamante el valor de i: la posición en la cual hay que insertar el
dato d.

Como ejemplo, consideremos el vector de la siguiente figura, y que se desea insertar el número
10 (d==10).
Algoritmos II
sfs

22

m n
1 2 3 4 5 6 7 8 9 10
V 3 5 7 11 18 23 36

Al ejecutar el método
BuscarDondeInsertar, este retorna i valiendo 4, es decir, en la posición 4 del vector V debe quedar
el dato 10.

Ahora veamos Conocimos que el dato debe quedar en una posición i, veamos cómo se efectúa
este proceso. Si el vector no está lleno (m == n), debemos mover todos los datos desde la posición
i del vector hacia la derecha.

1. PUBLICO ESTATICO VOID Insertar (V, m, n, i, d)


2. VARIABLES: j (ENTERO)
3. SI (m == n)
4. IMPRIMA (“vector lleno”)
5. RETORNE
6. SINO
7. PARA (j= m, i, -1)
8. V[j+1] = V[j]
9. FINPARA
10. V[i] = d
11. m=m+1
12. FINSI
13. FIN(Método)

Es la instrucción 1 se define el método con sus parámetros: V, variable que contiene el nombre
del vector en el cual hay que efectuar el proceso de inserción; m, variable que contiene el número
de posiciones utilizadas en el vector; n, tamaño del vector; i, variable que contiene la posición en
la cual se debe insertar el dato contenido en d; y d, variable que contiene el dato a insertar. Es
bueno resaltar que los parámetros V y m deben ser parámetros por referencia, mientras que n, i
y d son por valor.

En la instrucción 2 se define la variable j, la cual se utilizará para mover los datos del vector en
caso de que sea necesario.
Algoritmos II
sfs

23
En la instrucción 3 se controla que el vector no esté lleno. En caso de que el vector este lleno (m
== n), se ejecuta la instrucción 4, la cual produce el mensaje de que el vector está lleno y finaliza
el método sin realizar más acciones, la instrucción 5 indica que el método puede retornar en
donde fue invocado.

En caso de que el vector no esté lleno se ejecuta la instrucción 7, en la cual se plantea la


instrucción PARA, la cual inicializa el valor de j con el contenido de m, compara el valor de j con
el valor de i y define la forma como variara el de j. la variación de la variable j será restándole 1
cada vez que llegue a la instrucción fin del PARA. Cuando el valor de j sea menor que i se sale del
ciclo. En caso de que el valor inicial de j sea menor que i, no ejecutara la instrucción 8 y continuara
con la instrucción 10.

En la instrucción 8 se mueve el dato que está en la posición j a la posición siguiente (j + 1).

En la instrucción 9, que es el fin de la instrucción PARA, disminuye el valor de j, ejecutara


nuevamente las instrucciones 8 y 9, hasta que j sea menor que i.

Cuando se sale del ciclo, ejecuta la instrucción 10, en la cual se asigna a la posición i del vector el
dato d y continua con la instrucción 11 en la cual se incrementa el valor de m en 1, indicando que
el vector ha quedado con un elemento más.

Veamos cómo queda nuestro vector después de haber insertado el dato:

i m n
1 2 3 4 5 6 7 8 9 10
V 3 5 7 10 11 18 23 36

1.3.3 ORDENAMIENTO POR SELECCIÓN


Como ya hemos visto, el hecho de trabajar los datos ordenados hace que los algoritmos sean más
eficientes. Para ejecutar el proceso de ordenamientos se han desarrollado múltiples métodos,
buscando que este proceso sea lo más eficiente posible. Ahora veamos un método que ordena
los datos de un vector. El método utilizado en este algoritmo se denomina ordenamiento por
selección. Este método se enuncia así: de los datos que faltan por ordenar, determinar el menor
de ellos y ubicarlo de primero en ese conjunto de datos.
Algoritmos II
sfs

24
Con base en este enunciado se presenta el método:

1. PUBLICO ESTATICO VOID OrdenamientoAscendenteSeleccion (V, m)


2. VARIABLES: i, j, k (ENTEROS)
3. PARA (i= 1, m-1, 1)
4. k=i
5. PARA (j= i+1, m, 1)
6. SI (V[j] < V[k])
7. k=j
8. FINSI
9. FINPARA
10. OrdenamientoAscendenteSeleccion .Intercambiar (V, k, i)
11. FINPARA
12. FIN(Método)

NOTA: Si deseamos ordenar el vector de forma descendente, simplemente


cambiamos el símbolo menor que (<) por el de mayor que (>) en la instrucción
6.descubre el valor propio de las cosas.

• En la instrucción 1 se define el método con sus parámetros: V, variable en la cual se


almacena el vector que desea ordenar, y m, el número de elementos a ordenar. V es un
parámetro por referencia, ya que se modificará al cambiar de posición los datos dentro
del vector.

• En la instrucción 2 se define las variables necesarias para efectuar el proceso de


ordenamiento. La variable i se utiliza para identificar a partir de cual posición es que faltan
datos por ordenar. Inicialmente el valor de i es 1, ya que inicialmente faltan todos los datos
por ordenar y los datos comienzan en la posición 1. Cuando el contenido de la variable i
sea 2, significa que faltan por ordenar los datos que hay a partir de la posición 2 del vector;
cuando el contenido de la variable i sea 4, significa que faltan por ordenar los datos que
hay a partir de la posición 4; cuando el contenido de la variable i sea m, significa que
estamos en el último elemento del vector, el cual obviamente estará en su sitio, pues no
hay más datos con los cuales se puede comparar. Esta es la razón por la cual en la
instrucción 3 se pone a variar la i desde 1 hasta m–1.
Algoritmos II
sfs

25
• En la instrucción 3 se plantea el ciclo de la variable i, que varían desde 1 hasta m–1, tal
como explicamos en el párrafo anterior.

• En la instrucción 4 se le asigna a k el contenido de la variable i. la variable k se utiliza para


identificar la posición en la que se halla el menor dato. Inicialmente suponemos que el
menor dato se encuentra en la posición i del vector, es decir, suponemos que el primero
del conjunto de datos que faltan por ordenar.

• En la instrucción 5 se plantea el ciclo con el cual se determina la posición en la cual se halla


el dato menor del conjunto de datos que faltan por ordenar. En este ciclo se utiliza la
variable j, que tiene un valor inicial de i + 1 y varia hasta m.

• En la instrucción 6 se compara el dato que se halla en la posición j del vector con el dato
que se halla en la posición k. si el contenido de la posición j es menor que el contenido de
la posición k, se actualiza el contenido de la variable k con el contenido de j, de esta
manera en la variable k siempre estará la posición en la cual encuentra el menor dato.

• Al terminar la ejecución 10 se intercambia el dato que se halla en la posición k con el dato


que se halla en la posición i, logrando de esta manera ubicar el menor dato al principio del
conjunto de datos que faltan por ordenar. Nótese que invocamos al método Intercambiar
y lo hicimos con el nombre del método que invoca. El nombre del método invocado

• Al llegar a la instrucción 11 continúa con el ciclo externo incrementado a i, que es a partir


de esa posición que faltan los por ordenar.

Como ejemplo, consideramos el siguiente vector:

1 2 3 4 5 6
V 3 1 6 2 8 4

Al ejecutar por primera vez las instrucciones 3, 4 y 5, los valores de i, k y j son 1, 1 y 2,


respectivamente, lo cual indica que faltan por ordenar los datos a partir de la posición 1, en donde
se halla el menor dato de los que faltan por ordenar y se va a comenzar a comparar los datos del
vector, a partir de la siguiente posición, es decir, 2. Gráficamente se presenta esta situación en la
siguiente figura.
Algoritmos II
sfs

26
i k j

1 2 3 4 5 6
V 3 1 6 2 8 4

Al ejecutar la instrucción 6, por primera vez, comparará el dato de la posición 2 (j == 2) con el dato
de la posición 1 (k == 1), obteniendo como resultado que la condición es verdadera; por tanto,
ejecuta la instrucción 7, la cual asigna a k el valor de j, indicando que el menor dato se halla en la
posición 2 del vector.

Al ejecutar la instrucción 9, incrementa el contenido de j en 1, quedando j con el valor de 3. Esta


nueva situación se presenta a continuación.

i k j

1 2 3 4 5 6
V 3 1 6 2 8 4

Se ejecuta de nuevo la instrucción 6 y compara el dato de la posición 3 con el dato de la posición


2, obteniendo como resultado que la condición es falsa; por tanto, no ejecuta la instrucción 7, y
continúa en la instrucción 9, es decir, incrementa nuevamente el contenido de j en 1, la cual
queda valiendo 4.

Se continúan ejecutando las instrucciones del ciclo interno (instrucciones 6 a 9), comparando el
contenido de la posición j del vector con el contenido de la posición k, obteniendo siempre falso
como resultado, hasta que el contenido de la variable j es 7. Cuando esto sucede, pasa ejecutar
la instrucción 10, en la cual se intercambia el dato que se halla en la posición i con el dato que se
halla en la posición k. En este momento el vector se encuentra así:

i K J
Algoritmos II
sfs

27

1 2 3 4 5 6

V 1 3 6 2 8 4

El contenido de la posición 1 lo resaltamos indicando que el dato de esa posición ya está ubicado
en la posición que le corresponde.

Continúa con la ejecución de la instrucción 11, en la cual incrementa el contenido de i en 1, y


regresa a continuación con el ciclo externo. Se asigna nuevamente a k el contenido de i y se
comienza de nuevo la ejecución del ciclo interno, con valor inicial de j en 3, así:

i k j

1 2 3 4 5 6
V 1 3 6 2 8 4

Al terminar la ejecución del ciclo interno, por segunda vez, el contenido de la variable k será 4,
indicando que en esta posición se encuentra el menor de los datos que faltan por ordenar, es
decir, de los datos que hay a partir de la posición 2 del vector.

i k j

1 2 3 4 5 6
V 1 3 6 2 8 4

Terminando la ejecución del ciclo interno continúa con la instrucción 10 en la cual intercambia el
dato que está en la posición i (2) con el dato que está en la posición k (4) del vector. Ahora nuestro
vector se ve así:
Algoritmos II
sfs

28

i k j

1 2 3 4 5 6
V 1 2 6 3 8 4

Aquí resaltamos el contenido de las dos primeras posiciones indicando que esos dos datos ya
están ordenados y en el sitio que les corresponde.

Al ejecutar la instrucción 11 incrementa la i en 1, indicando que ya faltan por ordenar sólo los
datos que se encuentran a partir de la posición 3, y regresa a ejecutar nuevamente las
instrucciones 4 y56, en las cuales al valor de k le asigna el contenido de i, y a j la inicializamos en
4. Veámoslo gráficamente:

i k j

1 2 3 4 5 6
V 1 3 6 2 8 4

El proceso de ordenamiento continuara de esta manera hasta que el valor i sea 6. Cuando el valor
de i es 6 el proceso termina y los datos del vector están ordenados en forma ascendente.
Finalmente, el vector se verá así:

1 2 3 4 5 6
V 1 2 3 4 6 8

1.3.4 MARGE SORT, QUICK SORT, COCKTAIL SORT


Método de ordenamiento QUICK SORT
Es el método de ordenamiento más eficiente estadísticamente, en el caso promedio para ordenar
los datos de un vector. El Método consiste básicamente en elegir un dato del vector y ubicarlo en
el sitio que le corresponde, es decir, los anteriores serán todos menores que ese dato y los
Algoritmos II
sfs

29
siguientes serán todos mayores que ese dato. Luego de efectuar esta operación se procede a
ordenar los datos que quedaron antes y los que quedaron después usando la misma técnica.

Algoritmo Quicksort

Void quickSort (entero m, entero n)


Entero i, j
If (m<n) then
i=m
j=j+1
while (i<j) do
do
i=i+1
while ((i<=n) and (V[i]>=V[m]))
do
j=j-1
while ((j!=m) and (V[j]>=V[m]))
if (i<j) then
intercambia(i,j)
end(if)
end(while)
intercambia(j,m)
Quicksort(m,j-1)
quicksort (j+1, n)
end(if)
End(quinksort)

Método de ordenamiento MERGE SORT


Este método de ordenamiento no busca ubicar inicialmente un dato en la posición que le
corresponde. Simplemente divide el vector en dos subvectores de igual tamaño, los ordena por
separado y luego los intercala. Este método tiene la inconveniencia de que requiere memoria
adicional para ejecutarse.

El algoritmo para sortMerge se escribe a continuación:


Algoritmos II
sfs

30
Void sortMerge(entero primero, entero ultimo)
entero mitad
if (primero < ultimo) then
mitad=(primero+ultimo)/2
sortMerge(primero, mitad)
sortMerge(mitad + 1, ultimo)
intercalar(primero, mitad,ultimo)
end(if)
End(sortMerge)

El algoritmo para el método intercalar es:

Void intercalar(entero primero, entero mitad, entero ultimo)


Entero i, j, k, b[]
B=new entero[V[0]]
If (primero > mitad) or (mitad + 1 > ulimo) then return
For (k=primero; k <= ultimo; k++) do
b.asigneDato(V[k], k)
end(for)
i=primero
j=mitad + 1
k=primero – 1
while ((i<=mitad) and (j<=ultimo)) do
k=+1
if (b.datoEnPosicion(i)<=b.datoEnPosicion(j)) then
V[k]=b.datoEnPosicion(i)
i=i+1
else
V[k]=b.datoEnPosicion(j)
j=j+1
end(if)
end(while)
while (i<=mitad) do
k=k+1
V[k]=b.datoEnPosicion(i)
i=i+1
Algoritmos II
sfs

31
end(while)
while (j<=ultimo) do
k=k+1
V[k]=b.datoEnPosicion(j)
j=j+1
end(while)
Fin(intercalar)

Ordenam

Método de ordenamiento COCKTAIL SORT

Es un algoritmo de ordenamiento que surge como una mejora al algoritmo de ordenamiento de


burbuja. Funciona observando que los números grandes se están moviendo rápidamente hasta
el final de la lista (estas son las liebres), pero que los números pequeños (las tortugas) se mueven
solo muy lentamente al inicio de la lista.

La solución plantea ordenar por el método de burbuja y cuando llegamos al final de la primera
iteración, no volver a realizar el cálculo desde el principio, sino, que empecemos desde el final
hasta el inicio. De esa manera siempre se consigue que tanto los números pequeños como los
grandes se desplacen a los extremos de la lista lo más rápido posible

1.3.5 EJERCICIO DE APRENDIZAJE

1. PROCEDIMIENTO cocktail_sort ( VECTOR a[1:n])


2. dirección ← 'frontal', comienzo ← 1, fin ← n-1, actual ← 1
3. REPETIR
4. permut ← FALSO
5. REPETIR
6. SI a[actual] > a[actual + 1] ENTONCES
7. intercambiar a[actual] Y a[actual + 1]
8. permut ← VERDADERO
9. FIN SI
10. SI (dirección='frontal') ENTONCES
11. actual ← actual + 1
12. SI NO
13. actual ←actual - 1
14. FIN SI
Algoritmos II
sfs

32
15. MIENTRAS
QUE ((dirección='frontal') Y (actual<fin)) OU ((dirección='final) O (actual>comienzo))
16. SI (dirección='frontal') ENTONCES
17. dirección ← 'final'
18. fin ← fin - 1
19. SI NO
20. dirección ← 'frontal'
21. comienzo ← comienzo + 1
22. FIN SI
23. MIENTRAS permut = VERDADERO
24.
25. FIN PROCEDIMIENTO

1.3.6 TALLER DE ENTRENAMIENTO


Void quickSort (entero m, entero n)
Entero i, j
If (m<n) then
i=m
j=j+1
while (i<j) do
do
i=i+1
while ((i<=n) and (V[i]>=V[m]))
do
j=j-1
while ((j!=m) and (V[j]>=V[m]))
if (i<j) then
intercambia(i,j)
end(if)
end(while)
intercambia(j,m)
Quicksort(m,j-1)
quicksort (j+1, n)
end(if)
End(quinksort)
Algoritmos II
sfs

33

Nota: Realice una prueba de escritorio para un vector de 10 posiciones, la


información en el vector está inicialmente desordenada; después de realizar la
prueba, desde su punto de vista escriba que tan eficiente es el método.

TIPS
La implementación de los métodos de ordenamiento y búsqueda
permiten mejorar el rendimiento en colecciones de datos de grandes
volúmenes que requieren de esos procesos.

1.4 TEMA 3 ANÁLISIS DE EFICIENCIA DE ORDENAMIENTO Y


BÚSQUEDA
➢ Video: ” Análisis de Algoritmos , Complejidad de algoritmos (Actualizado)”
➢ ” Ver Video”: https://www.youtube.com/watch?v=Sibd8jSTdUQ&t=256s ”

A continuación de mostrara y se realzara un análisis de los algoritmos de ordenamiento y


búsqueda para encintar su contador de frecuencias y su respectivo orden de magnitud con el
objetivo de realizar un comparativo de cual algoritmo en búsqueda y en ordenamiento
respectivamente es el más eficiente:

Análisis del algoritmo Ordenamiento por selección

1. Void selección() 1
2. Entero i, j, k 1
3. For (i = 1; i < n; i++) do n
4. K=i n-1
5. For (j= j+1; j<=n; j++) do n(n -1)/2 +(n – 1)
6. If v[j] < V[k] then n(n -1)/2
Algoritmos II
sfs

34
7. K= j n(n -1)/2
8. End (if) n(n -1)/2
9. End (for) n(n -1)/2
10. Intercambia(i,k) n-1
11. End (for) n–1
12. End (selección) 1
Contador de frecuencia = 5n(n – 1)/2 + 5n -1
Y el orden de magnitud es O(n2)

Análisis del algoritmo Ordenamiento por inserción

1. Void Inserción() 1
2. Entero i, j, d 1
3. For (i = 2; i < n; i++) do n
4. d = V[i] n-1
5. j=i-1 n(n -1)/2 +(n – 1)
6. While ((j > 0) and (d <[j])) do n(n -1)/2
7. V[j + 1] = V[j] n(n -1)/2
8. j=j-1 n(n -1)/2
9. End (while) n(n -1)/2
10. V[j + 1] = d n-1
11. End (for) n–1
12. End (Inserción) 1
Contador de frecuencia = 4n(n – 1)/2 + 5n - 2
Y el orden de magnitud es O(n2)

Análisis del Algoritmo Búsqueda Binaria

1. primero = 1 1
2. ultimo = n 1
3. while (primero <= ultimo) do log2n + 1
4. Medio = (primero + ultimo) /2 log2n
Algoritmos II
sfs

35
5. If (V[medio] == x) then return medio log2n
6. If (V[medio] > x) then ultimo = medio - 1 log2n
7. else primero = medio + 1 log2n
8. End (while) log2n
9. write(“el dato” , x , “no existe”) 1
Contador de frecuencia = 6(log2n) + 4
Y el orden de magnitud es O(log2n)

Análisis del algoritmo de Búsqueda Secuencial

1. i=1 1
2. While ((i <= n) and (V[j] <> x) do n+1
3. i=i-1 n
4. End (while) n
5. If (i <= n) then return i 1
6. Else write(“el dato” , x , “no existe”) 1
Contador de frecuencia = 3(n + 4)
Y el orden de magnitud es O(n)

1.4.1 EJERCICIO DE APRENDIZAJE


Análisis del algoritmo Ordenamiento por burbuja

1. Void burbuja() 1
2. Entero i, j 1
3. For (i = 1; i < n; i++) do N
4. For (j= 1; j<=n-i; j++) do n(n -1)/2 +(n – 1)
5. If v[j] > V[j + 1] then n(n -1)/2
6. Intercambia(i, j + 1) n(n -1)/2
7. End (if) n(n -1)/2
8. End (for) n(n -1)/2
9. End (for) n–1
10. End (burbuja) 1
Algoritmos II
sfs

36
Contador de frecuencia = 5n(n – 1)/2 + 3n + 1
Y el orden de magnitud es O(n2)

1.4.2 TALLER DE ENTRENAMIENTO


Al siguiente algoritmo coloque al frente cada instrucción el número de veces que se ejecuta cada
una de ellas, además, hallar el contador de frecuencia y el orden de magnitud.

Análisis del algoritmo Ordenamiento por Burbuja mejorado

1. Void burbujaMejorado ()
2. Entero i, j, sw
3. For (i = 1; i < n; i++) do
4 Sw = 0
5. For (j= 1; j<=n-i; j++) do
6. If (V[j] > V[j + 1]) then
7. Intercambia(i, j + 1)
8. sw= 1
9. End (if)
10. End (for)
11. If (sw == 0) then return
12. End (for)
13. End (burbuja)
Contador de frecuencia = ¿?
Y el orden de magnitud es ¿?

TIPS

Los rendimientos de los algoritmos de ordenamiento y búsqueda


alcanzan a llegar a niveles de logarítmicos y semilogarítmicos,
mejorando los órdenes de los lineales y cuadráticos.
Algoritmos II
sfs

37
Algoritmos II

38
2 UNIDAD 2 MANEJO DE ESTRUCTURAS DINÁMICAS EN
MEMORIA (LISTAS LIGADAS)

2.1.1 OBJETIVO GENERAL


➢ Reconocer el concepto de nodos, el manejo de listas simples, listas dobles y las
operaciones con pilas y colas.

2.1.2 OBJETIVOS ESPECIFICOS


➢ Configurar la estructura nodo tanto simple como doble

➢ Realizar operaciones (buscar, insertar, borrar) con listas simples y dobles

➢ Realizar las operaciones de apilar, desapilar, encolar, desencolar tanto en vectores como
en listas simples.
Algoritmos II

39
2.1.3 PRUEBA INICIAL
• ¿Qué es un nodo?

• ¿Qué es un puntero?

• ¿Qué es una lista simplemente ligada?

• ¿Qué es una lista doblemente ligada?

• ¿Qué es un nodo cabeza?

• ¿Qué es una pila?

• ¿Qué es una cola?

2.2 TEMA 1 CONCEPTOS BÁSICOS Y LISTAS SIMPLEMENTE


LIGADAS
➢ Video: ” Conceptos basicos de listas lineales”
➢ ” Ver Video”: https://www.youtube.com/watch?v=1sObs2NdjrY”

2.2.1 TEMA 1 PUNTEROS


Los punteros son direcciones de memoria que indica una posición de memoria dentro de la
RAM, son muy utilizados con cuando se trabaja con memoria dinámica.

Para representar el concepto de puntero se mostrará una lista.

Donde cada cuadro contiene dos campos, uno para el dato y otro para la liga. Técnicamente, cada
cuadro se denomina nodo, donde cada nodo posee una dirección de memoria.
Algoritmos II

40
Ejemplo: el primer cuadro tiene la dirección 1F, el segundo 2F y el tercero 3F.

2.2.2 CLASE NODOSIMPLE


Como nuestro objetivo es trabajar PROGRAMACIÓN ORIENTADA A OBJETOS, y para ello usamos
clases con sus respectivos métodos, comencemos definiendo la clase en la cual manipulamos el
nodo que acabamos de definir. A dicha clase la denominaremos nodoSimple:

1. CLASE nodoSimple
2. Privado
3. Objeto dato
4. nodoSimple liga
5. publico
6. nodoSimple() // constructor
7. objeto retornaDato()
8. nodoSimple retornaLiga()
9. void asignaDato(Objeto d)
10. void asignaLiga(nodoSimple x)
11. end(Clase nodoSimple)

Los algoritmos correspondientes a los métodos definidos son:

1. nodoSimple(objeto d) // constructor
2. dato = d
3. liga = null
4. end(nodoSimple)

1. objeto retornaDato()
2. return dato
3. end(retornaDato)
Algoritmos II

41

1. nodoSimple retornaLiga()
2. return liga
3. end(retornaLiga)

1. void asignaDato(objeto d)
2. dato = d
3. end(asignaDato)

1. void asignaLiga(nodoSimple x)
2. liga = x
3. end(asignaLiga)

Para usar esta clase consideremos el siguiente ejemplo:

1. nodoSimple x
2. d = 3.1416
3. x = new nodoSimple(d)
Al ejecutar la instrucción 3 el programa se comunica con el sistema operativo y este le asigna una
posición de memoria a nuestro programa, el cual identifica esta posición de memoria con la
variable x. el resultado obtenido es:

En el cual 19 es la dirección del nodo en la que se almacenan los campos de dato y liga. Dicha
dirección la suministra el sistema operativo de una forma transparente para el usuario.

Si queremos acceder los campos del nodo x, lo haremos así:

d = x.retornaDato() // d queda valiendo 3.1416


z = x. retornaLiga() // z queda valiendo nulo
Algoritmos II

42
A continuación, presentamos un ejemplo más completo de la utilización de la clase nodo Simple.
Supongamos que tenemos el conjunto de datos a, b, c, f y que se procesan con el siguiente
algoritmo:
1. nodoSimple x, y, z, p
2. read(d) // lee la letra “a”
3. x = new nodoSimple(d) // digamos que envió el nodo 4
4. read(d) // lee la letra “b”
5. y = new nodoSimple(d) // digamos que envió el nodo 8
6. read(d) // lee la letra “c”
7. z = new nodoSimple(d) // digamos que envió el nodo 3

El resultado grafico al ejecutar estas instrucciones es:

Ahora, si ejecutamos las instrucciones:


8. x.asignaLiga(y)
9. y.asignaLiga(z)

Los nodos quedaran conectados así:

Si ejecutamos las siguientes instrucciones:


10. read(d) // lee la letra “f”
11. y = new nodoSimple(d) // digamos que envió el nodo 7

Nuestros nodos quedan así:


Algoritmos II

43

Ahora, ejecutemos estas instrucciones:


12. p= x.retornaLiga()
13. write(p.retornaDato())

La variable p queda valiendo 8 y escribe la letra “b”.


Y si ejecutamos esta otra instrucción:
14. x.retornaLiga().asignaLiga(y)

Nuestros nodos quedan así:

Observe que cuando se ejecuta x.retornaLiga() se está obteniendo el contenido del campo de liga
del nodo x, el cual es el nodo 8 en nuestro ejemplo. Entonces. El nodo 8 invoca el método
asignaLiga() y le estamos enviando como parámetro el nodo y, que en este momento vale 7.
Podemos reorganizar el dibujo de nuestros nodos así:

Ahora, si ejecutamos las siguientes instrucciones:


15. y.asignaLiga(z)
16. y = null
17. z = null
18. p=x
Algoritmos II

44

Nuestra lista queda así:

Y ahora ejecutemos estas instrucciones:


19. while (p != null) do
20. write(p.retornaDato())
21. p = p.retornaLiga()
22. end(while)

En la instrucción 20, la primera vez que entra al ciclo escribe la letra “a”, puesto que p vale 4. En
la instrucción 21 modifica el valor de p: a p le asigna lo que hay en el campo de liga del nodo 4,
es decir, 8. Nuestra lista está así:

La segunda vez que entra al ciclo escribe la letra “b” y la p queda valiendo 7. Nuestra lista está
así:

La tercera vez que entra al ciclo escribe la letra “f” y la p queda valiendo 3:
Algoritmos II

45
La cuarta vez que entra al ciclo escribe la letra “c” y la p queda valiendo null; por consiguiente,
termina el ciclo. Este ciclo, instrucciones 19 a 22, tiene orden de magnitud O(n), siendo n el
número de nodos de la lista.

Ahora, si por último ejecutamos estas instrucciones:


23. x.asignaLiga(x.retornaLiga().retornaLiga())
La lista queda así:

Observe que no hemos dibujado el nodo 8 ya que no está conectado con los otros, pues se ha
desconectado de la lista con la instrucción 23. Lo que hace la instrucción 23 se puede escribir
también con las siguientes instrucciones:

23a. y = x.retornaLiga()
23b. x.asignaLiga(y.retornaLiga())
o
23a. y = x.retornaLiga()
23b. y = y.retorna.Liga()
23c. x.asignaLiga(y)

Es supremamente importante que entienda bien este ejemplo de aplicación de la


clase nodoSimple.

2.2.3 LISTAS SIMPLEMENTE LIGADAS


Las listas simplemente ligadas son estructuras donde se maneja la información de forma
dinámica, donde el espacio donde se la guarda la información es un nodo, donde este se
subdivide en dos espacios un espacio para guardar la información y otro para guardar la dirección
de memoria al siguiente nodo, con estas listas se pueden realizar las operaciones de insertar,
ordenar, buscar, borrar y todo esto bajo el concepto de memoria dinámica.
Algoritmos II

46
2.2.3.1 CLASE LISTAS SIMPLEMENTE LIGADA
Es un conjunto de nodos conectados cuyo elemento básico son objetos de la clase nodoSimple.
Con el fin de poder operar sobre este conjunto de nodos es necesario conocer el primer nodo del
conjunto de nodos que están conectados, y en muchas situaciones el último nodo del conjunto.
Con base en esto vamos a definir la clase llamada LSL (lista simplemente ligada), la cual tendrá
dos datos privados de la clase nodoSimple, que llamaremos primero y último: primero apuntara
hacia el primer nodo de la lista y último apuntará hacia el último nodo de la lista. Además,
definiremos las operaciones que podremos efectuar sobre objetos de dicha clase.

1. CLASE LSL
2. Privado
3. nodoSimple primero, ultimo
4. Publico
5. LSL() // constructor
6. boolean esVacia()
7. nodoSimple primerNodo()
8. nodoSimple ultimoNodo()
9. nodoSimple anterior(nodoSimple x)
10. boolean finDeRecorrido(nodoSimple x)
11. void recorre()
12. nodoSimple buscaDondeInsertar(Objeto d)
13. void insertar(Objeto d, nodoSimple y)
14. void conectar(nodoSimple x, nodoSimple y)
15. nodoSimple buscarDato(Objeto d, nodoSimple y)
16. void borrar(nodoSimple x, nodoSimple y)
17. void desconectar(nodoSimple x, nodoSimple y)
18. void ordenaAscendentemente()
19. end(Clase)
Algoritmos II

47
Expliquemos brevemente cada uno de los métodos definidos. Comencemos con el constructor:
cuando se ejecuta el constructor lo único que se hace es crear una nueva instancia de la clase LSL
con sus correspondientes datos privados en null. Ejemplo: si tenemos estas instrucciones:

1. LSL a, b
2. a = new LSL()
3. b = new LSL()
Lo que se obtiene es:

Para explicar que es lo que hace cada uno de los métodos definidos en la clase LSL consideremos
el siguiente objeto, perteneciente a la clase LSL y que llamamos a:

La función esVacia() retorna verdadero si la lista que invoca el método está vacía; de lo contrario,
retorna falso. Una lista está vacía cuando la variable primero es null.

La función primerNodo() retorna el nodo que esta de primero en la lista. Si efectuamos la


instrucción p = a.primerNodo(), entonces p quedara valiendo 4.

La función ultimoNodo(), retorna el nodo que esta de ultimo en la lista. Si ejecutamos la


instrucción p = a.ultimoNodo(), entonces p quedara valiendo 5.

La función finDeRecorrido(p) retorna verdadero si el nodo p enviado como parámetro es null; de


lo contrario, retorna falso.
Algoritmos II

48
La función anterior(x) retorna el nodo anterior al nodo enviado como parámetro. Si tenemos que
x = 7 y ejecutamos la instrucción p = a.anterior(x), entonces p quedara valiendo 8.

El método recorre(), como su nombre lo dice, simplemente recorre y escribe los datos de una
lista simplemente ligada. Si ejecutamos la instrucción a.recorre(), el resultado que se obtiene es
la escritura de b, e, i, o, u.

La función buscaDondeInsertar(d) retorna el nodo a continuación del cual se debe insertar un


nuevo nodo con dato d en una lista simplemente ligada en la que los datos están ordenados
ascendentemente y deben continuar cumpliendo esta característica después de insertarlo.
Presentemos algunos ejemplos:

y = a.buscaDondeInsertar(“f”) entonces y queda valiendo 8.


y = a.buscaDondeInsertar(“z”) entonces y queda valiendo 5.
y = a.buscaDondeInsertar(“a”) entonces y queda valiendo null.

El método insertar(d, y) consigue un nuevo nodoSimple, lo carga con el dato d e invoca el método
conectar con el fin de conectar el nuevo nodo (llamémoslo x) a continuación del nodo y. Si
ejecutamos las siguientes instrucciones:

d = “f”
y = a.buscaDondeInsertar(d)
a.insertar(d, y)
El objeto a, quedara así:

El método conectar simplemente conecta el nodo x a continuación del nodo y, tal como se ve en
la figura anterior.

La función buscarDato(d, y), como su nombre lo dice, busca el dato d en la lista invoca el método:
si lo encuentra, retorna el nodo en el cual lo encontró; de lo contrario, retorna null. En el
Algoritmos II

49
parámetro y, el cual debe ser un parámetro por referencia, retorna el nodo anterior al nodo en
el cual encontró el dato d. algunos ejemplos son:

x = a.buscarDato(“f”, y) entonces x queda valiendo 9 y y queda valiendo 8.


x = a.buscarDato(“u”, y) entonces x queda valiendo 5 y y queda valiendo 3.
x = a.buscarDato(“b”, y) entonces x queda valiendo 4 y y queda valiendo null.
x = a.buscarDato(“m”, y) entonces x queda valiendo null y y queda valiendo último, es
decir 5.

El método borrar(x, y) controla que el parámetro x sea diferente de null: si x es null produce el
mensaje de que el dato d (el dato buscado con el método buscarDato) no se halla en la lista y
retorna; si x es diferente de null, invoca el método desconectar(x, y).

El método desconectar(x, y) simplemente desconecta el nodo x de la lista que invoca el método.


Para desconectar un nodo de una lista se necesita conocer cuál es el nodo anterior. Por lo tanto,
el nodo y, el segundo parámetro, es el nodo anterior a x. si ejecutamos las siguientes
instrucciones:

d = “i”

x = a. buscarDato(d, y) // x queda valiendo 7 y y queda valiendo 9

a.borrar(x, y) // desconecta el nodo x

Esta queda así:

El método ordenaAscendentemente(), como su nombre lo dice, reorganiza los nodos de una lista
simplemente ligada, de tal manera que los datos en ella queden ordenados ascendentemente.
Algoritmos II

50
Si tenemos la siguiente lista:

Y ejecutamos la instrucción a.ordenaAscendentemente(), la lista quedara así:

Observe que los datos no se han movido, son los nodos los que han cambiado de posición y por
consiguiente primero y último han variado.
Asegúrese de entender bien que es lo que hace cada método. Si usted conoce bien lo que hace
cada método, podrá usarlos apropiadamente; de lo contrario, no.

Como ingeniero de sistemas, debe saber cómo usar los métodos de una clase y
además como implementarlos.

2.2.3.2 IMPLEMENTACIÓN DE LOS MÉTODOS DE LA CLASE LISTA SIMPLEMENTE


LIGADA
El constructor:

LSL()

Primero = ultimo = null

end(LSL)

Simplemente inicializa los datos privados primero y último en null.


Algoritmos II

51
Boolean esVacia()

Return primero == null

end(esVacia)

Recuerde que tanto C como en java la instalación

return x == y

Es equivalente a:

if (x == y)

return true

else

return false

nodoSimple primerNodo()

return primero

end(primero)

nodoSimple ultimoNodo()

return ultimo

end(ultimo)

boolean finDeRecorrido(nodoSimple x)

return x == null

end(finDeRecorrido)

1. void recorre()
2. nodoSimple p
3. p = primerNodo()
4. while no finDeRecorrido(p) do
Algoritmos II

52
5. Write(p.retornaDato())
6. p = p.retornaLiga()
7. end(while)
8. end(Método)

Para recorrer y escribir los datos de una lista simplemente ligada se requiere una variable
auxiliar, la cual llamamos p. dicha variable se inicializa con el primer nodo de la lista simplemente
ligada(instrucción 3 del método anterior) y luego planteamos un ciclo (instrucciones 4 a 7), el cual
se ejecuta mientras p sea diferente a null (método finDeRecorrido()). Cuando p sea null se sale
del ciclo y termina la tarea.

Observe que, si por alguna razón el objeto que invoca este método tiene la lista
vacía, nuestro método funciona correctamente.

Simplemente no escribe nada. Es decir, la instrucción 4 se controlan dos situaciones: lista vacía y
fin de recorrido.

2.2.3.3 PROCESO DE INSERCIÓN DE UN DATO EN UNA LISTA SIMPLEMENTE


LIGADA
Para insertar un dato d en una lista ligada hemos definido tres métodos en nuestra clase LSL:
buscarDondeInsertar(d), insertar(d, y) y conectar(x, y). Analicemos cada uno de ellos. Es
importante recordar que en este proceso de inserción se requiere que los datos de la lista se
hallen ordenados en forma ascendente.

Consideremos la siguiente lista y que se desea insertar el dato “g”. Lo primero que se debe hacer
es determinar en qué sitio debe quedar la letra “g” para que se cumpla que los datos continúen
ordenados en forma ascendente. Por observación, nos damos cuenta de que la “g” debe quedar
entre la “f” y la “o”, es decir, a continuación del nodo 9.
Algoritmos II

53
Nuestro primer método, buscaDondeInsertar(d), efectúa la tarea que determina a continuación
de cual nodo debe quedar el dato a insertar. El método para esta tarea es:

1. nodoSimple buscaDondeInsertar(Objeto d)
2. nodoSimple p, y
3. p = primerNodo()
4. y = anterior(p)
5. while (no finDeRecorrido(p) and p.retornaDato() < d) do
6. y=p
7. p = p.retornaLiga()
8. end(while)
9. return y
10. end(buscaDondeInsertar)
En este método se requieren dos variables auxiliares, las cuales llamamos p y y. con la variable
p se recorre y a medida que la vamos recorriendo se va comparando el dato de p (p.retornaDato())
con el dato d que se desea insertar. Mientras que el dato de p sea menor que d se avanza en la
lista con p y con y. la variable y siempre estará apuntando hacia a p. como inicialmente p es el
primer nodo, inicialmente y es null. Fíjese que si el dato d a insertar es menor que el dato del
primer nodo de la lista simplemente ligada nuestro método retorna null. El hecho de que nuestro
método retorne null significa que el dato a insertar va a quedar de primero en la lista.

Veamos ahora como es el algoritmo para nuestro método Insertar(d, y):

1. void insertar(Objeto d, nodoSimple y)


2. nodoSimple x
3. x = nodoSimple(d)
4. conectar(x, y)
5. end(Método)
Nuestro método insertar consigue simplemente un nuevo nodo, lo carga con el dato d e invoca
el método conectar. El parámetro y se refiere al nodo a continuación del cual habrá que conectar
el nuevo x.

Ocupémonos del método conectar(x, y):

• Caso 1:
Al ejecutar y = buscaDondeInsertar(d) con d = “g” en el objeto de la figura anterior y el método
insertar(d, y), estamos en la situación mostrada en la figura siguiente:
Algoritmos II

54

Conectar el nodo x a continuación del nodo y implica que cuando lleguemos al nodo y
debemos trasladarlo hacia el nodo x, o sea que el campo de liga del nodo y debe quedar
valiendo 6, y cuando estemos en el nodo x debemos trasladarnos hacia el nodo 3, es decir,
que el campo liga del nodo x debe quedar valiendo 3. O sea que, hay que modificar dos
campos de liga: el campo de liga del nodo y y el campo de liga del nodo x. para lograr esto
debemos ejecutar las siguientes instrucciones:
x.asignaLiga(y.retornaLiga()) // modifica el campo de liga del nodo x
y.asignaLiga(x) // modifica el campo de liga del nodo y
Y la lista queda así:

Y como se podrá observar, se ha insertado el nodo x a continuación del nodo y

• Caso 2:
Consideremos que el dato a insertar es d = “z”. Al ejecutar y=buscaDondeInsertar(d), y el
método insertar(d, y), estamos en la siguiente situación:
Algoritmos II

55

Conectar x a continuación de y se logra con las mismas instrucciones del caso 1, teniendo en
cuenta que como el dato que se inserta queda de ultimo hay que actualizar la variable último.
Lo anterior significa que las instrucciones para conectar x a continuación de y serán:
x.asignaLiga(y.retornaLiga()) // modifica el campo de liga del nodo x
y.asignaLiga(x) // modifica el campo de liga del nodo y
if (y == ultimo) then
ultimo = x
end(if)
Y la lista queda así:

• Caso 3:
Consideremos que el dato a insertar es d = “a”. al ejecutar y=buscaDondeInsertar(d) y el
método insertar(d, y), estamos en la situación mostrada:
Algoritmos II

56
En esta situación, cuando la y es null, es decir que el dato a insertar quedara de primero, hay
que modificar el campo de la liga x y la variable primero:

x.asignaLiga(primero)

primero = x

Pero hay que tener en cuenta que la lista pudo haber estado vacía. Si esa fuera la situación
habría que actualizar también la variable última. Considerando estas situaciones, las
instrucciones completas para conectar un nodo x al principio de la lista son:

x.asignaLiga(primero)

if (primero == null) then

ultimo = null

end(if)

primero = x

El algoritmo completo para el método conectar consiste simplemente en agrupar las


instrucciones descritas para cada uno de los casos en un solo método. Dicho método se
presenta a continuación: en las instrucciones 2 a 7 se consideran los casos 1 y 2, mientras que
en las instrucciones 9 a 13 se considera el caso 3:

1. void conectar(nodoSimple x, nodoSimple y)


2. if (y != null) then
3. x.asignaLiga(y.retornaLiga())
4. y.asignaLiga(x)
5. if (y == ultimo) then
6. ultimo = x
7. end(if)
8. Else
9. x.asignaLiga(primero)
10. if (primero == null) then
11. ultimo = x
12. end(if)
13. primero = x
14. end(if)
15. end(Método)
Algoritmos II

57
2.2.3.4 PROCESO DE BORRADO DE UN DATO EN UNA LISTA SIMPLEMENTE LIGADA
Borrar un dato de una lista simplemente ligada consiste en ubicar el nodo en el cual se halla el
dato y desconectarlo de lista. Para ello hemos definido tres métodos que hemos llamado
buscarDato(d, y), borrar(x, y) y desconectarlo(x, y).

Si tenemos la siguiente lista:

Y queremos borrar el nodo que contiene la letra “f”, lo primero que debemos hacer es determinar
en cual nodo se halla la letra “f” y cuál es su nodo anterior. Esta tarea se efectúa con el método
buscarDato(d, y): ejecutamos la instrucción

x = a.buscarDato(d, y)

Y estamos en esta situación:

El algoritmo correspondiente al método buscarDato(d, y) es similar al método


buscarDondeInsertar(d) y lo presentamos a continuación.

1. nodoSimple buscarDato(Objeto d, nodoSimple y)


2. nodoSimple x
3. x = primerNodo()
4. y = anterior(x)
5. while (no finDeRecorrido(x) and x.retornaDato()) != d do
6. y=x
7. x = x.retornaLiga()
8. end(while)
Algoritmos II

58
9. return x
10. end(buscarDato)

Utilizamos la variable x para recorrer la lista e ir comparando el dato del nodo x con d. de este
ciclo, instrucciones 5 a 8, se sale cuando encuentre o cuando haya terminado de recorrer la lista.
Observe que cuando el dato que se busca no se halla en la lista el parámetro y queda valiendo
último. Además, cuando el dato que se busca es el primer dato de la lista, la variable y queda
valiendo null.

Nuestro método borrar(x, y) consiste simplemente en controlar que el nodo x sea diferente de
null. Si el nodo x es null significa que el dato no se encontró en la lista; por consiguiente no hay
ningún nodo para desconectar, entonces produce el mensaje correspondiente y retorna al
programa llamante puesto que no hay nada más que hacer. Si x es diferente de null se invoca el
método desconectar. El algoritmo correspondiente a dicho método se presenta a continuación:

void borrar(nodoSimple x, nodoSimple y)

if x == null then

write(“dato no existe”)

return

end(if)

desconectar(x, y)

end(Método)

Analicemos ahora las instrucciones correspondientes al método desconectar (x, y).

• Caso 1:
Es la situación mostrada en la figura anterior. Lo único que hay que hacer para desconectar el
nodo x es modificar el campo de liga del nodo y, asignándole lo que hay en el campo liga del
nodo x. esto se logra con la siguiente instrucción:
y.asignaLiga(x.retornaLiga())
Algoritmos II

59
• Caso 2:
Si el dato a borrar es d = “z”, al ejecutar la instrucción
x = a.buscarDato(d, y)

La situación es:

Por consiguiente, cuando se desconecte el nodo x la variable última debe quedar valiendo y y
las instrucciones serán:
y.asignaLiga(x.retornaLiga())
if (x == ultimo) then
ultimo = y
end(if)

• Caso 3:
Si el dato a borrar es d = “b” al ejecutar la instrucción
x = a.buscarDato(d, y)
La situación es:

Con la variable y valiendo null.


En esta situación lo que hay que hacer es actualizar la variable primero: a primero se le asigna
lo que haya en el campo de liga primero. Hay que tener en cuenta que puede suceder que la lista
solo hubiera tenido un nodo; en este caso la variable primero quedará valiendo null, y por lo tanto
Algoritmos II

60
la variable ultimo también deberá quedar valiendo null. Las instrucciones para resolver este caso
son:

primero = primero.retornaLiga()
if (primero == null) then
ultimo = null
end(if)

Agrupando las instrucciones correspondientes a los tres casos en un solo algoritmo obtenemos:
1. void desconectar (nodoSimple x, nodoSimple y)
2. if (x != primero) then
3. y.asignaLiga(x.retornaLiga())
4. if (x == ultimo) then
5. ultimo = y
6. end(if)
7. else
8. primero = primero.retornaLiga()
9. if (primero == null) then
10. ultimo = null
11. end(if)
12. end(if)
13. end(Método)

En las instrucciones 2 a 6 se resuelve para los casos 1 y 2, mientras que en las instrucciones 8 a 11
se resuelve para el caso 3.

Los métodos para conectar y desconectar son métodos con orden de magnitud 0(1), lo cual
mejora sustancialmente estas operaciones con respecto a la representación de datos en un
vector.
Algoritmos II

61
2.2.3.5 ORDENAMIENTO DE DATOS EN UNA LISTA SIMPLEMENTE LIGADA
Presentemos a continuación un método que utiliza el método selección, el cual, se enuncia así:
“de los datos que faltan por ordenar, determine cuál es el menor y colocarlo de primero en ese
conjunto de datos”.

1. void OrdenaAscendentemente()
2. nodoSimple p, ap, menor, amenor, q, aq
3. p = primerNodo()
4. ap = anterior(p)
5. while (p != ultimoNodo())
6. menor = p
7. amenor = ap
8. q = p.retornaLiga
9. aq =p
10. while (no finDeRecorrido(q))
11. if (q.retornaDato() < menor.retornaDato())
12. menor = q
13. amenor = aq
14. end(if)
15. aq =q
16. q = q.retornaLiga()
17. end(while)
18. if(menor == p)
19. ap = p
20. p = p.retornaLiga()
21. else
22. desconectar(menor, amenor)
23. conectar(menor, ap)
24. ap = menor
25. end(if)
26. end(while)
27. end(Método)

Con base en este enunciado definimos las variables necesarias para elaborar el método. Se
requiere una variable que indique a partir de cual nodo faltan datos por ordenar. Dicha variable
la llamamos p. inicialmente p apunta hacia el primer nodo ya que al principio faltan todos los
datos por ordenar. Se requiere otra variable para recorrer la lista e ir comparando los datos para
Algoritmos II

62
determinar en cual nodo se halla el menor dato. Esta variable la llamamos q. y por último
requerimos otra variable que apunte hacia el nodo que tiene el menor dato. Esta variable la
llamamos menor.

Nuestro método consistirá entonces en determinar cuál es el nodo que tiene el


menor dato entre los datos que faltan por ordenar; desconectamos el nodo de ese
sitio y lo conectamos al principio de ese conjunto de datos.

Como ya hemos visto, para efectuar las operaciones de conexión y desconexión se requiere
conocer los registros anteriores a los nodos con los cuales se desean ejecutar dichas
operaciones; por lo tanto, definimos otras tres variables que llamaremos ap, aq y amenor, las
cuales apuntan hacia los nodos anteriores a p, q y menor respectivamente.

En la instrucción 5 definimos el ciclo principal del ciclo. El método terminara cuando p este
apuntando hacia el último nodo de la lista, puesto que cuando p este apuntando hacia ese nodo
significa que solo falta un dato por ordenar, y un solo dato esta ordenado.

En las instrucciones 6 y 7 asignamos los valores iniciales de menor y amenor. Inicialmente


consideramos que el menor dato es el principio del conjunto de datos que faltan por ordenar, es
decir, p.

En las instrucciones 8 y 9 se asignan los valores iniciales de q y aq.

En las instrucciones 10 a 17 se efectúa el ciclo para determinar cuál es el nodo que contiene el
menor dato. Se compara el dato de nodo q con el dato del nodo menor. Si el dato de q es menor
que el dato de menor, actualizaremos menor y su interior. Cualquiera que hubiera sido el
resultado avanzamos con q y su anterior.

En la instrucción 18 confrontamos en cual posición está el nodo de tiene el menor dato. Si el


menor dato estaba de primero en ese conjunto de datos, es decir, en p, simplemente avanzamos
con p y su anterior (instrucciones 19 y 20); de lo contrario, desconectamos el nodo menor, cuyo
anterior es amenor, y lo conectamos a continuación de ap (instrucciones 22 y 23). De haber
ocurrido esto último, el anterior a p, es decir ap, ya es menor (instrucción 24).
Algoritmos II

63
2.2.3.6 MÉTODO ANTERIOR(X)
Ya hemos definido que nuestro método denominado anterior(x) retornara el nodo anterior al
nodo x enviado como parámetro. Para ello bastara con recorrer la lista simplemente ligada
utilizando dos variables auxiliares: una (llamémosla p) que se va comparando con x, y otra que
siempre apuntara hacia el nodo anterior a x (llamémosla y). cuando p sea igual a x, simplemente
se retorna y. inicialmente será el primer nodo y y será null. Nuestro método es el siguiente:

nodoSimple anterior(x)

nodoSimple p, y

p = primerNodo()

y = null

while (p != x) do

y=p

p = p.retornaLiga()

end(while)

return y

end(anterior)

2.2.3.7 CONSTRUCCIÓN DE LISTAS SIMPLEMENTE LIGADAS


Pasemos ahora a considerar como construir listas simplemente ligadas. Básicamente hay tres
formas de hacerlo: una, que los datos queden ordenados ascendentemente a medida que la lista
se va construyendo; otra, insertando nodos siempre al final de la lista; y una tercera, insertando
los nodos siempre al principio de la lista.

Para la primera forma de construcción (que los datos queden ordenados ascendentemente a
medida que se va construyendo la lista), un método general es:

LSL a

a = new LSL()
Algoritmos II

64
Mientras haya datos por leer haga

Lea(d)

y = a.buscaDondeInsertar(d)

a.insertar(d, y)

end(mientras)

Es decir, basta con plantear un ciclo para la lectura de datos e invocar los métodos para buscar
donde insertar e insertar desarrollados previamente.

Para la segunda forma de construcción (insertando nodos siempre al final de la lista), un método
es:

LSL a

nodoSimple

a = new LSL()

mientras haya datos por leer haga

lead(d)

y = a.ultimoNodo()

a.insertar(d, y)

end(mientras)

Nuevamente se plantea un ciclo para la lectura de datos y cada vez que se lea un dato se
determina el último y se invoca el método para insertar.

2.2.4 DIFERENTES TIPOS DE LISTAS SIMPLEMENTE LIGADAS Y SUS


CARACTERÍSTICAS
Comencemos presentando gráficamente los diferentes tipos de lista que se pueden construir:

• Listas simplemente ligadas:


Algoritmos II

65

• Listas simplemente ligadas circulares:

• Listas simplemente ligadas circulares con nodo cabeza:

• Listas simplemente ligadas con nodo cabeza:

2.2.5 EJERCICIO DE APRENDIZAJE


En cada tema anterior se muestra los ejercicios de aprendizaje.

2.2.6 TALLER DE ENTRENAMIENTO


Esta es la tercera forma de construcción de lista (insertando nodos siempre al principio de la lista),
nuestro método es:
Algoritmos II

66
LSL a

a = new LSL

Mientras haya datos por leer haga

lead(d)

a.insertar(d, null)

end(mientras)

Aquí, basta con plantear el ciclo para lectura de datos e invocar el método insertar enviado como
segundo parámetro null. Recuerde que nuestro método insertar(d, y) inserta un nodo con dato d
a continuación de y: si y es null significa que el dato d quedara de primero.

Nota: realice una prueba de escritorio para que visualice el proceso.

TIPS
El manejo dinámico de la memoria nos permite optimizar este recurso.

2.3 TEMA 2 LISTAS DOBLEMENTE LIGADAS Y OTROS TIPOS DE


TOPOLOGÍAS DE LISTAS
➢ Video: ” Lista Doblemente Ligada”
➢ ” Ver Video”:
https://www.youtube.com/watch?v=6C8_tKeGp6I&ab_channel=EdwinGonz%C3%A1lezTer%C3
%A1n ”

2.3.1 LISTA DOBLEMENTE LIGADAS


Las listas doblemente ligadas son estructuras que permiten manejar la información de forma
dinámica, donde el espacio donde se guarda la información es un nodo, que es una dirección de
memoria dividida en tres partes, una parte para guardar la información, otra para la dirección al
Algoritmos II

67
siguiente nodo y la otra para apuntar al nodo anterior, con estas listas se pueden realizar las
operaciones de insertar, borrar, buscar, ordenar y todo bajo el concepto de memoria dinámica.

2.3.1.1 DEFINICIÓN DE CARACTERÍSTICAS


Un nodo doble es un registro que tiene dos campos de liga: uno que llamaremos Li (Liga izquierda)
y otro que llamaremos Ld (Liga derecha). Un dibujo para representar dicho nodo es:

Liga izquierda Área de datos Liga derecha

Donde:

Li: apunta hacia el nodo anterior.

Ld: apunta hacia el nodo siguiente.

Con base en esto vamos a definir una clase que llamaremos nodoDoble:

1. CLASE nodoDoble
2. Privado
3. Objeto dato
4. nodoDoble Li, Ld
5. publico
6. nodoDoble(objeto d) // constructor
7. void asignaDato(objeto d)
8. void asignaLd(nodoDoble x)
9. void asignaLi(nodoDoble x)
10. objeto retornaDato()
11. nodoDoble retornaLd()
12. nodoDoble retornaLi()
13. end(Clase)
Algoritmos II

68
Los algoritmos correspondientes a cada uno de estos métodos son los siguientes:

1. nodoDoble(objeto d) // constructor
2. dato = d
3. Ld = null
4. Li = null
5. end(nodoDoble)

1. void asignaDato(objeto d)

2. dato = d

3. end(Método)

1. void asignaLd(nodoDoble x)

2. Ld = x

3. end(asignaLd)

1. void asignaLi(nodoDoble x)

2. Li = x

3. end(asignaLi)

1. objeto retornaDato()
2. return dato
3. end(retornaDato)
1. nodoDoble retorna(Li)
2. return Li
Algoritmos II

69
3. end(retornaLi)
1. nodoDoble Ld()
2. return Ld
3. end(retornaLd)

2.3.1.2 PROPIEDAD FUNDAMENTAL DE LAS LISTAS DOBLEMENTE LIGADAS


Es importante ver aquí lo que se conoce como la propiedad fundamental de las listas
doblemente ligadas ya que está basada en las características de los campos de liga de los nodos
dobles. Para ello, consideremos los siguientes tres registros consecutivos:

El campo de la liga izquierda del nodo x vale 9, lo cual indica que el nodo anterior es el nodo 9;
por lo tanto, el nodo 9 es el nodo x.retornaLi().

El campo de liga derecha del nodo x vale 5, lo cual indica que el nodo siguiente es el nodo 5; por
consiguiente, el nodo 5 es el nodo x.retornaLd().

El nodo x.retornaLi() tiene un campo de liga derecha, el cual vale 7, es decir, el mismo nodo x.

El nodo x.retornaLd() tiene un campo de liga izquierda, el cual también vale 7, es decir, el nodo
x.

Dado lo anterior, se tiene que:

x.retornaLi().retornaLd() == x == x.retornaLd().retornaLi()

Esta característica se conoce como la “propiedad fundamental de las listas doblemente ligadas”,
y es de suprema importancia entenderla bien con el fin de manipular apropiadamente los objetos
de la clase lista doblemente ligada.
Algoritmos II

70
2.3.2 EJERCICIO DE APRENDIZAJE
Teniendo definida esta clase (clase doble), procedemos a desarrollar un método en el cual
hagamos uso de ella y de sus métodos. Consideremos que se tiene el conjunto de datos b, e, i,
o, u, y que nuestro método los leerá en ese orden:

1. nodoDoble w, x, y, z
2. read(d)
3. w = new nodoDoble(d)
4. read(d)
5. x = new nodoDoble(d)
6. read(d)
7. y = new nodoDoble(d)
8. read(d)
9. z = new nodoDoble(d)
Al ejecutar estas instrucciones el escenario que se obtiene es el siguiente:

Continuemos nuestro algoritmo con estas instrucciones:


10. w.asignaLd(x)
11. x.asignaLi(w)
12. x.asignaLd(y)
13. y.asignaLi(x)
14. y.asignaLd(z)
15. z.asignaLi(y)
Al ejecutar estas instrucciones nuestro escenario es:

Fíjese que el campo Ld del nodo w quedo valiendo 2, en virtud de la instrucción 10, lo cual indica
que el nodo siguiente a w es el 2; el campo Li de x (el nodo 2) quedo valiendo 6, en virtud de la
instrucción 11, lo cual significa que el nodo anterior a x es el 6; el campo Ld del nodo x quedo
Algoritmos II

71
valiendo 7, en virtud de la instrucción 12, lo cual quiere decir que el nodo siguiente a x es el 7. De
una forma similar se han actualizado los campos Li y Ld de los nodos y y z. Asegúrese de entender
bien el escenario actual.
Continuemos ejecutando las siguientes instrucciones:
16. w = null
17. y = null
18. z = null
En este momento nuestra situación es:

Ahora ejecutemos estas otras instrucciones:

19. read(d)
20. y = new nodoDoble(d)
21. y.asignaLd(x.retornaLd())
22. y.asignaLi(x)
23. y.retornaLd().asignaLi(y)
24. x.asignaLd(y)

La situación queda así:

Ahora, al ejecutar esta instrucción:


25. y = null
La nueva situación es:

Por último, ejecutemos estas otras instrucciones:


Algoritmos II

72
26. x.retornaLi().asignaLd(x.retornaLd())
27. x.retornaLd().asignaLi(x.retornaLi())

Y nuestros nodos quedan así:

Fíjese que el resultado de ejecutar las instrucciones 26 y 27 fue eliminar el nodo x. unas de
instrucciones que harían la misma tarea que las instrucciones 26 y 27 son:

1. p = x.retornaLi()
2. q = x.retornaLd()
3. p.asignaLd(q)
4. q.asignaLi(p)
Si lo hubiéramos hecho de ese modo, la lista quedaría así:

Es muy importante entender que las instrucciones 26 y 27 ejecutan la misma tarea que las
instrucciones 1, 2, 3 y 4 mostradas a continuación de ellas.

2.3.3 TALLER DE ENTRENAMIENTO


Pautas para desarrollar los siguientes ejercicios: Para desarrollar cada uno de esos ejercicios,
debes tener muy claro el concepto de nodo y la forma de cómo vas a estructurar el nodo.
Recuerda que, para listas simplemente ligadas el nodo se compone de dos partes, una parte de
datos y una parte de liga; para las listas doblemente ligadas el nodo se conforma de tres partes,
una parte de dato, y dos partes de ligas, una liga que apunta al nodo anterior y una liga que apunta
al nodo siguiente.

1. Elabore un método que lea un entero n y que construya una lista simplemente ligada, de a
digito por nodo.

2. Elabore un método que borre de una lista simplemente ligada un dato dado todas las veces
que lo encuentre.
Algoritmos II

73
3. Se tiene una lista simplemente ligada, con un dato numérico en cada nodo. Elabore un método
que determine e imprima el promedio de datos de la lista.

4. Elabore un método que intercambie los registros de una lista doblemente ligada así: el
primero con el último, el segundo con el penúltimo, el tercero con el antepenúltimo, y así
sucesivamente.

5. Elabore un método que intercambie los registros de una lista doblemente ligada así: el
primero con el tercero, el segundo con el cuarto, el quinto con el séptimo, el sexto con el
octavo, y así sucesivamente.

TIPS
Las listas dobles permiten realizar recorridos de izquierda a derecha y de
derecha a izquierda.

2.4 TEMA 3 PILAS Y COLAS (ESTÁTICAS Y DINÁMICAS)


➢ Video: ” Manipulación de información. Pilas y colas”
➢ ” Ver Video”: https://www.youtube.com/watch?v=56jBvugTCn0 ”

2.4.1 RELACIÓN DE CONCEPTOS


➢ Pilas: Son estructuras de datos que se manejan de forma estática, donde se realiza las
operaciones de apilar y desapilar, manejando el concepto de último en llegar primero en
salir.

➢ Vector: En programación se denomina matriz, vector o formación (en inglés array) a una
zona de almacenamiento continuo que contiene una serie de elementos del mismo tipo,
los elementos de la matriz. Desde el punto de vista lógico una matriz se puede ver como
un conjunto de elementos ordenados en fila (o filas y columnas si tuviera dos
dimensiones).
Tomado de: https://es.wikipedia.org/wiki/Vector_(inform%C3%A1tica)

➢ Listas ligadas: Las listas ligadas son una mejor alternativa para añadir información a una
base de datos; en lugar de utilizar arreglos que son estáticos pues hay que reservar
Algoritmos II

74
memoria a la hora de programarlos, contrario a las listas ligadas que en un principio no
ocupan memoria y se va reservando (creando) conforme lo va requiriendo nuestro
programa. La memoria que ya no se utiliza se puede destruir y así ahorramos memoria en
nuestros programas.
Tomado de: http://sergioaj.blogspot.com.co/2011/01/listas-ligadas.html

➢ Crear: Crea una pila vacía.

➢ Apilar: Incluye el dato d en la pila.

➢ Desapilar: Elimina el último elemento de la pila y deja una nueva pila, la cual queda con
un elemento menos.

➢ Cima: Retorna el dato que esta de último en la pila, sin eliminarlo.

➢ esVacia: Retorna verdadero si la pila está vacía: de lo contrario; retorna falso.

➢ esLlena:Retorna verdadero si la pila está llena; de lo contrario; retorna falso.

➢ Cola: Son estructuras de almacenamiento de datos, donde se maneja de forma estática,


donde las operaciones de inserción se hace al final y las de borrado se hace al principio,
en otras palabras el primero que llega es el primero en salir, y el último en llegar será el
último en salir

➢ Crear: Crea una cola vacía.

➢ esVacia: Retorna verdadero si la cola está vacía; de lo contrario, retorna falso.

➢ esLlena: Retorna verdadero si la cola está llena; de lo contrario, retorna falso.

➢ Encolar: Inserta un dato d al final de la cola.

➢ Desencolar: Remueve el primer elemento de la cola.

➢ Siguiente: Retorna el dato que se halla de primero en la cola.


Algoritmos II

75
2.4.2 DEFINICIÓN
Son estructuras de datos que no tienen representación propia a nivel de programación, para ello
se apoyan en los vectores y en listas ligadas, permitiendo así manejar operaciones sobre ellas.

2.4.3 PILAS CON VECTORES Y LISTAS


Las pilas como vectores son estructuras de datos que se manejan de forma estática, donde se
realiza las operaciones de apilar y desapilar, manejando el concepto de último en llegar primero
en salir. Las pilas como lista son estructuras de datos, pero se maneja de forma dinámica en la
memoria.

2.4.3.1 DEFINICIÓN
Una pila es una lista ordenada en la cual todas las operaciones (inserción y borrado) se efectúan
en un solo extremo llamado tope. Es una estructura LIFO (Last Input First Output), que son las
iniciales de las palabras en inglés “ultimo en entrar primero en salir”, debido a que los datos
almacenados en ella se retiran en orden inverso al que fueron entrados.

Un ejemplo clásico de aplicación de pilas en computadores se representa en el proceso de


llamadas a métodos y sus retornos.

Supongamos que tenemos un programa principal y tres métodos, así:

Cuando se ejecuta el programa principal, se hace una llamada al método P1, es decir, ocurre una
interrupción en la ejecución del programa principal. Antes de iniciar la ejecución de este método
Algoritmos II

76
se guarda la dirección de la instrucción donde debe retornar a continuar la ejecución del
programa principal cuando termine de ejecutar el método; llamamos L1 esta dirección. Cuando
ejecuta el método P1 existe una llamada al método P2, hay una nueva interrupción, pero antes
de ejecutar P2 se guarda la dirección de la instrucción donde debe retornar a continuar la
ejecución del método P1, cuando termine de ejecutar el método P2; llamamos L2 esta dirección.

Hasta el momento hay guardados dos direcciones de retorno:

L1, L2

Cuando ejecuta el método P2 hay llamada a un método P3, lo cual implica una nueva interrupción
y, por ende, guardar una dirección de retorno al método P2, la cual llamamos L3.

Tenemos entonces tres direcciones guardadas, así:

L1, L2, L3

Al terminar la ejecución del método P3 retorna a continuar ejecutando en la última dirección que
guardo, es decir, extrae la dirección L3 y regresa a continuar ejecutando el método P2 en dicha
instrucción. Los datos guardados ya son:

L1, L2

Al terminar el método P2 extrae la última dirección que tiene guardada, en este caso L2, y retorna
a esta dirección a continuar la ejecución del método P1.

En este momento los datos guardados son:

L1

Al terminar la ejecución del método P1 retornara a la dirección que tiene guardada, o sea a L1.

Obsérvese que los datos fueron procesados en orden inverso al que fueron almacenados, es decir,
último en entrar primero en salir. En esta forma de procesamiento la que define una estructura
PILA.

Veamos cuales son las operaciones que definiremos para la clase pila.

Operaciones Características
Algoritmos II

77
Crear Crea una pila vacía.

Apilar Incluye el dato d en la pila.

Elimina el último elemento de la pila y deja una nueva pila, la cual queda con un
Desapilar
elemento menos.

Cima Retorna el dato que esta de último en la pila, sin eliminarlo.

esVacia Retorna verdadero si la pila está vacía: de lo contrario; retorna falso.

esLlena Retorna verdadero si la pila está llena; de lo contrario; retorna falso.

2.4.3.2 REPRESENTACIÓN DE PILAS EN UN VECTOR


La forma más simple es utilizar un arreglo de una dimensión y una variable, que llamaremos
tope, que indique la posición del arreglo en la cual se halla el último elemento de la pila. Por
consiguiente, vamos a definir la clase pila derivada de la clase vector. La variable m de nuestra
clase vector funciona coma la variable tope. Definamos entonces la clase pila.

1. CLASE Pila

2. Privado:

3. Object V[]

4. Entero tope, n

5. Publico:

6. pila(entero m) //constructor

7. boolean esVacia()

8. boolean esLlena()

9. void apilar(objeto d)

10. object desapilar()


Algoritmos II

78
11. void desapilar(entero i)

12. objeto tope()

13. end(Clase)

Como hemos definido la clase pila derivada de la clase vector, veamos como son los algoritmos
correspondientes a estos métodos:

1. pila(entero m) //constructor

2. V = new array[m]

3. n=m

4. Tope = 0

5. end(pila)

1. boolean esVacia()

2. return tope == 0

3. end(esVacia)

1. boolean esLlena()

2. return tope == n

3. end(esLlena)

1. void Apilar(objeto d)

2. if (esLlena()) then

3. write(“pila llena”)

4. return
Algoritmos II

79
5. end(if)

6. tope = tope + 1

7. V[tope] = d

8. end(Método)

Apilar consiste simplemente en sumarle 1 a tope y llevar el dato a esa posición del vector:

1. objeto desapilar()

2. if (esVacia()) then

3. write(“pila vacia”)

4. return null

5. end(if)

6. d = V[tope]

7. tope = tope -1

8. return d

9. end(desapilar)

Estrictamente hablando, desapilar consiste simplemente en eliminar el dato que se halla en el


tope de la pila. Sin embargo, es usual eliminarlo y retornarlo. Eso es lo que hace nuestro anterior
método para desapilar. El proceso de eliminación consiste simplemente en restarle 1 a tope.
Definamos, en forma polimórfica, otro método para desapilar. Este nuevo método tendrá un
parámetro i, el cual indicara cuantos elementos se deben eliminar de la pila:

1. void Desapilar(entero i )

2. if ((tope – 1) >= 0) then

3. tope = tope – i
Algoritmos II

80
4. else

5. write(“no se pueden eliminar tantos elementos”)

6. end(if)

7. end(Método)

1. Objeto cima()

2. if (esVacia()) then

3. write(“pila vacia”)

4. return null

5. end(if)

6. return V[tope]

7. end(cima)

2.4.3.3 REPRESENTACIÓN DE PILAS COMO LISTAS LIGADAS


Para representar una pila como listas ligadas basta definir la clase pila derivada de la clase LSL.
Nuevamente, como estamos definiendo la clase pila derivada de la clase. LSL, podremos hacer
uso de todos los métodos que hemos definido para esta clase LSL. A modo de ejemplo,
representemos como listas ligadas la siguiente pila:

A
Algoritmos II

81
Pila

Dibujémosla de la forma como solemos hacerlo con las listas ligadas:

Como hemos dicho las operaciones sobre una pila son apilar y desapilar:

• Apilar: consiste en insertar un registro al principio de una lista ligada.

• Desapilar: consiste en eliminar el primer nodo de la lista ligada y retornar el dato que se
hallaba en ese nodo.

Al definir la clase pila derivada de la clase LSL el método para controlar pila llena ya no se utiliza.
Los métodos para la clase pila son:

1. void Apilar(objeto d)

2. insertar(d, null)

3. end(Método)

1. Objeto desapilar()

2. If (esVacia()) then

3. Write(“pila vacia, no se puede desapilar”)

4. Return null

5. End(if)

6. nodoSimple p

7. p = primerNodo()

8. d = p.retornaDato()

9. borrar(p, null)

10. return d
Algoritmos II

82
11. end(desapilar)

Fíjese que en los métodos apilar y desapilar hemos usado los métodos insertar y borrar, los
cuales fueron definidos para la clase LSL:

1. Objeto tope()

2. if (esVacia()) then

3. write(“pila vacía, no hay elemento para mostrar”)

4. return null

5. end(if)

6. nodoSimple p

7. p = primerNodo()

8. return p.retornaDato

9. end(tope)

2.4.3.4 MANEJO DE DOS PILAS EN UN VECTOR


Puesto que es muy frecuente tener que manejar más de una pila en muchas situaciones, veremos
cómo manejar dos pilas en un solo vector. Si n es el número de elementos del vector, dividimos
inicialmente el vector en dos partes iguales. Llamamos m la variable que apunta hacia el elemento
de la mitad.

La primera mitad del vector será para manejar la pila 1, y la segunda mitad para manejar la pila
2.

Cada pila requiere una variable tope para conocer en qué posición está el ultimo dado de la pila.
Llamemos estas variables tope 1 y tope 2 para manejar las pilas 1 y 2, respectivamente.

Consideremos el siguiente ejemplo:


Algoritmos II

83
Sea V el vector en el cual manejaremos las dos pilas. El valor de n es 16. Inicialmente, el valor de
m es 8 (la mitad de n):

• La variable tope1 variara de 1 hasta m

• La variable tope2 variara desde m + 1 hasta n

• La pila 1 estará vacía cuando tope1 sea igual a 0

• La pila 1 estará llena cuando tope1 sea igual a m

• La pila 2 estará vacía cuando tope2 sea igual a m

• La pila 2 estará llena cuando tope2 sea igual a n

Si definimos una clase denominada dosPilas cuyos datos privados son el vector V, m, n, tope1 y
tope2, veamos cómo serán los métodos para manipular dicha clase.

Consideremos primero un método para apilar un dato en alguna de las dos pilas. Habrá que
especificar en cual pila es que se desea apilar. Para ello utilizaremos una variable llamada pila, la
cual enviamos como parámetro del método. Si pila es igual a 1 hay que apilar en pila 1, y si pila
es igual a 2, hay que apilar en pila 2. Nuestro método será:

1. void Apilar(entero pila, objeto d)

2. If (pila == 1) then

3. If (tope == m) then

4. pilaLlena(pila)

5. end(if)

6. tope1 = tope1 + 1

7. V[tope] = d
Algoritmos II

84
8. else

9. If (tope2 == n) then

10. pilaLlena(pila)

11. end(if)

12. tope2 = tope2 + 1

13. V[tope2] = d

14. end(if)

15. end(Método)

El método pilaLlena recibe como parámetro el valor pila.

La tarea de pilaLlena es: si el parámetro pila es 1 significa que la pila 1 es la que está llena, y por
lo tanto buscara espacio en la pila 2; en caso de que esta no esté llena, se moverán los datos de
la pila 2 una posición hacia la derecha, se actualizara m y se regresara al método apilar para apilar
en la pila 1. Si es la pila 2 la que está llena buscara si hay espacio en la pila 1, y en caso de haberlo
moverá los datos de la pila 2 una posición hacia la izquierda, actualizara m y regresara a apilar en
la pila 2. Un método que efectué esta tarea es:

1. void PilaLlena(entero pila)

2. entero i

3. if (pila == 1) then

4. if (tope2 < n) then //hay espacio en la pila 2

5. for (i = tope2; i > m; i--) do

6. V[i + 1] =V[i]

7. end(for)

8. tope2 = tope2 + 1

9. m=m+1
Algoritmos II

85
10. end(if)

11. else

12. if (tope1 < m) then

13. for (i = m; i < tope2; i++) do

14. V[i - 1] = V[i]

15. end(for)

16. tope2 = tope2 – 1

17. m=m–1

18. end(if)

19. end(if)

20. write(“pilas llenas”)

21. stop

22. end(Método)

Como se podrá observar, la operación de apilar implica mover datos en el vector debido a que
una pila puede crecer más rápido que la otra. En otras palabras, este método tiene orden de
magnitud lineal, el cual se considera ineficiente. Una mejor alternativa de diseño es la siguiente:

La pila 1 se llenara de izquierda a derecha y la pila 2 de derecha a izquierda. Con este diseño no
hay que preocuparse por cual pila crezca más rápido. Las condiciones a controlar son:

• La pila 1 estará vacía cuanto tope1 sea igual a cero

• La pila 2 estará vacía cuando tope2 sea igual a n + 1 (n =16)

• Las pilas estarán llenas cuando tope1 + 1 sea igual a tope2


Algoritmos II

86
El método apilar para este segundo diseño es:

1. void Apilar(entero pila, objeto d)

2. if (tope1 + 1 == tope2) then

3. pilaLlena

4. end(if)

5. if (pila == 1) then

6. tope1 = tope1 + 1

7. V[tope] = d

8. end(if)

9. end(Método)

Como podrá observar, con este diseño el proceso para pila llena no aparece; por lo
tanto, no hay que mover los datos del vector y nuestro método para apilar tiene
orden de magnitud constante.

2.4.4 TEMA 3 COLAS CON VECTORES Y LISTAS


Las cola como vectores son estructuras de almacenamiento de datos, donde se maneja de forma
estática, donde las operaciones de inserción se hace al final y las de borrado se hace al principio,
en otras palabras el primero que llega es el primero en salir, y el último en llegar será el último en
salir, de igual forma se maneja con las listas pero de forma dinámica.

2.4.4.1 DEFINICIÓN
Una cola es una lista ordenada en la cual las operaciones de inserción se efectúan en un extremo
llamado último y las operaciones de borrado se efectúan en el otro extremo llamado primero. Es
una estructura FIFO (First Input First Output).
Algoritmos II

87

En términos prácticos, es lo que supuestamente se debe hacer para tomar un bus, para comprar
las boletas y entrar a cine o para hacer uso de un servicio público. Las operaciones sobre una cola
son:

Operaciones Características

Crear Crea una cola vacía.

esVacia() retorna verdadero si la cola está vacía; de lo contrario, retorna falso

esLlena() Retorna verdadero si la cola está llena; de lo contrario, retorna falso.

Encolar(d) Inserta un dato d al final de la cola.

Desencolar() Remueve el primer elemento de la cola

Siguiente() Retorna el dato que se halla de primero en la cola.

2.4.4.2 REPRESENTACIÓN DE COLAS EN UN VECTOR, EN FORMA NO CIRCULAR

Para representar colas en esta forma se requiere un vector, que llamaremos V, y dos variables:
una que llamaremos primero, la cual indica la posición del vector en la que se halla el primer dato
de la cola, y otra, que llamaremos último, que indica la posición en la cual se halla el último dato
de la cola.
Algoritmos II

88
En el vector V se guardan los datos; primero apuntara hacia la posición anterior en la cual se halla
realmente el primer dato de la cola, y ultimo apuntara hacia la posición en la que realmente se
halla el último dato de la cola.

Si tenemos un vector con la siguiente configuración:

La variable primero valdrá 2 indicando que el primer elemento de la cola se halla en la posición 3
del vector; la variable ultimo valdrá 5 indicando que el último elemento de la cola se halla en la
posición 5 del vector.

Las operaciones sobre la cola, que son encolar y desencolar, funcionan de la siguiente forma: si
se desea encolar basta con incrementar la variable última en 1 y llevar a la posición último del
vector el dato a encolar; si se desea desencolar basta con incrementar en uno la variable primero
y retornar el dato que se halla en esa posición.

En general, si el vector V se ha definido con n elementos, cuando la variable último sean n se


podría pensar que la cola está llena:

Pero, como se observa en la figura, el vector tiene espacio al principio; por consiguiente,
podemos pensar en mover los datos hacia el extremo izquierdo y actualizar primero y ultimo para
luego encolar. Es decir, el hecho de que el ultimo sea igual a n no es suficiente para determinar
que la cola está llena. Para que la cola está llena se deben cumplir con dos condiciones: que
primero sea igual a 0 y ultimo sea igual a n.

Consideremos ahora operaciones sucesivas de desencole para el ejemplo dado: después de


ejecutar la primera operación de desencole los valores de las variables ultimo y primero y del
vector V serán los siguientes:
Algoritmos II

89

Donde la variable primero valdrá 3 y la variable ultimo 5

Al desencolar nuevamente, la configuración del vector será:

Donde la variable primero vale 4 y la variable ultimo 5.

Si desencolamos de nuevo, la configuración del vector será:

Y la variable primero vale 5 y la variable ultimo 5.

En otras palabras; primero es igual a ultimo y la cola está vacía, o sea que la condición de la cola
vacía será primero == ultimo. Teniendo definidas las condiciones de la cola llena y cola vacía
procedamos a definir la clase cola:

1. CLASE Cola
2. Privado
3. Entero primero, n
4. Objeto V[ ]
5. Publico
6. Cola(n) //constructor
7. boolean esVacia()
Algoritmos II

90
8. boolean esLlena()
9. void encolar(objeto d)
10. Objeto desencolar()
11. Objeto siguiente()
12. end(Clase)

1. Cola(entero m)
2. n=m
3. primero = ultimo = 0
4. V = new objeto[n]
5. end(cola)

1. Boolean esVacia()
2. Return primero == ultimo
3. end(esVacia)

1. Boolean esLlena()
2. return primero == 0 and ultimo == n
3. end(esLlena)

1. void encolar(objeto d)
2. if (esLlena()) then
3. write (“cola llena”)
4. return
5. end(if)
6. If (ultimo == n) then
7. for (i = primero + 1; i <=n; i ++)
8. V[I - primero] = V[i]
9. end(for)
10. ultimo = ultimo – primero
11. primero = 0
12. end(if)
13. ultimo = ultimo + 1
14. V[ultimo] = d
15. end(Método)

1. objeto desencolar()
2. If (esVacia()) then
3. write(“cola vacía”)
Algoritmos II

91
4. return null
5. end(if)
6. primero = primero + 1
7. return V[primero]
8. end(desencolar)

Si consideramos una situación como la siguiente:

n vale 7, ultimo vale 6 y primero vale 1.

Si se desea encolar el dato h y aplicamos el método encolar definido, la acción que efectúa dicho
método es mover los elementos desde la posición 1 hasta la 6, una posición hacia la izquierda de
tal forma que quede espacio en el vector para incluir la h en la cola. El vector quedara así:

Con primero valiendo -1 y ultimo valiendo 5. De esta forma continua la ejecución del método
encolar y se incluye el dato h en la posición 6 del vector. El vector queda así:

Con primero valiendo -1 y ultimo valiendo 6.

Si en este momento se desencola, el vector queda así:

Con primero valiendo 0 y ultimo valiendo 6.


Algoritmos II

92
Si la situación anterior se repite sucesiva veces, que sería el peor de los casos, cada vez que se
vaya a encolar se tendría que mover n-1 elementos del vector, lo cual haría ineficiente el manejo
de la cola ya que el proceso de encolar tendría orden de magnitud O(n). Para obviar este
problema y poder efectuar los método de encolar y desencolar en un tiempo O(1) manejaremos
el vector circularmente.

2.4.4.3 REPRESENTACIÓN DE COLAS CIRCULARES EN UN VECTOR


Para manejar una cola circularmente en un vector se requiere definir un vector de n elementos
con los subíndices en el rango desde 0 hasta n-1, es decir, si el vector tiene 10 elementos los
subíndices variaran desde 0 hasta 9, y el incremento de las variables primero y ultimo se hace
utilizando la operación modulo (%) de la siguiente manera:

Primero = (primero + 1) % n

Ultimo = (ultimo + 1) % n

Recuerde que la operación modulo (%) retorna el residuo de una división entera. Si se tiene una
cola en un vector, con la siguiente configuración:

n vale 7, los subíndices varían desde 0 hasta 6, primero vale 2 y ultimo vale 4.

Si se desea encolar el dato d incrementamos la variable último utilizando la operación modulo,


así:

Ultimo = (ultimo + 1) % n

O sea, ultimo = (4 + 1) % 7, la cual asignara a ultimo el residuo de dividir 5 por 7, que es 5.

El vector queda así:


Algoritmos II

93
Al encolar el dato e el vector queda así:

Y las variables: primera y última valdrán 2 y 6 respectivamente. Al encolar de nuevo el dato f,


aplicamos el operador % y obtenemos el siguiente resultado:

Ultimo = (6 + 1) % 7

El cual es 0, ya que el residuo de dividir 7 por 7 es 0. Por consiguiente, la posición del vector a la
cual se llevara el dato f es la posición 0. El vector quedara con la siguiente conformación:

Con ultimo valiendo 0 y primero 2. De esta forma hemos podido encolar en el vector sin
necesidad de mover elementos en él.

Los métodos para encolar y desencolar quedan así:

1. void encolar(objeto d)

2. Ultimo = (ultimo + 1) % n

3. if (ultimo == primero) then

4. colaLlena()

5. end(if)

6. V[ultimo] = d

7. end(Método)

1. objeto desencolar()

2. if (primero == ultimo) then


Algoritmos II

94
3. colaVacia

4. end(if)

5. primero = (primero + 1) % n

6. d = V[primero]

7. end(desencolar)

Es importante notar que la condición de cola llena y cola vacía es la misma, con la diferencia de
que en el método encolar se chequea la condición después de incrementar último. Si la condición
resulta verdadera invoca el método colaLlena, cuya función será dejar último con el valor que
tenía antes de incrementarla y detener el proceso, ya que no se puede encolar. Por consiguiente
habrá una posición del vector que no se utilizara, pero que facilita el manejo de las condiciones
de cola vacía y cola llena.

2.4.4.4 REPRESENTACIÓN DE COLAS COMO LISTAS LIGADAS


Definimos la cola como derivada de la clase LSL:

Encolar: consiste en insertar un registro al final de una lista ligada:

1. void encolar(objeto d)

2. Insertar(d, ultimoNodo())

3. end(Método)

Desencolar: consiste en borrar el primer registro de una lista ligada (exacto a desapilar). Habrá
que controlar si la cola está o no vacía
Algoritmos II

95
1. Objeto desencolar (primero, ultimo, d)

2. if(esVacia()) then

3. write(“cola vacia, no se puede desencolar”)

4. Return null

5. end(if)

6. nodoSimple p

7. p = primerNodo()

8. d = p.retornaDato()

9. borrar(p, null)

10. return d

11. end(desencolar)

2.4.5 EJERCICIO DE APRENDIZAJE

Nota: los ejercicios de aprendizaje del tema de pilas se encuentran en páginas dese
la 75 a la 83 y del tema de colas se encuentran e las páginas de la 87 a la 93.

2.4.6 TALLER DE ENTRENAMIENTO


Pautas para desarrollar los siguientes ejercicios: Para desarrollar estos ejercicios, debes tener
claro las operaciones que se pueden realizar en una pila, y las operaciones que se pueden realizar
en una cola.

1. Escriba un método que inserte un elemento en una pila. Considere todos los casos que puedan
presentarse.
Algoritmos II

96
2. Escriba un método que elimine un elemento de una pila. Considere todos los casos que
puedan presentarse.

3. Escriba un método que inserte un elemento en una cola circular. Considere todos los casos
que puedan presentarse.

4. Escriba un método que elimine un elemento de una cola circular. Considere todos los casos
que puedan presentarse.

5. Utilice una estructura de cola para simular el movimiento de clientes en una cola de espera
de un banco (puede auxiliarse con los métodos utilizados que están planteados en los
ejercicios 3 y 4).

TIPS
En las pilas los procesos de apilar y desapilar se hacen por un solo
extremo. En las colas las operaciones de encolar se hacen después del
último y desencolar se realiza por el primero.
Algoritmos II

97
3 UNIDAD 3 CONCEPTUALIZACIÓN DE RECURSIVIDAD,
ARBOLES Y GRAFOS

3.1.1 OBJETIVO GENERAL


➢ Realizar algoritmos de forma recursiva para árboles y grafos teniendo en cuenta los
conceptos básicos aplicados a estas estructuras.
Algoritmos II

98
3.1.2 OBJETIVOS ESPECÍFICOS
➢ Realizar pruebas de escritorio a los algoritmos recursivos

➢ Representar las estructuras tipo árbol y grafo de forma dinámicas

➢ Realizar los recorridos sobre las estructuras árbol y grafos

3.1.3 PRUEBA INICIAL


• ¿Qué es un algoritmo recursivo?

• ¿Qué es una estructura tipo árbol?

• ¿Qué es una estructura tipo grafo?

3.2 TEMA 1 DEFINICIÓN Y APLICACIÓN DE LA RECURSIVIDAD


➢ Video: ” Historia del Racismo - Documental BBC - Parte 1 - El poder del dinero”
➢ ” Ver Video”: https://www.youtube.com/watch?v=yX5kR63Dpdw&ab_channel=BettaTech ”
Se dice que un objeto es recursivo cuando forma parte de si mismo.

• Permite definir un número infinito de objetos mediante un enunciado finito. En


programación… Universidad Pontificia de Salamanca (Campus Madrid) Luis Rodríguez
Baena, Escuela Superior de Ingeniería y Arquitectura, 2012 2 En programación…

• La recursividad es la propiedad que tienen los procedimientos y funciones de llamarse a


si mismos para resolver un problema.

• Permite describir un número infinito de operaciones de cálculo mediante un programa


recursivo finito sin implementar de forma explícita estructuras repetitivas.

El concepto de recursividad no es nuevo. En el campo de la matemática podemos encontrar


muchos ejemplos relacionados con ´el. En primer lugar, muchas definiciones matemáticas se
realizan en términos de sí mismas. Considérese el clásico ejemplo de la función factorial:
Algoritmos II

99

3.2.1 EJERCICIO DE APRENDIZAJE

Partes de un algoritmo recursivo


Algoritmos II

100
3.2.2 TALLER DE ENTRENAMIENTO
1. Elabore un algoritmo recursivo que permita genera la serie Fibonacci, sabiendo que:

Fibonacci (i) = Fibonacci (i-2) + Fibonacci (i-1)

2. Torres de Hanoi (enunciado)

El objetivo de este famoso rompecabezas es trasladar todos los discos del poste A al C siguiendo
las siguientes reglas:

1. De cada pila de discos, solo se puede desplazar el disco situado encima del resto.

2. En ningún caso se puede colocar un disco sobre otro de menor tamaño.

3. Los discos han de ser trasladados de uno en uno.

4. Ha de realizarse el traslado completo en el mínimo número de movimientos.

Asociado a este juego existe un relato, tan famoso como este rompecabezas. Esta historia habla
de un templo en cuyo interior se encuentran, desde muy antiguo, estos tres postes. Los
sacerdotes del templo, de generación en generación, trasladan sin descanso 64 discos de oro, de
la pila original a la pila destino. Según esta leyenda, cuando se realice el último movimiento del
rompecabezas, el mundo se terminará. Si en la barra A hubiese 64 discos, como en la leyenda, se
tardarían miles de millones de años en completar ese traslado. Así que hemos decidido limitar los
discos a un máximo de 10. Si es la primera vez que te enfrentas a este rompecabezas, es preferible
que comiences a intentarlo con un número pequeño de discos (como tres o cuatro).

Consultar el algoritmo de torres de Hanói


Algoritmos II

101

TIPS
La recursividad optimiza el rendimiento de los algoritmos, eliminando
estructuras repetitivas.

3.3 TEMA 2 DEFINICIÓN, CONCEPTOS, REPRESENTACIÓN Y


RECORRIDOS DE ARBOLES
➢ Video: ” UTPL ÁRBOLES [(INFORMÁTICA)(ESTRUCTURA DE DATOS)]”

➢ ” Ver Video”: https://www.youtube.com/watch?v=DdCoaWzLw2g ”

3.3.1 RELACIÓN DE CONCEPTOS


La estructura árbol que es recursiva por definición es utilizada en diversos tipos de soluciones
como por ejemplo la estructura de almacenamiento en disco de los archivos y directorios a partir
el espacio particionado de disco en un sistema operativo.

3.3.2 ARBOLES
ARBOLES GENERALES Y SU REPRESENTACIÓN
La definición de árboles parte del concepto de árbol general que no incluye el árbol sin ningún
registro. Además, se definen los conceptos básicos asociados a los árboles.

3.3.2.1 DEFINICIÓN DE ARBOLES GENERALES


Un árbol es un conjunto de n registros(n>0), donde el árbol vacío no está definido, de tal manera
que hay un registro llamado raíz y los otros registros están partidos en conjuntos disjuntos cada
uno de los cuales tiene las mismas características de la definición del árbol (esta característica
hace comportar la estructura árbol como recursiva). Gráficamente:
Algoritmos II

102

La raíz principal del árbol es G de ella se desprenden tres arboles recursivamente cuyas raíces son
respectivamente R,T, A y así sucesivamente se pueden generar más árboles dependiendo de la
estructura.

3.3.2.2 TERMINOLOGÍA DE ARBOLES


Las ramificaciones de cada nodo se les denomina hijos y los nodos desde donde parten las
ramificaciones se denominan padres. Los registros con el mismo padre se denominan hermanos.

• Al número de ramificaciones que tiene un registro se le denomina el grado de un


registro

• El grado de un árbol se determina encontrando el registro con el más grande número


de ramificaciones.

• Los registros que no tienen hijos dentro del árbol se denominan hojas. Para calcular
el número de hojas de un árbol ver el video en YouTube:
https://www.youtube.com/watch?v=P1G9imv8Su4

• El árbol tiene niveles que comienzan con el registro raíz y se denomina nivel 1 y va
aumentando de a uno con la ramificación de sus hijos.

• Un registro de nivel k tiene el padre en el nivel k-1 e hijos en el nivel k+1

• Al máximo nivel alcanzado por el árbol se le denomina la altura del árbol.

• Para determinar los ancestros de un registro basta con encontrar la trayectoria


desde la raíz al registro en cuestión.

• Otro nombre dado a este tipo de árboles, es árbol n-arios


Algoritmos II

103
3.3.2.3 REPRESENTACIÓN DE ARBOLES N-ARIOS
Se representan de dos formas: como listas ligadas y como listas generalizadas

3.3.2.3.1 Representación con listas ligadas

Para representar con listas ligadas se debe definir el registro teniendo en cuenta el grado del
árbol. El nodo debe tener una cantidad de apuntadores o de ligas igual al grado del árbol además
de los datos que almacene la estructura. Esto hace que la representación desperdicie memoria
cuando un registro sea de grado inferior al del árbol y que además sea muy difícil de representar
con listas pues cada árbol con grado distinto tiene una definición del nodo completamente
distinta. Si el grado del árbol es 3 entonces el registro para la representación con listas tendría 3
campos de liga seria en ese caso:

Liga1 Liga2 Liga3 Dato

Ejemplo:

La representación del árbol para el ejemplo de arriba con listas ligadas será:

REPRESENTACION DE ARBOLES GENERALES CON LISTAS LIGADAS

Árbol:

Representación:
Algoritmos II

104

3.3.2.3.2 Representación con listas generalizadas (estilo, Título 4)

Para evitar los problemas de la representación con listas ligadas para los arboles generales
recurrimos a las listas generalizadas cuya definición del nodo en la representación es:

SW Dato Liga

Dónde: Si el sw =0 En el campo de dato hay un dato

Si sw=1 en el campo hay un apuntador hacia un sub árbol

La representación utiliza una lista simplemente ligada para cada raíz con sus hijos.

Ejemplo de representación de árbol general con listas generalizadas sea el árbol:


Algoritmos II

105

Explicación: La raíz principal forma una lista simplemente ligada con sus dos hijos (tres nodos), el
concepto de lista generalizada aparece par el segundo nodo de la raíz principal que tiene el campo
de sw=1 indicando que contiene en vez de un datos un apuntador a la segunda sublista que
representa los cuatro nodos, el raíz y los tres hijos del subárbol de la derecha (en este todos tienen
sw=0 y no hay mas sublistas).

Ejercicios de alistamiento:

1. Para el siguiente árbol general determine el grado, la altura y los predecesores del registro
L y como se representa el árbol con listas generalizadas

La raíz con la mayor cantidad de registros hijos es A o D con tres por eso el grado del árbol es 3.
El árbol tiene 4 niveles por eso su altura es igual a 4 (mayor nivel del árbol). Los predecesores de
L son G el padre, C es el abuelo, y el registro A es el bisabuelo.
Algoritmos II

106

3.3.3 ARBOLES BINARIOS Y SU REPRESENTACIÓN


Este tipo de estructuras de datos son los fundamentales en la teoría de árboles, pues permiten
realizar recorridos y búsquedas con la suficiente rapidez para lograr una más alta eficiencia en los
algoritmos con los que se pueden resolver diversos procesos. Hay un tipo especial de árboles
binarios que son los árboles de búsqueda que son creados como estructuras específicamente
hechas para realizar búsquedas rápidas de información sobre la estructura de datos.

3.3.3.1 DEFINICIÓN DE ARBOLES BINARIOS (ESTILO TÍTULO 3)


Un árbol binario es un conjunto de n registros (n>=0), el cual tiene una raíz o puede ser
una estructura sin datos y en la cual los otros registros están partidos en dos conjuntos
disjuntos llamados hijo izquierdo e hijo derecho que no son otra cosa que dos árboles
también binarios. Es importante anotar que los arboles binarios son también una
estructura de datos recursiva por definición, esto quiere decir que en su creación se debe
asumir por donde se quiere crear el árbol y se debe llamar al procedimiento de inserción
con su hijo izquierdo o derecho de acuerdo a hacia donde se esté creando el nuevo dato.
Un ejemplo de árbol binario es:

Buscar más información sobre arboles binarios de búsqueda en el siguiente video:

➢ Video: ” Árboles Binarios de Búsqueda”

➢ ” Ver Video”: https://www.youtube.com/watch?v=qzSHM4N5yDo ”


Algoritmos II

107
Buscar más información sobre arboles de búsqueda binarios en:

➢ Video: ” 09-Árboles de búsqueda binarios-02-Definición”

➢ ” Ver Video”: https://www.youtube.com/watch?v=9kxU4wBOhec ”

La terminología usada para los arboles generales se usa de la misma forma con los arboles
binarios, pero teniendo en cuenta que el grado del árbol binario es 2. Por ejemplo:

El registro D es hijo del registro B y nieto del registro A, mientras que D, E, F y G son
hermanos, el grado del árbol binario es 2 y la altura del árbol es 3 pues tiene tres niveles.

3.3.3.2 PROPIEDADES DE LOS ARBOLES BINARIOS (ESTILO TÍTULO 3)


Se pueden presentar las siguientes propiedades de este tipo de árboles:

• El número máximo de registros de un nivel k del árbol es 2k-1

• Un árbol binario lleno: es un árbol de altura k que tiene 2 k -1 registros, ósea cada
registro tiene sus dos hijos hasta el último nivel que solo hay hojas

• Sea n0 = al número de hojas del árbol y n2 número de registros de grado 2. Siempre


se cumplirá que n0=n2+1, pero no el árbol vacío.

3.3.3.3 REPRESENTACIÓN DE LOS ARBOLES BINARIOS (ESTILO TÍTULO 3)


Se pueden representar como un vector de forma estática o como una lista ligada de forma
dinámica. Veamos cada una de estas dos representaciones:
Algoritmos II

108
3.3.3.4 REPRESENTACIÓN DE ÁRBOLES BINARIOS CON UN VECTOR (ESTILO,
TÍTULO 4)
El nivel 1 corresponde a una posición donde colocamos el dato de la raíz principal del árbol, a las
dos posiciones siguientes les corresponde los datos de los dos registros hijos del nivel 2, las cuatro
siguientes posiciones del vector serán para los dos pares de hijos del nivel 3, de tal manera que
el siguiente nivel tendrá 8 posiciones del vector para representar la cantidad posible de hijos del
4 nivel del árbol y así sucesivamente como una potencia de 2. Gráficamente para el árbol del
ejemplo inicial:

A B C D E F G

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Por tener el árbol solo tres niveles el tamaño de la estructura estática será de 7 un dato en el
primer nivel, dos datos en el segundo nivel y cuatro datos en el tercer nivel del árbol. En el caso
de que el árbol no tenga alguno de los hijos de los dos lados el árbol, no ubica en la posición
correspondiente al nodo faltante en el vector ninguna información.

3.3.3.5 REPRESENTACIÓN DE ÁRBOLES COMO UNA LISTA LIGADA


Para representar la estructura árbol binario de manera dinámica se requiere definir el registro del
nodo de la siguiente manera:

LI D LD

Con:

LI: Apuntador al subárbol izquierdo del árbol

LD: Apuntador al subárbol derecho del árbol

D: Representa el dato almacenado en la estructura (este puede ser también un registro si es


necesario almacenar más datos).

Creación e inserción de árboles binarios en Java:

➢ Video: ” Árboles Binarios de Búsqueda, Creación e Inserción (EDDJava)”


Algoritmos II

109
➢ ” Ver Video”:
https://www.youtube.com/watch?v=ZKnwBJ8q2TE&ab_channel=MasterHeHeGar ”

Ver creación de árboles binario en Java en el video en YouTube:

➢ Video: ” Árbol Binario de Búsqueda Implementado en Java”

➢ ” Ver Video”: https://www.youtube.com/watch?v=sAZDf87g7rk ”

Para el árbol del ejemplo la representación quedaría:

Las hojas siempre deben tener las dos ligas iguales a 0 (Esto se debe tener en cuenta para el
algoritmo cuenta hojas en arboles binarios).

3.3.3.6 REPRESENTACIÓN DE ÁRBOLES COMO UNA LISTA LIGADA CON LA


DIRECCIÓN DEL PADRE (ESTILO, TÍTULO 4)
Esta representación difiere de la anterior por que la definición del nodo tiene un campo más para
el registro que es un apuntador con la dirección del padre. Así:

LI RP D LD

LI, LD, D: Son la misma definición anterior y RP: Es la dirección del registro del padre para cada
nodo representado. Si el registro es la raíz del árbol no tendrá RP. Esta representación es muy
útil cuando se usan algoritmos donde con frecuencia debemos regresar al registro del padre de
cualquiera de los nodos del árbol.

La representación del árbol binario anterior con el registro del padre es:
Algoritmos II

110

Explicación: La diferencia en la anterior representación es que cada registro exceptuando al padre


tienen la dirección del registro padre como segundo campo del nodo representado.

3.3.3.7 RECORRIDOS SOBRE ARBOLES BINARIOS


Teniendo en cuenta que los recorridos sobre arboles binarios dan la posibilidad de moverse en la
estructura de datos para realizar cualquier tipo de operación que permita su modificación o
actualización. Se dan tres recorridos principales en los árboles binarios: (Se debe tener en cuenta
que cuando se representan operaciones en un árbol binario la raíz siempre tiene al operador y
los hijos son los operados)

• Recorrido Inorden: Los recorridos comienzan por la dirección de la raíz principal del árbol
binario y consiste en visitar primero el hijo izquierdo, segundo imprimir el dato de la raíz
y por último visitar el hijo derecho, su forma de representación es: IRD (IZQUIERDO, RAIZ,
DERECHO), es de anotar que los recorridos son recursivos como son también los árboles,
es por eso que el llamado a los hijos izquierdo y derecho del árbol es llamar a otro subárbol
binario. Un ejemplo para este recorrido:
Algoritmos II

111
Explicación: partimos de la raíz principal que es 10, pero como el recorrido primero va a
la izquierda pasamos al subárbol de raíz 5 pero a su vez este también tiene subárbol
izquierdo que es el registro hoja 3, como este no tiene hijos se escribe la raíz que es 3 y se
regresa a la raíz 5 que también se escribe pues ya se visitó su hijo izquierdo y se pasa a su
hijo derecho que también es una hoja y se imprime la raíz. Como ya se visitó el hijo
izquierdo se regresa a la raíz principal y se imprime 10 pasando a recorrer el hijo derecho
del árbol que debe imprimir respectivamente 11, 12 y 15 con el mismo análisis anterior.

Revisar recorrido inorden en java en el siguiente video:

➢ Video: ” Árboles Binarios de Búsqueda, Recorrido InOrden (EDDJava)”

➢ ” Ver Video”: https://www.youtube.com/watch?v=l8XPkY_q4Qs ”


• Recorrido preorden: Consiste en imprimir el dato de la raíz y después visitar al hijo
izquierdo y posteriormente al derecho como dos llamados recursivos a los subárboles
respectivos, su forma de representación es: RID (RAIZ, IZQUIERDO, DERECHO). Un
ejemplo para este recorrido es:

➢ Video: ” Árboles Binarios de Búsqueda, Recorrido PreOrden (EDDJava)”

➢ ” Ver Video”: https://www.youtube.com/watch?v=Nz-9ZQrhgO0 ”


Algoritmos II

112
• Recorrido posorden: Consiste en recorrer inicialmente los hijos izquierdo y derecho para
dejar de ultimo la impresión del dato de la raíz, su forma de representación es: IDR
(IZQUIERDO, DERECHO y RAIZ). Un ejemplo de este recorrido es:

Profundizar con el video de Youtube sobre postorden:

➢ Video: ” Árboles Binarios de Búsqueda, Recorrido PostOrden (EDDJava)

➢ ” Ver Video”: https://www.youtube.com/watch?v=52mLzH97gYA ”

• Ejemplo de un árbol con los tres recorridos definidos:


Algoritmos II

113

Inorden: 1 3 4 6 7 8 10 13 14

Preorden: 8 3 1 6 4 7 10 14 13

Posorden: 1 4 7 6 3 13 14 10 8

• Algoritmos para los recorridos de árboles binarios: Se definen los algoritmos para una
representación de árboles con listas ligadas, donde la raíz es de tipo nodo Float R y se
utiliza dentro de la clase árbol un método que puede devolver el hijo izquierdo y el hijo
derecho o el dato del nodo para determinar el recorrido recursivo respectivo:

• Inorden(nodoFloat R)
• If(R!=0) then
• Inorden(R.devli()) //llamado recursivo con LI(R)
• Imprima(R.devDato()) //imprime el dato(R)
• Inorden(R.devld()) //llamado recursivo con LD(R)
• End(if)
• Fin(Inorden)

• Preorden(nodoFloat R)
• If(R!=0) then
• Imprima(R.devDato()) //imprime el dato(R)
• Preorden(R.devli()) //llamado recursivo con LI(R)
Algoritmos II

114
• Preorden(R.devld()) //llamado recursivo con LD(R)
• End(if)
• Fin(Preorden)

• Posorden(nodoFloat R)
• If(R!=0) then
• Posorden(R.devli()) //llamado recursivo con LI(R)
• Posorden(R.devld()) //llamado recursivo con LD(R)
• Imprima(R.devDato()) //imprime el dato(R)
• End(if)
• Fin(Posorden)

Nota: para realizar las pruebas de escritorio a estos algoritmos se debe manejar la
estructura pila con las direcciones de retorno a los llamados respectivos en los
métodos que se deben apilar cuando se hace el llamado y posteriormente se
desapilan cuando termina el llamado.

Adicionalmente todos estos algoritmos se pueden escribir en un lenguaje de programación como


java para simular la creación de esta estructura de datos como ya fue ilustrado en videos
anteriores en este mismo capítulo.

3.3.3.8 REPRESENTACIÓN DE UN ÁRBOL GENERAL COMO UN ÁRBOL BINARIO


Cualquier árbol general sin importar su grado, puede ser representado como un árbol binario. La
construcción del árbol binario es como sigue: Para cada registro r que tenga k hijos, su primer hijo
se representa como hijo izquierdo y los otros se insertan como hijos derechos del primero
respectivamente. Lo que quiere decir que los registros que están en una rama derecha del árbol
son los hermanos de la raíz que los contiene. Ejemplo:

Para el siguiente árbol General Esta es la Representación como un árbol binario


Algoritmos II

115

Taller del capítulo:

1. Para el siguiente árbol binario:

Representar como listas ligadas y como listas ligadas con el registro del padre

3.4 LISTAS GENERALIZADAS


Es una estructura que permite escribir listas ligadas dentro de otras cuando la necesidad de
manejo de información lo requiera. Un ejemplo importante son los polinomios de varias variables
en la teoría matemática o una forma de representar árboles generales como listas generalizadas.
Es posible que algún tipo de estructuras recursivas se puedan representar como una lista
generalizada.
Algoritmos II

116
3.4.1 DEFINICIÓN DE LISTAS GENERALIZADAS (ESTILO TÍTULO 3)
Es un conjunto finito de n elementos (n>=0) cada uno de los cuales representa un dato
almacenado o apunta a otra lista generalizada, es una estructura que se define así misma
ósea que es recursiva. Los datos que puede representar se denominan átomos.
Realizando una representación de acuerdo a la teoría formal de conjuntos la notación
usada será: letras mayúsculas representan listas y letras minúsculas representan átomos.
Un ejemplo seria:

A={x,y}

C={a,A,c}

B={d,C,f,A}

Se pueden mostrar los conjuntos C y B por extensión:

C={a,(x,y},c}

B={d,{a,A,c},f, {x,y}}= {d,{a,{x,y},c},d, {x,y}}

3.4.1.1 REPRESENTACIÓN DE LISTAS GENERALIZADAS (ESTILO, TÍTULO 4)


Su forma de representación básica es una lista ligada que define el registro de la lista como sigue:

sw dato liga

El sw se define de la si8guiente forma:

0: si en el campo de dato hay un átomo

1: si en el campo de dato hay un apuntador a una sublista

Cada elemento de la lista generalizada utiliza un nodo. A continuación se representa la lista del
ejemplo propuesto en la representación de conjuntos:
Algoritmos II

117

3.4.1.2 CONSTRUCCIÓN DE UNA LISTA LIGADA QUE REPRESENTA UNA LISTA


GENERALIZADA (ESTILO, TÍTULO 4)
Se debe tener en cuenta la forma en que se define la hilera de entrada que representa la lista
generalizada así: paréntesis abierto, átomos, comas, paréntesis cerrado. El algoritmo recibe como
parámetro de entrada la hilera h que representa el conjunto:

Void conslg(string s)
Stack pila=new stack() //estructura pila para las sublistas
X=new nodolg(null) //consigue nuevo nodo de la lista a crear
L=x //inicialización de punteros de la lista
Ultimo =x
n=longitud(s) // n contiene el tamaño de la cadenas
for(i=2;i<n;i++) do //ciclo para recorrer la cadena
Casos para s[i] //casos para determinar que viene en la cadena
Átomo:
Ultimo.asignasw(0) //crea el átomo en la lista
Ultimo.asignadato(s[i])
“,” x=new nodolg(null) //crea otro nodo para lo que venga
Ultimo.asignaliga(x) //en la lista a continuación
Ultimo=x
“(“ pila.apilar(ultimo) //se debe construir una sublista
x=new nodolg(null)
Ultimo.asignasw(1)
Ultimo.asignadato(x)
Ultimo=x
“)” ultimo=pila.desapilar //se terminó de la construcción de la
Algoritmos II

118
//sublista y se debe regresar a la
//construcción que se
interrumpió

Fin (casos) //terminan los casos


End(for) //termina el ciclo
Fin(consig) //termina crear lista generalizada

Nota: una de las aplicaciones básicas de las listas generalizadas se da en la


representación de polinomios con muchas variables. La definición del registro en
este caso:

Sw coef exp Liga

Teniendo en cuenta que el sw se define como:

0: si el campo coef es un escalar

1: si el campo es un apuntador al polinomio que es coeficiente.

Ejercicios propuestos sobre listas generalizadas:

1. Representar con listas generalizadas el siguiente polinomio:

3x2+(2y+3)x-2

2. Representar como lista generalizada el siguiente conjunto al conjunto C:

A=(b, B, d, e)

B=(f,g)

C=(z, A,B)

3. Representar con listas generalizadas el siguiente árbol n-ario


Algoritmos II

119

Ejercicios de alistamiento:

1. Represente el siguiente polinomio de varias variables como una lista generalizada

(2+y)x2+(1-y2)x+3

2. Representar el árbol binario del ejercicio propuesto 3 del capítulo

3.4.2 EJERCICIO DE APRENDIZAJE


1. Representar el siguiente árbol binario usando vectores y listas ligadas
Algoritmos II

120

Representación como vectores:

27 14 47 7 32 59 11 50 77

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Representación con listas ligadas:

2. ¿Cuántos registros máximos, puede tener el anterior árbol representado?

El número máximo registros= 2k-1, siendo k la altura del árbol binario, en este caso k=4 y
así:

24-1=16-1=15 registros máximo.

3. ¿Cuál de los recorridos sobre arboles visita por último la raíz?


Algoritmos II

121
Es el recorrido postorden que visita el hijo izquierdo después el derecho y por último la
raíz

4. Escribir los recorridos inorden y preorden del árbol de la pregunta primer punto del taller:

Inorden: 7-11-14-27-32-47-50-59-77
Preorden: 27-14-7-11-47-32-59-50-77

3.4.3 TALLER DE ENTRENAMIENTO


1. Para el siguiente Árbol general

• Representar como: Listas ligadas y listas generalizadas

• Convertir el árbol del punto 1 en binario

• Describir los recorridos inorden, postorden y preorden asociados al árbol del árbol
convertido.

• Escribir un algoritmo que cree un árbol binario de forma recursiva

• Hacer un seguimiento recursivo en Inorden para el árbol que tiene tres registros
utilizando la pila

• Escribir un algoritmo que busque un dato que puede encontrarse dentro del árbol.
Algoritmos II

122

TIPS
Los arboles mejora procesos de búsqueda en grandes volúmenes de
información.

3.5 TEMA 3 DEFINICIÓN, CONCEPTOS, REPRESENTACIÓN Y


RECORRIDOS DE GRAFOS
➢ Video: ” Árboles dirigidos con raíz | MOOC Ap. Teoría de Grafos a la vida real I (43-49) |
UPV”

➢ ” Ver Video”: https://www.youtube.com/watch?v=faWG-T18ph4 ”

3.5.1 RELACIÓN DE CONCEPTOS


Los grafos son estructuras de datos que se utilizan en diversas aplicaciones, especialmente en
problemas de transporte que requieren encontrar rutas más cortas entre ciudades o países para
optimizar él envió de productos o que permita reducir los tiempos de envió entre dos puntos de
distinta ubicación. Hay algún tipo de grafos específicos (no dirigidos) que son equivalentes a la
estructura árbol. Como ejemplos de grafos se pueden mencionar: Las rutas de una empresa de
transporte, una red de computadoras, el sistema vial de un país, los sitios de interés de una
agencia de viajes, etc.

3.6 GRAFOS
3.6.1 DEFINICIÓN Y TERMINOLOGÍA BÁSICA SOBRE GRAFOS
3.6.1.1 DEFINICIÓN DE GRAFOS
Se define como un conjunto finito de puntos o vértices que se comunican con una traza llamada
lado para formar una figura con estas relaciones (un grafo contiene un conjunto finito de lados).
Ejemplos:
Algoritmos II

123

Los vértices y lados para los grafos 1 y 2 son:

Grafo 1: V1={1,2,3,4}

L1={<1,2>,<1,3>,<2,3>,<2,4>,<3,4>}

Grafo 2: V2={A,B,C,D}

L2={(A,B),(A,C),(B,C),(C,D)}

3.6.1.2 CLASIFICACIÓN

Los grafos se clasifican así:

• Grafos no dirigidos: Se caracterizan por que sus lados no están orientados, se representan
entre paréntesis. Como en los grafos 2 y 3 donde los vértices (v1,v2) es igual al vértice
(v2,v1) que conforman el mismo lado.
Algoritmos II

124
• Grafos Dirigidos: Son los que tienen sus lados orientados, gráficamente se realiza con una
flecha indicando hacia dónde va dirigido el lado y los vértices se representan entre
ángulos. El grafo 1 es dirigido y el vértice <1,2> es diferente del vértice <2,1>.

Cuando se trata de un grafo dirigido cada vértice de un lado se diferencia de la siguiente


forma:

<Vi,Vj>: Vi: cabeza del lado; Vj: cola del dado

3.6.1.3 TERMINOLOGÍA BÁSICA DE GRAFOS (ESTILO, TÍTULO 4)


• Adyacencia: Dos vértices son adyacentes si conforman un lado: por ejemplo en el grafo 1,
los vértices 1 y 2 son adyacente, también los son 1 y 3.

• La adyacencia en los grafos dirigidos se da de dos formas según la orientación del grafo:

o <Vi,Vj>: Vi es adyacente hacia Vj y que Vj: Es adyacente desde Vi.

• El lado que forman dos vértices es incidente sobre ellos

• Grado de un vértice: Es el número de lados incidentes sobre él. En el Grafo 3, el vértice 3


tiene grado=1; el vértice 1 tiene grado=2.

• En el caso de los grafos dirigidos se define:

o Grado entrante: Numero de lados que llegan al vértice

o Grado Saliente: Numero de lados que salen del vértice

o El grado total es la suma del grado entrante más el saliente

• Trayectoria: describe el camino para ir de un vértice i a un vértice j en un grafo. Por


ejemplo en el grafo 1 para ir del vértice 1 al 4 puedo ir con tres trayectorias: 1234, 134,
124. Es de anotar que para que exista la trayectoria los lados sobre la trayectoria deben
pertenecer al conjunto de lados del grafo. Así <1,2>, <2,3>,<3,4> pertenecen al conjunto
de lados del Grafo 1, lo anterior quiere decir que en un grafo pueden haber trayectorias
que no son válidas.
Algoritmos II

125
• Longitud de una trayectoria: Se define con la cantidad de lados que contiene. En el
ejemplo anterior para la trayectoria 1234 su longitud es: 3. En el caso de 134 y 124 la
longitud en cada caso es: 2

• Trayectoria Simple: Se da cuando todos los vértices excepto posiblemente el primero y el


ultimo son distintos. Por ejemplo: 1234 es trayectoria simple en el Grafo 1. Pero si se
diera la trayectoria 12324 en este grafo no sería una trayectoria simple. Otro caso
analizable seria la trayectoria 12341 que efectivamente seria simple (el primero y el último
de los vértices son iguales).

• Ciclo: Se define como una trayectoria simple en el cual el primero y el último vértice son
iguales. En el Grafo 2 la trayectoria ABCA forma un ciclo.

• Grafo conectado: se denomina así si desde cualquier vértice i del grafo se puede ir a
cualquier vértice j del grafo (para grafos no dirigidos). Para grafos dirigidos se usa el
concepto grafo fuertemente conectado con la misma definición que el anterior.

• El máximo número de lados en un grafo no dirigido se calcula como: n*(n-1)/2 con n igual
al número de vértices del grafo.

• Un grafo dirigido completo tiene un número de lados igual a: n*(n-1) con n igual al
número de vértices

3.6.1.4 REPRESENTACIÓN DE GRAFOS:


Hay 4 formas de representar los grafos entre las representaciones estáticas y dinámicas que se
describen a continuación:

• Matriz de adyacencia: Es una matriz cuadrada de orden n*n, siendo n el número de


vértices del grafo. La relación que se da entre los vértices del grafo se representa en la
matriz teniendo en cuenta lo siguiente:

Si existe lado (Vi, Vj) el cruce entre i y j se llena con 1 (hay adyacencia)

En otro caso es 0 (se deja el cruce en blanco).

A continuación, se representa el Grafo 1 y el Grafo 2 como matriz de adyacencia:

Representación Grafo 1
Algoritmos II

126
1 2 3 4

1 1 1

2 1 1

3 1

Representación Grafo 2

A B C D

A 1 1

B 1 1

C 1 1 1

D 1

Nota: Se coloca un 1 en los respectivos lados adyacentes de un lado dado así:


adyacentes a lado A los lados B y C por lo que esos cruces en la matriz tienen asignado
1 respectivamente.

• Listas ligadas de adyacencia: Se representa utilizando una lista simplemente ligada por
cada vértice que enlaza con la dirección de los nodos adyacentes a él. Los apuntadores
de entrada a cada vértice se encuentran en un vector de apuntadores de entrada. La
configuración del nodo de la representación es:

Vértice Liga
Algoritmos II

127
La representación con listas ligadas de adyacencia para el Grafo1 será:

• Multilista de adyacencia: Se define un registro para representar cada lado del grafo. El
lado está conformado por dos vértices. La configuración del nodo es la siguiente:

Vi Vj LVi L Vj Sw

➢ LVi: Apunta hacia otro registro que representa un lado incidente a Vi

➢ LVj: Apunta hacia otro registro que representa un lado incidente a V j

➢ Sw: bandera usada para métodos sobre el grafo.

Donde se crea un vector V[i] apunta al primer nodo de la lista de nodos con los que se representan
los lados incidentes al vértice i. La lista ligada que corresponde a un vértice v contiene los lados
incidentes sobre el vértice v

• Matriz de incidencia: se define como una matriz de m filas y n columnas con: n: número
de vértices del grafo y n: número de lados del grafo.

Para hacer la representación se deben numerar los lados del grafo (aleatoriamente).

Para el grafo 2 del ejemplo inicial:


Algoritmos II

128

Lado (A,B) se numera con 1

Lado (A,C) se numera con 2

Lado (B,C) se numera con 3

Lado (C,D) se numera con 4

En este caso m=4, n=4 (la matriz tiene 4 filas representan los vértices y 4 columnas
representan los lados)

Si denominados la matriz como In

In[i][j] es igual a 1 si el lado j es incidente sobre el vértice i y es 0 si el lado j no es incidente


sobre el vértice i

La representación del grafo seria:

In 1 2 3 4

A 1 1

B 1 1

C 1 1
Algoritmos II

129
D 1

Nota: Cada columna solo tiene dos unos, los cuales se ubican en las filas
correspondientes a los vértices que conforman ese lado. El grado de un vértice i se
halla contando los lados sobre la fila i.

¿Cuál representación de las propuestas para grafos se debe usar?

Todo depende del problema que se esté intentando resolver y además se debe acomodar
a las intenciones de diseño del programador.

Para crear un grafo en programación a partir de su representación que se recomienda y


aplicación de un proyecto de grafos en una situación real:

➢ Video: ” GRAFOS c++ distancias mas cortas”

➢ ” Ver Video”:
https://www.youtube.com/watch?v=oHNR3o5udag&ebc=ANyPxKrlDwIWwaEElAss8ikJXUwFUK_
qyOtK2AVOIhD-Ebr8ftGNMqTr7xWB64cYsO8mZmConXR6I2Lo3rzilNq3iiiK35yWWg ”

Además sería muy bueno ver representación de grafos en java:

➢ Video: ” Representação Gráfica de um Grafo em Java V. 3.2”

➢ ” Ver Video”: https://www.youtube.com/watch?v=pu0Rg0QutwY ”


3.6.1.5 RECORRIDOS SOBRE GRAFOS:
El principio básico para el manejo algorítmico de la estructura grafos se fundamenta en sus dos
recorridos

• Recorrido DFS : Es la sigla en inglés que quiere decir primero búsqueda en profundidad.
Este algoritmo funciona ubicándose en un vértice cualquiera del grafo y determina los
vértices adyacentes a este y escoge uno que aún no haya sido visitado y a partir de ahí
realizar un llamado recursivo al mismo algoritmo. El algoritmo DFS requiere controlar
cuales vértices han sido visitados y cuáles no.
Algoritmos II

130
o Para ejercer el control sobre los visitados o no se utiliza un vector v[i] que tiene 0
si el vértice I no ha sido visitado y que vale 1 si el vértice ya fue visitado.
Inicialmente el vector de visitados se inicializa con ceros.

Un algoritmo para el DFS seria:

Void DFS ( int i ) //el método recibe el vértice donde arranca el grafo
V[i]=1 // el vértice ya fue visitado se pone en uno el vector
Para todo vertice w adyacente a i haga //ciclo que recorre los vértices adyacentes
If (V[i]==0) entonces // pregunta si el vértice no ha sido visitado
DFS(w) // hace un llamado recursivo a DFS con w
Fin (if)
Fin (para)
Fin (DFS)

El parámetro i indica el vértice a partir del cual se comienza a hacer el recorrido DFS. La
determinación de los vértices adyacentes depende de la forma en que está representado
el grafo.
1. Realizar el recorrido DFS sobre grafos representado como matriz de incidencia: Debe
recorrer la matriz de incidencia para determinar, por cual vértice realizo el recorrido.
Después debe llamar recursivamente a dfs con el siguiente lado del grafo y así de manera
sucesiva.

Void dfs(entero v)
Visitado[v]=1
Para (w=1,w<n,w++) do
Para (v=1,v<m,v++) do
Si (incidente[w] [v]==1) entonces
Si (visitado[w]==0) entonces
dfs(w)
fin si
fin si
fin mientras
fin mientras
fin(dfs)

• Recorrido BFS:
Su nombre en inglés lo que hace es justificar primero la búsqueda a lo ancho del grafo. En
este algoritmo se visitan todos los vértices adyacentes a un vértice dado.
Algoritmos II

131
La forma en que este recorrido se maneja sobre el grafo, supone que debe manejar una
cola que indique el orden en que se han ido visitando los vértices, a este vector cola lo
denominaremos visitado
El algoritmo para hacer un recorrido BFS sobre grafos en forma general es:
void BFS(entero v) //recibe como parámetro el vértice
donde inicia
visitado[v]=1 // marca el vértice como visitado
cola.encolar(v) // encola el vértice en los
visitados
while ( cola.esvacia<>vacia) do // ciclo para preguntar por
visitados
v=cola.desencolar() // desencola el vértice v
imprima(v) // imprime el vértice en el recorrido
para todo vértice w adyacente a v haga // ciclo para recorrer vértices
adyacentes a v
if(visitado(w)==0) then // pregunta si w fue visitado o
no
visitado(w)=1 // marca como visitado a w
cola.encolar(w) // lleva el vértice a la cola
fis(si)
fin(para)
fin(while)
fin(BFS)
La determinación de los vértices w adyacentes a un vértice v dependerá de la forma como
se tenga representado el grafo.

A continuación se muestra un recorrido BFS con el grafo representado como matriz de


adyacencia:
void BFS(entero v) //recibe como parámetro el vértice
donde inicia
visitado[v]=1 // marca el vértice como visitado
cola.encolar(v) // encola el vértice en los
visitados
while ( cola.esvacia<>vacia) do // ciclo para preguntar por
visitados
Algoritmos II

132
v=cola.desencolar() // desencola el vértice v
imprima(v) // imprime el vértice en el recorrido
para (w=1;w<=n;w++) haga // ciclo para recorrer adyacentes
if(adya[v] [w]==1) then // pregunta por vector de
adyacentes
if(visitado(w)=0) then // pregunta si w fue visitado o no
visitado[w]=1 // marca w como visitado
cola.encolar(w) // encola el visitado
fin(si)
fis(si)
fin(para)
fin(while)
fin(BFS)

Revisar implementación de grafos en Java Caminos mínimos:

➢ Video: ” Grafos, El Camino Más Corto, Implementación (EDDJava)”

➢ ” Ver Video”: https://www.youtube.com/watch?v=xK0ShW9G-


Ts&ebc=ANyPxKqDE3I0CdiQnRu402itJt-OKWO_7yr1-oQdD-
29MRJDIyV35kY0a6nUelWFSD30vC01d5Kr2Q5bSyG_8vYDFwGrWNv2jg ”

Revisar grafos en Netbeans también interesante desde la programación con grafos


➢ Video: ” Grafos(Graphs) En NetBeans(java). prim y dijkstra”

➢ ” Ver Video”: https://www.youtube.com/watch?v=NQxpzlGpZS0 ”

Ejercicios propuestos del capítulo:

1. Elabore un algoritmo que imprima el recorrido DFS de un grafo representado como matriz
de adyacencia.
2. Elabore un algoritmo que imprima el recorrido BFS de un grafo representado como listas
ligadas de adyacencia
Algoritmos II

133
3. Elabore un algoritmo que imprima el recorrido BFS de un grafo representado como
multilistas de adyacencia
4. Elabore un algoritmo que imprima el recorrido DFS de un grafo representado como
multilistas de adyacencia
5. Elabore un algoritmo que imprima el recorrido BFS de un grafo representado como matriz
de incidencia.

Ejercicios de alistamiento:

2. Identificar los lados y vértices del siguiente grafo no dirigido:

Vertices={A,B,C,D}
Lados={(A,B),(A,C),(B,C),(C,D)}
3. Represente un grafo dirigido y diga la diferencia con el grafo del numeral 1
Algoritmos II

134
La diferencia es que el grafo dirigido indica que vértices forman el lado según la dirección
de la flecha y los lados se relacionan entre llaves. Los grafos dirigidos no diferencian ente
el lado (1,2) y el (2,1).
4. Hallar el número máximo de lados en los grafos de los ejercicios 1 y 2
El grafo del ejercicio 1 es no dirigido y su número de vértices es n=4, la fórmula para
encontrar el número de lados es:
Numero de lados= n*(n-1)/2 con n igual al número de vértices del grafo. De tal manera
que:

Numero de lados=4(4-1)/2=6, Para el grafo completo.

El segundo grafo es dirigido y la fórmula para hallar el número máximo de lados es:

Numero de lados= n*(n-1) con n igual al número de vértices

Numero de lados=4(4-1)=12 lados en el grafo dirigido completo.

5. Hallar las representaciones de matriz de adyacencia y matriz de incidencia para el grafo


problema 1
Matriz de adyacencia

VÉRTICE A B C D
VÉRTICE
1 1
A
1 1
B
1 1 1
C
1
D

Para representar como matriz de incidencia se deben numerar los lados de la siguiente
manera en el grafo: (la numeración es de forma aleatoria)
Algoritmos II

135

Y la matriz de incidencia seria:

LADO 4
1 2 3
VÉRTICE

A 1 1

B 1 1

1 1 1
C
1
D

6. Representar el grafo dirigido del ejercicio propuesto dos como una lista ligada de
adyacencia:
Cada vértice tiene su propia representación como lista simplemente ligada de adyacencia:
Algoritmos II

136
3.6.2 EJERCICIO DE APRENDIZAJE
Un método general de representación para un grafo teniéndolo representado como listas ligadas
de adyacencia seria:

Void DFS (entero i) //el método recibe el vértice donde arranca el grafo
V[i]=1 // el vértice ya fue visitado se pone en uno el vector
p=vec[i] // p apunta al primer nodo del vector de la lista
While (p<>null) do // recorre con p hasta el último nodo
w=p.retornaDato() // asigna a w el dato de p
If(V[w]==0) entonces // pregunta si w fue visitado o no
DFS(w) // si no fue visitado hace llamado recursivo a DFS
Fin(if)
P=p.retornaLiga() // Actualiza la liga de p
Fin(mientras)
Fin (DFS)

3.6.3 TALLER DE ENTRENAMIENTO


7. Realizar el recorrido DFS sobre grafos representado como matriz de incidencia: Debe
recorrer la matriz de incidencia para determinar, por cual vértice realizo el recorrido.
Después debe llamar recursivamente a dfs con el siguiente lado del grafo y así de manera
sucesiva.

8. Realizar el recorrido DFS sobre grafos representado como matriz de adyacencia: Debe
recorrer la matriz de adyacencia para determinar, por cual vértice realizo el recorrido.
Después debe llamar recursivamente a dfs con el siguiente vertice adyacente según la
matriz de representación del grafo y así de manera sucesiva.

TIPS
Los grafos son estructuras de gran utilidad para soluciones reales a nivel
de programación.
Algoritmos II

137
4 PISTAS DE APRENDIZAJE

➢ Recuerde que: Un árbol general es un concepto recursivo por que el árbol se define en
función del mismo (ósea llamado en su creación a otra estructura con las mismas
características. Así gráficamente:

Un hijo de una raíz en un árbol puede ser otro árbol con las mismas características.

➢ Tenga en cuenta que: Un árbol general no puede ser vacío, pero un árbol binario se define
con: n>=0 registros ósea que ese si puede ser una estructura de datos vacía.

➢ Recuerde que: La altura de un árbol binario depende del número de niveles que tiene el
árbol (es el mayor nivel que este puede alcanzar). Para encontrar en java como calcular
la altura de un árbol puedo remitirme al siguiente video en YouTube:

https://www.youtube.com/watch?v=_P0JRbqEEX0
Algoritmos II

138
➢ Tenga en cuenta que: Que el recorrido preorden visita primero la raíz (la imprime) y el
recorrido postreden visita por último la raíz (la imprime)

➢ No puede olvidar: Que un seguimiento recursivo sobre arboles debe guardar las
direcciones de retorno necesarias para hacer todo su trabajo recursivo en una pila de
direcciones vea el siguiente video:

https://www.youtube.com/watch?v=35Qdl0p3m6E

➢ Recuerden por favor: que las representaciones que tienen en cuenta el registro de padre
en sus nodos hijos pueden devolverse a sus respectivos padres cuando se requiera en el
método de la clase trabajada.

➢ Recuerde bien: La diferencia entre un grafo dirigido y uno no dirigido.

El grafo dirigido marca la orientación exacta de los lados, el no dirigido tiene las dos
orientaciones.

➢ No olvide que: los grafos tiene representaciones dinámicas y estáticas. Ver el video
siguiente en YouTube sobre el tema en la siguiente dirección:
https://www.youtube.com/watch?v=cEfOVtaBcKw
Algoritmos II

139
➢ Recuerde cual es: el concepto de lado, vértice, ciclo, camino, grado a nivel de un grafo
cuando se habla de ruta o camino entre grafos:

Ver enlace de internet con el pdf correspondiente:

http://www.itnuevolaredo.edu.mx/takeyas/apuntes/Estructura%20de%20Datos/Apunte
s/grafos/Apuntes_Grafos.pdf

➢ Recuerde: la diferencia ente incidencia y adyacencia en la teoría de grafos. Ver la imagen


siguiente:

INCIDENCIA, ABYACENCIA Y GRADO

➢ No olvide por favor: Las diferencias entre los recorrido en anchura y profundidad.
Algoritmos II

140
➢ Para reforzar el conocimiento ver video en la siguiente dirección:
https://www.youtube.com/watch?v=Y4tndzfQkoE

➢ Recuerde Bien: Cual es la importancia de las listas generalizadas a nivel de las estructuras
de datos?
Leer el siguiente pdf y confrontar las diferencias:
http://www.inf.udec.cl/~andrea/cursos/estructura/Listas.pdf

➢ No olviden: Por favor sobre la importancia de las matrices dispersas en aplicaciones


lineales y en matemáticas ver:
https://www.youtube.com/watch?v=kzJ9Ux2xwSI

➢ Recuerden Muy bien: Lo importante que es optimizar el almacenamiento de memoria


cuando se usa aun estructuras de datos
Como las matrices dispersas que tienen muchos datos en ceros. Es más conveniente
aprender a realizar almacenamiento de los datos de la matriz en una estructura lineal
(baja el orden de magnitud de los algoritmos optimizando código de programación)
Recuerde también: la representación estática con tripletas en Geogebra de una matriz
dispersa:
https://www.youtube.com/watch?v=cicfbhRJKm0
Algoritmos II

141
5 GLOSARIO
• Raíz del árbol: Es principal concepto para la creación de la estructura, es el registro inicial
con el que se crea el árbol.

• Árbol general: Estructura recursiva con n>0 registros, en la cual una raíz puede tener 0, 1
, 2 o más registros que se derivan de ella.

• Árbol binario: Estructura recursiva con n>=0 registros, en la cual una raíz pude tener 0, 1,
2 registros que se derivan de ella.

• Registro padre: Es el registro que es raíz del árbol o de un subárbol de la estructura y tiene
hijos.

• Registro hijo: Es el registro que se deriva de otro registro del árbol

• Estructura recursiva: La estructura se define en función de si misma

• Grado: Es el número de registros que se derivan del árbol

• Nivel: El primer nivel es la raíz y si hay subárboles se va aumentando el nivel de a 1

• Altura del árbol: Es el mayor nivel que alcanza el árbol

• Inorden: Recorrido del árbol binario que va primero al hijo izquierdo después la raíz y por
último el hijo derecho

• Preorden: Recorrido del árbol binario que va primero a la raíz, después al hijo izquierdo y
por último al hijo derecho.

• Posorden: Recorrido del árbol binario que va primero al hijo izquierdo, después al derecho
y por ultimo a la raíz.

• Grafo: Conjunto de n vértices y m lados

• Lado: Es la unión de dos vértices

• Adyacencia: Conformación de un lado

• Incidencia: El lado que forman dos vértices es incidente sobre ellos


Algoritmos II

142
• Grafo dirigido: El lado que relaciona dos vértices es una flecha dirigida, mostrando la
relación

• Grafo no dirigido: El lado es la doble relación entre los vértices y se conecta con una línea

• Grado de un vértice: Cantidad de lados que salen del vértice

• Trayectoria: Recorrido para ir de un vértice i a un vértice j

• Conectado: Para un grafo no dirigido es la propiedad de ir a todos los otros vértices desde
eses vértice

• DFS: Búsqueda primero en profundidad

• BFS: Búsqueda primero a lo ancho

• Matriz dispersa: Es una matriz cuya propiedad es tener mayor cantidad de elementos en
cero

• Tripletas: Forma de representar la matriz dispersa que consiste en fila, columna y valor

• Direccionamiento: Formula para ubicar los elementos o datos de una matriz dispersa

• Transpuesta de una Matriz: Es en la que se cambian filas por columnas y columnas por
filas

• Matriz triangular Superior: Los elementos situados debajo de la diagonal principal son
ceros

• Matriz triangular inferior: Los elementos situados encima de la diagonal principal son
ceros

• Diagonal principal: Los elementos situados en la diagonal principal de la matriz

• Diagonal secundaria: Los elementos situados en la diagonal secundaria de la matriz

• Inducción matemática: Método usado para encontrar las fórmulas de direccionamiento


Algoritmos II

143
6 BIBLIOGRAFÍA
• Marti, O. O. (2004). Estructuras de
Este capítulo recomienda al estudiante las
datos y metodos algoritmicos.
fuentes de consulta bibliográficas y digitales
para ampliar su conocimiento, por lo tanto, Madrid : Prentice Hall.
deben estar en la biblioteca digital de la
Remington. Utilice la biblioteca digital FUENTES DIGITALES O ELECTRÓNICAS
http://biblioteca.remington.edu.co/es/ para
• Luna, C. (2012). Estructura de Datos.
la consulta de bibliografía a la cual puede
Obtenido de
acceder el estudiante.
http://www.aliat.org.mx/BibliotecasDigitale
FUENTES BIBLIOGRÁFICAS s/sistemas/Estructura_de_datos/Estructura
_de_datos_Parte_1.pdf
• Aguilar, J, (2008). Fundamento de
Programación Madrid: MCGRAW-HILL. • Bottazzi, C. (2015). Algoritmos y Estructuras
de Datos. Obtenido de
• Flórez, R. (2010). Algoritmia II. Medellín: https://cimec.org.ar/~mstorti/aed/aednotes
Imprenta Universidad de Antioquia. .pdf

• Oviedo, E (2015). Lógica de


Programación Orientada a Objetos. 1ª
.ed. Bogotá: Ecoe Ediciones: Universidad
de Antioquia

• Becerra, S. C. (2000). Estructura de datos


en java. bogota: Kimpres limitada.

• Florez, r. (2012). Algoritmia 3. Medellin:


universidad de antioquia.

• Gotieb, C. C. (1978). Data type and


structures. New jersey: Prentice Hall.

• Joyanes Aguilar, l. (1999). Estructura


de datos, libro de problemas. Madrid:
McGrawHill.

También podría gustarte