Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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
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
3
TABLA DE CONTENIDO
Pág.
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
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
5 GLOSARIO 141
6 BIBLIOGRAFÍA 143
Algoritmos II
sfs
PROPÓSITO GENERAL
ALGORITMOS II
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
7
1 UNIDAD 1 ALGORITMOS DE ORDENAMIENTO Y
BÚSQUEDA
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.
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.
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 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.
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.
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.
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.
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
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 ”
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.
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.
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.
19
1 2 3 4 5 6
1 2 3 4 6 8
1 2 3 4 5 6
1 2 3 4 6 8
1 2 3 4 5 6
1 2 3 4 6 8
Cuarta pasada: dos comparaciones
1 2 3 4 6 8
1 2 3 4 5 6
1 2 3 4 6 8
1 2 3 4 5 6
1 2 3 4 6 8 Quinta pasada: una comparación
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.
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:
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 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).
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.
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.
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.
i m n
1 2 3 4 5 6 7 8 9 10
V 3 5 7 10 11 18 23 36
24
Con base en este enunciado se presenta el método:
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 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.
1 2 3 4 5 6
V 3 1 6 2 8 4
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.
i k j
1 2 3 4 5 6
V 3 1 6 2 8 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.
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
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
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)
31
end(while)
while (j<=ultimo) do
k=k+1
V[k]=b.datoEnPosicion(j)
j=j+1
end(while)
Fin(intercalar)
Ordenam
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
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
33
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. 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)
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)
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)
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. 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. 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
37
Algoritmos II
38
2 UNIDAD 2 MANEJO DE ESTRUCTURAS DINÁMICAS EN
MEMORIA (LISTAS LIGADAS)
➢ 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?
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.
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)
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)
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.
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
43
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í:
44
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.
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)
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.
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.
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:
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).
d = “i”
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:
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.
LSL()
end(LSL)
51
Boolean esVacia()
end(esVacia)
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.
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.
• 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í:
• 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)
ultimo = null
end(if)
primero = x
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).
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)
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:
if x == null then
write(“dato no existe”)
return
end(if)
desconectar(x, y)
end(Método)
• 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:
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.
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 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.
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)
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()
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.
65
66
LSL a
a = new LSL
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.
TIPS
El manejo dinámico de la memoria nos permite optimizar este recurso.
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.
Donde:
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)
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.
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:
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:
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)
72
26. x.retornaLi().asignaLd(x.retornaLd())
27. x.retornaLd().asignaLi(x.retornaLi())
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.
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.
➢ 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
➢ Desapilar: Elimina el último elemento de la pila y deja una nueva pila, la cual queda con
un elemento menos.
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.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.
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.
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.
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.
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.
Elimina el último elemento de la pila y deja una nueva pila, la cual queda con un
Desapilar
elemento menos.
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)
78
11. void desapilar(entero i)
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)
1. void Desapilar(entero i )
3. tope = tope – i
Algoritmos II
80
4. else
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)
A
Algoritmos II
81
Pila
Como hemos dicho las operaciones sobre una pila son apilar y desapilar:
• 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
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
4. return null
5. end(if)
6. nodoSimple p
7. p = primerNodo()
8. return p.retornaDato
9. end(tope)
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.
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):
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á:
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)
13. V[tope2] = d
14. end(if)
15. end(Método)
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:
2. entero i
3. if (pila == 1) then
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
15. end(for)
17. m=m–1
18. end(if)
19. end(if)
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:
86
El método apilar para este segundo diseño es:
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.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
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.
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.
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.
89
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 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í:
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.
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.
Ultimo = (ultimo + 1) % n
93
Al encolar el dato e el vector queda así:
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.
1. void encolar(objeto d)
2. Ultimo = (ultimo + 1) % n
4. colaLlena()
5. end(if)
6. V[ultimo] = d
7. end(Método)
1. objeto desencolar()
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.
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
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)
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.
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
98
3.1.2 OBJETIVOS ESPECÍFICOS
➢ Realizar pruebas de escritorio a los algoritmos recursivos
99
100
3.2.2 TALLER DE ENTRENAMIENTO
1. Elabore un algoritmo recursivo que permita genera la serie Fibonacci, sabiendo que:
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.
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).
101
TIPS
La recursividad optimiza el rendimiento de los algoritmos, eliminando
estructuras repetitivas.
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.
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.
• 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.
103
3.3.2.3 REPRESENTACIÓN DE ARBOLES N-ARIOS
Se representan de dos formas: como listas ligadas y como listas generalizadas
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:
Ejemplo:
La representación del árbol para el ejemplo de arriba con listas ligadas será:
Árbol:
Representación:
Algoritmos II
104
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
La representación utiliza una lista simplemente ligada para cada raíz con sus hijos.
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
107
Buscar más información sobre arboles de búsqueda binarios en:
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.
• 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
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.
LI D LD
Con:
109
➢ ” Ver Video”:
https://www.youtube.com/watch?v=ZKnwBJ8q2TE&ab_channel=MasterHeHeGar ”
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).
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
• 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.
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:
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.
115
Representar como listas ligadas y como listas ligadas con el registro del padre
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}
C={a,(x,y},c}
sw dato liga
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
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ó
3x2+(2y+3)x-2
A=(b, B, d, e)
B=(f,g)
C=(z, A,B)
119
Ejercicios de alistamiento:
(2+y)x2+(1-y2)x+3
120
27 14 47 7 32 59 11 50 77
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
El número máximo registros= 2k-1, siendo k la altura del árbol binario, en este caso k=4 y
así:
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
• Describir los recorridos inorden, postorden y preorden asociados al árbol del árbol
convertido.
• 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.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
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
• 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>.
• La adyacencia en los grafos dirigidos se da de dos formas según la orientación del grafo:
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
• 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
Si existe lado (Vi, Vj) el cruce entre i y j se llena con 1 (hay 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
• 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
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).
128
En este caso m=4, n=4 (la matriz tiene 4 filas representan los vértices y 4 columnas
representan los lados)
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.
Todo depende del problema que se esté intentando resolver y además se debe acomodar
a las intenciones de diseño del programador.
➢ ” Ver Video”:
https://www.youtube.com/watch?v=oHNR3o5udag&ebc=ANyPxKrlDwIWwaEElAss8ikJXUwFUK_
qyOtK2AVOIhD-Ebr8ftGNMqTr7xWB64cYsO8mZmConXR6I2Lo3rzilNq3iiiK35yWWg ”
• 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.
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.
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)
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:
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:
El segundo grafo es dirigido y la fórmula para hallar el número máximo de lados es:
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
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)
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.
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:
http://www.itnuevolaredo.edu.mx/takeyas/apuntes/Estructura%20de%20Datos/Apunte
s/grafos/Apuntes_Grafos.pdf
➢ 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
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.
• 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.
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
• Conectado: Para un grafo no dirigido es la propiedad de ir a todos los otros vértices desde
eses vértice
• 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
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