0% encontró este documento útil (0 votos)
129 vistas288 páginas

LibroEstructurasOrganizacionDatos PDF

Este documento presenta un libro de texto sobre estructuras y organización de datos. El libro cubre temas como estructuras de datos lineales y no lineales, listas, pilas, colas, árboles, grafos, ordenamiento y búsqueda. Incluye definiciones, representaciones en memoria, operaciones básicas y análisis de algoritmos para cada estructura de datos.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
129 vistas288 páginas

LibroEstructurasOrganizacionDatos PDF

Este documento presenta un libro de texto sobre estructuras y organización de datos. El libro cubre temas como estructuras de datos lineales y no lineales, listas, pilas, colas, árboles, grafos, ordenamiento y búsqueda. Incluye definiciones, representaciones en memoria, operaciones básicas y análisis de algoritmos para cada estructura de datos.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 288

Tecnológico Nacional de México

Instituto Tecnológico de Puebla

Departamento de Sistemas y Computación

REPORTE DE AÑO SABÁTICO

Libro de Texto de Apoyo a la Asignatura

ESTRUCTURAS Y ORGANIZACIÓN DE
DATOS

de la Ingenierı́a en TICs

Presentado por Georgina Flores Becerra

Puebla, Pue., Agosto de 2017


Índice general

Índice de tablas V

Índice de figuras VII

1. Fundamentos de estructura de datos 1

1.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.2. Clasificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.3. Estructuras lineales y no lineales . . . . . . . . . . . . . . . . 4

1.4. Estructuras estáticas y dinámicas . . . . . . . . . . . . . . . . 5

1.5. Tipos de datos abstractos (TDA) . . . . . . . . . . . . . . . . 7

1.6. Análisis de algoritmos . . . . . . . . . . . . . . . . . . . . . . 9

1.6.1. Criterios para analizar un algoritmo . . . . . . . . . . 10

1.6.2. Eficiencia de un algoritmo . . . . . . . . . . . . . . . . 12

1.6.3. Complejidad en el tiempo . . . . . . . . . . . . . . . . 14

1.6.4. Análisis de estructuras de control . . . . . . . . . . . . 19

1.6.5. Complejidad en el espacio . . . . . . . . . . . . . . . . 28

i
ÍNDICE GENERAL

2. Estructuras lineales 31

2.1. Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

2.1.1. Representación en memoria . . . . . . . . . . . . . . . 32

2.1.2. Operaciones básicas . . . . . . . . . . . . . . . . . . . 33

2.1.3. Tipos de listas: circulares y dobles . . . . . . . . . . . 51

2.2. Pilas estáticas y dinámicas . . . . . . . . . . . . . . . . . . . . 56

2.2.1. Representación en memoria . . . . . . . . . . . . . . . 57

2.2.2. Operaciones básicas . . . . . . . . . . . . . . . . . . . 58

2.2.3. Pila estática . . . . . . . . . . . . . . . . . . . . . . . . 61

2.2.4. Pila dinámica . . . . . . . . . . . . . . . . . . . . . . . 66

2.3. Colas estáticas y dinámicas . . . . . . . . . . . . . . . . . . . 69

2.3.1. Representación en memoria . . . . . . . . . . . . . . . 69

2.3.2. Operaciones básicas . . . . . . . . . . . . . . . . . . . 70

2.3.3. Cola estática . . . . . . . . . . . . . . . . . . . . . . . 73

2.3.4. Cola dinámica . . . . . . . . . . . . . . . . . . . . . . 77

2.3.5. Tipos de colas: circulares y dobles . . . . . . . . . . . 81

2.4. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

2.4.1. Aplicaciones de las pilas . . . . . . . . . . . . . . . . . 96

2.4.2. Aplicaciones de las colas . . . . . . . . . . . . . . . . . 103

3. Estructuras no lineales 107

3.1. Recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

3.1.1. Definición . . . . . . . . . . . . . . . . . . . . . . . . . 108

ITPuebla ii
ÍNDICE GENERAL

3.1.2. Procedimientos recursivos . . . . . . . . . . . . . . . . 109

3.1.3. Ejemplos de casos recursivos . . . . . . . . . . . . . . 111

3.2. Grafos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119

3.2.1. Representación de grafos . . . . . . . . . . . . . . . . . 121

3.2.2. Operaciones básicas . . . . . . . . . . . . . . . . . . . 123

3.2.3. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . 143

3.3. Árboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

3.3.1. Árbol binario de búsqueda . . . . . . . . . . . . . . . . 152

3.3.2. Representación de árboles de búsqueda . . . . . . . . 155

3.3.3. Operaciones básicas de árboles de búsqueda . . . . . . 157

3.3.4. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . 173

4. Métodos de ordenamiento y búsqueda 176

4.1. Métodos de Ordenamiento . . . . . . . . . . . . . . . . . . . . 176

4.1.1. Métodos de ordenamiento por Inserción . . . . . . . . 179

4.1.2. Métodos de ordenamiento por Intercambio . . . . . . . 184

4.1.3. Métodos de ordenamiento por Selección . . . . . . . . 195

4.2. Métodos de Búsqueda . . . . . . . . . . . . . . . . . . . . . . 204

4.2.1. Búsqueda Secuencial . . . . . . . . . . . . . . . . . . . 204

4.2.2. Búsqueda Binaria . . . . . . . . . . . . . . . . . . . . 206

4.3. Medición Teórica del Tiempo de Ejecución de los Algoritmos de Ordenamiento y Búsqueda20

4.4. Recuperación de datos . . . . . . . . . . . . . . . . . . . . . . 218

ITPuebla iii
ÍNDICE GENERAL

Índice alfabético 265

Bibliografı́a 270

ITPuebla iv
Índice de tablas

1.1. Manejo de memoria estática vs memoria dinámica . . . . . . 6

1.2. Ejemplos de operaciones básicas . . . . . . . . . . . . . . . . 11

1.3. Ejemplos de tamaños de problema . . . . . . . . . . . . . . . 11

1.4. Jerarquı́a de funciones . . . . . . . . . . . . . . . . . . . . . . 19

1.5. Complejidad espacial de algoritmos no recursivos y recursivos 29

2.1. Ejemplos de operaciones básicas con una lista ligada simple . 37

2.2. Ejemplos de operaciones básicas con una lista ligada doble . . 52

2.3. Ejemplos de operaciones básicas con una pila . . . . . . . . . 60

2.4. Ejemplos de operaciones básicas con una pila como un vector 65

2.5. Ejemplos de operaciones básicas con una cola . . . . . . . . . 72

3.1. Prueba de escritorio del algoritmo de Dijkstra . . . . . . . . . 149

4.1. Archivo de los alumnos del Tec–Puebla. . . . . . . . . . . . . 177

4.2. Archivo de los alumnos del Tec–Puebla, con registros ordenados de acuerdo al número de con

4.3. Archivo de los alumnos del Tec–Puebla, con registros ordenados de acuerdo al apellido.178

v
ÍNDICE DE TABLAS

4.4. Tiempos de ejecución teóricos y órdenes de complejidad de algoritmos de ordenamiento y bús

ITPuebla vi
Índice de figuras

1.1. Ejemplo de una estructura lineal: vector . . . . . . . . . . . . 4

1.2. Ejemplo de una estructura no lineal: árbol . . . . . . . . . . . 5

1.3. Asignación/liberación de almacenamiento en ED . . . . . . . 7

1.4. Definición de O . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.5. Ejemplo gráfico de O . . . . . . . . . . . . . . . . . . . . . . . 15


2 n
1.6. Contraste gráfico de n y 2 . . . . . . . . . . . . . . . . . . . 16
2 n
1.7. Detalle de contraste gráfico de n y 2 . . . . . . . . . . . . . 17

1.8. Otro ejemplo gráfico de O . . . . . . . . . . . . . . . . . . . . 17

1.9. Detalle de otro ejemplo gráfico de O . . . . . . . . . . . . . . 18

2.1. Representación en memoria de una lista ligada simple . . . . 32

2.2. Representación en memoria de una nodo simple . . . . . . . . 32

2.3. Ejemplo de una lista ligada simple . . . . . . . . . . . . . . . 33

2.4. Interfaz Lista . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

2.5. Diagrama de clases de lista simple . . . . . . . . . . . . . . . 39

2.6. Inserción de un nodo a la cabeza de una lista simple . . . . . 42

vii
ÍNDICE DE FIGURAS

2.7. Inserción de un nodo al final de una lista simple . . . . . . . . 42

2.8. Inserción de un nodo en una posición de una lista simple . . . 44

2.9. Eliminación de nodos en una lista simple . . . . . . . . . . . . 47

2.10. Recorrido de nodos en una lista simple . . . . . . . . . . . . . 49

2.11. Representación en memoria de una lista ligada doble . . . . . 51

2.12. Representación en memoria de un nodo doble . . . . . . . . . 51

2.13. Representación en memoria de una lista ligada circular . . . . 52

2.14. Recorrido de nodos en una lista doble, del útlimo al primero . 55

2.15. Diagrama de clases de lista simple, doble y circular . . . . . . 56

2.16. Representación en memoria de una pila . . . . . . . . . . . . 58

2.17. Interfaz Pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

2.18. Interfaz Pila implementada con un vector . . . . . . . . . . . 62

2.19. Pila implementada con lista simple:push,pop . . . . . . . . . 66

2.20. Diagrama de clases de pila implementada con lista simple . . 67

2.21. Representación en memoria de una cola . . . . . . . . . . . . 70

2.22. Interfaz Cola . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

2.23. Interfaz Cola implementada con un vector . . . . . . . . . . . 74

2.24. Cola implementada con lista simple:encolar,desencolar . . . . 78

2.25. Diagrama de clases de cola implementada con lista simple . . 78

2.26. Representación en memoria de una cola circular . . . . . . . . 82

2.27. Configuraciones de una cola circular . . . . . . . . . . . . . . 83

2.28. Cola circular vacı́a y llena . . . . . . . . . . . . . . . . . . . . 83

ITPuebla viii
ÍNDICE DE FIGURAS

2.29. Interfaz Cola implementada con una cola circular con vector . 84

2.30. Cola circular donde se ”incrementa en 1” el valor de final . 86

2.31. Inserción y eliminación en una cola doble . . . . . . . . . . . 89

2.32. Cola doble llena y vacı́a . . . . . . . . . . . . . . . . . . . . . 89

2.33. Interfaz Cola implementada con una cola doble con vector . . 90

2.34. Encolar en el frente en bicola, caso especial . . . . . . . . . . 93

2.35. Aplicación de pila: inversión de un vector . . . . . . . . . . . 97

2.36. Diagrama de clases para invertir un vector utilizando una pila 98

2.37. Pila para verificar paréntesis nivelados . . . . . . . . . . . . . 99

2.38. Pila para verificar paréntesis desnivelados (falta cerrado) . . . 100

2.39. Pila para verificar paréntesis desnivelados (falta abierto) . . . 100

2.40. Diagrama de clases para verificar paréntesis nivelados . . . . 101

2.41. Pila para convertir notación infija a posfija con un operador . 102

2.42. Pila para convertir notación infija a posfija con dos operadores 103

2.43. Notación infija a posfija con dos operadores en diferente orden 103

2.44. Aplicación de cola: control de un cajero . . . . . . . . . . . . 105

2.45. Diagrama de clases para el control de un cajero con una cola 106

3.1. Prueba de escritorio del cálculo recursivo del factorial . . . . 110

3.2. Invocaciones para el cálculo recursivo del factorial . . . . . . 112

3.3. Suma recursiva de n números . . . . . . . . . . . . . . . . . . 116

3.4. Suma recursiva de n números aplicando divide y vencerás . . 117

3.5. Grafo utilizado para representar una red de estados de la región oriente e la República Mexic

ITPuebla ix
ÍNDICE DE FIGURAS

3.6. Grafo no conexo . . . . . . . . . . . . . . . . . . . . . . . . . 121

3.7. Grafo utilizado para representar amistad entre personas . . . 122

3.8. Inserción de nodos (b) y lados (c) en un grafo, con matrices de adyascencia A e incidencia I 1

3.9. Eliminación de nodos (a) y lados (b) en un grafo, con matrices de adyascencia A e incidencia

3.10. Recorrido a lo ancho de un grafo, partiendo de Querétaro . . 126

3.11. Recorrido a lo largo de un grafo, partiendo de Querétaro . . . 126

3.12. Interfaz Grafo . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

3.13. Diagrama de clases de grafo no dirigido . . . . . . . . . . . . 131

3.14. Ejemplo de atributos de la clase GrafoNoDirigido . . . . . . 132

3.15. Recorrido a lo ancho de un Grafo, desencolar un nodo a visitar139

3.16. Recorrido a lo ancho de un Grafo, agregar el nodo al recorrido y marcarlo como visitado140

3.17. Recorrido a lo ancho de un Grafo, encolar nodos adyascentes al nodo visitado141

3.18. Grafo dirigido para representar el orden en que Batman debe ponerse la ropa [17]143

3.19. Grafo ponderado para representar una red de estados de la región oriente e la República Mex

3.20. Grafo dirigido y ponderado, con tres posibles caminos entre Hidalgo y Guerrero, siendo el m

3.21. Ejemplo de funcionamiento del algoritmo de Dijkstra para calcular el costo mı́nimo de 566 K

3.22. Ejemplos de grafos que no son árboles (a) y (b), y que sı́ lo son (c) y (d)150

3.23. Ejemplo de árbol de altura 4 y niveles de sus nodos . . . . . . 151

3.24. Árbol (a), con sus subárboles (b–d) . . . . . . . . . . . . . . . 152

3.25. Árboles de aridad tres (a), y aridad dos (árboles binarios) (b) 153

3.26. Árboles binarios, no ordenado (a), y ordenados o de búsqueda (b–d)154

3.27. Recorrido (a) a lo ancho (por niveles), y (b) a lo largo (a lo profundo), de un árbol de búsque

ITPuebla x
ÍNDICE DE FIGURAS

3.28. Estructura de un nodo de un árbol binario . . . . . . . . . . . 156

3.29. Acceso a los nodos de un árbol binario desde la raı́z . . . . . 156

3.30. Buscar un dato en un árbol de búsqueda . . . . . . . . . . . . 157

3.31. Inserta un nuevo nodo en un árbol de búsqueda . . . . . . . . 158

3.32. Eliminar un nodo hoja (Costa) y un nodo con un sólo hijo (Serrat) en un árbol de búsqueda

3.33. Eliminar un nodo con dos hijos (Veloso) en un árbol de búsqueda160

3.34. Esquema de recorrido en orden de un árbol binario . . . . . . 161

3.35. Esquema de recorrido en preorden de un árbol binario . . . . 161

3.36. Esquema de recorrido en postorden de un árbol binario . . . 162

3.37. Diagrama de clases de un árbol de búsqueda . . . . . . . . . . 166

3.38. Árboles de búsqueda de altura máxima y mı́nima que almacenan los mismos datos174

3.39. Árboles de búsqueda de altura máxima y mı́nima que almacenan los mismos datos175

4.1. Ordenamiento de los registros de un archivo por dirección, de acuerdo al número de control y

4.2. Ejemplo de ordenamiento de datos por el Método del Ordenamiento Rápido.188

4.3. Ejemplo de implementación de ordenamiento de datos por el Método del Ordenamiento Rápi

4.4. Ejemplo de implementación de ordenamiento de datos por el Método del Ordenamiento por C

4.5. Ejemplo de implementación de ordenamiento de datos por el Método del Ordenamiento por C

4.6. Ejemplo de un montı́culo. . . . . . . . . . . . . . . . . . . . . 197

4.7. Ejemplo de inserción de datos en un montı́culo. . . . . . . . . 199

4.8. Ejemplo de eliminación de datos de un montı́culo. . . . . . . 200

4.9. Ejemplo de la Búsqueda Binaria. . . . . . . . . . . . . . . . . 207

4.10. Abrir un archivo . . . . . . . . . . . . . . . . . . . . . . . . . 220

ITPuebla xi
ÍNDICE DE FIGURAS

4.11. Leer, consultar o recuperar un registro de un archivo . . . . . 221

4.12. Escribir un registro en un archivo . . . . . . . . . . . . . . . . 222

4.13. Esquema de la ejecución del Algoritmo 120 que da de alta un registro al final de un Archivo S

4.14. Esquema de la ejecución del Algoritmo 121 que da de alta un registro al inicio de un Archivo

4.15. Esquema de la ejecución del Algoritmo 122 que inserta un nuevo registro en la posición lógica

4.16. Esquema de la ejecución del Algoritmo 123 que elimina el primer registro de un Archivo Secu

4.17. Esquema de la ejecución del Algoritmo 124 que elimina el último registro de un Archivo Secu

4.18. Tabla de Directorio para indizar un archivo . . . . . . . . . . 237

4.19. Ejemplo de la inserción de una pareja {clave, dirRelativa} en una Tabla de Directorio240

4.20. Ejemplo de la eliminación de una pareja {clave, dirRelativa} en una Tabla de Directorio.241

4.21. Ejemplo de organización de registros en un Archivo Directo con transformación de claves med

4.22. Ejemplo de organización de registros en un Archivo Directo con transformación de claves med

4.23. Ejemplo de uso de una Tabla de Acceso Directo como estructura de datos auxiliar para el acc

4.24. Ejemplo de uso de una Tabla Hash como estructura de datos auxiliar para el acceso a un Arc

4.25. Ejemplo de uso de una tabla de dispersión como estructura de datos auxiliar para el acceso a

4.26. Ejemplo del Método de la Dispersión Abierta para resolver colisiones cuando se utiliza una T

ITPuebla xii
Índice de algoritmos

1. sumaVec(v:int[n], w:int[n]):int[n] . . . . . . . . . . . 24
2. sumaMat(A:int[n][n],B:int[n][n]):int[n][n] . . . . . . 25
3. multiplicaMat(A:int[n][n],B:int[n][n]):int[n][n] . . 25
4. busca(v:char[n],carac:char):int . . . . . . . . . . . . . 27
5. muestraDesde(carac:char):void . . . . . . . . . . . . . . . 28
6. sumaVecIterativo(v:int[n],w:int[n]):int[n] . . . . . . 29
7. sumaVecRecursivo(v:int[n],w:int[n],n,i:int):int[n] 30
8. ListaSimple() . . . . . . . . . . . . . . . . . . . . . . . . . 39
9. ListaSimple(cab:NodoSimple) . . . . . . . . . . . . . . . . 40
10. esVacia():booleano . . . . . . . . . . . . . . . . . . . . . . 40
11. insertarCabeza(dato:Object):Object . . . . . . . . . . . 40
12. insertarFinal(dato:Object):Object . . . . . . . . . . . . 41
13. insertarPosi(dato:Object, posi:int):Object . . . . . . 43
14. eliminarCabeza():Object . . . . . . . . . . . . . . . . . . . 44
15. eliminarFinal():Object . . . . . . . . . . . . . . . . . . . 45
16. eliminarPosi(posi:int):Object . . . . . . . . . . . . . . . 46
17. recorrer():VectorObject . . . . . . . . . . . . . . . . . . . 48
18. verFinal():Object . . . . . . . . . . . . . . . . . . . . . . . 48
19. verCabeza():Object . . . . . . . . . . . . . . . . . . . . . . 50
20. verPosi(posi:int):Object . . . . . . . . . . . . . . . . . . 50
21. insertarCabeza(dato:Object):Object . . . . . . . . . . . 53
22. insertarCabeza(dato:Object):Object . . . . . . . . . . . 53
23. recorrerUltimoPrimero():VectorObject . . . . . . . . . . 54
24. PilaVector() . . . . . . . . . . . . . . . . . . . . . . . . . . 62
25. PilaVector(longitud:int) . . . . . . . . . . . . . . . . . . 63
26. esVacia(): booleano . . . . . . . . . . . . . . . . . . . . . 63
27. esLlena(): booleano . . . . . . . . . . . . . . . . . . . . . 63
28. push(dato: Objeto): Objeto . . . . . . . . . . . . . . . . . 64
29. pop(): Objeto . . . . . . . . . . . . . . . . . . . . . . . . . 64

xiii
ÍNDICE DE ALGORITMOS

30. verTope(): Objeto . . . . . . . . . . . . . . . . . . . . . . . 65


31. PilaListaSimple() . . . . . . . . . . . . . . . . . . . . . . . 67
32. PilaListaSimple(unaPila:ListaSimple) . . . . . . . . . . 68
33. esVacia():booleano . . . . . . . . . . . . . . . . . . . . . . 68
34. push(dato:Object):Object . . . . . . . . . . . . . . . . . . 68
35. pop():Object . . . . . . . . . . . . . . . . . . . . . . . . . . 68
36. verTope():Object . . . . . . . . . . . . . . . . . . . . . . . 69
37. ColaVector() . . . . . . . . . . . . . . . . . . . . . . . . . . 74
38. ColaVector(longitud:int) . . . . . . . . . . . . . . . . . . 75
39. esVacia(): booleano . . . . . . . . . . . . . . . . . . . . . 75
40. esLlena():booleano . . . . . . . . . . . . . . . . . . . . . . 75
41. encolar(dato:Object):Object . . . . . . . . . . . . . . . . 76
42. desencolar():Object . . . . . . . . . . . . . . . . . . . . . 76
43. verFrente():Object . . . . . . . . . . . . . . . . . . . . . . 77
44. verFinal():Object . . . . . . . . . . . . . . . . . . . . . . . 77
45. ColaListaSimple() . . . . . . . . . . . . . . . . . . . . . . . 79
46. ColaListaSimple(longitud:int) . . . . . . . . . . . . . . . 79
47. esVacia(): booleano . . . . . . . . . . . . . . . . . . . . . 79
48. encolar(dato:Object):Object . . . . . . . . . . . . . . . . 80
49. desencolar():Object . . . . . . . . . . . . . . . . . . . . . 80
50. verFrente():Object . . . . . . . . . . . . . . . . . . . . . . 80
51. verFinal():Object . . . . . . . . . . . . . . . . . . . . . . . 80
52. ColaCircularVector() . . . . . . . . . . . . . . . . . . . . . 84
53. ColaCircularVector(longitud:int) . . . . . . . . . . . . . 85
54. esVacia(): booleano . . . . . . . . . . . . . . . . . . . . . 85
55. esLlena():booleano . . . . . . . . . . . . . . . . . . . . . . 85
56. encolar(dato:Object):Object . . . . . . . . . . . . . . . . 87
57. desencolar():Object . . . . . . . . . . . . . . . . . . . . . 87
58. verFrente():Object . . . . . . . . . . . . . . . . . . . . . . 88
59. verFinal():Object . . . . . . . . . . . . . . . . . . . . . . . 88
60. ColaDobleVector() . . . . . . . . . . . . . . . . . . . . . . . 91
61. ColaCircularVector(longitud:int) . . . . . . . . . . . . . 91
62. esVacia(): booleano . . . . . . . . . . . . . . . . . . . . . 91
63. esLlena():booleano . . . . . . . . . . . . . . . . . . . . . . 92
64. encolarFrente(dato:Object):Object . . . . . . . . . . . . 93
65. encolar(dato:Object):Object . . . . . . . . . . . . . . . . 94
66. desencolar():Object . . . . . . . . . . . . . . . . . . . . . 94
67. desencolarFinal():Object . . . . . . . . . . . . . . . . . . 95
68. verFrente():Object . . . . . . . . . . . . . . . . . . . . . . 95
69. verFinal():Object . . . . . . . . . . . . . . . . . . . . . . . 96

ITPuebla xiv
ÍNDICE DE ALGORITMOS

70. invierteVector(vec[n]):vec[n] . . . . . . . . . . . . . . . 97
71. invierteVectorConPila(vec[n]):vec[n] . . . . . . . . . . 98
72. verificaParen():boolean . . . . . . . . . . . . . . . . . . . 101
73. despachaClientes(): void . . . . . . . . . . . . . . . . . . 106
74. calculaFactorialIterativo(n:int):long . . . . . . . . . 109
75. calculaFactorialRecursivo(n:int):long . . . . . . . . . 109
76. buscaIterat(v:Object[n],n:int,dato:Object):boolean 113
77. buscaRecur1(v:Object[n],i:int,n:int,dato:Object) . . 114
78. buscaRecur2(v:Object[],ini,fin,n:int,dato:Object) . 115
79. sumaRecursivoPrimeroResto(v:int[n],n:int,i:int) . . 118
80. sumaRecursivoDivideVenceras(v:int[n],n:int,i:int) . 118
81. GrafoNoDirigido() . . . . . . . . . . . . . . . . . . . . . . . 132
82. insertarNodo(nodo:String):String . . . . . . . . . . . . . 133
83. insertarLado(lado,n1,n2:String):String . . . . . . . . . 134
84. eliminarNodo(nodo:String):String . . . . . . . . . . . . . 135
85. eliminarLado(lado:String):String . . . . . . . . . . . . . 136
86. recorrerAncho(nodoFuente:String):Vector<String> . . 137
87. recorrerLargo(nodoFuente:String):Vector<String> . . 142
88. dijkstra(nodoFuente:String,nodoDestino:String):int 148
89. ArbolBusqueda() . . . . . . . . . . . . . . . . . . . . . . . . 166
90. esVacio():boolean . . . . . . . . . . . . . . . . . . . . . . . 167
91. buscarNodo(dato:String,raiz,padreRaiz:Nodo):Vector<Nodo> 167
92. insertarNodo(dato:String):String . . . . . . . . . . . . . 168
93. eliminarNodo(dato:String):String . . . . . . . . . . . . . 169
94. eliminarHoja(padreAux,aux:Nodo):void . . . . . . . . . . 170
95. eliminarHijoIzq(dato:String):String . . . . . . . . . . . 170
96. eliminarHijoDer(dato:String):String . . . . . . . . . . . 171
97. buscaPredecesor(aux:Nodo):String . . . . . . . . . . . . . 171
98. recorrerEnorden(raiz:Nodo):Vector<String> . . . . . . 172
99. recorrerPreorden(raiz:Nodo):Vector<String> . . . . . . 172
100. recorrerPostorden(raiz:Nodo):Vector<String> . . . . . 173
101. insercionDirecta(A:Object[N]):Object[N] . . . . . . . . 181
102. insercionDirectaGralizada(A:Object[N]):Object[N] . . 184
103. burbuja(A:Object[N]):Object[N] . . . . . . . . . . . . . . 186
104. quickSort(A:Object[N],p:int,u:int):Object[N] . . . . 190
105. concatenación(A:Object[N]):Object[N] . . . . . . . . . . 193
106. concatenar(A:Object[N],i1,f1,i2,f2:int):Object[N] . 194
107. selecciónDirecta(A:Object[N]):Object[N] . . . . . . . . 197
108. montı́culo(A:Object[N]):Object[N] . . . . . . . . . . . . . 201
109. insertarEnMontı́culo(A:Object[N]):Object[N] . . . . . . 202

ITPuebla xv
ÍNDICE DE ALGORITMOS

110. eliminarMontı́culo(mont:Object[N]):Object[] . . . . . . 203


111. busquedaSecuencial(A:Object[N],dato:Object):int . . 206
112. busquedaBinaria(A:Object[N],dato:Object):int . . . . 208
113. insercionDirecta(A:Object[N]):Object[N] . . . . . . . . 210
114. burbuja(A:Object[N]):Object[N] . . . . . . . . . . . . . . 211
115. quickSort(A:Object[N],p:int,u:int):Object[N] . . . . 213
116. insertarEnMontı́culo(A:Object[N]):Object[N] . . . . . . 215
117. busquedaSecuencial(A:Object[N],dato:Object):int . . 216
118. busquedaBinaria(A:Object[N],dato:Object):int . . . . 217
119. insertarArchSecNuevo(archivo:File,registro:Object): void 224
120. insertarFinalArchSec(archivo:File,registro:Object): void 224
121. insertarInicioArchSec(archivoFuente:File, nuevoRegistro: Object):void 226
122. insertarPosiArchSec(archivoFuente:File, nuevoRegistro: Object, posición:int):vo
123. eliminarInicioArchSec(archivoFuente:File):Object . . 230
124. eliminarFinalArchSec(archivoFuente:File):Object . . 232
125. modificaPosiArchSec(archivo:File, reg:Object, posi:int): Object 234
126. consultaPosiArchSec (archivo:File, posi:int): Object 235
127. recorreArchSec(archivo:File, posi:int): Object . . . 235
128. accederRegistroArchIndex(archivo:File,clave: String): Object 236
129. buscar(tablaDir:Vector<Object>, clave:String): int . 238
130. insertar(tablaDir:Vector<Object>, clave:String, dirRel:int): boolean 239
131. eliminar(tablaDir:Vector<Object>, clave:String): int 241
132. insertar(archivo:File,tablaDir:Vector<Object>, ListaLibres:ListaLigada, reg:Obj
133. eliminar(archivo:File,tablaDir:Vector<Object>, clave:String): Object 244
134. modificar(archivo:File,tablaDir:Vector<Object>, reg:Object): Object 245
135. consultar(archivo:File,tablaDir:Vector<Object>): Object 246
136. recorrer(archivo:File,tablaDir:Vector<Object>): void 246
137. accederRegistroArchDirec(archivo:File,clave: String): Object 251
138. buscarClaveTD(TD:Nodo[],clave:String):int . . . . . . . 256
139. insertarClaveTD(TD:Nodo[], clave:String, dirRel:int): Object 257
140. eliminarClaveTD(TD:Nodo[],clave:String):int . . . . . 258
141. insertar(archivo:File, TD:Nodo[], reg:Object): Object 260
142. eliminar(archivo:File, TD:Nodo[], clave:String): Object 261
143. modificar(archivo:File, TD:Nodo[], reg:Object): Object 262
144. consultar(archivo:File, TD:Nodo[], clave:String): Object 263
145. recorrer(archivo:File, TD:Nodo[]): Object . . . . . . . 264

ITPuebla xvi
Capı́tulo 1

Fundamentos de estructura
de datos

En este capı́tulo se presentan la definición y clasificación de las estruc-


turas de datos, entendiendo a una estructura de datos como un tipo de dato
que mantiene y organiza más de un componente. También se presentan el
concepto y algunos ejemplos de tipos de datos abstractos, que sirven para
definir formalmente tipos de datos (ya sea simples o estructurados) que luego
se implementan por los lenguajes de programación o por los programadores.

Se estudia la forma en que se asigna memoria a las estructuras de datos,


que puede ser estática (cuando la asignación se realiza antes del tiempo
de ejecución del programa que define a la estructura) o dinámica (cuando
la asignación o liberación de memria se realiza durante la ejecución del
programa que define la estructura).

El análisis de algoritmos es un tópico fundamental que permite calcu-


lar la eficiencia de un algoritmo, es decir, calcular el tiempo de ejecución
que tarda un algoritmo para resolver un problema. Este análisis se realiza
teóricamente, calculando una cota superior a la función de tiempo que carac-
teriza al algoritmo. Con esta herramienta, dado un conjunto de algoritmos
que resuelven el mismo problema, se puede seleccionar el algoritmo que lo
haga más eficientemente, es decir, en menor tiempo de ejecución. También
se toca el aspecto de la eficiencia en el espacio, con la que se calcula el monto
de memoria que requiere un algoritmo para almacenar los datos que debe

1
1.1. DEFINICIÓN

procesar, los que pueden ser simples o estructurados.

1.1. Definición

Tipo de dato. Un tipo de dato es una restricción de valores que puede


tomar una variable. Es un conjunto de valores y un grupo de operaciones
sobre tales valores [20]. Por ejemplo, el tipo de dato booleano es el conjunto
de valores {true, f alse} y el grupo de operaciones {and, or, not} sobre tales
valores.

Clasificación de los tipos de datos. De acuerdo al número de compo-


nentes, los tipos de datos se clasifican en [5][7][20]:

Tipos de datos simples. Indivisibles, conformados de un sólo com-


ponente. No están definidos en términos de otros tipos de datos. Ejem-
plos de tipos de datos simples: enteros, decimales o punto fijo, reales
o punto flotante, caracteres, booleanos.

Tipos de datos estructurados o Estructura de datos. Con-


formados por dos o más componentes que pueden ser simples o es-
tructurados. Estos tipos de datos permiten almacenar, manipular y
ordenar información. Las operaciones básicas sobre estos tipos de
datos son: insertar un componente, eliminar un componente, buscar
un componente, recorrer todos los componentes. Ejemplos de tipos de
datos estructurados:

• Arreglo. Es un conglomerado de componentes en el que un compo-


nente individual se identifica por su posición en el conglomerado,
relativa al primer elemento [5][20]. Implementaciones: cadenas de
caracteres, vectores, matrices, pilas, colas. Ejemplos de uso: re-
gistro de un conjunto de calificaciones, control de la cola de un
banco, implementación del recorrido de un árbol.
• Registro. Es un agregado posiblemente heterogéneo de compo-
nentes en el que cada componente se identifica mediante un nom-
bre [12][20]. Ejemplos de uso: almacén de la información de un
alumno, almacén de la información de un libro dentro de una bi-
blioteca. En el paradigma de la programación orientada a objetos,
un objeto es un conjunto de caracterı́sticas y comportamiento de

ITPuebla 2
1.2. CLASIFICACIÓN

alguna entidad o sujeto; el conjunto de caracterı́stcas obedece a


la definición de registro.
• Lista. Una lista ligada o enlazada es una colección de nodos que
en conjunto forman un ordenamiento lineal. Cada nodo es una
estructura de datos que guarda una referencia a un elemento y
una referencia a otro nodo [5][14]. Implementaciones: pilas, colas.
Ejemplos de uso: control de la cola de un banco, implementacin
del recorrido de un árbol.
• Grafo. Es un conjunto de nodos relacionados mediante un con-
junto de lados [11]. Ejemplos de uso: representación de mapas de
ciudades, representación de árboles genealógicos, representación
de redes de caminos y vuelos.
• Árbol. Es un caso particular de un grafo. Es un tipo de dato
que guarda elementos en forma jerárquica [11]. Ejemplos de uso:
control de la información en una base de datos, control de un dic-
cionario ordenado, control de archivos en un sistema de archivos
de un sistema operativo.

1.2. Clasificación

Los tipos de datos estructurados o estructuras de datos, se clasifican de


acuerdo a [5][7][20]:

La forma de almacenamiento: a nivel lógico, los componentes de las


estructuras de datos pueden almacenarse”uno después de otro”, como
en un vector, o bien almacenarse, por ejemplo, de manera jerárquica,
como en un organigrama. Ası́, por la forma en que se almacenan u or-
ganizan sus componentes, las estructuras de datos pueden ser Lineales,
No lineales.
El tiempo en que se asocia un tipo con almacenamiento: en este caso
se van a considerar dos momentos en los que la memoria se asigna a las
estructuras de datos: antes del tiempo de ejecución de un programa o
durante el tiempo de ejecución de un programa. Dependiendo de este
tiempo, las estructuras de datos se clasifican en Estáticas, Dinámicas.
El tipo de dato de sus componentes: dado que una estructura de datos
es un conjunto de componentes, cada componente puede tener su pro-

ITPuebla 3
1.3. ESTRUCTURAS LINEALES Y NO LINEALES

pio tipo de dato, o bien todos los componentes pueden ser del mismo
tipo de dato. Con esto, se tienen dos tipos de estructuras de datos:

• Homogéneas. Es una estructura de datos en que cada componente


es del mismo tipo. Por ejemplo: arreglos, listas, grafos.
• Heterogéneas. Es una estructura de datos en que cada compo-
nente puede tener su propio tipo de dato. Por ejemplo: registros.

1.3. Estructuras lineales y no lineales

Estructuras lineales. Son estructuras de datos en que a cada componente


le sucede o precede sólo un componente [5][7][20]. Por ejemplo: arreglos
(vectores, matrices, cadenas, pilas, colas), listas (pilas, colas), registros. En la
Figura 1.1 se tiene un vector de 5 componentes. Este vector es una estructura
lineal porque, por ejemplo, el componente 1, Luis tiene un sólo componente
que le precede, Paty, y un sólo componente que le sucede, Bianca; esto
mismo sucece con los componentes 2 y 3. A Paty no le precede ningún
componente y le sucede un sólo componente. A Jesy le precede un sólo
componente y no le sucede ninguno.

Figura 1.1: Ejemplo de una estructura lineal: vector

Estructuras no lineales. Son estructuras de datos en que a cada com-


ponente le sucede o precede más de un componente [5][7][20]. Por ejemplo:
grafos, árboles. En la Figura 1.2 se muestran los mismos componentes que
en el vector del ejemplo anterior, pero si los componentes obedecen a cierta
jerarquı́a, es más conveniente representarlos de esa forma y no como un vec-
tor. En la jerarquı́a se tiene una estructura no lineal porque, por ejemplo, a
Jesy le sucede más de un componente: Luis y Paty.

ITPuebla 4
1.4. ESTRUCTURAS ESTÁTICAS Y DINÁMICAS

Figura 1.2: Ejemplo de una estructura no lineal: árbol

1.4. Estructuras estáticas y dinámicas

Estructuras estáticas. Estructuras de datos a las que se les asocia el


almacenamiento antes del tiempo de ejecución (tiempo de cargado) [5][7][20].
Por ejemplo: arreglos, registros.

Estructuras dinámicas. Estructuras de datos a las que se les asocia el


almacenamiento en tiempo de ejecución [5][7][20]. Ejemplo: listas, grafos.

Como ya se ha visto, las estructuras de datos pueden clasificarse en


estáticas y dinámicas, dependiendo del momento en que se asigna memo-
ria a sus componentes. Por esto es que el manejo de memoria puede ser
estático o dinámico: estático si la asignación se da antes de ejecutar un pro-
grama, dinámico si la asignación se realiza durante la ejecución del programa
[5][7][20].

En la Tabla 1.1 se tiene un comparativo de caracterı́sticas entre estruc-


turas de datos estáticas y dinámicas de acuerdo al manejo de memoria. El
manejo de memoria estática tiene como ventaja que no se emplea tiempo
de ejecución para asociar y liberar memoria para los componentes de una
estructura de datos, y tiene como inconvenientes que se puede desperdiciar
memoria, ya que las celdas asociadas no pueden ocuparse por ninguna otra
estructura aunque no se utilicen, y si se llega a requerir más memoria pa-
ra mantener más componentes, esto no es posible. El manejo de memoria
dinámica tiene como ventaja que no hay desperdicio, ya que se asocia memo-

ITPuebla 5
1.4. ESTRUCTURAS ESTÁTICAS Y DINÁMICAS

ria a cada componente que realmente se esté utilizando en la estructura de


datos; el inconveniente es que se emplea tiempo de ejecución para asociarla
y liberarla.

Tabla 1.1: Manejo de memoria estática vs memoria dinámica

Estructura de datos Estructura de datos


estática dinámica
Declaración Se especifica número No se especifica número
de componentes de componentes
Número de No cambia en tiempo Cambia en tiempo
componentes de ejecución de ejecución
Tiempo de Para ejecutar sentencias Para ejecutar sentencias
ejecución y asociar/liberar espacio
en memoria
Aprovechamiento Puede desperdiciar No desperdicia memoria,
de memoria memoria, aunque no se asigna solamente la
utilice sus componentes, memoria de los
la memoria está componentes que se
asignada a ellos están utilizando
Asignación de Se asigna antes del Se asigna durante el
memoria tiempo de ejecución tiempo de ejecución

Ahora se ilustra con un ejemplo las caracterı́sticas del uso de memoria


estática y dinámica. Cuando en un programa se declara un vector, se debe
especificar su longitud (número de componentes), ya que la asignación de las
celdas de memoria para almacenar el valor de cada componente se realiza
antes de comenzar la ejecución del programa. Se utilicen o no las celdas de
memoria, estarán asignadas al vector desde que comienza hasta que termina
la ejecución del programa que lo ha declarado. Si se requieren más celdas de
memoria en tiempo de ejecución, no será posible asignarlas ni accederlas.

En lugar de un vector se puede manejar una lista (este tema se intro-


duce ampliamente en el Capı́tulo 2). Una lista también es una estructura
lineal, pero a diferencia de un vector, sus componentes se van asociando y
desasociando a celdas de memoria conforme se vayan utilizando o dejando
de utilizar, todo esto durante el tiempo de ejecución. Al comenzar la eje-
cución del programa que la define, la lista contiene cero componentes, por

ITPuebla 6
1.5. TIPOS DE DATOS ABSTRACTOS (TDA)

eso se dice que es vacı́a. En la Figura 1.3 se muestra el comportamiento en


la memoria de una estructura de datos estática (edd) y una dinámica (ede)
para estas dos estructuras de datos.

Figura 1.3: Asignación/liberación de almacenamiento en ED

1.5. Tipos de datos abstractos (TDA)

Un tipo de dato abstracto (TDA) es una herramienta útil para especificar


las propiedades lógicas de un tipo de dato. Consiste de dos partes [20]:

Definición de valores. Consiste de:

ITPuebla 7
1.5. TIPOS DE DATOS ABSTRACTOS (TDA)

• Cláusula de definición (Abstract typedef).


• Cláusula de condición (Condition).

Definición de operadores (función abstracta). Consiste de:

• Encabezado (Abstract).
• Precondiciones (Precondition).
• Postcondiciones (Postcondition).

Ası́, el esquema general para definir un TDA es:

Definición de valores:
Abstract typedef <Val> <Id>
Condition <Cond1>;<Cond2>;...;<Condn>
Definición de operaciones:
Abstract <tipo> <op> (<arg1>,<arg2>,...,<argm>)
Precondition <ConP1>;<ConP2>;...; <ConPp>
Postcondition <ConQ1>;<ConQ2>;...;<CondQq>;
<op> := <resul>

donde:

<Val>: valores que puede tomar el tipo de dato

<Id>: palabra reservada con la que se identifica al tipo de dato

<Cond>: condiciones sobre los valores del tipo que se define

<tipo> : el tipo de dato que regresa la operación que se está definiendo

<op>: identificador del operador

<arg>: operandos con los que se trabaja en la operación que se está de-
finiendo

<ConP>,<ConQ>: condiciones necesarias para operar

<resul>: indica la(s) operación(es) a realizar (indica qué hace la ope-


ración)

Por ejemplo, para definir el TDA de los enteros:

ITPuebla 8
1.6. ANÁLISIS DE ALGORITMOS

Definición de valores:
Abstract typedef <subconjunto de Z> int
Definición de operaciones:
Abstract int +(int a, int b)
Postcondition + := a+b
Abstract int *(int a, int b)
Postcondition * := a*b
Abstract int -(int a, int b)
Postcondition - := a-b
Abstract int div(int a, int b)
Precondition b <>0
Postcondition div := a/b

1.6. Análisis de algoritmos

Un algoritmo es una secuencia finita de instrucciones, cada una tiene un


significado preciso y puede ejecutarse con una cantidad finita de esfuerzo en
un tiempo finito [2][5].

Diseñar un algoritmo es construir la secuencia finita de instrucciones


para resolver determinado problema. Se pueden diseñar muchos algoritmos
para resolver el mismo problema [4][10][15].

Analizar un algoritmo es determinar cuál de todos los algoritmos que


resuelven el mismo problema es el mejor. Suele suponerse que para obte-
ner altas velocidades de cálculo basta con una computadora de alta veloci-
dad, sin embargo, un buen algoritmo ejecutado en una computadora lenta
puede ejecutarse mucho mejor que un mal algoritmo en una computadora
rápida. Dependiendo del problema a resolver, el mejor algoritmo puede ser
[4][10][15]:

El que requiera menos tiempo de ejecución.

El que utilice menos espacio de almacenamiento.

El que sea más fácil de programar.

ITPuebla 9
1.6. ANÁLISIS DE ALGORITMOS

1.6.1. Criterios para analizar un algoritmo

Algunos criterios que se utilizan para analizar un algoritmo son [4][10][15]:

Corrección.

Cantidad de trabajo realizado.

Cantidad de espacio utilizado.

Sencillez y claridad.

Optimidad.

Corrección. Un algoritmo es correcto cuando hace lo que se espera que


haga. Para ello, se deben plantear de manera precisa:

Precondiciones. Caracterı́sticas de las entradas con las que se espera


trabaje el algoritmo.

Postcondiciones. El resultado que se espera que produzca el algoritmo


con cada entrada.

Cantiad de trabajo realizado. A medida que un algoritmo reduce la


cantidad de trabajo realizado, éste se vuelve más eficiente. La cantidad de
trabajo se puede medir mediante:

La toma de tiempo de ejecución real, que depende de la computadora


empleada.

El conteo de las instrucciones que ejecuta, que depende del lenguaje


de programación empleado y del estilo del programador.

Más que medir la implementación de un algoritmo, hay que medir el método


empleado por él. Se necesita de una medida precisa y general. Para medir
la cantidad de trabajo se podrı́a aislar una operación especı́fica (operación
básica) que sea fundamental para el problema que se estudia. En la Tabla
1.2 se muestran algunos ejemplos de operaciones básicas.

ITPuebla 10
1.6. ANÁLISIS DE ALGORITMOS

Tabla 1.2: Ejemplos de operaciones básicas


Problema Operación
Encontrar un objeto en un arreglo Comparación del objeto con un
elemento del arreglo
Multiplicar dos matrices Multiplicacin de dos reales
con entradas reales
Ordenar un conjunto de elementos Comparacin de dos elementos
del conjunto
Recorrer un árbol binario Recorrer una arista

Ası́, la cantidad de trabajo de un algoritmo se puede calcular en fun-


ción de la cantidad de operaciones básicas realizadas y también depende
del tamaño del problema, que se definirá más adelante. En la Tabla 1.3 se
muestran algunos ejemplos de tamaños de problema.

Tabla 1.3: Ejemplos de tamaños de problema


Problema Tamaño del problema
Encontrar un objeto El número de objetos en el arreglo
en un arreglo
Multiplicar dos matrices con Las dimensiones de las matrices
entradas reales
Ordenar un conjunto de elementos El número de elementos
del conjunto
Recorrer un árbol binario El número de nodos del árbol

Cantidad de espacio utilizado. Un programa requiere espacio de alma-


cenamiento para:

Instrucciones

Constantes

Variables

Datos de entrada

ITPuebla 11
1.6. ANÁLISIS DE ALGORITMOS

La cantidad de espacio mide el número de celdas de memoria que ocupa un


algoritmo.

Sencillez y claridad. Suele suceder que un algoritmo sencillo para resolver


un problema no es el más eficiente. Sin embargo, la sencillez es deseable, pues
incide en facilitar:

La verificación de correctitud del algoritmo.


La escritura/lectura del algoritmo.
La depuración/modificación de los programas correspondientes.

Si un programa que resuleve un problema se utilizará:

Pocas veces, el criterio de seleccin de un algoritmo puede ser su senci-


llez.
Muchas veces, el criterio de seleccin de un algoritmo puede ser su
eficiencia.

Optimidad. Todo problema tiene una complejidad inherente. Existe una


cantidad mı́nima de trabajo que debe efectuarse para resolverlo. Un algo-
ritmo es óptimo si ningún otro (existente o no) efectúa menos operaciones
básicas.

1.6.2. Eficiencia de un algoritmo

El análisis de algoritmos estudia, desde el punto de vista teórico, los re-


cursos computacionales que necesita la ejecución de un programa de compu-
tadora: su eficiencia. Tal eficiencia se mide en función de la cantidad de
recursos informáticos consumidos por el algoritmo, por ejemplo: uso de me-
moria o tiempo de CPU, T (n), donde n es el tamaño del problema [4][10][15].

El tamaño o talla de un problema, denotado con n, es la cantidad de


datos que procesa un algoritmo [2]. Por ejemplo, ordenar n números; dados
los enteros a y b, con a ≤ b, sumar los enteros comprendidos entre ellos,
donde n = b − a + 1; calcular el producto interno de dos vectores de tamaño
n; realizar una consulta a una tabla de n registros.

ITPuebla 12
1.6. ANÁLISIS DE ALGORITMOS

El tiempo de CPU, o tiempo de ejecución, que necesita un algoritmo


concreto varı́a en función de [2]:

La computadora que se utilice.

El compilador que se utilice.

La habilidad del programador.

Para calcular tal tiempo de ejecución se deben ignorar las constantes


dependientes del contexto. Para ello, hay que fijarse en el crecimiento de
T (n) cuando n tiende a infinito, n → ∞. Ası́, el tiempo de ejecución puede
considerarse en función de:

El tamaño de los datos de entrada. Por ejemplo, si se tiene un


algoritmo que ordena un conjunto de n datos, ¿cuánto tiempo tarda
su ejecución si se ordenan n = 10 datos, n = 100 datos, n = 1000 datos,
n = 10, 000 datos? Este caso corresponde a estudiar el comportamiento
de T (n) cuando n → ∞.

Las diferentes entradas del mismo tamaño. Por ejemplo, si se


necesitan ordenar n = 10, 000 datos con un algoritmo, ¿cuánto tiempo
tarda su ejecución si los datos están ordenados, están inversamente
ordenados, están desordenados? Este caso corresponde a estudiar el
comportamiento de T (n) en el peor, mejor y caso medio. El mejor caso
se refiere a las instancias que, para cada valor de n, se resuelven más
rápidamente con el algoritmo estudiado. El peor caso lo determinan
aquellas instancias que, para cada valor de n, hacen que el algoritmo
se ejecute con el mayor número posible de pasos [2][15]. Por ejemplo,
si se tiene el arreglo
0 1 2 3 4 5 6 7
Luis Ana José Pedro Mary Carmen Pablo Helena

el peor caso serı́a buscar a Helena (que está al final del arreglo) o a
Alberto (que no está); mientras que el mejor caso serı́a buscar a Luis.

ITPuebla 13
1.6. ANÁLISIS DE ALGORITMOS

1.6.3. Complejidad en el tiempo

Ahora, se centra la discusión en el análisis de T (n) cuando n → ∞ que


es, como se ha dicho, cuando T (n) está en función del tamaño de los datos
de entrada, con lo que al tiempo de ejecución de un algoritmo también se
le denomina Complejidad de tiempo de un algoritmo. Al hecho de
estudiar el comportamiento de T (n) a medida que n crece se le denomina
estudiar el comportamiento lı́mite de la Complejidad de tiempo cuando n
crece, es decir, la Complejidad asintótica. En resumen, se debe estudiar
la complejidad asintótica del tiempo de ejecución T (n) [2][15].

Notación asintótica

Para especificar la complejidad asintótica del tiempo de ejecución se


utiliza la Notación asintótica, representada por O, llamada ”O grande” y
que se lee como ”orden de”. O sirve para expresar la cota superior asintótica
de una función.

Definición de O. Sean t(n) y f (n) dos funciones arbitrarias tales que [2][15]:

t(n), f (n) ∶ Z → R ,
+ +

entonces t(n) es O de f (n), es decir

t(n) = O(f (n))

si existen constantes positivas c y n0 tales que:

t(n) ≤ cf (n),

para toda n ≥ n0 (ver Figura 1.4).

Por ejemplo, sean t(n) = 3n+2 y f (n) = n, entonces t(n) = O(f (n)) porque:
3n = 3n
3n + 2 ≤ 3n + n, para n ≥ 2
3n + 2 ≤ 4n, para n ≥ 2,

para c = 4 y n0 = 2, como se ven en la Figura 1.5. Que 3n + 2 = O(n) quiere


decir que cuando n tiende a infinito el valor de 2 se puede despreciar con
respecto al de n.

ITPuebla 14
1.6. ANÁLISIS DE ALGORITMOS

Figura 1.4: Definición de O

Figura 1.5: Ejemplo gráfico de O

ITPuebla 15
1.6. ANÁLISIS DE ALGORITMOS

sean t(n) = (6)(2 ) + n y f (n) = 2 , entonces t(n) =


n 2 n
En otro ejemplo,
O(f (n)) porque:
(6)(2 ) (6)(2 )
n n
=
(6)(2 ) + n (6)(2 ) + 2 ,
n 2 n n
≤ para n ≥ 4 (ver Figuras 1.6 y 1.7)
(6)(2 ) + n (7)(2 ),
n 2 n
≤ para n ≥ 4

para c = 7 y n0 = 4, como se ven en las Figuras 1.8 y 1.9. Que (6)(2 ) + n =


n 2

O(2 ) quiere decir que cuando n tiende a infinito el valor de n se puede


n 2
n
despreciar con respecto al de 2 .

En los ejemplos anteriores, si t(n) es el tiempo que tarda en ejecutarse


un algoritmo, se dice que el algoritmo es del orden de f (n). Es decir, una
vez que se determina el tiempo de ejecución de un algoritmo t(n), la idea es
encontrar una función f (n) que funja como su cota superior; de esta forma se
puede decir que el algoritmo tardará en ejecutarse no más que f (n) unidades
de tiempo a partir de cierta cantidad de datos que procese (n).

2 n
Figura 1.6: Contraste gráfico de n y 2

ITPuebla 16
1.6. ANÁLISIS DE ALGORITMOS

2 n
Figura 1.7: Detalle de contraste gráfico de n y 2

Figura 1.8: Otro ejemplo gráfico de O

ITPuebla 17
1.6. ANÁLISIS DE ALGORITMOS

Figura 1.9: Detalle de otro ejemplo gráfico de O

Jerarquı́a de funciones

Habiendo introducido el concepto de la complejidad de tiempo y la nota-


ción asintótica, se puede ”ordenar” un conjunto de funciones, especificando
qué funciones son cotas superiores de qué funciones, definiendo ası́ la si-
guiente jerarquı́a [15]:

O(1) ⊂ O(logn) ⊂ O( n) ⊂ O(n) ⊂ O(nlogn) ⊂
⊂ O(n ) ⊂ O(n ) ⊂ O(2 ) ⊂ O(n ),
2 3 n n

con lo que estas funciones se clasifican de acuerdo a la Tabla 1.4.

Ası́, un algoritmo de complejidad constante ejecuta un número constan-


te de instrucciones (está acotado por una función constante) independien-
temente del tamaño del problema; por esto, un algoritmo que soluciona un
problema en tiempo constante es ideal, ya que será más eficiente (será mejor)
que cualquier otro algoritmo que resuelva el mismo problema con comple-
jidad no constante. El costo en tiempo de un algoritmo logarı́tmico crece
muy lentamente conforme n crece, por ejemplo, cada vez que el tamaño

ITPuebla 18
1.6. ANÁLISIS DE ALGORITMOS

de un problema es x veces más grande, el tiempo necesario para resolverlo


mediante el algoritmo crece sólamente una unidad de tiempo.

Tabla 1.4: Jerarquı́a de funciones


Constantes O(1)

Sublineales Logart́imicas O(logn)
O( n)
Lineales O(n)
O(nlogn)
O(n )
2
Polinómicas Cuadráticas
O(n )
3
Polinómicas Cúbicas
O(2 )
n
Superlineales Exponenciales
O(n )
n
Exponenciales

En un algoritmo lineal, tanto n como el tiempo crecen en la misma pro-


porción. En cambio, pasar a tratar un problema el doble de grande, con un
algoritmo cuadrático se requiere cuatro veces más tiempo, y con un cúbico se
requiere ocho veces más tiempo, con lo que este tipo de algoritmos son útiles
para problemas de tamaño pequeño. Por otra parte, un algoritmo exponen-
cial raramente es útil, por ejemplo, si n = {1, 2, 3, 4, 5}, 2 = {2, 4, 8, 16, 32},
n

es decir, cuando n se incrementa en una unidad, T (n) aumenta al doble de


su valor.

1.6.4. Análisis de estructuras de control

Dado un conjunto de algoritmos que resuelven el mismo problema, hay


que analizarlos, es decir, hay que determinar cuál de ellos es el más eficiente:
hay que determinar el que tarda el menor tiempo de ejecución T (n) conforme
crece el tamaño del problema n.

Para analizar un algoritmo, hay que hacer una medición teórica de su


tiempo de ejecución, es decir, hay que contar el número de operaciones
que realiza y cada operación puede o no ser un paso.

Un paso es una operación o un segmento de código cuyo tiempo de


ejecución no depende del tamaño del problema considerado y está acotado

ITPuebla 19
1.6. ANÁLISIS DE ALGORITMOS

por alguna constante [15]. Algunos ejemplos de pasos son:

Operaciones aritméticas.

Operaciones lógicas.

Comparaciones entre escalares.

Accesos a variables escalares.

Accesos a elementos de vectores o matrices.

Asignaciones de valores a escalares.

Asignaciones de valores a elementos de vectores o matrices.

Lectura/escritura de un valor escalar.

El tiempo de ejecución de las operaciones que no son pasos está en


función del número de pasos con que se efectúan. Algunos ejemplos de ope-
raciones que no son pasos son:

Cálculo del número de componentes de una estructura de datos (cal-


cular la longitud de una cadena).

Comparaciones de colecciones de componentes (comparar dos cadenas,


comparar dos conjuntos).

Operaciones aritméticas sobre vectores o matrices.

Operaciones de búsqueda (buscar una clave en un estructura de ı́ndice,


verificar la pertenencia de un elemento en un conjunto).

Ası́, el tiempo de ejecución de un algoritmo se define como el número


de pasos expresado en función del tamaño del problema T (n). Antes de
presentar las reglas para analizar el tiempo de ejecución de las estructuras
de control de un algoritmo, se introducirán las siguientes reglas: suponer
que se tienen dos tiempos de ejecución T1 (n) = O(f (n)) y T2 (n) = O(g(n)),
entonces [15]:

ITPuebla 20
1.6. ANÁLISIS DE ALGORITMOS

Regla de la suma.

T1 (n) + T2 (n) = O(máx{f (n), g(n)}). (1.1)

Regla del producto.

T1 (n) ∗ T2 (n) = O(f (n) ∗ g(n)). (1.2)

A continuación se presentan las reglas generales para el análisis de


algoritmos.

Tiempo de ejecución de una sentencia

Considerando que una sentencia es un paso, en general, su tiempo de


ejecución puede tomarse como una constante:

Tpaso (n) = O(1) (1.3)

Tiempo de ejecución de un bloque de sentencias

El tiempo de ejecución de un bloque de sentencias se determina por la


regla de la suma. Ası́, dado el bloque de dos sentencias, cada una con sus
respectivos tiempos de ejecución:

Sentencia1 ; Tsentencia1 (n) = O(f (n))


Sentencia2 ; Tsentencia2 (n) = O(g(n))

el tiempo de ejecución del bloque, es decir, el tiempo que se utiliza para


ejecutar todas las sentencias del bloque, es Tbloque (n):

Tbloque (n) = Tsentencia1 (n) + Tsentencia2 (n) = O(máx{f (n), g(n)}). (1.4)

ITPuebla 21
1.6. ANÁLISIS DE ALGORITMOS

Tiempo de ejecución de una sentencia decisiva

El tiempo de ejecución de una sentencia decisiva es la suma de: los tiem-


pos de los bloques ejecutados condicionalmente y el tiempo para evaluar la
condición. Ası́, dada la sentencia decisiva:

if Condición Tcond (n) = O(f (n))


Bloque1 ; Tbloq1 (n) = O(g(n))
else
Bloque2 ; Tbloq2 (n) = O(h(n))

su tiempo de ejecución es:

Tdecisión (n) = Tcond (n) + Tbloq1 (n) + Tbloq2 (n) =


= O(máx{f (n), g(n), h(n)}) (1.5)

Tiempo de ejecución de una sentencia iterativa

El tiempo de ejecución de una sentencia iterativa es la suma, sobre todas


las iteraciones del ciclo, del tiempo de ejecución del cuerpo y de la condición
de terminación. Ası́, dada la sentencia iterativa:

for i=ini, ini+1, ..., fin


Bloque; Tbloq (n) = O(f (n))

su tiempo de ejecución es, con Tbloq (n) variable es:

Titeración (n) = Tbloq ini (n) + Tbloq ini+1 (n) + ... + Tbloq fin (n) =
= O((f in − ini + 1) ∗ máx{fini (n) + fini+1 (n) + ... + ffin (n), }
(1.6)

o bien, con Tbloq (n) constante es:


f in
Titeración (n) = ∑ Tbloq (n) = O((f in − ini + 1) ∗ f (n)). (1.7)
i=ini

Ahora, dada la sentencia iterativa:

while condición Tcond (n) = O(f (n))


Bloque; Tbloq (n) = O(g(n))

ITPuebla 22
1.6. ANÁLISIS DE ALGORITMOS

su tiempo de ejecución es:

Titeración (n) = m ∗ (Tcond (n) + Tbloq 1 (n) + ... + Tbloq m (n) =


= O(m ∗ máx{f (n), g1 (n), ..., gm (n)}, (1.8)

donde m es el número de iteraciones.

Tiempo de ejecución de un algoritmo que invoca a otro algoritmo

Si se tiene un algoritmo en cuyo conjunto de operaciones se invocan a


otros algoritmos: primero se calcula el tiempo de ejecución de cada algoritmo
invocado, luego se calcula el tiempo de ejecución del cuerpo del algoritmo
analizado. Por ejemplo, si se tiene el algoritmo alg1 que invoca a alg2 y a
alg3:

void alg1 ()
{ bloque1; Tbloq1 (n) = O(f (n))
alg2(); Talg2 (n) = O(g(n))
bloque2; Tbloq2 (n) = O(h(n))
alg3(); Talg3 (n) = O(p(n))
}

entonces: Talg1 (n) = O(máx(f (n), g(n), h(n), p(n))).

Ejemplos de cálculos de tiempos de ejecución

En el algoritmo 1 se tienen las sentencias para sumar dos vectores de


tamaño n, llamados v y w, quedando el resultado en el vector v. Para calcular
el tiempo de ejecución de este algoritmo, se comienza calculando el tiempo
de ejecución de cada paso, como es el caso de la prueba de la condición
de parada del ciclo, implı́cita en la lı́nea 1 (Si i<n), y la sentencia de la
lı́nea 2, donde se tienen una suma y una asignación, por lo que sus tiempos
de ejecución son Tcond (n) = Tbloq (n) = 1. Después se calcula el tiempo de
ejecución de la sentencia iterativa de la lı́nea 1, utlizando la ecuación (1.7),
ya que se tiene una sentencia for con tiempo constante de bloque:

Titer (n) = ∑(máx{Tcond (n), Tbloq (n)}) = ∑ 1 = n,


n n

i=1 i=1

ITPuebla 23
1.6. ANÁLISIS DE ALGORITMOS

entonces se puede decir que el algoritmo que suma dos vectores de tamaño
n es de orden lineal:
T (n) = n = O(n).

Algoritmo 1 sumaVec(v:int[n], w:int[n]):int[n]


no n **
/* Suma dos vectores de tama~
** Entradas: **
** v:int[n] **
** w:int[n] **
** Salidas: **
** v:int[n] */
1. Para i=1,2,...,n Tcond (n) = 1 Titer (n) = n
2. v[i] = v[i] + w[i] Tbloq (n) = 1

En el siguiente ejemplo, en el algoritmo 2 se tienen las sentencias para


sumar dos matrices de tamaño n × n, llamadas A y B, quedando el resultado
en la matriz A. Se comienza calculando el tiempo de ejecución de cada paso,
como es el caso de la prueba de la condición de parada de ambos ciclos,
implı́citas en las lı́neas 1 (Si i<n) y 2 (Si j<n), y la sentencia de la lı́nea
3, donde se tienen una suma y una asignación, por lo que sus tiempos de
ejecución son:
Tcond1 (n) = Tcond2 (n) = Tbloq (n) = 1.
Después se calcula el tiempo de ejecución de la sentencia iterativa más in-
terna controlada por j (de la lı́nea 2), utlizando la ecuación (1.7), ya que se
tiene una sentencia for con tiempo constante de bloque:

Titerj (n) = ∑ (máx{Tcondj (n), Tbloq (n)}) = ∑ 1 = n,


n n

j=1 i=1

con esto, el bloque de sentencias del ciclo externo controlado por i, consiste
de una sentencia iterativa (controlada por j, del ciclo interno) con tiempo
de ejecución:
Titerj (n) = n.
Ahora, para calcular el tiempo de ejecución del ciclo externo, se vuelve a
utilizar la ecuación (1.7):

Titeri (n) = ∑(máx{Tcondi (n), Titerj (n)}) = ∑ n = n ,


n n
2

i=1 i=1

ITPuebla 24
1.6. ANÁLISIS DE ALGORITMOS

entonces se puede decir que el algoritmo que suma dos matrices de tamaño
n × n es de orden cuadrático:

T (n) = n = O(n ).
2 2

Algoritmo 2 sumaMat(A:int[n][n],B:int[n][n]):int[n][n]
no n × n **
/* Suma dos matrices de tama~
** Entradas: **
** A:int[n][n] **
** B:int[n][n] **
** Salidas: **
** A:int[n][n] */
Tcondi (n) = 1 Titeri (n) = n
2
1. Para i=1,2,...,n
2. Para j=1,2,...,n Tcondj (n) = 1 Titerj (n) = n
3. A[i][j]=A[i][j]+B[i][j] Tbloq (n) = 1

Para multiplicar dos matrices de tamaño n × n se puede utilizar el algo-


ritmo 3 que tiene un tiempo de ejecución del orden cúbico, T (n) = O(n ),
3

como se muestra enseguida.

Algoritmo 3 multiplicaMat(A:int[n][n],B:int[n][n]):int[n][n]
/* Multiplica dos matrices de tama~ no n × n **
** Entradas: **
** A:int[n][n] **
** B:int[n][n] **
** Salidas: **
** C:int[n][n] */
Tcondi (n) = 1, Titeri (n) = n
3
1. Para i=1,2,...,n
Tcondj (n) = 1, Titerj (n) = n
2
2. Para j=1,2,...,n
3. C[i][j]=0 Tpaso1 (n) = 1
4. Para k=1,2,...,n Tcondk (n) = 1, Titerk (n) = n
3. C[i][j]=C[i][j]+A[i][k]*B[k][j] Tpaso2 (n) = 1
4. Return C Tpaso3 (n) = 1

En la lı́nea 3 se tiene un paso con tiempo constante, que es el bloque


de sentencias de la iteración más interna controlada por k, por lo que esta
iteración tiene un tiempo de ejecución de Titerk (n) = n. Luego, el bloque de

ITPuebla 25
1.6. ANÁLISIS DE ALGORITMOS

sentencias de la iteración conrolada por j (lı́nea 2) está formado por dos


sentencias: la asignación del paso 3 y el ciclo del paso 4, ası́, este bloque
tiene un tiempo de ejecución de:

Tbloquej (n) = máx{Tpaso1 (n), Titerk (n)} = máx{1, n} = n;

y aplicando la ecuación (1.7), el tiempo de ejecución del ciclo j es:

Titerj (n) = ∑ Tbloquej = ∑ n = n .


n n
2

j=1 j=1

El tiempo de ejecución de la sentencia iterativa del paso 1, se calcula nue-


vamente con la ecuación (1.7) como sigue:

Titeri (n) = ∑ Titerj = ∑ n = n .


n n
2 3

i=1 i=1

Por último, el tiempo de ejecución del algoritmo completo depende de dos


sentencias: la sentencia iterativa de la lı́nea 1, y el paso de la lı́nea 4, que-
dando como:

TMultiMat (n) = máx{Titeri (n), Tpaso3 (n)} = máx{n , 1} = n = O(n ).


3 3 3

Ahora se introduce un ejemplo de un algoritmo que invoca a otro algorit-


mo. En el algoritmo 4 se tienen las sentencias para buscar un elemento en un
vector de n caracteres. Luego, en el algoritmo 5 se tienen las sentencias que
muestran lo elementos de un vector de n elementos a partir de la posición
de un caracter buscado. Por ejemplo, si se tiene el vector:

1 2 3 4 5 6 7 8 9 10
’b’ ’s’ ’w’ ’r’ ’q’ ’m’ ’t’ ’x’ ’h’ ’y’

y se requiere recorrer el vector a partir de la posición que ocupa el caracter


’q’, el recorrido resultante es:

5 6 7 8 9 10
’q’ ’m’ ’t’ ’x’ ’h’ ’y’

El tiempo de ejecución del algoritmo busca 4 es de orden lineal: Tbusca (n) =


n = O(n), dado que se tiene un ciclo que se repite n veces (cabe decir que
este algoritmo es mejorable si se termina el ciclo en cuanto se encuentra el

ITPuebla 26
1.6. ANÁLISIS DE ALGORITMOS

caracter buscado). Ahora, el tiempo de ejecución del algoritmo 5 se calcu-


la como sigue: en la lı́nea 4 se tiene una asignación (un paso) con tiempo
Tpaso1 (n) = 1, que es un bloque se sentencias de la sentencia iterativa del
paso 3, que tiene un tiempo de ejecución de:

Titeri (n) = ∑ máx{TcondiPara (n), Tpaso1 (n)} =


n

posi
n n
= ∑ máx{1, 1} = ∑ 1 = n − posi + 1;
posi posi

con esto, el tiempo de la sentencia decisiva de la lı́nea 2 depende del tiempo


de la comparación y del tiempo de la iteración:

Tdeci (n) = máx{TcondiSi (n), Titeri (n)} =


= máx{1, n − paso + 1} = n − paso + 1;

Ası́, el algoritmo 5 tiene un tiempo de ejecución que es el máximo de los


tiempos de la lı́neas 1 y 2. En la lı́nea 1 se invoca al algoritmo busca con
tiempo Tbusca (n) = n, y en la lı́nea 2 la decisión tiene tiempo Tdeci (n) =
n − paso + 1, entonces, el algoritmo 5 tiene un tiempo del orden lineal:

TmuestraDesde (n) = máx{n, n − paso + 1} = n = O(n).

Algoritmo 4 busca(v:char[n],carac:char):int
/* Busca la posición que tiene un caracter en un vector **
no n. Si el caracter no se encuentra,
/* de tama~ **
/* el algoritmo retorna -1 **
** Entradas: **
** v:char[n] **
** carac:char **
** Salidas: **
** posi:int */

1. posi = -1 Tpaso1 (n) = 1


2. Para i=1,2,...,n Tcondi (n) = 1, Titeri (n) = n
3. Si carac = v[i] Tdeci (n) = 1
4. posi = i Tpaso2 (n) = 1
5. Return posi Tpaso3 (n) = 1

ITPuebla 27
1.6. ANÁLISIS DE ALGORITMOS

Algoritmo 5 muestraDesde(carac:char):void
/* Muestra los caracteres de un vector de tama~ no n **
/* a partir de la posición que ocupa un caracter dado **
** Entradas: **
** v:char[n] **
** carac:char **
** Salidas: **
** ninguna */
1. posi = busca(carac) Tbusca (n) = n
2. Si posi != -1 Tdeci (n) = n − posi + 1
3. Para i=posi,posi+1,...,n Titeri (n) = n − posi + 1
4. Print (v[i]) Tpaso1 (n) = 1

Se han visto ejemplos de algoritmos con tiempos de orden lineal y polino-


mial; a este tipo corresponden los algoritmos que se verán en los Capı́tulos
2 y 4, donde los datos están organizados como una lı́nea (arreglos, listas,
colas, pilas). Los algoritmos de tiempo de orden logart́imico O(logn) y su-
perlineal O(nlogn) son los que procesan datos organizados como árboles,
como se verá en el Calpı́tulo 3 (árboles, grafos), también los que procesan
los datos en forma de árbol equilibrado, como se verá en el Capı́tulo 4 (orde-
namiento rápido y búsqueda binaria). Un ejemplo de un algoritmo de orden
exponencial O(2 ) es el que verifica una tautologı́a mediante una tabla de
n

verdad, ya que:

Con 2 variables lógicas, se procesa una tabla con 4 renglones;


Con 3 variables lógicas, se procesa una tabla con 8 renglones;
Con 4 variables lógicas, se procesa una tabla con 16 renglones;
... ...
n
Con n variables lógicas, se procesa una tabla con 2 renglones;

entonces cuando las entradas (número de variables) crecen en una unidad,


el tiempo de ejecución (tamaño de la tabla de verdad) crece al doble.

1.6.5. Complejidad en el espacio

La complejidad en el espacio de un algoritmo se refiere a la cantidad


de memoria que utiliza. Haciendo una analogı́a con la complejidad en el
tiempo, en este caso se estudia el comportamiento del consumo de memoria

ITPuebla 28
1.6. ANÁLISIS DE ALGORITMOS

para determinar si está o no en función del tamaño del problema. Para


esto, primero hay que considerar si el algoritmo estudiado es o no recursivo.
En el Capı́tulo 3 se abundará sobre los algoritmos recursivos (un algoritmo
recursivo es un algoritmo que se invocan a sı́ mismo, es decir, es un algoritmo
en cuyo bloque de sentencias hay llamados a él mismo) [2][10][15].

En la Tabla 1.5 se presenta la complejidad en el espacio de algoritmos


no recursivos y recursivos.

Tabla 1.5: Complejidad espacial de algoritmos no recursivos y recursivos

Algoritmos no recursivos
Núm. de variables Cantidad de memoria Complejidad
c variables escalares T (n) = c = O(1) Constante
c vectores de tamaño n T (n) = cn = O(n) Lineal
Algoritmos recursivos
Núm. de llamados/variables Cantidad de memoria Complejidad
n llamadas recursivas con T (n) = cn = O(n) Lineal
c variables escalares
T (n) = cnn = O(n )
2
n llamadas recursivas con Cuadrática
c vectores de tamaño n

Por ejemplo, en el algoritmo 6 se suman dos vectores de tamaño n con


un procedimiento iterativo, utilizando 2×n+1 celdas de memoria: 2 vectores
de tamaño n y un contador de iteración i, por lo que la cantidad de memoria
que utiliza es de orden lineal:
T (n) = 2n + 1 = O(n).

Algoritmo 6 sumaVecIterativo(v:int[n],w:int[n]):int[n]
/* Suma iterativamente dos vectores de tama~ no n **
** Entradas: **
** v,w:int[n] **
** Salidas: **
** v:int[n] */
1. Para i=1,2,...,n Tcond (n) = 1 Titer (n) = n
2. v[i] = v[i] + w[i] Tbloq (n) = 1

ITPuebla 29
1.6. ANÁLISIS DE ALGORITMOS

En el algoritmo 7 también se suman dos vectores de tamaño n con un


procedimiento recursivo, como se ve en la lı́nea 3. Este algoritmo tiene cuatro
paraámetros de entrada: 2 vectores, el tamaño del vector n y la posición i
que indica qué elementos se están sumando en el llamado recursivo actual. El
algoritmo suma los vectores de la siguiente forma: si la posición i es menor
o igual que el tamaño n, se debe sumar la posición i de v con la misma
posición de w, quedando el resultado en la misma posición de v, luego el
resto de posiciones de los vectores se suman recursivamente, por eso en la
lı́nea 3 se invoca al mismo algoritmo para que se encargue de sumar los
elementos a partir de la posición i + 1. En cada llamado recursivo se utiliza
nueva memoria para almacenar los dos vectores (2 × n) y los dos escalares
(el del tamaño de los vectores y el del ı́ndice de la posición a sumar, 2), y
como se realizan n llamados recursivos para sumar n elementos, la cantidad
de memoria que se utiliza es de orden cuadrático:

T (n) = n(2n + 2) = 2n + 2n = O(n ).


2 2

Algoritmo 7 sumaVecRecursivo(v:int[n],w:int[n],n,i:int):int[n]
no n **
/* Suma recursivamente dos vectores de tama~
** Entradas: **
** v,w:int[n] **
** n,i:int **
** Salidas: **
** v:int[n] */
1. Si i<n
2. v[i] = v[i]+w[i]
3. sumaVecRecursivo(v,w,n,i+1)

ITPuebla 30
Capı́tulo 2

Estructuras lineales

En este capı́tulo se presentan estructuras de datos clasificadas como li-


neales, es decir, aquellas a las que a cada componente le antecede y le sucede,
a lo más, un componente. Un vector es una estructura de datos lineal de la
que ya se tiene conocimiento desde los primeros cursos de programación, y
es una estructura estática, porque su número de componentes no varı́a en
tiempo de ejecución. Aquı́ se revisan las listas, las colas y las pilas, que son
estructuras lineales: las listas, estructuras dinámicas; las colas y las pilas,
estructuras estáticas o dinámicas, dependiendo de su implementación.

2.1. Listas

Una lista ligada simple (lista enlazada simple) es una estructura de


datos lineal (a cada componente le precede y le sucede a lo más un com-
ponente), homogénea (cada componente es del mismo tipo) y dinámica (su
número de componentes puede variar durante el tiempo de ejecución). Es
una colección de nodos que juntos forman un ordenamiento lineal. Un nodo
(o nodo simple) es un objeto que almacena una referencia a un elemento y
una referencia a otro nodo. El acceso a los nodos se realiza mediante una
referencia al primero de ellos en el ordenamiento lineal, llamado cabeza. El
último nodo de la lista, al no hacer referencia a ningún otro nodo, se indica
que hace referencia a null, nill o tierra [20][5][9][7][12].

31
2.1. LISTAS

Ejemplos de aplicaciones de listas pueden ser: control de una lista de


compras (datos desordenados), control de una lista de estudiantes de un
curso (datos ordenados). Con listas ligadas pueden implementarse las pilas
y las colas, con lo que éstas se convierten en estructuras de datos dinámicas,
ya que cada uno de sus componentes (los nodos de la lista) pueden crearse
y destruirse en tiempo de ejecución.

2.1.1. Representación en memoria

Una lista ligada puede verse gráficamente en la Figura 2.1, donde se tiene
una lista ligada de 4 nodos, el primero de ellos es el nodo cabeza, el último
apunta a null. La lista tiene una referencia al primer nodo, al nodo cabeza,
en la Figura esta referencia se llama cabeza. Para acceder al cualquier nodo
debe hacerce referencia al nodo cabeza.

Figura 2.1: Representación en memoria de una lista ligada simple

Cada nodo de una lista simple tiene dos componentes (ver Figura 2.2)
que son dos referencias (también llamados apuntadores o direcciones de me-
moria): una referencia al dato que almacena el nodo, y una referencia al
siguiente nodo.

Figura 2.2: Representación en memoria de una nodo simple

ITPuebla 32
2.1. LISTAS

Por ejemplo, en la Figura 2.3 se muestra una lista simple de 4 nodos,


cada nodo almacena: una referencia a un objeto que contiene el número
de control y nombre de un alumno; y una referencia al siguiente nodo, el
último nodo hace referencia a null. El acceso a culquier nodo se realiza
desde cabeza, que hace referencia al primer nodo.

Figura 2.3: Ejemplo de una lista ligada simple

2.1.2. Operaciones básicas

A diferencia de una pila o una cola en las que la inserción, eliminación y


consulta se realizan por algún extremo de la estructura lineal, en una lista
ligada estas operaciones pueden realizarse en cualquier componente de la
estructura: a la cabeza, al final o enmedio; además de que se puede recorrer.
Por esto, el TDA de una lista ligada puede ser el que sigue [20]:

Definición de valores
int
Abstract typedef <algúnTipo> Lista

Definición de operaciones
Abstract <algúnTipo> insertarCabeza (Lista lis,
<algúnTipo> componente)
Postcondition
ligar componente con el nodo cabeza de lis
la nueva cabeza es componente
lis tiene un componente más
inertarCabeza := componente

ITPuebla 33
2.1. LISTAS

Definición de operaciones
Abstract <algúnTipo> insertarFinal (Lista lis,
<algúnTipo> componente)
Postcondition
ligar componente como siguiente nodo
del nodo final de lis
lis tiene un componente más
insertarFinal := componente

Definición de operaciones
Abstract <algúnTipo> insertarPosi (Lista lis,
<algúnTipo> componente, int posi)
Precondition
lis tiene al menos el nodo posi-1
posi ≥ 0
Postcondition
ligar componente como siguiente nodo
del nodo posi-1 y como nodo anterior
del nodo posi+1 de lis
lis tiene un componente más
insertarPosi := componente

Definición de operaciones
Abstract <algúnTipo> eliminarCabeza (Lista lis)
Precondition
lis no está vacı́a
Postcondition
eliminarCabeza := componente de la cabeza de lis
nueva cabeza de lis es el segundo nodo
lis tiene un componente menos

Definición de operaciones
Abstract <algúnTipo> eliminarFinal (Lista lis)
Precondition
lis no está vacı́a
Postcondition
eliminarFinal := componente del nodo final de lis
lis tiene un componente menos

ITPuebla 34
2.1. LISTAS

Definición de operaciones
Abstract <algúnTipo> eliminarPosi (Lista lis, int posi)
Precondition
lis no está vacı́a
lis tiene al menos el nodo posi
posi ≥ 0
Postcondition
eliminarPosi := componente del nodo de la posición
posi de lis
lis tiene un componente menos

Definición de operaciones
Abstract boolean esVacı́a (Lista lis)
Postcondition
esVacı́a := true si lis no tiene componentes
esVacı́a := false si lis tiene al menos un componente

Definición de operaciones
Abstract Vector<Object>recorrer (Lista lis)
Postcondition
recorrer := todos los componentes de lis en un
vector de objetos

Definición de operaciones
Abstract <algúnTipo>verCabeza (Lista lis)
Precondition
lis no es vacı́a
Postcondition
verCabeza := componente de la cabeza de lis

Definición de operaciones
Abstract <algúnTipo>verFinal (Lista lis)
Precondition
lis no es vacı́a
Postcondition
verFinal := componente del nodo final de lis

ITPuebla 35
2.1. LISTAS

Definición de operaciones
Abstract <algúnTipo>verPosi (Lista lis, int posi)
Precondition
lis no es vacı́a
lis tiene al menos el nodo posi
Postcondition
verPosi := componente del nodo de la posición
posi de lis

En el TDA se especifica que una lista es una colección de cierto número


(int) de componentes (nodos), cada uno de cierto tipo de dato. Las ope-
raciones son las tı́picas de cualquier estructura de datos, en este caso, se
definen:

Tres operaciones de inserción de componente: inserción a la cabeza


(inicio), al final y en la posicón posi de la lista ligada, considerando
que el nodo cabeza está en la posición 0.

Tres operaciones de eliminación de componente: eliminación del nodo


de la cabeza (inicio), del nodo final y del nodo de la posición posi.

Una operación de recorrido, en la que se accede a cada componente


desde la cabeza hasta el final y ser van almacenando en un vector.

También se puede ver (consultar) el componente de la cabeza, del nodo


final y de un nodo que esté en la posición posi de la lista.

La operación que verifica si la lista está vacı́a o no.

En la Tabla 2.1 se presenta cómo se comporta una lista ligada al aplicarle


las operaciones definidas en el TDA. En los pasos 2 y 3 se insertan dos
elementos en la cabeza de la lista ligada, con lo que 7 queda como primer
nodo y 3 como segundo. En el paso 4, se inserta 5 al final de la lista, quedando
como nodo final. En el paso 5 se inserta 9 en la posición 1, es decir, queda
como segundo nodo en la lista.

En el paso 9 se recorre la lista, esta operación retorna todos los elementos


de la lista en un vector. Al eliminar el nodo de la cabeza en el paso 10 de la
Tabla 2.1, éste se retorna (7) y la lista queda con un nodo menos. En el paso
11 se elimina el nodo de la posición 2 (el tercer nodo), retornando el valor

ITPuebla 36
2.1. LISTAS

5. En el paso 14 se intenta eliminar un nodo de una posición inexistente, y


el retorno de la operación es null.

Tabla 2.1: Ejemplos de operaciones básicas con una lista ligada simple
No. de Operación(Entrada) Salida Lista
paso
1 ninguna ninguna [null] (lista vacı́a)
2 insertarCabeza(3) 3 [3→null]
3 insertarCabeza(7) 7 [7→3→null]
4 insertarFinal(5) 5 [7→3→5→null]
5 insertarPosi(9,1) 9 [7→9→3→5→null]
6 verFinal() 5 [7→9→3→5→null]
7 verPosi(3) 5 [7→9→3→5→null]
8 esVacia() false [7→9→3→5→null]
9 recorrer() [7,9,3,5] [7→9→3→5→null]
10 eliminarCabeza() 7 [9→3→5→null]
11 eliminarPosi(2) 5 [9→3→null]
12 insertarPosi(8,5) null [9→3→null]
13 eliminarFinal() 3 [9→null]
14 verPosi(2) null [9→null]
15 eliminarFinal() 9 [null]
16 esVacia() true [null]

El TDA lista se puede implementar mediante la interfaz de la Figura 2.4:

La operación esVacia verifica si la lista está vacı́a (no tienen nodos)


o no.

Las operaciones de inserción reciben como parámetro el dato a insertar,


y la posición donde se insertará el dato para el método insertaPosi,
retornando el dato insertado.

Los métodos de eliminación retornan el dato del nodo correspondiente


de la lista (el de la cabeza, el último o el de la posición posi).

El método de recorrido retorna un vector con todos los componentes


de la lista, desde la cabeza hasta el último.

ITPuebla 37
2.1. LISTAS

Figura 2.4: Interfaz Lista

Con esta interfaz se puede implementar una lista ligada simple, como
en el diagrama de clases de la Figura 2.5. La ListaSimple implementa la
interfaz Lista, y está compuesta por un conjunto de objetos de la clase
NodoSimple.

Un NodoSimple tiene dos atributos privados: el dato a almacenar (un


objeto, es decir, una referencia o apuntador) y una referencia (apuntador o
liga) al siguiente nodo; y define las operaciones de acceso a sus atributos.

La clase ListaSimple tiene un sólo atributo privado: la referencia a la


cabeza de la lista (al nodo inicial), y sus métodos son los de la interfaz, cuyos
algoritmos pueden verse del 8 al 20.

ITPuebla 38
2.1. LISTAS

Figura 2.5: Diagrama de clases de lista simple

Algoritmo 8 ListaSimple()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */
1. cabeza=null

ITPuebla 39
2.1. LISTAS

Algoritmo 9 ListaSimple(cab:NodoSimple)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase con valores de argumentos **
** Entradas: **
** cab: NodoSimple, referencia a un nodo simple **
** Salidas: **
** ninguna */
1. cabeza=cab

Algoritmo 10 esVacia():booleano
/* Verifica si la lista está vacı́a **
** Regresa true si la lista está vacı́a **
** Regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un booleano */
1. Si cabeza=null
2. Return true
3. Sino
4. Return false

Algoritmo 11 insertarCabeza(dato:Object):Object
/* Inserta un dato (nodo) en la cabeza de la lista **
** Retorna el dato **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado **
** Variables/Objetos locales: **
** nuevoNodo:NodoSimple */
1. nuevoNodo=new NodoSimple(dato,null)
2. Si esVacia()
3. cabeza=nuevoNodo
4. Sino
5. nuevoNodo.setSig(cabeza)
6. cabeza=nuevoNodo
7. Return dato

ITPuebla 40
2.1. LISTAS

Algoritmo 12 insertarFinal(dato:Object):Object
/* Inserta un dato como nodo final de la lista. **
** Retorna el dato **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado **
** Variables/Objetos locales: **
** nuevoNodo:NodoSimple **
** aux:NodoSimple */
1. nuevoNodo=new NodoSimple(dato, null)
2. Si esVacia()
3. cabeza=nuevoNodo
4. Sino
5. aux=cabeza
6. Mientras aux.getSig()≠null
7. aux=aux.getSig()
8. aux.setSig(nuevoNodo)
9. Return dato

El algoritmo insertarCabeza (algoritmo 11) funciona como en la Figura


2.6 para los paos 5 y 6. Una vez creado el nuevoNodo hay que mover en
el orden indicado las dos ligas punteadas, de manera que no se pierda la
referencia al primer nodo de la lista.

En la Figura 2.7 se muestra cómo funcionan los pasos 5 a 8 del algoritmo


insertarFinal (algoritmo 12). En primer lugar, se requiere un apuntador
aux que apunte al último nodo para poder modificar su campo sig que
apunta a null, y que ahora debe apuntar al nuevoNodo. El ciclo mientras
del algoritmo permite colocar a aux en el nodo final.

ITPuebla 41
2.1. LISTAS

Figura 2.6: Inserción de un nodo a la cabeza de una lista simple

Figura 2.7: Inserción de un nodo al final de una lista simple

ITPuebla 42
2.1. LISTAS

Algoritmo 13 insertarPosi(dato:Object, posi:int):Object


/* Inserta un dato en la posición posi de la lista **
** Retorna el dato si posi es alcanzable **
** Retorna null en otro caso **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado **
** Variables/Objetos locales: **
** nuevoNodo:NodoSimple **
** aux:NodoSimple */
1. nuevoNodo=new NodoSimple(dato, null)
2. Si posi=0
3. Si esVacia()
4. cabeza=nuevoNodo
5. Sino
6. nuevoNodo.setSig(cabeza)
7. cabeza=nuevoNodo
8. Return dato
9. Sino
10. Si esVacia()
11. Return null
12. Sino
13. aux=cabeza
14. Mientras (aux.getSig()≠null)&&(posi>1)
15. aux=aux.getSig()
16. posi--
17. Si posi=1
18. nuevoNodo.setSig(aux.getSig())
19. aux.setSig(nuevoNodo)
20. Return dato
21. Sino
22. Return null

En la Figura 2.8 se muestra cómo funcionan los pasos 13 a 20 del algorit-


mo insertarPosi (algoritmo 13) cuando se requiere insertar un nuevoNodo
en posi=2 (insertar como tercer nodo). En primer lugar, se requiere un apun-
tador aux que apunte al nodo anterior a posi (en este ejemplo, al segundo
nodo). Luego se mueven las ligas de lı́neas punteadas.

ITPuebla 43
2.1. LISTAS

Figura 2.8: Inserción de un nodo en una posición de una lista simple

Algoritmo 14 eliminarCabeza():Object
/* Elimina el dato de la cabeza de la lista. Lo retorna **
** Entradas:
** ninguna **
** Salidas: **
** dato:Object, dato eliminado **
** Variables/Objetos locales: **
** dato:Object */

1. Si esVacia()
2. Return null
3. Sino
4. dato=cabeza.getDato()
5. cabeza=cabeza.getSig()
6. Return dato

ITPuebla 44
2.1. LISTAS

Algoritmo 15 eliminarFinal():Object
/* Elimina el dato del nodo final de la lista **
** y lo retorna. **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado **
** Variables/Objetos locales: **
** dato:Object **
** ultimo:NodoSimple **
** penultimo:NodoSimple */
1. Si esVacia()
2. Return null
3. Sino
4. ultimo=cabeza
5. penultimo=null
6. Mientras ultimo.getSig()≠null
7. penultimo=ultimo
8. ultimo=ultimo.getSig()
9. dato=ultimo.getDato()
10. Si penultimo=null
11. cabeza=null
12. Sino
13. penultimo.setSig(null)
14. Return dato

ITPuebla 45
2.1. LISTAS

Algoritmo 16 eliminarPosi(posi:int):Object
/* Elimina el dato del nodo de la posición posi de la **
** lista y lo retorna **
** Entradas: **
** posi:int **
** Salidas: **
** dato:Object, dato eliminado **
** Variables/Objetos locales: **
** dato:Object **
** ultimo:NodoSimple **
** penultimo:NodoSimple */
1. Si esVacia()
2. Return null
3. Sino
4. Si posi=0
5. dato=cabeza.getDato()
6. cabeza=cabeza.getSig()
7. Return dato
8. Sino
9. ultimo=cabeza
10. penultimo=null
11. Mientras(posi>0)&&(ultimo≠null)
12. penultimo=ultimo
13. ultimo=ultimo.getSig()
14. posi--
15. Si ultimo=null
16. Return null
17. Sino
18. dato=ultimo.getDato()
19. penultimo.setSig(ultimo.setSig())
20. Return dato

ITPuebla 46
2.1. LISTAS

En la Figura 2.9 se muestra cómo se eliminan el primero, el último y el


nodo de una posición dada, de acuerdo a como lo indican los algoritmos 14,
15 y 16, respectivamente.

Figura 2.9: Eliminación de nodos en una lista simple

ITPuebla 47
2.1. LISTAS

Algoritmo 17 recorrer():VectorObject
/* Recorre los elementos de la lista desde el primero **
** hasta el último, asignándolos a un vector y **
** lo retorna **
** Entradas: **
** ninguna **
** Salidas: **
** vec:Vector<Object> **
** Variables/Objetos locales: **
** aux:NodoSimple */
1. Si esVacia()
2. Return null
3. Sino
4. aux=cabeza
5. Mientras(aux≠null)
6. vec=vec.add(aux.getDato())
7. aux=aux.getSig()
8. Return vec

Algoritmo 18 verFinal():Object
/* Consulta y regresa el dato del nodo final **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato consultado **
** Variables/Objetos locales: **
** ultimo:NodoSimple **
1. Si esVacia()
2. Return null
3. Sino
4. ultimo=cabeza
5. Mientras ultimo.getSig()≠null
6. ultimo=ultimo.getSig()
5. Return ultimo.getDato()

ITPuebla 48
2.1. LISTAS

En la Figura 2.10 se presenta el funcionamiento del algoritmo 17 que


recorre los nodos de una lista simple, utilizando un apuntador aux que co-
mienza apuntando a la cabeza de la lista y se va moviendo al siguiente nodo
dentro de un ciclo (pasos 5,6 y 7 del algoritmo) hasta llegar a nulo. Duran-
te el recorrido, los datos almacenados en cada nodo se van asignando a un
vector, que al final se retorna.

Figura 2.10: Recorrido de nodos en una lista simple

ITPuebla 49
2.1. LISTAS

Algoritmo 19 verCabeza():Object
/* Consulta y regresa el dato del nodo cabeza (primer **
** nodo) **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato consultado **
1. Si esVacia()
2. Return null
3. Sino
4. Return cabeza.getDato()

Algoritmo 20 verPosi(posi:int):Object
/* Consulta y retorna el dato del nodo de la posición **
** posi de la lista y lo retorna **
** Entradas: **
** posi:int **
** Salidas: **
** dato:Object, dato eliminado **
** Variables/Objetos locales: **
** dato:Object **
** aux:NodoSimple */
1. Si esVacia()
2. Return null
3. Sino
4. Si posi=0
5. Return cabeza.getDato()
6. Sino
7. aux=cabeza
8. Mientras (posi>0)&&(aux≠null)
9. aux=aux.getSig()
10. posi--
11. Si aux=null
12. Return null
13. Sino
14. Return aux.getDato()

ITPuebla 50
2.1. LISTAS

2.1.3. Tipos de listas: circulares y dobles

Además de las listas ligadas simples, se pueden manejar las listas ligadas
dobles y las listas ligadas circulares.

Una lista ligada doble (lista doblemente enlazada) es una lista donde
cada nodo (o nodo doble) es un objeto que almacena una referencia a un
elemento, una referencia al nodo siguiente y una referencia al nodo anterior
en el ordenamiento linea [5][9][14][6].

El acceso a los nodos de una lista ligada doble se realiza mediante una
referencia al primer nodo llamado cabeza, el que apunta a null en su ferencia
al nodo anterior (ya que éste no existe en la lista). El último nodo de la lista,
al no haber siguiente nodo, apunta a null en su referencia al siguiente nodo.

En la Figura 2.11 se muestra una lista doble y en la Figura 2.12 la


estructura de uno de sus nodos. Un ejemplo de funcionamiento de esta lista
se muestra en la Tabla 2.2.

Figura 2.11: Representación en memoria de una lista ligada doble

Figura 2.12: Representación en memoria de un nodo doble

ITPuebla 51
2.1. LISTAS

Tabla 2.2: Ejemplos de operaciones básicas con una lista ligada doble
No. de Operación Salida Lista
paso (Entrada)
1 ninguna ninguna [null] (lista vacı́a)
2 insertarCabeza(3) 3 [3↔null]
3 insertarCabeza(7) 7 [7↔3↔null]
4 insertarFinal(5) 5 [7↔3↔5↔null]
5 insertarPosi(9,1) 9 [7↔9↔3↔5↔null]
6 verFinal() 5 [7↔9↔3↔5↔null]
7 verPosi(3) 5 [7↔9↔3↔5↔null]
8 esVacia() false [7↔9↔3↔5↔null]
9 eliminarCabeza() 7 [9↔3↔5↔null]
10 eliminarPosi(2) 5 [9↔3↔null]
11 insertarPosi(8,5) null [9↔3↔null]
12 eliminarFinal() 3 [9↔null]
13 verPosi(2) null [9↔null]
14 eliminarFinal() 9 [null]
15 esVacia() true [null]

Una lista ligada circular (lista enlazada circular) es una lista simple
donde el último nodo hace referencia al primer nodo de la lista (cabeza). A
diferencia de las otras listas, en una lista circular ningún nodo hace referencia
a null (ver Figura 2.13) [20][5][9][14][6].

El acceso a los nodos de una lista ligada circular se realiza mediante una
referencia al nodo cabeza. Cada nodo de esta lista tiene la misma estructura
que los de una lista simple (nodo simple).

Figura 2.13: Representación en memoria de una lista ligada circular

ITPuebla 52
2.1. LISTAS

Algoritmo 21 insertarCabeza(dato:Object):Object
/* Inserta un dato (nodo) en la cabeza de una lista **
** doble. Retorna el dato **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado **
** Variables/Objetos locales: **
** nuevoNodo:NodoDoble */
1. nuevoNodo=new NodoDoble(dato,null,null)
2. Si esVacia()
3. cabeza=nuevoNodo
4. Sino
5. nuevoNodo.setSig(cabeza)
6. cabeza.setAnt(nuevoNodo)
7. cabeza=nuevoNodo
8. Return dato

Algoritmo 22 insertarCabeza(dato:Object):Object
/* Inserta un dato (nodo) en la cabeza de una lista **
** circular. Retorna el dato **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado **
** Variables/Objetos locales: **
** nuevoNodo,ultimo:NodoSimple */
1. nuevoNodo=new NodoSimple(dato, null)
2. Si esVacia()
3. cabeza=nuevoNodo
4. nuevoNodo.segSig(cabeza)
5. Sino
6. nuevoNodo.setSig(cabeza)
7. ultimo=cabeza
8. Mientras ultimo.getSig()≠cabeza
9. ultimo=ultimo.getSig()
10. ultimo.setSig(nuevoNodo)
11. cabeza=nuevoNodo
12. Return dato

ITPuebla 53
2.1. LISTAS

El TDA especificado para una lista simple es aplicable a una lista doble
y a una lista circular, ya que el TDA sólamente define qué operaciones se
pueden realizar con una lista, mientras que la implementación de ésta varı́a
de acuerdo al tipo de lista, como pueden verse los tres algoritmos diferentes
para implementar insertarCabeza en una lista simple (Algoritmo 11), en
una doble (Algoritmo 21) y en una circular (Algoritmo 22).

Con respecto a los recorridos, para una lista ligada doble, el recorrido
convencional, desde el primero hasta el último nodo, tiene un algoritmo
idéntico al de una lista simple. En una lista doble, también se pueden recorrer
los nodos desde el último hasta el primero, como en el algoritmo 23, donde
el primer ciclo (pasos 5 y 6) coloca aux en el último nodo, y el segundo ciclo
(pasos 7 a 9) aux va apuntando a los nodos desde el último hasta la cabeza,
haciendo referencia a la liga que apunta al nodo anterior, y en cada iteración
se van recolectando los datos en un vector. Los pasos 7 a 9 se ilustran en la
Figura 2.14

Algoritmo 23 recorrerUltimoPrimero():VectorObject
/* Recorre los elementos de la lista doble desde **
** el último hasta el primero, asignándolos a **
** un vector y lo retorna **
** Entradas: **
** ninguna **
** Salidas: **
** vec:Vector<Object> **
** Variables/Objetos locales: **
** aux:NodoSimple */
1. Si esVacia()
2. Return null
3. Sino
4. aux=cabeza
5. Mientras aux.getSig()≠null
6. aux=aux.getSig()
7. Mientras aux≠null
8. vec=vec.add(aux.getDato())
9. aux=aux.getAnt()
10. Return vec

ITPuebla 54
2.1. LISTAS

Figura 2.14: Recorrido de nodos en una lista doble, del útlimo al primero

En la Figura 2.15 se muestra un diagrama de clases de todos los tipos


de listas vistos aquı́, simple, doble y circular, que implementan la interfaz
Lista. Una lista doble utiliza la clase NodoDoble (nodo con dos referencias,
una al nodo siguiente y otra al nodo anterior de la lista), una lista simple y
una lista circular utilizan la clase NodoSimple (nodo con una sóla referencia
al nodo siguiente de la lista). Todas las listas implementan los métodos
especificados en la interfaz.

ITPuebla 55
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Figura 2.15: Diagrama de clases de lista simple, doble y circular

2.2. Pilas estáticas y dinámicas

Una pila es una estructura de datos lineal (a cada componente le sucede


y precede a lo más un componente), homogénea (cada componente es del
mismo tipo), y puede ser dinámica o estática, dependiendo de su implemen-
tación (por ejemplo, si se implementa con un vector, es estática).

En una pila el conjunto de componentes homogéneos que la forman, se


insertan y se eliminan de acuerdo con el principio LIFO (Last In, First Out):

ITPuebla 56
2.2. PILAS ESTÁTICAS Y DINÁMICAS

el último componente en insertarse es el primero en eliminarse, es decir, una


pila es una colección de datos que sólo puede accederse por un extremo, el
cual se conoce normalmente como tope [20][5][9][7][12].

Las pilas se utilizan en diversas aplicaciones, por ejemplo: los navega-


dores de Internet guardan en una pila las direcciones de los sitios recién
visitados, ası́ es como regresamos a la última página visitada al dar la or-
den de volver a la página anterior; los editores de texto suelen tener una
función de deshacer que cancela las operaciones más recientes de edición,
cancelando en primer lugar la última operación realizada, haciendo regresar
al documento a sus estados anteriores.

2.2.1. Representación en memoria

Una representación gráfica de una pila está en la Figura 2.16, donde,


antes del primer uso de la pila en un programa, ésta está vacı́a (paso 1 de
la Figura).

La inserción de componentes se realiza ”uno encima de otro”, es decir,


se van apilando o empilando, y el elemento que está en la cima de la pila
está referenciado por un apuntador (una dirección de memoria) llamado
tope; en la Figura 2.16, el Compo 1 se inserta en primer lugar, sucesivamentes
se insertar Compo 2, Compo 3 y Compo 4 en el tope. Dada una colección de
componentes almacenados en una pila, el único elemento accesible es el del
tope.

Para eliminar elementos, se elimina primero el del tope. A partir del paso
6 de la Figura 2.16, se eliman componentes, comenzando con el último que
se introdujo, Compo 4.

ITPuebla 57
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Figura 2.16: Representación en memoria de una pila

2.2.2. Operaciones básicas

Los valores que puede tomar una pila y las operaciones que se pueden
realizar con ella se introducen formalmente mediante la definición de su
correspondiente TDA, como se ve enseguida [20]:

Definición de valores
int
Abstract typedef <algúnTipo> Pila

Definición de operaciones
Abstract <algúnTipo> push(Pila pil,<algúnTipo> componente)
Postcondition
en el tope de pil := componente
pil tiene un componente más
push := componente

Definición de operaciones
Abstract <algúnTipo> pop (Pila pil)
Precondition
pil no está vacı́a
Postcondition
pop := componente del tope de pil
pil tiene un componente menos

ITPuebla 58
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Definición de operaciones
Abstract boolean esVacı́a (Pila pil)
Postcondition
esVacı́a := true si pil no tiene componentes
esVacı́a := false si pil tiene al menos un componente

Definición de operaciones
Abstract <algúnTipo>verTope (Pila pil)
Precontidion pil no es vacı́a
Postcondition
verTope := componente del tope de pil

En el TDA anterior, se especifica que la pila es una colección de cierto


número (int) de componentes de algún tipo, y que las operaciones son:

Insertar un elemento a la pila. Push o Empilar. El elemento se inserta


en el tope de la pila.

Eliminar un elemento de la pila. Pop o Desempilar. El elemento que


se elimina es el del tope de la pila.

Acceder a un elemento de la pila. Peek o verTope. El elemento que se


accede sin eliminarlo es el del tope de la pila.

Verificar si la pila está vacı́a.

En la Tabla 2.3 se ilustran las operaciones que hacen funcionar una pila.
En el paso 1, la pila está vacı́a, representada con corchetes vacı́os. En los
pasos 2 y 3, se insertan 2 elementos en la pila, los que se añaden en el tope,
que es el extremo derecho de los corchetes. En el paso 4, la operación pop
regresa el elemento del tope de la pila, es decir el del extremo derecho del
corchete, que es el valor 3, y lo elimina, quedando el elemento 5. En el paso
7, a diferencia de pop, la operación verTope regresa el elemento del tope
pero sin eliminarlo. En el paso 10 se verifica si la pila está vacı́a, dando
como resultado true, ya que es cierto que no tiene elementos.

ITPuebla 59
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Tabla 2.3: Ejemplos de operaciones básicas con una pila


No. de paso Operación(Entrada) Salida Pila
1 ninguna ninguna [ ] (pila vacı́a)
2 push(5) 5 [5]
3 push(3) 3 [5,3]
4 pop() 3 [5]
5 push(7) 7 [5,7]
6 pop() 7 [5]
7 verTope() 5 [5]
8 pop() 5 []
9 pop() null []
10 esVacia() true []
11 verTope() null []
12 push(9) 9 [9]
13 push(7) 7 [9,7]
14 push(3) 3 [9,7,3]
15 push(5) 5 [9,7,3,5]
16 pop() 5 [9,7,3]
17 esVacia() false [9,7,3]

Ahora, una pila como TDA puede implementarse mediante una interfaz,
como se ve en el diagrama de clases de la Figura 2.17:

El método push recibe y retorna el dato a insertar en el tope de la


pila, y la pila incrementa en uno su número de componentes.

pop elimina el elemento del tope de la pila y regresa el elemento, de-


crementando el número de componentes; si la pila está vacı́a, regresa
null.

El método esVacia verifica si la pila está vacı́a, en cuyo caso regresa


true, y false en caso contrario.

verTope es un método que regresa el elemento almacenado en el tope


de la pila, o null en caso contrario, dejando sin cambios el número de
componentes de la pila.

ITPuebla 60
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Figura 2.17: Interfaz Pila

2.2.3. Pila estática

Una pila puede implementarse con un vector, con lo que la pila es una
pila estática [3][18]. El diagrama de clases correspondiente a esta imple-
mentación se ve en la Figura 2.18.

En la clase PilaVector se definen dos atributos privados: un vector de


objetos que representa la pila, y un entero que representa el tope de la pila
y que es un ı́ndice al vector.

Además de los métodos que debe implementar la clase PilaVector, como


lo indica la interfaz, hay un método adicional que se requiere para verificar
si la pila está llena, método necesario dado que un vector, formalmente, es
una estructura de datos estática (su número de componentes se define antes
del tiempo de ejecución y permanece sin cambios durante la ejecución).

En los algoritmos 24 al 30, se muestran los correspondientes a los métodos


de esta clase.

ITPuebla 61
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Figura 2.18: Interfaz Pila implementada con un vector

Algoritmo 24 PilaVector()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */
1. tope=-1
2. vec=new Object[5]

ITPuebla 62
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Algoritmo 25 PilaVector(longitud:int)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** longitud: int, tama~
no del vector **
** Salidas: **
** ninguna */
1. tope=-1
2. vec=new Object[longitud]

Algoritmo 26 esVacia(): booleano


/* Verifica si la pila está vacı́a. Regresa true si la **
** pila está vacı́a, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** bandera:boolean */
1. Si tope=-1
2. Return true
3. Sino
4. Return false

Algoritmo 27 esLlena(): booleano


/* Verifica si la pila está llena. Regresa true si la **
** pila está llena, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** bandera:boolean */
1. Si tope=vec.length-1
2. Return true
3. Sino
4. Return false

ITPuebla 63
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Algoritmo 28 push(dato: Objeto): Objeto


/* Inserta un dato en el tope de la pila. Retorna el dato **
** si inserción exitosa, retorna null en caso contrario **
** Entradas: **
** dato: Object, dato a insertar **
** Salidas: **
** dato: Object, dato insertado */

1. Si esLlena()
2. Return null
3. Sino
4. tope++
5. vec[tope]=dato
6. Return dato

Algoritmo 29 pop(): Objeto


/* Elimina un dato del tope de la pila. Retorna el dato **
** si la pila no es vacı́a, retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato: Object, dato eliminado del tope */

1. Si esVacia()
2. Return null
3. Sino
4. tope--
5. Return vec[tope+1]

ITPuebla 64
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Algoritmo 30 verTope(): Objeto


/* Regresa el dato del tope si pila no vacı́a, **
** regresa null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato: Object, dato del tope */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[tope]

En la Tabla 2.4 se da un ejemplo del funcionamiento de una pila im-


plementada con un vector de 4 componentes. Al inicio, en el paso 1, las 4
casillas de la pila están vacı́as y el ı́ndice tope tiene el valor -1, indicando
que no está apuntando a ninguna casilla válida del vector. En el paso 2, la
verificación de si la pila está vacı́a arroja como resultado un true.

Tabla 2.4: Ejemplos de operaciones básicas con una pila como un vector
No. de Operación Salida Pila tope
paso (Entrada)
1 ninguna ninguna [ ][ ][ ][ ] (pila vacı́a) -1
2 esVacia() true [ ][ ][ ][ ] -1
3 pop() null [ ][ ][ ][ ] -1
4 push(3) 3 [3][ ][ ][ ] 0
5 push(5) 5 [3][5][ ][ ] 1
6 esLlena() false [3][5][ ][ ] 1
7 push(7) 7 [3][5][7][ ] 2
8 push(9) 9 [3][5][7][9] 3
9 push(11) null [3][5][7][9] 3
10 verTope() 9 [3][5][7][9] 3
11 esLlena() true [3][5][7][9] 3
12 pop() 9 [3][5][7][ ] 2

En la misma Tabla 2.4, cada vez que se introduce un elemento en la pila


(push), tope queda incrementado en 1, y por cada eliminación (pop) queda

ITPuebla 65
2.2. PILAS ESTÁTICAS Y DINÁMICAS

decrementado en 1. Como hay un número limitado de casillas, la inserción


de un quinto dato no es posible, como en el paso 9, y la verifiación de si
la pila está llena en el paso 11 da como resultado true, porque todas las
casillas del vector están llenas.

2.2.4. Pila dinámica

Una pila implementada con una lista simple, como la de la Figura 2.19,
es una pila dinámica [3][18]. Del conjunto de nodos de la lista, se puede
tomar como tope de la pila a algún nodo de los extremos (al nodo cabeza o
al último nodo). Sin embargo, lo más eficiente es que el nodo cabeza sea el
tope de la pila; ası́, el método push de la pila se implementa insertando un
nuevo nodo en la cabeza de la lista, es decir con el método insertarCabeza
de la lista; pop de la pila se implementa eliminado el nodo cabeza con el
método eliminarCabeza de la lista; y verTope de la pila se implementa
accediendo al contenido del nodo cabeza con el método verCabeza de la
lista.

Figura 2.19: Pila implementada con lista simple:push,pop

ITPuebla 66
2.2. PILAS ESTÁTICAS Y DINÁMICAS

En la Figura 2.20 se tiene un diagrama de clases de una pila implemen-


tada con una lista simple (también puede implementarse con una lista doble
o circular). La clase PilaListaSimple debe implementar el comportamien-
to de la interfaz Pila haciendo uso de una lista simple como único atributo
privado (atributo pila); a su vez, la clase ListaSimple debe implementar el
comportamiendo de la interfaz Lista haciendo uso de un conjunto de nodos
simples.

Como el nodo cabeza es el tope, realmente no hay que implementar


nada en PilaListaSimple, ya que los métodos de la pila se implementan
invocando a los correspondientes métodos de la lista (que ya están imple-
mentados), como se ve en los algoritmos del 31 al 36. Nótese que el método
esLlena, necesario en la implementación de una pila con un vector, ya no
tiene cabida aquı́, dado que una lista ligada es dinámica.

Figura 2.20: Diagrama de clases de pila implementada con lista simple

Algoritmo 31 PilaListaSimple()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */
1. pila=new ListaSimple()

ITPuebla 67
2.2. PILAS ESTÁTICAS Y DINÁMICAS

Algoritmo 32 PilaListaSimple(unaPila:ListaSimple)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** unaPila: ListaSimple **
** Salidas: **
** ninguna */
1. pila=unaPila

Algoritmo 33 esVacia():booleano
/* Verifica si la pila está vacı́a. Regresa true si **
** la pila está vacı́a, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** bandera: boolean */
1. Return pila.esVacia()

Algoritmo 34 push(dato:Object):Object
/* Inserta un dato en el tope de la pila. Retorna el **
** dato si inserción exitosa, retorna null en otro caso **
** Entradas: **
** dato: Object, dato a insertar **
** Salidas: **
** dato: Object, dato insertado */

1. Return pila.insertarCabeza(dato)

Algoritmo 35 pop():Object
/* Elimina un dato del tope de la pila. Retorna el **
** dato si la pila no es vacı́a, retorna null en caso **
** contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato: Object, dato eliminado del tope */
1. Return pila.eliminarCabeza()

ITPuebla 68
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 36 verTope():Object
/* Regresa el dato del tope si pila no vacı́a, regresa **
** null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object */
1. Return pila.verCabeza()

2.3. Colas estáticas y dinámicas

Una cola o cola simple es una estructura de datos lineal (a cada com-
ponente le sucede y precede a lo más un componente), homogénea (cada
componente es del mismo tipo), y puede ser dinámica o estática, dependien-
do de su implementación (por ejemplo, si se implementa con un vector, es
estática).

En una cola el conjunto de componentes homogéneos que la forman, se


insertan y se eliminan de acuerdo con el principio FIFO (First In, First Out):
el primer componente en insertarse es el primero en eliminarse, es decir, una
cola es una colección de datos que puede accederse por dos extremos, uno
llamado frente y el otro llamado final [20][5][9][7][12].

Las colas se utilizan en diversas aplicaciones, por ejemplo: para acceder a


un cajero, los clientes forman una cola; en las consultas de la seguridad social,
los pacientes se atienden de acuerdo a una cola; una impresora compartida
por varias computadoras, despacha las tareas de impresión conforme éstas
se forman en una cola.

2.3.1. Representación en memoria

En la Figura 2.21 se muestra una representación gráfica de una cola.


En el paso 1, antes de insertar cualquier elemento, la cola está vacı́a. Cada
nuevo componente se inserta al final de la cola, como en los pasos 2 a 5 de
la Figura. Los únicos elementos que se pueden acceder en una cola son los
del frente y los del final. El elemento que se elimina siempre es el del frente,

ITPuebla 69
2.3. COLAS ESTÁTICAS Y DINÁMICAS

como en los pasos 6 a 9 de la Figura. En una cola siempre se mantienen dos


referencias: una para el frente y otra para el final de la cola, las que se van
moviendo conforme se insertan y eliminan los componentes.

Figura 2.21: Representación en memoria de una cola

2.3.2. Operaciones básicas

Mediante el siguiente TDA se especifican formalmente los valores que


puede tomar y las operaciones que se pueden realizar con una cola [20]:

Definición de valores
int
Abstract typedef <algúnTipo> Cola

ITPuebla 70
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Definición de operaciones
Abstract <algúnTipo> encolar(Cola col,
<algúnTipo> componente)
Postcondition
en el final de col := componente
col tiene un componente más
encolar := componente

Definición de operaciones
Abstract <algúnTipo> desencolar (Cola col)
Precondition
col no está vacı́a
Postcondition
desencolar := componente del frente de col
col tiene un componente menos

Definición de operaciones
Abstract boolean esVacı́a (Cola col)
Postcondition
esVacı́a := true si col no tiene componentes
esVacı́a := false si col tiene al menos un componente

Definición de operaciones
Abstract <algúnTipo>verFrente (Cola col)
Precondition
col no es vacı́a
Postcondition
verFrente := componente del frente de col

Definición de operaciones
Abstract <algúnTipo>verFinal (Cola col)
Precontidion col no es vacı́a
Postcondition
verFinal := componente del final de col

En este TDA se especifica que los valores que puede tomar una cola es el
de una colección de cierto número (int) de componentes de algún tipo. Las
operaciones del TDA se describen enseguida:

ITPuebla 71
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Insertar un elemento en la cola. Encolar. El elemento se inserta en el


final de la cola.
Eliminar un elemento de la cola. Desencolar. El elemento que se elimina
es el del frente de la cola.
Verificar si la cola está vacı́a.
Acceder a un elemento de la cola. Los elementos que se acceden son el
del final, verFinal, o el del frente de la cola, verFrente.

En la Tabla 2.5 se ilustran las operaciones que hacen funcionar una cola.
En el paso 1, la cola está vacı́a, representada con corchetes vacı́os, siendo el
frente el extremo izquierdo de los corchetes, y el final el extremo derecho.
En los pasos 2, 3 y 4, se insertan 3 elementos en la cola, los que se añaden
en el final, que es el extremo derecho de los corchetes. En los pasos 5 y 6
se accede a los valores del frente y del final, respectivamente. El paso 7
elimina el componente del frente al desencolar el valor 5. La prueba de si
la cola es vacı́a en el paso 8, dá como resultado false. Desencolar elementos
de una cola vacı́a dá como resultado null, indicando que no hay elementos
qué desencolar, como en el paso 11; similarmente no pueden obtenerse los
valores del frente y final cuando la cola es vacı́a (pasos 13 y 14).

Tabla 2.5: Ejemplos de operaciones básicas con una cola


No. de paso Operación(Entrada) Salida Cola
1 ninguna ninguna [ ] (cola vacı́a)
2 encolar(5) 5 [5]
3 encolar(3) 3 [5,3]
4 encolar(7) 7 [5,3,7]
5 verFrente() 5 [5,3,7]
6 verFinal() 7 [5,3,7]
7 desencolar() 5 [3,7]
8 esVacia() false [3,7]
9 desencolar() 3 [7]
10 desencolar() 7 []
11 desencolar() null []
12 esVacia() true []
13 verFrente() null []
14 verFinal() null []

ITPuebla 72
2.3. COLAS ESTÁTICAS Y DINÁMICAS

El TDA cola se puede implementar mediante la interfaz de la Figura


2.22. En la interfaz se especifica que la operación encolar recibe un objeto
a encolar, el que se inserta en el final de la cola, siendo este objeto el dato
de regreso del método. desencolar es un método que retorna el elemento del
frente de la cola, eliminándolo. El método esVacia retorna true cuando
la cola está vacı́a, y false en caso contrario. Los métodos verFrente y
verFinal retornan el elemento del frente y del final, respectivamente,
sin eliminarlo de la cola.

Figura 2.22: Interfaz Cola

2.3.3. Cola estática

Al igual que una pila, una cola puede implementarse con un vector,
dando como resultado una cola estática [18][3].

En la Figura 2.23 se puede ver esta implementación, donde se definen


dos atributos privados de la clase ColaVector: el vector que representa la
cola y el ı́ndice del final de la cola, suponiendo que el frente siempre está en
la posición 0 del vector.

Los métodos de la clase ColaVector son los de la interfaz, agregando


el método esLlena para verificar si el vector tiene todos sus componentes
ocupados o no (porque un vector es una estructura de datos estática). Estos
métodos se presentan en los algoritmos del 37 al 44.

ITPuebla 73
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.23: Interfaz Cola implementada con un vector

Algoritmo 37 ColaVector()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */

1. final=-1
2. vec=new Object[5]

ITPuebla 74
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 38 ColaVector(longitud:int)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** longitud:int, tama~
no del vector **
** Salidas: **
** ninguna */
1. final=-1
2. vec=new Object[longitud]

Algoritmo 39 esVacia(): booleano


/* Verifica si la cola está vacı́a. Regresa true si la **
** cola está vacı́a, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final=-1
2. Return true
3. Sino
4. Return false

Algoritmo 40 esLlena():booleano
/* Verifica si la cola está llena. Regresa true si la **
** cola está llena, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final=vec.length-1
2. Return true
3. Sino
4. Return false

ITPuebla 75
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 41 encolar(dato:Object):Object
/* Inserta un dato en el final de la cola. Retorna el **
** dato si inserción exitosa, retorna null en caso **
** contrario **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado */
1. Si esLlena()
2. Return null
3. Sino
4. final++
5. vec[final]=dato
6. Return dato

Algoritmo 42 desencolar():Object
/* Elimina un dato del frente de la cola (posición 0 del **
** vector). Retorna el dato si la cola no es vacı́a, **
** retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado del frente **
** Variables/Objetos locales: **
** aux:Object */

1. Si esVacia()
2. Return null
3. Sino
4. aux = vec[0]
5. Para i=0,1,...,final-1
6. vec[i]=vec[i+1]
7. final--
8. Return aux

ITPuebla 76
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 43 verFrente():Object
/* Regresa el dato del frente (posición 0 del **
** vector) si cola no vacı́a, regresa null en otro caso **
** Entradas: ninguna **
** Salidas: **
** dato: Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[0]

Algoritmo 44 verFinal():Object
/* Regresa el dato del final si cola no vacı́a, **
** regresa null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[final]

2.3.4. Cola dinámica

En la Figura 2.24 se tiene una cola implementada con una lista simple,
y en la Figura 2.25 el diagrama de clases correspondiente (también puede
implementarse con una lista doble o una lista circular). Una cola implemen-
tada con una lista ligada es una cola dinámica [18][3]. En este caso, lo más
natural es que el nodo cabeza sea el frente de la cola, y el último nodo de
la lista sea el final de la cola. En la figura el final está indicado con lı́nea
punteada porque esa referencia no está definida en la clase ListaSimple.

ITPuebla 77
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.24: Cola implementada con lista simple:encolar,desencolar

Figura 2.25: Diagrama de clases de cola implementada con lista simple

La implementación de una cola con una lista simple se reduce, nuevamen-


te, a invocar a los métodos de ListaSimple para que funcionen los métodos
de ColaListaSimple, análogo a como se hizo con una pila, como se ve en
los algoritmos 45 al 51. El método esLlena no se implementa porque la lista
simple es dinámica.

ITPuebla 78
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 45 ColaListaSimple()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */

1. cola=new ListaSimple()

Algoritmo 46 ColaListaSimple(longitud:int)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** unaCola:ListaSimple **
** Salidas: **
** ninguna */
1. cola=unaCola

Algoritmo 47 esVacia(): booleano


/* Verifica si la cola está vacı́a. Regresa true si la **
** cola está vacı́a, regresa false en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Return cola.esVacia()

ITPuebla 79
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 48 encolar(dato:Object):Object
/* Inserta un dato en el final de la cola. **
** Retorna el dato **
** contrario **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado */
1. Return cola.insertarFinal(dato)

Algoritmo 49 desencolar():Object
/* Elimina un dato del frente de la cola. **
** Retorna el dato si la cola no es vacı́a, **
** retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado del frente */
1. Return cola.eliminarCabeza()

Algoritmo 50 verFrente():Object
/* Regresa el dato del frente si cola no vacı́a, **
** regresa null en otro caso **
** Entradas: ninguna **
** Salidas: **
** dato: Object */
1. Return cola.verCabeza()

Algoritmo 51 verFinal():Object
/* Regresa el dato del final si cola no vacı́a, **
** regresa null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object */
1. Return cola.verFinal()

ITPuebla 80
2.3. COLAS ESTÁTICAS Y DINÁMICAS

2.3.5. Tipos de colas: circulares y dobles

El tipo de cola que se ha visto hasta el momento es una cola simple.


También se pueden manejar las colas circulares y las colas dobles (bicolas).

Una cola circular es una cola implementada con un vector, donde los
ı́ndices frente y final siempre se incrementan al insertar o eliminar compo-
netes, a diferencia de una cola simple donde, de acuerdo a la implementación
presentada en la sección anterior, el ı́ndice frente está fijo en la posición
0 del vector y final se incrementa o decrementa, al insertar o eliminar
componetes [7][12][14].

En la Figura 2.26 se muestra una representación gráfica de una cola


circular con 5 componentes, el primero apuntado por frente y el quinto
apuntado por final. Cuando se inserta un nuevo componente (Compo 6)
al final de la cola, final avanza una posición ”hacia adelante”. Cuando
se elimina un componente (Compo 1) del frente de la cola, frente también
avanza una posición ”hacia adelante”.

Con lo visto en el párrafo anterior, se podrı́an tener las configuraciones


de la Figura 2.27, es decir, en el vector, frente no necesariamente indica
una posición menor que la de final: en la configuración de la izquierda:

frente=0 y final=4;

en la configuración de la derecha:

frente=4 y final=0.

En la Figura 2.28 se muestra una cola circular vacı́a y otra llena. Ambas
colas tienen la particularidad de que frente es mayor que final:

frente=4 >final=3.

Entonces, para saber si la cola está vacı́a o llena, no es suficiente con


hacer referencia a los valores de frente y final, hay que saber también si
las casillas del vector están ocupadas o no.

ITPuebla 81
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.26: Representación en memoria de una cola circular

ITPuebla 82
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.27: Configuraciones de una cola circular

Figura 2.28: Cola circular vacı́a y llena

Una cola circular se puede implementar de acuerdo al diagrama de la


Figura 2.29. Como atributos privados se mantienen el frente y el final,
además del vector. Se implementan todos los métodos de la interfaz y el
método esLlena, porque se utiliza un vector que es una estructura de datos
estática, con número predeterminado de componentes.

Los métodos de la clase ColaCircularVector se muestran en los algo-


ritmos 52 al 59.

ITPuebla 83
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.29: Interfaz Cola implementada con una cola circular con vector

Algoritmo 52 ColaCircularVector()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */

1. frente=0
2. final=-1
3. vec=new Object[5]

ITPuebla 84
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 53 ColaCircularVector(longitud:int)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** longitud:int, tama~
no del vector **
** Salidas: **
** ninguna */
1. frente=0
2. final=-1
3. vec=new Object[longitud]

Algoritmo 54 esVacia(): booleano


/* Verifica si la cola circular está vacı́a. Regresa **
** true si la cola está vacı́a, regresa false en caso **
** contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final<frente && vec[frente]=null
2. Return true
3. Sino
4. Return false

Algoritmo 55 esLlena():booleano
/* Verifica si la cola circular está llena. Regresa **
** true si la cola está llena, regresa false en caso **
** contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final<frente && vec[frente]!=null
2. Return true
3. Sino
4. Return false

ITPuebla 85
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Como se ilustró anteriormente, para implementar los métodos que prue-


ban si la cola circular es vacı́a o llena, hay que probar que final<frente
además de probar si el vector en la posición frente almacena null o no
(algoritmos 54 y 55).

Por otra parte, frente se debe incrementar en uno cuando se inserta


un componente, y final se debe imcrementar en uno cuando se elimina un
componente; estos incrementos no son incrementos simples, pues se puede
tener la configuración de la Figura 2.30, donde al eliminar Compo 1 del frente
de la cola circular, frente debe pasar de la posición 7 a la 0 (no a la 8 que
no existe). Lo mismo sucede cuando se inserta Compo 5, final debe pasar
de la posición 7 a la 0.

Para lograr ”incrementar” a la posición correcta, se utiliza la operación


módulo, por ejemplo, si el vector es de longitud 8:

si final=4, entonces final=(final+1) %8=5 %8=5;

si final=7, entonces final=(final+1) %8=8 %8=5;

(ver algoritmos 56 y 57).

Figura 2.30: Cola circular donde se ”incrementa en 1” el valor de final

ITPuebla 86
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 56 encolar(dato:Object):Object
/* Inserta un dato en el final de la cola circular. **
** Retorna el dato si inserción exitosa, **
** retorna null en caso contrario **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado */
1. Si esLlena()
2. Return null
3. Sino
4. final=(final+1) %vec.length()
5. vec[final]=dato
6. Return dato

Algoritmo 57 desencolar():Object
/* Elimina un dato del frente de la cola circular. **
** Retorna el dato si la cola no es vacı́a, **
** retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado del frente **
** Variables/Objetos locales: **
** aux:Object */
1. Si esVacia()
2. Return null
3. Sino
4. aux = vec[frente]
5. vec[frente]=null
6. frente=(frente+1) %vec.length()
7. Return aux

ITPuebla 87
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 58 verFrente():Object
/* Regresa el dato del frente si cola circular no es **
** vacı́a, regresa null en otro caso **
** Entradas: ninguna **
** Salidas: **
** dato: Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[frente]

Algoritmo 59 verFinal():Object
/* Regresa el dato del final si cola circular no es **
** vacı́a, regresa null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[final]

Una cola doble o bicola es una cola donde la inserción y eliminación de


datos puede hacerse por sus dos extremos: el frente y el final, como se ve
en la Figura 2.31 [7][12][14].

En la Figura 2.32 se muestra una cola llena, en cuyo caso, frente=0 y


final=longitud del vector; también se muestra una cola que tiene un
sólo elemento que se puede eliminar por el final, con lo que final se
decrementa en uno, o por el frente, con lo que frente se incrementa en
uno. Ası́, la cola está vacı́a si final<frente.

ITPuebla 88
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.31: Inserción y eliminación en una cola doble

Figura 2.32: Cola doble llena y vacı́a

ITPuebla 89
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Una cola doble también puede implementar la interfaz Cola con un vec-
tor, como se ve en la Figura 2.33. La clase ColaDobleVector mantiene los
mismos métodos de la interfaz, pero agrega comportamientos propios de
una cola doble y no de una cola en general: los métodos encolarFrente
y desencolarFinal. El método encolar corresponde a encolar un dato al
final, y el método desencolar coresponde a desencolar un dato del frente,
siguiendo la lógica de cualquier cola.

Figura 2.33: Interfaz Cola implementada con una cola doble con vector

Los métodos de la clase ColaDobleVector pueden verse en los algoritmo


60 al 69. Para inicializar frente y final, se asigna a frente un valor
mayor que a final, utilizando los valores del ı́ndice de enmedio del vector
(algoritmos 60 y 61), de manera que se puedan insertar elementos por ambos
extremos (algoritmos 64 al 67).

ITPuebla 90
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 60 ColaDobleVector()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */

1. frente=2
2. final=1
3. vec=new Object[5]

Algoritmo 61 ColaCircularVector(longitud:int)
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores de argumentos **
** Entradas: **
** longitud:int, tama~
no del vector **
** Salidas: **
** ninguna */
1. frente=longitud/2
2. final=frente-1
3. vec=new Object[longitud]

Algoritmo 62 esVacia(): booleano


/* Verifica si la cola doble está vacı́a. Regresa **
** true si la cola está vacı́a, regresa false en caso **
** contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final<frente
2. Return true
3. Sino
4. Return false

ITPuebla 91
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 63 esLlena():booleano
/* Verifica si la cola doble está llena. Regresa **
** true si la cola está llena, regresa false en caso **
** contrario **
** Entradas: **
** ninguna **
** Salidas: **
** un boolean */
1. Si final=vec.length-1 && frente=0
2. Return true
3. Sino
4. Return false

En la Figura 2.34 hay una configuración que se puede tener al requerir


encolar por el frente, que es cuando la cola no está llena pero frente apunta
a la primera casilla del vector y no puede decrementarse en 1.

En este caso, se recorren todos los componentes un lugar a la derecha


(pasos 5 y 6 del algoritmo 64) para hacer un espacio en el frente e insertar
el nuevo componente.

Se procede de manera similar cuando se inserta un nuevo componente


al final de la cola y final no puede incrementarse en 1 por apuntar a la
última casilla el vector, entonces se recorren los componentes un lugar a la
izquierda (pasos 5 y 6 del algoritmo 65).

ITPuebla 92
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Figura 2.34: Encolar en el frente en bicola, caso especial

Algoritmo 64 encolarFrente(dato:Object):Object
/* Inserta un dato en el frente de la cola doble. **
** Retorna el dato si inserción exitosa, **
** retorna null en caso contrario **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado */
1. Si esLlena()
2. Return null
3. Sino
4. Si frente=0
5. Para i=final,final-1,...,frente
6. vec[i+1]=vec[i]
7. Sino
8. frente-- 9. vec[frente]=dato
10. Return dato

ITPuebla 93
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 65 encolar(dato:Object):Object
/* Inserta un dato en el final de la cola doble. **
** Retorna el dato si inserción exitosa, **
** retorna null en caso contrario **
** Entradas: **
** dato:Object, dato a insertar **
** Salidas: **
** dato:Object, dato insertado */
1. Si esLlena()
2. Return null
3. Sino
4. Si final=vec.length-1
5. Para i=frente,frente+1,...,final
6. vec[i-1]=vec[i] 7. Sino
8. final++
9. vec[final]=dato
10. Return dato

Algoritmo 66 desencolar():Object
/* Elimina un dato del frente de la cola doble. **
** Retorna el dato si la cola no es vacı́a, **
** retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado del frente **
** Variables/Objetos locales: **
** aux:Object */
1. Si esVacia()
2. Return null
3. Sino
4. aux = vec[frente]
5. frente++
6. Return aux

ITPuebla 94
2.3. COLAS ESTÁTICAS Y DINÁMICAS

Algoritmo 67 desencolarFinal():Object
/* Elimina un dato del final de la cola doble. **
** Retorna el dato si la cola no es vacı́a, **
** retorna null en caso contrario **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object, dato eliminado del frente **
** Variables/Objetos locales: **
** aux:Object */
1. Si esVacia()
2. Return null
3. Sino
4. aux = vec[final]
5. final--
6. Return aux

Algoritmo 68 verFrente():Object
/* Regresa el dato del frente si cola doble no es **
** vacı́a, regresa null en otro caso **
** Entradas: ninguna **
** Salidas: **
** dato: Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[frente]

ITPuebla 95
2.4. APLICACIONES

Algoritmo 69 verFinal():Object
/* Regresa el dato del final si cola doble no es **
** vacı́a, regresa null en otro caso **
** Entradas: **
** ninguna **
** Salidas: **
** dato:Object */
1. Si esVacia()
2. Return null
3. Sino
4. Return vec[final]

2.4. Aplicaciones

Con cualquier tipo de listas ligadas se pueden implementar las pilas y


las colas, y sus respectivas aplicaciones [5][9], algunas de las cuales se verán
enseguida.

2.4.1. Aplicaciones de las pilas

Además de las aplicaciones mencionadas al inicio de esta sección, se


presentan ahora otras más que pueden programarse fácil y rápidamente.
Una de ellas es la inversión de elementos de un vector, que consiste
en tomar como entrada un vector de n elementos y obtener como salida el
mismo vector con los elementos en orden inverso.

Este es un problema que se resuelve tı́picamente como en el algoritmo


70, donde se utilizan ciclos controlados por contador, accediendo al vector
vecaux de la posición final a la inicial, mientras se accede al vector vec de la
posición inicial a la final. Sin embargo, la idea es utilizar una pila para ello,
como se ilustra en la Figura 2.35. En esta Figura se ve que el vector tiene las
cadenas Ana, Beto, Dany, Mary, el vector se recorre de la posición inicial a
la final, insertando cada cadena en la pila, cuyo tope va subiendo conforme se
insertan las cadenas. Luego, se van desempilando las cadenas, moviéndose el
tope hacia abajo, y se van almacenando de vuelta en el vector, que se recorre
nuevamente de su posición inicial a la final. Este proceso puede verse en el

ITPuebla 96
2.4. APLICACIONES

algoritmo 71. Una forma de implementar la solución de este problema desde


el punto de vista de la programación orientada a objetos es siguiendo el
diseño del diagrama de clases de la Figura 2.36.

Algoritmo 70 invierteVector(vec[n]):vec[n]
/* Invierte el contenido de un vector **
** Entradas: **
** vec:Object[n], vector de n elementos **
** Salidas: **
** vec:Object[n], vector de n elementos en **
** orden inverso **
** Variables/Objetos locales:
** vecaux:vector auxiliar de n cadenas */
1. Para i=0,1,...,n-1
2. vecaux[n-1-i]=vec[i]
3. Para i=0,1,...,n-1
4. vec[i]=vecaux[i]
5. Return vec

Figura 2.35: Aplicación de pila: inversión de un vector

ITPuebla 97
2.4. APLICACIONES

Algoritmo 71 invierteVectorConPila(vec[n]):vec[n]
/* Invierte el contenido de un vector utilizando una pila **
** Entradas: **
** vec:Object[n], vector de n elementos **
** Salidas: **
** vec:Object[n], vector de n elementos **
** en orden inverso **
** Variables/Objetos locales: **
** pila:Pila */

1. Para i=0,1,...,n-1
2. pila.push(vec[i])
3. Para i=0,1,...,n-1
4. vec[i]=pila.pop()
5. Return vec

Figura 2.36: Diagrama de clases para invertir un vector utilizando una pila

Las pilas también se utilizan en la verificación de paréntesis, donde


se toma como entrada una expresión (que puede ser aritmética, lógica, re-
lacional, etc.) que contenga paréntesis, y como salida se tiene un valor de
verdad que indica si los paréntesis están o no bien nivelados (cada paréntsis
abierto tiene su correspondiente cerrado).

En la Figura 2.37 se ve un ejemplo del problema de la verificación de


paréntesis, donde se tiene una expresión aritmética con 4 paréntesis nivela-
dos. Al inicio, la pila está vacı́a y se comienza a analizar la expresión, encon-

ITPuebla 98
2.4. APLICACIONES

trando en primer y segundo lugares unos paréntesis abiertos con subı́ndices


1 y 2, los que se insertan en la pila con 2 operaciones push, como se ve
en los pasos 2 y 3. Al continuar analizando la expresión, se encuentra un
paréntesis cerrado, correspondiente al último abierto, es decir, al último in-
sertado en la pila, que es el que se elimina con la operación pop. Ası́, cada
vez que se encuentre un paréntesis abierto, se inserta en la pila, y cada vez
que se encuentre un paréntesis cerrado, se elimina uno abierto de la pila. Al
terminar de analizar la expresión, si la pila queda vacı́a, los paréntesis están
bien nivelados; si la pila no queda vacı́a, los paréntesis no lo están.

En las Figuras 2.38 y 2.39, se tienen 2 ejemplos de paréntesis desnive-


lados. En el primer caso, al terminar de analizar la expresión, la pila no
queda vacı́a, es decir, hay al menos un paréntesis abierto que no tiene su
correspondiente cerrado. En el segundo caso, al analizar el último paréntesis
cerrado con subı́ndice 0, no se puede efectuar una operación pop porque la
pila está vacı́a; es decir, todos los paréntesis abiertos se han cerrado y no
hubo un correspondiente abierto del último paréntesis cerrado.

La verificación de paréntesis puede implementarse siguiendo el diseño de


la Figura 2.40 y el método verificaParen se presenta en el algoritmo 72.

Figura 2.37: Pila para verificar paréntesis nivelados

ITPuebla 99
2.4. APLICACIONES

Figura 2.38: Pila para verificar paréntesis desnivelados (falta cerrado)

Figura 2.39: Pila para verificar paréntesis desnivelados (falta abierto)

ITPuebla 100
2.4. APLICACIONES

Figura 2.40: Diagrama de clases para verificar paréntesis nivelados

Algoritmo 72 verificaParen():boolean
/* Verifica que los paréntesis de una expresión estén **
** nivelados. Regresa true si los paréntesis están **
** nivelados, regresa false en caso contrario **
** Entradas: **
** exp:String, expresión de n sı́mbolos **
** Salidas: **
** bandera:boolean **
** Variables/Objetos locales: **
** pila:Pila */
1. Para i=0,1,...,n-1
2. Si exp[i]=’(’
3. pila.push(vec[i])
4. Sino
5. Si exp[i]=’)’
6. Si pila.pop()=null
7. Return false
8. Si pila.esVacia()
9. Return true
10. Sino
11. Return false

Para convertir una expresión infija en posfija también se utiliza


una pila. Por ejemplo, en la Figura 2.41 la expresión aritmética infija A+B se
convierte en la posfija AB+. Todos los operandos que se van encontrando en

ITPuebla 101
2.4. APLICACIONES

la expresión de entrada se van directo a la salida (como el caso de A y B en


los pasos 1 y 3 de la figura), y de acuerdo a su jerarquı́a los operadores se
insertan en la pila (en este ejemplo el único operador es +, que se inserta en
la pila en el paso 2 de la figura). Al terminar de analizar los sı́mbolos de la
expresión, se eliminan todos los elementos de la pila (todos los operadores)
y se insertan en la salida (paso 4 de la figura).

Ahora, tomando la expresión A+B*C y suponiendo que el operador * tiene


más alta jerarquı́a que +, la expresión posfija correspondiente es ABC*+. En
este caso, cada operador de la expresión infija se inserta en la pila siempre
que en el tope no haya un operador de más alta prioridad; si ası́ no fuera,
antes de insertar en la pila al operador en turno, se elimina el del tope, el
que pasa a la respuesta.

En el ejemplo de la Figura 2.42 el primer operador encontrado en + y el


segundo es * que se inserta en la pila sobre + porque el producto es de más
alta jerarquı́a (paso 4 de la figura); al terminar de analizar los sı́mbolos de la
expresión se vacı́a la pila en la salida (pasos 6 y 7 de la figura). En cambio,
en la Figura 2.43, la expresión A*B+C equivale a AB*C+, y antes de insertar
+ en la pila, primero se elimina * que está en el tope (paso 4 de la figura).

Figura 2.41: Pila para convertir notación infija a posfija con un operador

ITPuebla 102
2.4. APLICACIONES

Figura 2.42: Pila para convertir notación infija a posfija con dos operadores

Figura 2.43: Notación infija a posfija con dos operadores en diferente orden

2.4.2. Aplicaciones de las colas

Una aplicación de las colas simples es el control de una cola de clien-


tes que utilizan un cajero. Los clientes van llegando y se forman al final
de la cola. Cuando el cajero está desocupado, el cliente del frente de la cola
lo utiliza. Suponer que cada cliente requiere realizar n transacciones y que

ITPuebla 103
2.4. APLICACIONES

utiliza el cajero por n unidades de tiempo. Suponer también que hay un


número limitado de clientes que pueden formarse en la cola.

En la Figura 2.44 se tiene un esquema de esta aplicación. En el tiempo


0 el cajero está desocupado y hay 5 clientes formados en la cola colaCaj,
cada cliente identificado con un nombre y con el número de transacciones a
realizar. En los tiempos 1 y 2 se despacha al cliente del frente de la cola, Jos
que requiere realizar 2 transacciones. En los tiempos 3, 4 y 5 se despacha a
Ale con 3 transacciones. Este proceso continúa con todos los clientes hasta
que la cola queda vacı́a.

Para resolver este problema se puede utilizar el diagrama de clases de la


Figura 2.45 que define dos clases:

La clase Cliente que mantiene el nombre del cliente y el número de


transacciones que requiere realizar en el cajero.

La clase Cajero que despacha a un conjunto de clientes que están


formados en la cola colCaj, que registra la unidad de tiempo en que
despacha y un identificador llamado despachandoA que indica los datos
del cliente que se está despachando. En esta clase Cajero:

• El método formaClientes forma los n clientes en la cola colaCaj.


• El método despachaClientes va tomando los clientes de la cola
y los va despachando hasta que la cola está vacı́a. Este método
puede verse en el algoritmo 73.

Esta misma aplicación se puede implementar utilizando una cola circular


o con una cola doble. Si se utilizara la cola doble, los métodos encolarFrente
y desencolarFinal quedarı́an sin uso.

ITPuebla 104
2.4. APLICACIONES

Figura 2.44: Aplicación de cola: control de un cajero


ITPuebla 105
2.4. APLICACIONES

Figura 2.45: Diagrama de clases para el control de un cajero con una cola

Algoritmo 73 despachaClientes(): void


/* Despacha los clientes de la cola colaCaj hasta que **
** esté vacı́a **
** Entradas: **
** ninguna **
** Salidas:
** ninguna */

1. Mientras !(colaCaj.esVacia())
2. despachandoA=colaCaj.desencolar()
3. Para i=1,2,...,despachandoA.getnumTran()
4. tiempo++
5. Print (’’Tiempo: ’’+tiempo)
6. Print (’’Despachando a: ’’+despachandoA.getNombre())

ITPuebla 106
Capı́tulo 3

Estructuras no lineales

En este capı́tulo se presenta el concepto de recursividad como técnica


de programación. También se estudian las estructuras no lineales, que son
las que a cada componente le sucede y le precede más de un componente,
tales como: árboles y grafos, las que son dinámicas por variar su número de
componentes en tiempo de ejecución.

3.1. Recursividad

En este capı́tulo se estudia una técnica de programación llamada recursi-


vidad, que se refiere al diseño de algoritmo que se invocan a sı́ mismos. Este
tipo de algoritmos pueden resolver problemas instrı́nsecamente recursivos,
que también pueden resolverse iterativamente.

En un algoritmo iterativo, el programador debe diseñar explı́citamente


una iteración, utilizando sentencias repetitivas; en cambio, en un algoritmo
recursivo, el sistema operativo es el encargade de implementar tal iteración,
ya que el programador únicamente lo indica implı́citamente mediante llama-
dos recursivos.

107
3.1. RECURSIVIDAD

3.1.1. Definición

La recursividad es una herramienta de programación, es una técnica


de diseño de algoritmos para resolver un problema de manera simplifica-
da cuando el problema es definible en sus propios términos [4][10][12]. Por
ejemplo, el problema de calcular el factorial de un número, es un problema
que se define en sus propios términos, ya que se define como:

1! = 1, Caso base
n! = n(n − 1)! para n ≥ 2, Caso recursivo;

esta definición consiste de dos partes: el caso base y el caso recursivo. El


caso base define un caso simple o trivial, en este caso, el cálculo del factorial
del 1, que es un caso trivial, simple, concreto, cuyo resulado es 1, un resul-
tado también concreto. El caso recursivo es el que se define en sus propios
términos, es decir, para calcular el factorial de n se debe calcular el factorial
de n − 1. A este tipo de problemas se les denomina problemas recursivos.

Para resolver el cálculo del factorial de un número, se analiza el problema


como sigue:

1! = 1,
2! = 2(1)! = (2)(1) = 2
3! = 3(2)! = (3)2! = (3)(2)1! = (3)(2)(1) = (3)(2)
4! = (4)(3)(2)(1) = (4)(3)(2)
5! = (5)(4)(3)(2)
...
n! = (n)(n − 1)(n − 2)...(2), para n ≥ 2;

se observa que se puede diseñar el algoritmo iterativo 74, pero también se


puede diseñar un algoritmo recursivo, como se verá en la siguiente sección,
es decir, un algoritmo que se ”utilice a sı́ mismo”, es decir, que se invoque
a sı́ mismo. A este tipo de algoritmos se les llama algoritmos recursivos
[4][10]. Esto da lugar el diseño de algoritmos más simples, pero que utilizan
más memoria, como se verá enseguida.

ITPuebla 108
3.1. RECURSIVIDAD

Algoritmo 74 calculaFactorialIterativo(n:int):long
/* Calcula iterativamente el factorial de un número **
** Entradas: **
** n:int **
** Salidas: **
** fac:long */
1. Si n ≤ 1
2. fac=1
3. Sino
4. fac=1
5. Para i=n,n-1,n-2,...,2
6. fac=fac*i
7. Return fac

3.1.2. Procedimientos recursivos

Retomando el problema del cálculo del factorial, en el algoritmo 74 se


tiene toma la decisión de si n corresponde al caso base (lı́nea 2) o no (lı́neas 4
a 6). Para el caso base, no hay cálculos, la respuesta es directa. Para el caso
no base, la respuesta se debe calcular con un producto acumulado. Al caso
no base se le llama caso recursivo, ya que es en este punto donde el algoritmo
que se diseñe para resolver el problema se puede invocar a sı́ mismo, como en
el algoritmo 75, en el que se programa literalmente la definición de factorial.
Enseguida se presentará una prueba de escritorio de este algoritmo para
entender su funcionamiento.

Algoritmo 75 calculaFactorialRecursivo(n:int):long
/* Calcula recursivamente el factorial de un número **
** Entradas: **
** n:int **
** Salidas: **
** fac:long */
1. Si n ≤ 1
2. fac=1
3. Sino
4. fac=n*calculaFactorialRecursivo(n-1)
5. Return fac

ITPuebla 109
3.1. RECURSIVIDAD

Suponer que la primera invocación del algoritmo recursivo es


calculaFactorialRecursivo(3),
calculaFac en la Figura 3.1, donde los llamados recursivos se indican en
azul: para el primer llamado calculaFac(3), n = 3 y se ejecuta la lı́nea
4 que consiste de un llamado recursivo, un producto y una asignación; el
llamado recursivo produce que el control del programa pase a la lı́nea 1 del
algoritmo y que queden pendientes por ejecutarse el producto y la asigna-
ción, ası́ como el resto de las sentencias del algoritmo. Lo mismo sucede
para el llamado recursivo calculaFac(2). En el último llamado recursivo
calculaFac(1), se ejecuta la lı́nea 2, asignando directamente el resultado
fac = 1 y retornándolo en la lı́nea 5, con lo que el algoritmo termina su
ejecución, y el control del programa regresa al llamado recursivo previo (los
regresos se indican con lı́neas rojas), en este caso el control regresa a la lı́nea
4 para calcular el producto 2*calculaFac(1)=2*1=2, asignando el resultado
a la variable fac, y para después ejecutar la lı́nea 5 y retornar el valor de
fac. Ası́, el primer retorno es 1, el segundo retorno es 2 y el tercero y último
retorno es 6. Con esto, el factorial de 3 es 6.

En el ejemplo anterior, por cada llamado recursivo, se requieren celdas de


memoria para n y f ac. Al ser 3 llamados recursivos, en memoria se llegan a
tener 3 celdas diferentes para n y otras 3 celdas diferentes para f ac: 6 celdas
en total. Aquı́ se ilustra cómo un algoritmo recursivo es más sencillo que uno
iterativo (ver Algoritmo 74), pero utiliza más memoria, pues el algoritmo
iterativo correspondiente, sólo utiliza una celda de memoria para n y otra
para f ac, además de una celda para i: 3 celdas en total.

Figura 3.1: Prueba de escritorio del cálculo recursivo del factorial

ITPuebla 110
3.1. RECURSIVIDAD

En resumen, un procedimiento recursivo es el que consiste al menos de


dos caminos de ejecución:

El procedimiento para resolver el caso base, que es el criterio de para-


da de la recursividad (el criterio de parada del ciclo implı́cito que se
maneja en los llamados recursivos).

El procedimiento para resolver el caso recursivo, que es un conjunto


de sentencias donde al menos una de ellas es una invocación al mismo
procedimiento; cada vez que se invoca a ı́ mismo un procedimiento, se
ejecuta un ciclo que está impı́cito en los llamados recursivos.

Cuando se ejecuta un procedimiento recursivo, se hace una serie de invo-


caciones al mismo procedimiento, dejando ”pendiente” terminar la ejecución
de tales invocaciones, hasta alcanzar el criterio de parada (el caso base). En
la Figura 3.2 se muestra un esquema de la invocaciones en lı́neas azules pa-
ra el problema del cálculo del factorial. Cuando se satisface el criterio de
parada y se termina la ejecución de la última invocación recursiva, el con-
trol del programa va ”regresando” a la invocación anterior, regresando los
resultados parciales del subproblema que se resolvió. En programación estos
”regresos” se llaman vuelta atrás o backtracking (lı́neas rojas en la Figura
3.2) [4][2][15][10]. En programación, al conjunto de invocaciones se le llama
pila o stack de registros de activación, donde un registro de activación
es una estructura de datos que utiliza el sistema operativo para controlar
las invocaciones y las vueltas atrás al ejecutar un procedimiento recursivo
[20].

3.1.3. Ejemplos de casos recursivos

Para resolver problemas que manejan un conjunto de datos con algorit-


mos recursivos, se pueden aplicar dos criterios:

Criterio 1. Resolver el problema en dos partes: primero resolver para


el primer dato del conjunto y luego resolver para el resto de datos. La
forma en que se ha presentado la resolución del cálculo del factorial
de un número corresponde a la aplicación de este criterio: el problema
de n datos se dividió para resolver para el primer dato 1! y luego para
resolver recursivamente para el resto de los datos n − 1.

ITPuebla 111
3.1. RECURSIVIDAD

Figura 3.2: Invocaciones para el cálculo recursivo del factorial

Criterio 2. Utilizar la técnica de programación divide y vencerás


[4][2][15][10]: dividir el problema en 2 subproblemas aproximadamente
del mismo tamaño y resolver cada uno de ellos recursivamente. En este
caso, se requieren dos llamados recursivos.

ITPuebla 112
3.1. RECURSIVIDAD

Algoritmo 76 buscaIterat(v:Object[n],n:int,dato:Object):boolean

/* Busca iterativamente un dato en un vector **


** Entradas: **
** v:Object[n], vector de datos **
** n:int, longitud de v **
** dato:Object, dato a buscar **
** Salidas: **
** encontrado:boolean */
1. encontrado=false
2. i=0
3. Mientras !encontrado && i<n
4. Si v[i]=dato
5. encontrado=true
6. Sino
7. i++
8. Return encontrado

Tı́picamente, la búsqueda de un elemento en un vector no ordenado


de datos tiene el algoritmo 76. Este mismo procedimiento puede realizarse
con una búsqueda recursiva, la que puede hacerse aplicando los criterios
mencionados:

Criterio 1. Buscar en el primer elemento del vector y recursivamente


buscar en el resto del vector. Aquı́, hay varios criterios de parada de la
recursividad: uno es buscar el dato en un vector de un sólo elemento
(no hay resto del vector); otro es buscar el dato en el primer elemento
del vector y encontrarlo, independientemente de que haya resto del
vector o no. Ver algoritmo 77.

Criterio 2. Aplicar la técnica de programación divide y vencerás, di-


vidiendo el arreglo en dos subarreglos aproximadamente del mismo
tamaño y realizando recursivamente la búsqueda en ambos subarre-
glos. En este caso, el criterio de parada es buscar el dato en un vector
de un sólo elemento, o bien, buscar el dato en un vector que no tiene
elementos. Como la búsqueda se realiza en dos subarreglos, los subre-
sultados de estos llamados recursivos se operan con la operación lógica
or para obtener el resultado final de la búsqueda. Ver algoritmo 78.

ITPuebla 113
3.1. RECURSIVIDAD

Algoritmo 77 buscaRecur1(v:Object[n],i:int,n:int,dato:Object)
/* Busca recursivamente un dato en un vector **
** Entradas: **
** v:Object[n], vector de datos **
** i:int, ı́ndice al elemento i-ésimo, que funge **
** como primer elemento **
** n:int, longitud de v **
** dato:Object, dato a buscar **
** Salidas: **
** un booleano */
1. Si i=n-1 /* el elemento i es el único en el
vector, no hay resto */
2. Si v[i]=dato
3. Return true
4. Sino
5. Return false
6. Sino /* el elemento i es el primero del vector, y el
vector tiene un resto */
7. Si v[i]=dato
8. Return true
9. Sino
10. Return BuscaRecursivo1(v,i+1,n,dato) /* busca
recursivamente el dato en el resto
del vector */

ITPuebla 114
3.1. RECURSIVIDAD

Algoritmo 78 buscaRecur2(v:Object[],ini,fin,n:int,dato:Object)
/* Busca recursivamente un dato en un vector, utilizando **
** divide y vencerás **
** Entradas: **
** v:Object[n], vector de datos **
** ini:int, ı́ndice del elemento que funge como **
** primer elemento del subarreglo **
** fin:int, ı́ndice del elemento que funge como **
** último elemento del subarreglo **
** n:int, longitud de v **
** dato:Object, dato a buscar **
** Salidas: **
** un booleano */

1. Si fin≥n /* subvector sin elementos */


2. Return false
3. Sino
4. Si ini=fin /* el subvector tiene un sólo elemento */
5. Si v[ini]=dato
6. Return true
7. Sino
8. Return false
9. Sino /* el subvector tiene muchos elementos */
10. Return BuscaRecursivo2(v,ini,(ini+fin)/2,n,dato)
|| BuscaRecursivo2(v,(ini+fin)/2+1,fin,n,dato)

Otros ejemplos de problemas donde se manejan un conjunto de datos


y que se pueden resolver recursivamente, análogos al presentado anterior-
mente, son: sumar n números y encontrar el máximo de un conjunto de n
números. Ambos problemas se pueden resolver con el criterio de procesar el
primero y el resto de los elementos, o con la técnica de divide y vencerás.

Utilizando el Criterio 1 la suma de n datos se procesa como en la


Figura 3.3 que funciona como sigue: en cada llamado recursivo se suman
dos números, el elemento i-ésimo (el primero) con el resultado de sumar
el resto de los elementos, desde el elemento (i + 1)-ésimo hasta el último.
Utilizando divide y vencerás, como en la Figura 3.4, en cada llamado
recursivo el arreglo se divide en dos mitades y con dos llamados recursivos
se suma cada mitad, y los dos resultados parciales se suman. Los algoritmos

ITPuebla 115
3.1. RECURSIVIDAD

79 y 80 corresponden a las dos formas de sumar n números recursivamente.

La forma de resolver el problema de calcular el máximo de un conjunto


de n datos es análoga a la de la suma de n datos.

Figura 3.3: Suma recursiva de n números

En los siguientes capı́tulos, cuando se estudien las listas, pilas, colas,


árboles y grafos, se presentarán formas de procesar recursivamente estas
estructuras de datos; del mismo modo se presentarán algunos algoritmos
recursivos de ordenamiento y búsqueda.

ITPuebla 116
3.1. RECURSIVIDAD

Figura 3.4: Suma recursiva de n números aplicando divide y vencerás

ITPuebla 117
3.1. RECURSIVIDAD

Algoritmo 79 sumaRecursivoPrimeroResto(v:int[n],n:int,i:int)
/* Suma n números recursivamente utilizando la técnica **
** de procesar el primero y el resto de los números **
** Entradas: **
** v:int[n] **
** n: int **
** i: int **
** Salidas: **
** resul:int */
1. Si i=n
2. resul = v[i]
3. Sino
4. resul = v[i] + sumaRecursivoPrimeroResto(v,n,i+1)
5. Return resul

Algoritmo 80 sumaRecursivoDivideVenceras(v:int[n],n:int,i:int)
/* Suma n números recursivamente utilizando la técnica **
** de divide y vencerás **
** Entradas: **
** v:int[n] **
** ini: int **
** fin: int **
** Salidas: **
** resul:int */
1. resul = 0
2. Si ini=fin
3. resul = v[ini]
4. Sino
5. Si ini<fin
6. medio = (ini+fin)/2
7. resul = sumaRecursivoDivideVenceras(v,ini,medio) +
sumaRecursivoDivideVenceras(v,medio+1,fin)
8. Return resul

ITPuebla 118
3.2. GRAFOS

3.2. Grafos

Un grafo simple no dirigido o grafo G es un conjunto de nodos N =


{n1 , n2 , ..., np } relacionados con un conjunto de lados L = {l1 , l2 , ..., lq }, donde
cada lado puede representarse como un par no ordenado (ni , nj ) = (nj , ni ), de
ahı́ el nombre de grafo no dirigido [19][11]. Por ejemplo, en la Figura 3.5 con
un grafo se modela la región oriente de la República Mexicana, utilizando
nodos para los estados de la región: Puebla, Veracruz, Tlaxcala e Hidalgo,
y utilizando los lados para modelar los estados colindantes a la región. En
este ejemplo, el grafo G = {N, L} tiene los nodos:

N = {P uebla, T laxcala, V eracruz, Hidalgo, Edo.M éxico,


M orelos, Guerrero, Oaxaca, Chiapas, Querétaro,
SanLuísP otosí, T amaulipas, T abasco},

y los lados:

L = {l1 , l2 , ..., l20 } =


= {(Hidalgo, P uebla), (Hidalgo, V eracruz), ...,
(Guerrero, Oaxaca)}.

Figura 3.5: Grafo utilizado para representar una red de estados de la región
oriente e la República Mexicana

Un grafo es una estructura de datos homogénea, no lineal y dinámi-


ca. Homogénea, porque todos sus componentes son del mismo tipo, en este

ITPuebla 119
3.2. GRAFOS

caso, todos sus nodos. No lineal, porque a cada componente le puede pre-
ceder o suceder uno o más componentes. Dinámica, porque el número de
componentes puede variar en tiempo de ejecución [20][5][9][7][12].

Hay dos conceptos fundamentales a considerar para la representación de


grafos y su tratamiento automático [19][11][8]:

Adyascencia. Es la propiedad que tienen un par de nodos en un grafo


cuando están relacionados con un lado, es decir, dado G = {N, L},
si l = (n, m) (si el lado l relaciona a los nodos m y n), entonces n
y m son adyascentes [19]. En la Figura 3.5, los nodos V eracruz y
P uebla son adyascentes porque están relacionados con el lado l19 ; en
cambio, P uebla y SanLuíP otosí no son adyascentes, pues no están
relacionados directamente con un lado.

Incidencia. Es la propiedad que tiene un nodo y un lado cuando tal


lado relaciona a tal nodo con otro, ası́, si G = {N, L} y si l = (n, m),
entonces l incide en n y en m, m incide en l y n incide en l [19]. En la
Figura 3.5, el nodo V eracruz incide en el lado l19 , y viceversa.

Los siguientes conceptos se utilizan para implementar operaciones con


grafos [19][11][8]:

Camino. Dado un grafo G = {N, L}, un camino de longitud p entre


dos nodos n y m es una secuencia de p lados (n, ni ),(ni , nj ), ..., (nk , m)
[19]. Por ejemplo, en el grafo de la Figura 3.5, un camino de longitud
6 entre Hidalgo y Chiapas es

(Hidalgo, T laxcala), (T laxcala, Edo.M exico), (Edo.M exico, P uebla),


(P uebla, Guerrero), (Guerrero, Oaxaca), (Oaxaca, Chiapas);

es decir, l12 , l13 , l14 , l17 , l20 , l8 .

Grafo conexo. Un grafo es conexo, si para cualquier par de nodos


del grafo hay un camino [19]. Por ejemplo, el grafo de la Figura 3.5
es conexo, en cambio el grafo de la Figura 3.6 no es conexo porque
se identifica al menos el par de nodos Queretaro y Chiapas entre los
cuales no hay ningún camino.

ITPuebla 120
3.2. GRAFOS

Grado o valencia de un nodo. Dado un grafo, el grado o valen-


cia de un nodo, δ, es el número de lados que indicen en el nodo[19].
Por ejemplo, en la Figura 3.6, Veracruz es un nodo de grado 5,
δ(Veracruz)=5, porque cinco lados inciden en él: l3 , l4 , l5 , l6 y l7 ;
también δ(Chiapas)=2, δ(Morelos)=1.

Figura 3.6: Grafo no conexo

3.2.1. Representación de grafos

Basándose en el concepto de adyascencia, un grafo G = {N, L} puede


representarse mediante una matriz A llamada matriz de adyascencia, de
tamaño n × n, donde n es el número de nodos del grafo: n = ∣N ∣. Cada
renglón de la matriz se etiqueta con un nodo, y cada columna también se
etiqueta con un nodo en correspondencia con los de los renglones [5][9]. La
matriz es booleana y simétrica, donde:

A(i, j) = A(j, i) = {
1, si los nodos i, j son adyascentes,
0, en otro caso.

ITPuebla 121
3.2. GRAFOS

Figura 3.7: Grafo utilizado para representar amistad entre personas

Por ejemplo, el grafo G = {N, L} de la Figura 3.7, que representa un


conjunto de personas que son amigas entre sı́, tiene la siguiente matriz de
adyascencia:

Chico V inicius Caetano Gal Astrud


⎡⎢ ⎤⎥
⎢⎢ ⎥⎥
0 1 1 1 1
⎢⎢ ⎥⎥
Chico
⎢⎢ ⎥⎥
1 1 1 0 0
⎢⎢ ⎥⎥ ,
V inicius
A= ⎢⎢ 1 1 0 1 0 ⎥⎥
⎢⎢ ⎥⎥
⎢⎢ ⎥⎥
Caetano
⎢⎣ ⎥⎦
1 0 1 1 1
Gal
1 0 0 1 0
Astrud

que es una matriz de tamaño 5 × 5, ya que N = {Gal, Astrud, V inicius,


Chico, Caetano} y ∣N ∣ = 5. Los renglones y columnas de la matriz se eti-
quetan con los nodos del grafo, siguiendo el mismo orden. Como Chico y
Gal son amigos, ya que l7 = (Chico, Gal) ∈ L, entonces A(Chico, Gal) =
A(Gal, Chico) = 1. Como Astrud y Caetano no son amigos, no son adyas-
centes, entonces A(Astrud, Caetano) = A(Caetano, Astrud) = 0.

Un grafo también puede representarse con una matrix de indicencia


I, de tamaño n × l, donde n = ∣N ∣ y l = ∣L∣. Los renglones de la matriz
se etiquetan con los nodos y las columnas con los lados [5][9]. La matriz es
booleana, de modo que:

ITPuebla 122
3.2. GRAFOS

I(i, j) = {
1, si el nodo i incide en el lado j,
0, en otro caso.

Por ejemplo, el grafo de la Figura 3.7 tiene la siguiente matriz de inci-


dencia:
l3 l2 l1 l6 l5 l9 l7 l4 l8
⎡⎢ ⎤⎥
⎢⎢ ⎥⎥
1 0 0 0 1 0 1 1 0
⎢⎢ ⎥⎥
Chico
⎢⎢ ⎥⎥
1 1 0 0 0 1 0 0 0
⎢⎢ ⎥⎥ ,
V inicius
I= ⎢⎢ 0 1 1 0 1 0 0 0 0 ⎥⎥
⎢⎢ ⎥⎥
⎢⎢ ⎥⎥
Caetano
⎢⎣ ⎥⎦
0 0 1 1 0 0 1 0 1
Gal
0 0 0 1 0 0 0 1 0
Astrud

que es una matriz de tamaño 5 × 9, ya que N = {Gal, Astrud, V inicius,


Chico, Caetano}, ∣N ∣ = 5, y L = {l3, l2, l1, l6, l5, l9, l7, l4, l8}, ∣L∣ = 9.
En la matriz de incidencia I(Chico, l4) = 1 y I(Astrud, l4) = 1 porque l4
incide en Chico y Astrud.

3.2.2. Operaciones básicas

Las operaciones básicas de un grafo son:

Insertar un nodo. Al insertar un nodo, la matriz de adyascencia A


incrementa en uno sus renglones y columnas, y la matriz de incidencia
I incrementa en uno sus renglones, como en el ejemplo de la Figura 3.8,
donde en (a) se tiene el grafo original, luego se inserta el nuevo nodo
Edo.M exico, quedando el grafo (b). El nuevo renglón y columna de la
matriz de adyascencia, y el nuevo renglón de la matriz de incidencia
se llenan con ceros porque el nuevo nodo no es adyascente a ningún
otro nodo; con esto se genera un grafo no conexo.

Insertar un lado. Cuando se inserta un lado, l4 en la Figura 3.8


(c), a la matriz de adyascencia se le coloca un 1 en el componente
correspondiente al par de nodos que se relacionan con el nuevo lado, en
el ejemplo el lado es l4 = (Hidalgo, Edo.M exico), A(H, E) = A(E, H) =
1. En este ejemplo, la matriz de incidencia incrementa en uno el número
de columnas, indicando con 1 las incidencias con H y E.

ITPuebla 123
3.2. GRAFOS

Figura 3.8: Inserción de nodos (b) y lados (c) en un grafo, con matrices de
adyascencia A e incidencia I

Eliminar un nodo. Eliminar un nodo implica eliminar todos sus la-


dos incidentes. Por ejemplo, en la Figura 3.9 (a) se elimina el nodo
T laxcala, teniendo que eliminar sus lados incidentes l1 y l2 ; esto pro-
voca que la matriz de adyascencia decremente en 1 sus renglones y
columnas, y la matriz de incidencia decremente en 1 sus renglones,
pero en 2 sus columnas, ya que se eliminan dos lados.

Eliminar un lado. Eliminar un lado es colocar un 0 en la matriz de


adyascencia en la posición correspondiente al par de nodos en los que
incide el lado, como se ve en la Figura 3.9 (b), donde se elimina el lado
l3 , con lo que A(P, H) = A(H, P ) = 0, y se elimina la columna l3 de la
matriz de incidencia, quedando el grafo (c).

ITPuebla 124
3.2. GRAFOS

Figura 3.9: Eliminación de nodos (a) y lados (b) en un grafo, con matrices
de adyascencia A e incidencia I

Recorrer el grafo a lo ancho. Recorrer a lo ancho un grafo conexo


es acceder (o visitar) todos los nodos del grafo, partiendo de un nodo
llamado nodo de partida o nodo fuente. El recorrido es un camino en
el que se van visitando todos los nodos de manera que, estando en
un nodo, enseguida se visitan sus nodos adyascentes. Por ejemplo, el
recorrido a lo ancho del grafo conexo de la Figura 3.10, partiendo del
nodo fuente Querétaro es:

Querétaro, Hidalgo, Edo.M éxico, T laxcala, P uebla,


V eracruz, SnLuísP otosí, M orelos, Guerrero,
Oaxaca, T amaulipas, Chiapas, T abasco.

ITPuebla 125
3.2. GRAFOS

Figura 3.10: Recorrido a lo ancho de un grafo, partiendo de Querétaro

Recorrer el grafo a lo largo. Recorrer a lo largo un grafo conexo


es acceder (o visitar) todos los nodos del grafo, partiendo de un nodo
llamado nodo de partida o nodo fuente. El recorrido es un camino en
el que se van visitando todos los nodos de manera que, estando en
un nodo, enseguida se visita sólamente uno de sus nodos adyascentes,
quedando pendiente el resto. Por ejemplo, el recorrido a lo largo del
grafo de la Figura 3.11, partiendo del nodo fuente Querétaro es:

Querétaro, Hidalgo, SnLuísP otosí, V eracruz, T abasco,


Chiapas, Oaxaca, Guerrero, P uebla, M orelos, T laxcala,
Edo.M éxico, T amaulipas.

Figura 3.11: Recorrido a lo largo de un grafo, partiendo de Querétaro

ITPuebla 126
3.2. GRAFOS

Ası́, el TDA de un grafo es [20]:

Definición de valores
int int
Abstract typedef <Nodos> <Lados>
int×int int×int
<MatInc> , <MatAdy> Grafo

Definición de operaciones
Abstract <Nodos> insertarNodo(<Nodos> nuevoNodo,
Grafo G)
Precondition
NuevoNodo ∉ <Nodos>
Postcondition
MatAdy incrementa un renglón y una columna,
que se etiquetan con nodo
MatInc incrementa un renglón,
que se etiquetan con nodo
el grafo G tiene un componente más
<Nodos> := <Nodos> U {nuevoNodo}
insertarNodo := nodo

Definición de operaciones
Abstract <Lados> insertarLado(<Lados< nuevoLado,
<Nodos> n1, <Nodos> n2, Grafo G)
Precondition
n1,n2 ∈<Nodos>
nuevoLado ∉<Lados>
Postcondition
MatAdy[n1][n2]:=1
MatAdy[n2][n1]:=1
MadInc[n1][nuevoLado]:=1
MadInc[n2][nuevoLado]:=1
el grafo G tiene un lado más
<Lados> := <Lados> U {nuevoLado}
insertarLado := nuevoLado

ITPuebla 127
3.2. GRAFOS

Definición de operaciones
Abstract <Nodos> eliminarNodo(<Nodos> nodo,
Grafo G)
Precondition
nodo ∈ <Nodos>
Postcondition
MatAdy decrementa un renglón y una columna,
etiquetada con nodo
MatInc decrementa un renglón,
etiquetada con nodo
el grafo G tiene un componente menos
<Nodos> := <Nodos> - {nodo}
<Lados> := <Lados> - {lados incidentes en nodo}
eliminarNodo := nodo

Definición de operaciones
Abstract <Lados> eliminarLado(<Lados> lado, Grafo G)
Precondition
lado ∈<Lados>
lado incide en n1 y n2
n1,n2 ∈ <Nodos>
Postcondition
MatAdy[n1][n2]:=0
MatAdy[n2][n1]:=0
MatInc decrementa una columna,
etiquetada con lado
el grafo G tiene un lado menos
<Lados> := <Lados> −{lado}
eliminarLado := lado

ITPuebla 128
3.2. GRAFOS

Definición de operaciones
int
Abstract <Nodos> recorrerAncho(<Nodos> nodoFuente,
Grafo G)
Precondition
nodoFuente ∈<Nodos>
Postcondition
recorrido := {visita todos los nodos de manera que,
estando en un nodo, enseguida se visitan sus
nodos adyascentes }
recorrerAncho := recorrido

Definición de operaciones
int
Abstract <Nodos> recorrerLargo(<Nodos< nodoFuente,
Grafo G)
Precondition
nodoFuente ∈<Nodos>
Postcondition
recorrido := {visita todos los nodos de manera que,
estando en un nodo, enseguida se visita
sólamente uno de sus nodos adyascentes, quedando
pendiente el resto }
recorrerLargo := recorrido

El TDA Grafo puede implementarse con la interfaz de la Figura 3.12.


Este diseño considera que los nodos y los lados se etiquetan con cadenas, y
las relaciones entre nodos se respresentan con una matriz de adyacencia. Los
recorridos tienen como resultado un vector con la lista de nodos que forman
el recorrido.

ITPuebla 129
3.2. GRAFOS

Figura 3.12: Interfaz Grafo

El TDA Grafo puede implementarse con el diagrama de clases de la


Figura 3.13, donde la clase GrafoNoDirigido tiene tres atributos privados,
un método constructor y los definidos en la superclase Grafo. Por ejemplo,
en la Figura 3.14, los atributos del grafo son dos vectores de cadenas para
nodos y lados, una matriz booleana para las adyascencias, y una matriz
booleana para las incidencias.

ITPuebla 130
3.2. GRAFOS

Figura 3.13: Diagrama de clases de grafo no dirigido

ITPuebla 131
3.2. GRAFOS

Figura 3.14: Ejemplo de atributos de la clase GrafoNoDirigido

Los métodos del diagrma de la Figura 3.13 se implementan con los algo-
ritmos del 81 al 87.

Algoritmo 81 GrafoNoDirigido()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */
1. nodos = new Vector<String>()
2. lados = new Vector<String>()
3. matAdy = new int[0][0]
4. matInc = new int[0][0]

ITPuebla 132
3.2. GRAFOS

Algoritmo 82 insertarNodo(nodo:String):String
/* Inserta un nuevo nodo en el grafo no dirigido. **
** Retorna el nuevo nodo si la inserción fue exitosa, **
** retorna null en caso de que el nodo ya exista en el **
** grafo **
** Entradas: **
** nodo:String, nuevo nodo **
** Salidas: **
** nodo:String, nuevo nodo insertado **
** Variables/Objetos locales: **
** matAux:int[][] */
1. Si nodo∈nodos
2. Return null
3. Sino
4. nodos.add(nodo)
5. /* agrega un renglón y una columna a matAdy */
6. matAux=new int[matAdy.length+1][matAdy.length+1]
7. /* copia matAdy en matAux */
8. Para i=0,1,...,matAdy.length-1
9. Para j=0,1,...,matAdy.length-1
10. matAux[i][j] = matAdy[i][j]
11. /* actualiza matAdy */
12. matAdy = matAux
13. /* agrega un renglón a matInc */
14. matAux=new int[matInc.length+1][matInc[0].length]
15. /* copia matInc en matAux */
16. Para i=0,1,...,matInc.length-1
17. Para j=0,1,...,matInc[0].length-1
18. matAux[i][j] = matInc[i][j]
19. /* actualiza matInc */
20. matInc = matAux
21. Return nodo

ITPuebla 133
3.2. GRAFOS

Algoritmo 83 insertarLado(lado,n1,n2:String):String
/* Inserta un nuevo lado en el grafo no dirigido. **
** Retorna el nuevo lado si la inserción fue exitosa, **
** retorna null en caso de que n1 o n2 no existan en **
** el grafo, o lado ya exista en el grafo **
** Entradas: **
** lado:String, nuevo lado **
** n1:String, nodo incidente en lado **
** n2:String, nodo incidente en lado **
** Salidas: **
** lado:String, nuevo lado insertado **
** Variables/Objetos locales: **
** matAux:int[][] */
1. Si (n1∈nodos)&&(n2∈nodos)&&(lado∉lados)
2. lados.add(lado)
3. ren=nodos.get(n1)
4. col=nodos.get(n2)
5. matAdy[ren][col]=1
6. matAdy[col][ren]=1
7. /* agrega una columna a matInc */
8. matAux=new int[matInc.length][matInc[0].length+1]
9. /* copia matInc en matAux */
10. Para i=0,1,...,matInc.length-1
11. Para j=0,1,...,matInc[0].length-1
12. matAux[i][j] = matInc[i][j]
13. /* actualiza matInc */
14. matInc = matAux
15. /* marca incidencias del lado con dos nodos */
16. matInc[ren][matInc[0].length-1]=1
17. matInc[col][matInc[0].length-1]=1
18. Return lado
19. Sino
20. Return null

ITPuebla 134
3.2. GRAFOS

Algoritmo 84 eliminarNodo(nodo:String):String
/* Elimina un nodo del grafo no dirigido. Retorna el **
** nodo si la eliminación fue exitosa, retorna null si **
** el nodo no existe en el grafo **
** Entradas: **
** nodo:String, nodo a eliminar **
** Salidas: **
** nodo:String, nodo eliminado **
** Variables/Objetos locales: **
** matAux:int[][] */
1. Si nodo∉nodos
2. Return null
3. Sino
4. k=nodos.get(nodo)
5. /* elimina un renglón y una columna a matAdy */
6. matAux=new int[matAdy.length-1][matAdy.length-1]
7. /* copia matAdy en matAux, excepto el k-ésimo
8. renglón y columna */
9. ii=0
10. Para i=0,1,...,matAdy.length-1
11. Si i≠k
12. jj=0
13. Para j=0,1,...,matAdy.length-1
14. Si j≠k
15. matAux[ii][jj] = matAdy[i][j]
16. jj=jj+1
17. ii=ii+1
18. matAdy = matAux /* actualiza matAdy */
19. /* elimina un renglón a matInc */
20. matAux=new int[matInc.length-1][matAdy.length]
21. /* copia matInc en matAux, excepto el renglón k
22. ii=0
23. Para i=0,1,...,matAdy.length-1
24. Si i≠k
25. Para j=0,1,...,matAdy.length-1
26. matAux[ii][j] = matAdy[i][j]
27. ii=ii+1
28. matInc = matAux /* actualiza matInc */
29. nodos.remove(nodo)
30. Return nodo

ITPuebla 135
3.2. GRAFOS

Algoritmo 85 eliminarLado(lado:String):String
/* Elimina un lado en el grafo no dirigido. Retorna el **
** lado si la eliminación fue exitosa, retorna null si **
** el lado no existe en el grafo **
** Entradas: **
** lado:String, lado a eliminar **
** Salidas: **
** lado:String, lado eliminado **
** Variables/Objetos locales: **
** matAux:int[][] */
1. Si lado∈lados
2. ren=lados.get(n1)
3. col=nodos.get(n2)
4. matAdy[ren][col]=1
5. matAdy[col][ren]=1
6. k=lados.get(lado)
7. /* elimina una columna a matInc */
8. matAux=new int[matInc.length][matInc[0].length-1]
9. /* copia matInc en matAux, excepto columna k-ésima
10. columna */
11. Para i=0,1,...,matInc.length-1
12. jj=0
13. Para j=0,1,...,matInc.length-1
14. Si j≠k
15. matAux[i][jj] = matAdy[i][j]
16. jj=jj+1
17. /* actualiza matInc */
18. matInc = matAux
19. lados.remove(lado)
20. Return lado
21. Sino
22. Return null

ITPuebla 136
3.2. GRAFOS

Algoritmo 86 recorrerAncho(nodoFuente:String):Vector<String>
/* Recorre a lo ancho un grafo, a partir de un nodo **
** fuente. Retorna el recorrido resultante si el nodo **
** fuente existe, retorna null en otro caso. **
** Entradas: **
** nodoFuente:String, nodo de partida **
** Salidas: **
** recorrido:Vector<String>, recorrido a lo **
** ancho **
** Variables/Objetos locales: **
** cola:ColaVector<String>, auxiliar para **
** recorrido **
** visitados:Vector<String>, banderas de nodos **
** visitados **
** nod:String, nodo que se está visitando */
1. Si nodoFuente∈nodos
2. recorrido=new Vector<String>()
3. k=nodos.get(nodoFuente)
4. cola=new ColaVector<String>()
5. cola.encolar(nodoFuente)
6. visitados=new Vector<String>()
7. /* copia los nodos en visitados */
8. Para i=0,1,...,nodos.length()
9. visitados.add(nodos.get(i))
10. Mientras !cola.esVacia()
11. nod = cola.desencolar()
12. Si !visitados.exists(nod)
13. recorrido.add(nod)
14. visitado.delete(nod)
15. /* encola los nodos adyascentes a nod */
16. Para i=0,1,...,matAdy.length-1
17. Si matAdy[k][i]==1
18. Si !visitados.exists(nodos.get(i))
19. cola.encolar(nodos.get(i))
20. Return recorrido
21. Sino
22. Return null

ITPuebla 137
3.2. GRAFOS

Para recorrer un grafo a lo ancho, además de la matriz de adyascencia y


el vector de nodos, se utilizan estructuras de datos auxiliares, como se indica
en el algoritmo 86:

Una cola donde se van almacenando los nodos que queda pendiente
por recorrerse. La forma en que funciona una cola (el primer elemento
en insertarse es el primero en eliminarse), permite que los nodos del
grafo se recorran visitando primero todos los adyascentes de un nodo
dado. En el algoritmo se utiliza un objeto de la clase ColaVector.

Un vector donde se va construyendo el recorrido a lo ancho.

Un vector donde se va verificando si los nodos ya se han visitado y


forman parte del recorrido, o no.

En los pasos de inicialización del algoritmo 86, pasos 2 a 9, se instancia


el vector donde se almacena recorrido, se instancia la cola y se encola el nodo
fuente, se copian los nodos en el vector que verifica si ya se han visitado.
Luego, en los pasos 10 a 20 se tiene un ciclo que va construyendo el recorrido
mientras la cola no esté vacı́a. El funcionamiento de este ciclo se ilustra con
un ejemplo en las Figuras 3.15, 3.16 y 3.17, donde se presenta una s’ola
iteración.

Teniendo a Querétaro como nodo fuente, en la Figura 3.15 ya se han


visitado Querétaro e Hidalgo, por lo que están en el vector recorrido y se
han marcado como visitados en el vector visitados, y se han encolado los
adyascentes de Hidalgo que aún no se han visitado, Edo.M éxico, T laxcala,
P uebla, V eracruz y SanLuisP otosí. Dentro del ciclo del paso 10 del al-
goritmo 86 que se repite mientras la cola no esté vacı́a, en el paso 11 se
desencola Edo.M éxico (ver Figura 3.15). Si el nodo desencolado no se ha
visitado (paso 12 del algoritmo), se agrega al recorrido (paso 13) y se marca
como visitado (paso 14), como se ve en la Figura 3.16. Luego, en los pasos
15 a 19 se encolan los nodos adyascentes a Edo.M éxico que no se han visi-
tado: los nodos adyascentes se pueden ver en la matriz de adyascencia y los
nodos no visitados en el vector visitados, como en la Figura 3.17, donde los
adyascentes son Hidalgo, T laxcala y P uebla, pero sólo se encolan los dos
últimos porque Hidalgo ya se visitó.

ITPuebla 138
3.2. GRAFOS

Figura 3.15: Recorrido a lo ancho de un Grafo, desencolar un nodo a visitar

ITPuebla 139
3.2. GRAFOS

Figura 3.16: Recorrido a lo ancho de un Grafo, agregar el nodo al recorrido


y marcarlo como visitado

ITPuebla 140
3.2. GRAFOS

Figura 3.17: Recorrido a lo ancho de un Grafo, encolar nodos adyascentes


al nodo visitado

ITPuebla 141
3.2. GRAFOS

El recorrido a lo largo de un grafo es análogo al recorrido a lo ancho,


como en el algoritmo 87, sólo que la estructura de datos auxiliar fundamental
para lograr el recorrido, es una pila.

Algoritmo 87 recorrerLargo(nodoFuente:String):Vector<String>
/* Recorre a lo largo un grafo, a partir de un nodo **
** fuente. Retorna el recorrido resultante si el nodo **
** fuente existe, retorna null en otro caso. **
** Entradas: **
** nodoFuente:String, nodo de partida **
** Salidas: **
** recorrido:Vector<String>, recorrido a lo **
** largo **
** Variables/Objetos locales: **
** pila:PilaVector<String>, auxiliar para **
** recorrido **
** visitados:Vector<String>, banderas de nodos **
** visitados **
** nod:String, nodo que se está visitando */
1. Si nodoFuente∈nodos
2. recorrido=new Vector<String>()
3. k=nodos.get(nodoFuente)
4. pila=new PilaVector<String>()
5. pila.push(nodoFuente)
6. visitados=new Vector<String>()
7. Para i=0,1,...,nodos.length()
8. visitados.add(nodos.get(i))
9. Mientras !pila.esVacia()
10. nod = pila.pop()
11. Si !visitados.exists(nod)
12. recorrido.add(nod)
13. visitado.delete(nod)
14. /* empila los nodos adyascentes a nod */
15. Para i=0,1,...,matAdy.length-1
16. Si matAdy[k][i]==1
17. pila.push(nodos.get(i))
18. Return recorrido
19. Sino
20. Return null

ITPuebla 142
3.2. GRAFOS

3.2.3. Aplicaciones

Una aplicación tı́pica de los grafos es el cálculo del camino más corto o
más barato entre dos nodos en un grafo dirigido ponderado. Para ello, se
define [15][19][8]:

Grafo dirigido. Es un grafo G = {N, L} donde cada lado li ∈ L es


un par ordenado de la forma (nj , nk ), es decir, (nj , nk ) ≠ (nk , nj ). Por
ejemplo, en la Figura 3.18, los nodos del grafo G = {N, L} son las
prendas de vestir de Batman, y los lados son lados dirigidos, ya que
indican el orden en que Batman debe ponerse la ropa; ası́, por ejem-
plo, primero debe ponerse los calcetines antes que las botas, con lo
que (calcetines, botas) ∈ L, pero (botas, calcetines) ∉ L. En un gra-
fo dirigido se distingue el grado interno y externo de un nodo:
el grado interno es el número de lados que llegan al nodo, el gra-
do externo es el número de lados que salen del nodo; por ejemplo,
en la Figura 3.18, el grado externo de la camisa es 3 (los lados que
van a los guantes, la capa y el escudo), y el grado interno es 1 (el
lado que viene de los pantalones): δext (camisa)=3, δint (camisa)=1, y
δ(camisa)=δext (camisa)+δint (camisa)=3+1=4.

Figura 3.18: Grafo dirigido para representar el orden en que Batman debe
ponerse la ropa [17]

ITPuebla 143
3.2. GRAFOS

Grafo ponderado. Es un grafo G = {N, L} donde cada lado li ∈ L


de la forma (nj , nk ) = (nk , nj ) está ponderado o tiene asociado un pe-
so wj,k = wk,j [19]. Por ejemplo, en la Figura 3.19 se tiene el grafo
de la región oriente de la República Mexicana, donde cada lado tie-
ne asociado un peso que corresponde a la cantidad de kilómetros que
hay entre los estados (nodos); la matriz de adyascencia correspondien-
te ahora se denomina matriz de pesos, en ella se puede ver, por
ejemplo, que la distancia entre Puebla e Hidalgo y viceversa es de
wP ue,Hid = wHid,P ue = 146Km.

Figura 3.19: Grafo ponderado para representar una red de estados de la


región oriente e la República Mexicana y sus distancias en kilómetros. Se
muestra su matriz de pesos

ITPuebla 144
3.2. GRAFOS

Dado un grafo dirigido y ponderado, el camino más corto o más barato


entre dos nodos, desde el nodo de partida llamado nodo fuente y hasta el
nodo de llegada llamado nodo destino, es el camino que comienza con el
nodo fuente y termina con el nodo destino, y que al sumar los pesos de los
lados involucrados, la suma es mı́nima. Por ejemplo, en la Figura 3.20 se
tienen tres posibles caminos entre el nodo fuente Hidalgo y el nodo destino
Guerrero: Hidalgo, Puebla, Guerrero; Hidalgo, Tlaxcala, Edo.México,
Puebla, Guerrero; Hidalgo, Tlaxcala, Puebla, Guerrero. El camino más
barato o más corto es el de 566 Km: Hidalgo, Puebla, Guerrero.

Figura 3.20: Grafo dirigido y ponderado, con tres posibles caminos entre
Hidalgo y Guerrero, siendo el más barato o más corto el de 566 Km.

Uno de los algoritmos que permite calcular el camino más barato o corto
entre un nodo fuente y el resto de los nodos en un grafo ponderado dirigido,
es el Algoritmo de Dijkstra[19][11]. Este algoritmo hace uso de la matriz
de pesos del grafo, pero también mantiene un vector de costos, cada elemento

ITPuebla 145
3.2. GRAFOS

del vector corresponde a un nodo y representa el costo más barato del camino
que va desde el nodo fuente hasta el nodo. También mantiene un vector de
banderas, denominado vector de marcas, donde se indica por cada nodo si se
ha considerado como nodo de costo mı́nimo o no. El algoritmo de Dijkstra
funciona como en el ejemplo de la Figura 3.21, donde se identifican:

El paso de inicialización, donde se asigna a todos los nodos el costo


de ∞, excepto al nodo fuente, que en el ejemplo es Hidalgo, al que se
le asigna el costo 0, ya que ir del nodo fuente a Hidalgo, tiene costo
cero; en cambio, ir del nodo fuente a cualquier nodo tiene un costo
inicial de ∞. En este paso también se inicializan todos los nodos como
no marcados.

Un ciclo de actualización de costos, que en la Figura 3.21 es de 5


iteraciones. Este ciclo termina cuando el nodo destino, Guerrero en el
ejemplo, ya se ha marcado. En cada iteración, primero se identifica de
todos los nodos no marcados, el nodo con costo mı́nimo y se marca: en
el ciclo 1 es Hidalgo con costo 0; en el ciclo 2 es Tlaxcala con costo 125
(el costo de Hidalgo (0) es menor que el de Tlaxcala, pero Hidalgo ya
está marcado y no se debe tomar en cuenta); en el ciclo 3 es Puebla con
costo 146, etc. Luego, se actualizan los costos de los nodos adyascentes
al nodo de costo mı́nimo que no estén marcados; por ejemplo, en la
Figura 3.21, en el paso 2, el nodo de costo mı́nimo es Tlaxcala y
se actualizan los costos de sus adyascentes: Edo. México con costo
∞ y Puebla con costo 146; la actualización para Edo. México es de
125+208=333 que es menor que el costo actual ∞; la actualización para
Puebla no se realiza, porque 125+46=171 que es mayor que el costo
actual 146. En el paso 4, el nodo de costo mı́nimo es Edo. México, y
no se actualiza el costo de ninguno de sus adyascentes porque todos
están marcados.

El paso de finalización, donde el nodo destino Guerrero ha sido


marcado y se ha calculado el costo mı́nimo de ir desde el nodo fuente
Hidalgo hasta el nodo destino, que es de 566 Km, que coincide con el
análisis hecho en la Figura 3.20. En este punto, todos lo nodos que han
sido marcados tienen calculado el costo mı́nimo de ir desde el fuente
hasta ellos. Hay que notar que en caso de una sexta iteración, el nodo
no marcado de costo mı́nimo serı́a Morelos con costo ∞ y no se actua-
lizarı́an ninguno de sus adyascentes; ası́, el costo de Morelos quedarı́a
en ∞ porque no hay forma de llegar a este nodo desde Hidalgo.

ITPuebla 146
3.2. GRAFOS

Figura 3.21: Ejemplo de funcionamiento del algoritmo de Dijkstra para cal-


cular el costo mı́nimo de 566 Km desde Hidalgo hasta Guerrero

En el algoritmo 88 está el algoritmo de Dijkstra.

ITPuebla 147
3.2. GRAFOS

Algoritmo 88 dijkstra(nodoFuente:String,nodoDestino:String):int

/* Calcula el costo más barato, o camino más corto **


** entre un nodo fuente y un nodo destino dados, de un **
** grafo ponderado dirigido. Regresa el costo más **
** barato si lo hay, o ∞ en otro caso. **
** Entradas: **
** nodoFuente:String, nodo de partida **
** nodoDestino:String, nodo de llegada **
** Salidas: **
** costoMin:int, costo más barato desde nodo **
** fuente hasta nodo destino **
** Variables/Objetos locales: **
** costos:int[n], costo de cada nodo **
** marcas:boolean[n], marca de cada nodo **
** nodMin:int, ı́ndice del nodo de costo mı́nimo **
** cosMin:int, costo mı́nimo **
** nodDes:int, ı́ndice del nodo destino */
1. Si nodoFuente ∈ nodos && nodoDestino ∈ nodos
2. /* Inicialización */
3. Para i=0,1,...,nodos.length
4. costos[i]=∞
5. marcas[i]=false
6. costos[nodoFuente]=0
7. nodDes=nodos.get(nodoDestino)
8. /* Ciclo de actualización de costos
9. Mientras marcas[nodDes]=false
10. /* busca nodo/costo mı́nimo no marcado */
11. nodMin=-1
12. cosMin=∞
13. Para i=0,1,...,nodos.length
14. Si marcas[i]=false && costos[i]<cosMin
15. nodMin= i
16. cosMin=costos[i]
17. marcas[nodMin]=true
18. /* actualiza costos de nodos adyascentes a
19. nodMin no marcados */
20. Para i=0,1,...,nodos.length
21. Si matAdy[nodMin][i]≠0 && marcas[i]=false
22. Si costos[i]>cosMin+matAdy[nodMin][i]
23. costos[i]=cosMin+matAdy[nodMin][i]
24.
ITPuebla /* Finalización */ 148
25. Return costos[nodDes]
26. Sino
27. Return ∞
3.3. ÁRBOLES

En la Tabla 3.1 se muestra una prueba de escritorio del algoritmo de


Dijkstra aplicado al grafo de la Figura 3.21.

Tabla 3.1: Prueba de escritorio del algoritmo de Dijkstra


Paso Nodo Costo Vectores COSTOS/MARCAS (✓)
barato barato Hid EdoM Tla Pue Mor Gue
inicia X 0 0 ∞ ∞ ∞ ∞ ∞
1 Hid 0 0✓ ∞ 125 146 ∞ ∞
2 Tla 125 0✓ 333 125 ✓ 146 ∞ ∞
3 Pue 146 0✓ 333 125 ✓ 146 ✓ ∞ 566
4 EdoM 333 0✓ 333 ✓ 125 ✓ 146 ✓ ∞ 566
5 Gue 566 0✓ 333 ✓ 125 ✓ 146 ✓ ∞ 566 ✓
finaliza 0✓ 333 ✓ 125 ✓ 146 ✓ ∞ 566 ✓

3.3. Árboles

Un árbol es un grafo dirigido, conexo, acı́clico (que no tiene ciclos o


circuitos, es decir, que no tiene caminos que comiencen y terminen en el
mismo nodo), donde un sólo nodo tiene grado interno cero, llamado raı́z;
los nodos que tienen grado externo cero se llaman hojas, y el resto de los
nodos (que no son raı́z ni hojas) tienen grado interno uno y se llaman nodos
interiores [11][19].

En la Figura 3.22 (a) se tiene un grafo no dirigido que representa una rela-
ción de amistad entre los nodos, pero tiene varios ciclos, por ejemplo (Jobim,
De Moraes, Viglietti, Buarque, Veloso, Jobim) o (Rodrı́guez, Cos-
ta, Serrat, Rodrı́guez), por lo que no es un árbol. En (b) se tiene un grafo
no conexo, entonces no es un árbol. Para representar las personas que son
jefas de otras, en (c) se utiliza un grafo dirigido, que también es conexo y no
tiene ciclos, además se indentifica a De Moraes como raı́z porque tiene gra-
do interno cero, las hojas son Serrat, Buarque, Gilberto, Rodrı́guez,
Chávez porque tienen grado externo cero, y Jobim, Viglietti, Veloso,
Costa son los nodos interiores porque tienen grado interno uno; con esto,
este grafo es un árbol, que puede redibujarse como en (d), colocando los
nodos por niveles o renglones, desde la raı́z hasta las hojas.

ITPuebla 149
3.3. ÁRBOLES

Figura 3.22: Ejemplos de grafos que no son árboles (a) y (b), y que sı́ lo son
(c) y (d)

En la Figura 3.22 (d), De Moraes es la raı́z del árbol y sus hijos son Jobim
y Viglietti; los hijos de Viglietti son Gilberto, Costa y Chávez; etc.,
las hojas no tienen hijos. El padre de Serrat y Veloso es Jobim; el padre
de Buarque es Veloso; etc., la raı́z no tiene padre. Serrat y Veloso son
hermanos. Los descendientes de Viglietti son Gilberto, Costa, Chávez y
Rodrı́guez. Los ancestros de Buarque son Veloso, Jobim y De Moraes.

En un árbol cada nodo tienen un nivel , que es la longitud del camino


que va desde la raı́z hasta el nodo [11][19]. Por ejemplo, en la Figura 3.23
Gilberto tiene nivel 0, ya que es la raı́z; Chávez y Veloso tienen nivel 1,
porque el camino desde la raı́z hasta estos nodos es de longitud uno; Buarque,
De Moraes, Jobim y Viglietti tienen ninvel 2; etc. La altura de un árbol

ITPuebla 150
3.3. ÁRBOLES

es el nivel máximo de sus nodos [11][19]. En el árbol de la Figura 3.23 tiene


altura 4.

Figura 3.23: Ejemplo de árbol de altura 4 y niveles de sus nodos

Dado un árbol, un subárbol es un árbol cuya raı́z es cualquiera de los


nodos del árbol [11][19]. Por ejemplo, en la Figura 3.24, en (a) se tiene un
árbol, y en (b—d) todos sus posibles subárboles.

ITPuebla 151
3.3. ÁRBOLES

Figura 3.24: Árbol (a), con sus subárboles (b–d)

Desde el punto de vista de las estructuras de datos, como un árbol es un


caso particular de un grafo, al igual que éste, un árbol es una estructura
de datos homogénea (porque todos sus componentes son del mismo tipo),
no lineal (porque a cada componente le puede preceder o suceder uno o más
componentes) y dinámica (porque el número de componentes puede variar
en tiempo de ejecución) [20][5][9][7][12].

3.3.1. Árbol binario de búsqueda

Una de las caracterśticas de un árbol es su aridad, que se define como el


grado externo máximo de sus nodos. Un árbol de aridad n es el que cada nodo
tiene a lo más n hijos [11][19]. Por ejemplo, en la Figura 3.25, el árbol de (a)

ITPuebla 152
3.3. ÁRBOLES

es de aridad n = 3, los de (b) son de aridad n = 2. Los árboles de aridad n = 2


se llaman árboles binarios, donde cada nodo tiene cero hijos, un sólo hijo
o dos hijos; los hijos se distinguen como hijo izquierdo o hijo derecho, como
en la Figura 3.25 (b), donde Veloso tiene hijo izquierdo Buarque, Gilberto
tiene hijo derecho Rodrı́guez, Viglietti tiene hijo izquierdo Gilberto y
derecho Chávez, etc.

Figura 3.25: Árboles de aridad tres (a), y aridad dos (árboles binarios) (b)

Un árbol binario ordenado, también llamado árbol de búsqueda,


es en el que cada nodo tiene como subárbol izquierdo a elementos menores
que el nodo, y como subárbol derrecho a elementos mayores que el nodo
[11][19][5][9].

Por ejemplo, en la Figura 3.26, el árbol en (a) no es ordenado o de búsque-


da porque, por ejemplo, De Moraes en su subárbol izquierdo tiene a Veloso
y Veloso ≮ De Moraes. El árbol en (b) es ordenado o de búsqueda, por ejem-
plo Gilberto tiene nodos menores que él en su subárbol izquierdo (Chávez,
Buarque, De Moraes, Costa) y nodos mayores que él en su subárbol dere-
cho (Veloso, Jobim, Viglietti, Serrat, Rodrı́guez); y esto se puede
comprobar para cualquier otro nodo. En (c) y (d) también se tienen dos
árboles de búsqueda, pues son binarios y están ordenados, pero con diferen-
te configuración, en (c) el árbol degenera en una lista ligada y en (d) se tiene
un árbol equilibrado porque para cada nodo, los niveles de sus subárboles
izquierdo y derecho difieren solamente en 1 [11][19].

ITPuebla 153
3.3. ÁRBOLES

Figura 3.26: Árboles binarios, no ordenado (a), y ordenados o de búsqueda


(b–d)

Como un árbol es un grafo, cualquier árbol, y en particular un árbol


de búsqueda, también puede recorrerse a lo ancho o por niveles, o
recorrese a lo largo o a lo profundo, siendo la raı́z el nodo de partida,

ITPuebla 154
3.3. ÁRBOLES

como en el ejemplo de la Figura 3.27, con recorrido a lo ancho en (a):


Gilberto, Chávez, Veloso, Buarque, De Moraes, Jobim, Viglietti, Costa,
Serrat, Rodrı́guez; y recorrido a lo largo en (b): Gilberto, Chávez, Buar-
que, De Moraes, Costa, Veloso, Jobim, Serrat, Rodrı́guez, Viglietti.

Figura 3.27: Recorrido (a) a lo ancho (por niveles), y (b) a lo largo (a lo


profundo), de un árbol de búsqueda

3.3.2. Representación de árboles de búsqueda

Los árboles de búsqueda se representan con nodos. Cada nodo tiene tres
campos: el que contiene la información que se requiere almacenar, el que
apunta al hijo izquierdo y el que apunta al hijo derecho, como en la Figura
3.28.

ITPuebla 155
3.3. ÁRBOLES

Figura 3.28: Estructura de un nodo de un árbol binario

Al igual que una lista ligada, no se puede acceder directamente a los


nodos de un árbol, el acceso a cualquiera de ellos es mediante un sólo apun-
tador, en este caso el apuntador al nodo raı́z. Ası́, el acceso al nodo raı́z
en el árbol de la Figura 3.29 es mediante el apuntador raı́z; el acceso al
nodo que contiene a Chávez, que es hijo izquierdo de la raı́z, es: raı́z.izq;
el acceso al nodo Veloso, que es hijo derecho de la raı́z es: raı́z.der; El ac-
ceso al nodo De Moraes, que es hijo derecho del hijo izquierdo de la raı́z es:
raı́z.izq.der; etc. Ahora, para acceder a la información de la raı́z, se tiene:
raı́z.info; para acceder a la información del nodo Veloso: raı́z.der.info;
etc.

Figura 3.29: Acceso a los nodos de un árbol binario desde la raı́z

ITPuebla 156
3.3. ÁRBOLES

3.3.3. Operaciones básicas de árboles de búsqueda

Como con toda estructura de datos, para un árbol de búsqueda se definen


las operaciones:

Buscar un nodo. Dado un dato, debe buscarse en el árbol ordena-


do. En la Figura 3.30 se busca Serrat mediante un ciclo, utilizando
dos apuntadores auxiliares: aux que va apuntando al nodo donde se
busca el dato, padreAux que va apuntando al padre de aux. Al ini-
cio, aux apunta a la raı́z y padreAux apunta a nulo porque la raı́z no
tiene padre. En cada ciclo, se compara aux.info con el dato que se
está buscando; si no se encuentra el dato, se decide si aux avanza a su
hijo izquiero o derecho, y antes de avanzar se deja a padreAux apun-
tando a aux; si se encuentra el dato, el ciclo acaba. En la Figura 3.30,
en 1, Gilberto < Serrat, con lo que padreAux apunta a Gilberto y
aux apunta a su hijo derecho Veloso; en 2, Veloso > Serrat, movien-
do padreAux a Veloso y aux a su hijo izquierdo Jobim; en 3, Jobim <
Serrat, moviendo padreAux a Jobim y aux a su hijo derecho Serrat;
en 4, aux.info es Serrat, el dato que se está buscando, con lo que el
ciclo de búsqueda termina con éxito en la búsqueda.

Figura 3.30: Buscar un dato en un árbol de búsqueda

ITPuebla 157
3.3. ÁRBOLES

Insertar un nodo. Dado un nuevo dato, se crea un nuevo nodo que


lo contenga y se busca en el árbol el lugar donde insertar el nuevo nodo
de manera ordenada. Por ejemplo, en la Figura 3.31 se inserta el nuevo
dato León, para ello: 1 se crea un nuevoNodo y se almacena el dato; 2
se utiliza un apuntador auxiliar aux que comienza apuntando a la raı́z,
y que va recorriendo el árbol buscando la posición donde insertar el
nuevoNodo (aplicando el procedimiento de buscar un nodo), quedando
aux apuntando al nodo Rodrı́guez; 3 Se liga Rodrı́guez con León con
la asignación aux.izq=nuevoNodo.

Figura 3.31: Inserta un nuevo nodo en un árbol de búsqueda

Eliminar un nodo. Dado un dato, se busca ordenadamente en el


árbol y se elimina el nodo correspondiente. Esta es una operación
compleja porque hay que cuidar dos aspectos, primero: la variedad de
configuraciones en que está un nodo a eliminar para cuidar la actua-
lización de las ligas (el nodo es hoja; tiene un sólo hijo, izquierdo o
derecho, y es o no raı́z; tiene dos hijos, y es o no ra’iz; segundo: pre-
servar el orden del árbol. En la Figura 3.32 se presentan dos casos
de eliminación: un caso es eliminar el nodo Costa que no tiene hijos,
es hoja y es hijo izquierdo de su padre (puede darse el caso de que
sea hijo derecho de su padre), otro caso es eliminar el nodo Serrat
que tiene un sólo hijo izquierdo (podrı́a darse el caso de que tenga
un sólo hijo derecho). En ambos casos, el primer paso es localizar el

ITPuebla 158
3.3. ÁRBOLES

nodo a eliminar, utilizando la operación de buscar, quedando aux y


padreAux apuntando a los nodos correspondientes. Luego del paso 1,
se actualizan ligas como en 2.

Figura 3.32: Eliminar un nodo hoja (Costa) y un nodo con un sólo hijo
(Serrat) en un árbol de búsqueda

Eliminar un nodo con dos hijos se realiza localizando el siguiente dato


mayor o menor que el nodo a eliminar; por ejemplo, en el árbol de la
Figura 3.33 el siguiente dato mayor que Chávez es Costa y el siguiente
dato menor es Buarque; para Gilberto, el siguiente dato mayor es
Jobim y el siguiente menor es De Moraes; para Veloso, el siguiente
dato mayor es Viglietti y el siguiente menor es Serrat. En general,
el siguiente dato mayor de un nodo es el que está ”más a la izquierda”
del subárbol derecho del nodo, y el siguiente dato menor de un nodo
es el que está ”más a la derecha” del subárbol izquierdo del nodo; y
este nodo sólo tiene un hijo siempre. Una vez localizado el siguiente
dato mayor o menor del nodo a eliminar, se copia tal dato en el nodo
a eliminar y se elimina el nodo del que se realizó la copia. Esto puede
verse en la Figua 3.33, donde se requiere eliminar a Veloso, por lo que
1, se localiza Veloso con el proceso de buscar; 2, se localiza el nodo
del siguiente dato menor que Veloso (podrı́a localizarse el siguiente
dato mayor), que es Serrat; 3 se copia Serrat en el nodo de Veloso;
4, se elimina el nodo que contiene a Serrat utlizando el proceso de

ITPuebla 159
3.3. ÁRBOLES

eliminar un nodo con un sólo hijo.

Figura 3.33: Eliminar un nodo con dos hijos (Veloso) en un árbol de búsque-
da

Recorrer un árbol. En particular, un árbol binario, además de reco-


rrerse a lo ancho y a lo largo, también puede recorrerse de tres formas
[11][19]:

• En Orden: primero recorriendo en orden el subárbol izquier-


do de un nodo, luego visitando el nodo, y luego recorriendo en
orden el subárbol derecho del nodo (izquierda–raı́z–derecha, la
raı́z queda enmedio) . Al recorrer un árbol de búsqueda en este
orden, el resultado es una lista ordenada de elementos. Por ejem-
plo, en la Figura 3.34, el recorrido del árbol con raı́z Gilberto es:
subárbolIzquierdoDeGilberto–Gilberto–subárbolDerechoDeGilber-
to; el recorrido del subárbol con raı́z Veloso es: subárbolIzquierdoDeVeloso–
Veloso–subárbolDerechoDeVeloso. Esto se aplica a todos los subárbo-
les, quedando el recorrido: Buarque, Chávez, Costa, De Moraes,
Gilberto, Jobim, Rodrı́guez, Serrat, Veloso, Viglietti.

ITPuebla 160
3.3. ÁRBOLES

Figura 3.34: Esquema de recorrido en orden de un árbol binario

• En Preorden: primero visitando un nodo, luego recorriendo en


preorden su subárbol izquierdo, y luego recorriendo en preorden
su subárbol derecho (raı́z–izquierda–derecha, la raı́z queda pri-
mero). Este recorrido es el mismo que el recorrido a lo largo o a
lo profundo. Por ejemplo, en la Figura 3.35, el recorrido del árbol
con raı́z Gilberto es: Gilberto–subárbolIzquierdoDeGilberto–subárbol-
DerechoDeGilberto; esto se aplica a cada subárbol, quedando el
recorrido como: Gilberto, Chávez, Buarque, De Moraes, Costa,
Veloso, Jobim, Serrat, Rodrı́guez, Viglietti.

Figura 3.35: Esquema de recorrido en preorden de un árbol binario

ITPuebla 161
3.3. ÁRBOLES

• En Postorden: primero recorriendo en postorden el subárbol


izquierdo de un nodo, luego visitando el nodo, y luego reco-
rriendo en postorden el subárbol derecho del nodo (izquierda–
derecha–raı́z, la raı́z queda al final). En la Figura 3.36, el recorri-
do del árbol con raı́z Gilberto es: subárbolIzquierdoDeGilberto–
subárbolDerechoDeGilberto–Gilberto, que al recorrer todos los
subárboles en el mismo orden, el recorrido queda como: Buarque,
Costa, De Moraes, Chávez, Rodrı́guez, Serrat, Jobim, Viglietti,
Veloso, Gilberto.

Figura 3.36: Esquema de recorrido en postorden de un árbol binario

La operaciones ilustradas anteriormente se definen formalmente el el


TDA de un árbol de búsqueda, donde las operaciones de insertar, eliminar
y recorrer son recursivas [20]:

Definición de valores
Abstract typedef <Nodo> ÁrbolBús

Definición de operaciones
Abstract boolean esVacı́o (ÁrbolBús raı́z)
Postcondition
esVacio := true si raı́z no apunta a ningún nodo
esVacio := false si raı́z apunta a un nodo

ITPuebla 162
3.3. ÁRBOLES

Definición de operaciones
Abstract <Nodo> buscarNodo(<AlgúnTipo> dato,
ÁrbolBús raı́z)
Postcondition
Si raı́z = null
buscarNodo := null
Si raı́z ≠ null
apunta aux a raı́z
Si aux.info = dato
buscarNodo := aux Si aux.info <dato
buscarNodo:= buscarNodo(dato,raı́z.derecho)
Si aux.info >dato
buscarNodo := buscarNodo(dato,raı́z.izquierdo)

Definición de operaciones
Abstract <AlgúnTipo> insertarNodo
(<AlgúnTipo> nuevoDato, ÁrbolBús raı́z)
Postcondition
crea nuevoNodo para almacenar nuevoDato
Si raı́z = null
raź := nuevoNodo
insertarNodo := nuevoDato
Sino
aux := buscarNodo(nuevoDato, raı́z)
Si aux <nuevoDato
aux.derecho := nuevoNodo
Sino
aux.izquierdo := nuevoNodo
insertarNodo := nuevoDato

ITPuebla 163
3.3. ÁRBOLES

Definición de operaciones
Abstract <AlgúnTipo> eliminarNodo(<AlgúnTipo> dato,
ÁrbolBús raı́z)
Postcondition
Si raı́z = null
eliminarNodo := nuevoDato
Sino
aux := buscarNodo(dato, raı́z)
Si aux es hoja
apuntar el padre de aux a null
eliminarNodo := nuevoDato
Si aux tiene un solo hijo
apuntar al padre de aux a su hijo
eliminarNodo := nuevoDato
Si aux tiene dos hijos
buscar predecesor de aux
aux.info = predecesor.info
eliminarNodo := eliminarNodo(aux.info,aux)

Definición de operaciones
Abstract Vector recorrerEnorden(ÁrbolBús raı́z)
Postcondition
Si raı́z = null
recorrerEnorden := null
Sino
recorrido := recorrerEnorden(raı́z.izquierdo)
recorrido := recorrido.add(raı́z.info)
recorrido := recorrido.add(
recorrerEnorden(raı́z.derecho))
recorrerEnorden := recorrido

ITPuebla 164
3.3. ÁRBOLES

Definición de operaciones
Abstract Vector recorrerPreorden(ÁrbolBús raı́z)
Postcondition
Si raı́z = null
recorrerPreorden := null
Sino
recorrido := raı́z.info
recorrido := recorrido.add(recorrerPreorden(
raı́z.izquierdo))
recorrido := recorrido.add(recorrerPreorden(
raı́z.derecho))
recorrerPreorden := recorrido

Definición de operaciones
Abstract Vector recorrerPostorden(ÁrbolBús raı́z)
Postcondition
Si raı́z = null
recorrerPostorden := null
Sino
recorrido := recorrerPostorden(raı́z.izquierdo)
recorrido := recorrido.add(recorrerPostorden(
raı́z.derecho))
recorrido := recorrido.add(raı́z.info)
recorrerPostorden := recorrido

El TDA ÁrbolBús puede implementarse con el diagrama de clases de la


Figura 3.37, que define la interfaz ArbolBinario y las operaciones corres-
pondientes, que se heredan a la clase ArbolBusqueda que tiene un atributo
privado para la raı́z. ArbolBusqueda hace uso de una colección de nodos que
tienen tres atributos: el dato a almacenar, el apuntador al hijo izquierdo y
el apuntador al hijo derecho. Las operaciones pueden verse del algoritmo 89
al 100.

ITPuebla 165
3.3. ÁRBOLES

Figura 3.37: Diagrama de clases de un árbol de búsqueda

Algoritmo 89 ArbolBusqueda()
/* Constructor, inicializa/instancia variables/objetos **
** atributos de la clase, con valores por defecto **
** Entradas: **
** ninguna **
** Salidas: **
** ninguna */
1. raiz = null

ITPuebla 166
3.3. ÁRBOLES

Algoritmo 90 esVacio():boolean
/* Verifica si el árbol está vacı́o o no **
** Entradas: **
** ninguna **
** Salidas: **
** bandera:boolean */
1. Si raiz = null
2. Return true
3. Sino
4. Return false

Algoritmo 91 buscarNodo(dato:String,raiz,padreRaiz:Nodo):Vector<Nodo>

/* Busca recursivamente un dato, si lo encuentra **


** retorna dos apuntadores a nodo: el apuntador al **
** nodo del dato encontrado y el apuntador al padre **
** de éste. Si el dato no se encuentra, retorna null **
** Entradas: **
** dato:String, dato a buscar **
** raiz:Nodo, raı́z del árbol **
** padreRaiz:Nodo, padre de raı́z **
** Salidas: **
** vec:Vector<Nodo> */
1. Si raiz=null
2. vec.add(raiz)
3. vec.add(padreRaiz)
4. Return vec
5. Sino
6. Si raiz.getDato()>dato
7. Return buscarNodo(dato,aux.getIzq(),aux)
8. Si aux.getDato()<dato
9. Return buscarNodo(dato,aux.getDer(),aux)

ITPuebla 167
3.3. ÁRBOLES

Algoritmo 92 insertarNodo(dato:String):String
/* Inserta un dato ordenadamente en el árbol. Retorna **
** el dato si no está en el árbol. Retorna null si **
** el dato ya está en el árbol **
** Entradas: **
** dato:String, dato a insertar **
** Salidas: **
** dato:String */
1. nuevoNodo = new Nodo(dato,null,null)
2. Si raiz=null
3. raiz=nuevoNodo
4. Return dato
5. Sino
6. apuntadores=buscarNodo(dato,raiz,null)
7. aux=apuntadores.get(0)
8. padreAux=apuntadores.get(1)
9. Si aux=null /* dato no está en el árbol */
10. Si padreAux.getDato()<dato
11. padreAux.setDer(nuevoNodo)
12. Sino
13. padreAux.setIzq(nuevoNodo)
14. Return dato
15. Sino
16. Return null

El algoritmo 93 para eliminar un nodo tiene cierta complejidad, por eso


invoca a otros algoritmos: para eliminar un nodo que es hoja (algoritmo 94),
para eliminar un nodo que tiene un sólo hijo izquierdo (algoritmo 95) y para
eliminar un nodo que tiene un sólo hijo derecho (algoritmo 96). También
se invoca al algoritmo 97 buscaPredecesor que busca al sustituto del dato
que se quiere eliminar y que está almacenado en un nodo con dos hijos.

ITPuebla 168
3.3. ÁRBOLES

Algoritmo 93 eliminarNodo(dato:String):String
/* Elimina un dato del árbol. Retorna null si el dato **
** no está en el árbol. Retorna el dato si se eliminó **
** Entradas: **
** dato:String, dato a eliminar **
** Salidas: **
** dato:String */
1. Si raiz=null
2. Return null
3. Sino
4. apuntadores=buscarNodo(dato,raiz,null)
5. aux=apuntadores.get(0)
6. padreAux=apuntadores.get(1)
7. Si aux=null /* dato no está en el árbol */
8. Return null
9. Sino
10. /* el nodo el hoja */
11. Si aux.getIzq()=null && aux.getDer()=null
12. Si aux=raiz raiz=null
13. Sino eliminarHoja(padreAux,aux)
14. /* el nodo tiene un sólo hijo izquierdo */
15. Si aux.getIzq()≠null && aux.getDer()=null
16. Si aux=raiz raiz=raiz.getIzq()
17. Sino eliminarHijoIzq(padreAux,aux)
18. /* el nodo tiene un sólo hijo derecho */
19. Si aux.getDer()≠null && aux.getIzq()=null
20. Si aux=raiz raiz=raiz.getDer()
21. Sino eliminarHijoDer(padreAux,aux)
22. /* el nodo tiene dos hijos */
23. Si aux.getDer()≠null && aux.getIzq()≠null
24. apuntadores=buscaPredecesor(aux.getIzq())
25. pred=apuntadores.get(0)
26. padrePred=apuntadores.get(1)
27. aux.setDato(pred.getDato())
28. Si padrePred=null
29. aux.setIzq(null)
30. Sino
31. eliminarHijoIzq(padrePred,pred)
32. Return dato

ITPuebla 169
3.3. ÁRBOLES

Algoritmo 94 eliminarHoja(padreAux,aux:Nodo):void
/* Elimina un nodo hoja de un árbol de búsqueda **
** Entradas: **
** aux:Nodo, nodo a eliminar **
** padreAux:Nodo, padre de aux **
** Salidas: **
** ninguna */
1. /* nodo es hijo izquierdo de su padre */
2. Si padreAux.getIzq()=aux
3. padreAux.setIzq(null)
4. /* nodo es hijo derecho de su padre */
5. Si padreAux.getDer()=aux
6. padreAux.setDer(null)

Algoritmo 95 eliminarHijoIzq(dato:String):String
/* Elimina un nodo que tiene un sólo hijo izquierdo **
** de un árbol de búsqueda **
** Entradas: **
** aux:Nodo, nodo a eliminar **
** padreAux:Nodo, padre de aux **
** Salidas: **
** Ninguna */
1. /* nodo es hijo izquierdo de su padre */
2. Si padreAux.getIzq()=aux
3. padreAux.setIzq(aux.getIzq())
4. /* nodo es hijo derecho de su padre */
5. Si padreAux.getDer()=aux
6. padreAux.setDer(aux.getIzq())
7. Return dato

ITPuebla 170
3.3. ÁRBOLES

Algoritmo 96 eliminarHijoDer(dato:String):String
/* Elimina un nodo que tiene un sólo hijo derecho **
** de un árbol de búsqueda **
** Entradas: **
** aux:Nodo, nodo a eliminar **
** padreAux:Nodo, padre de aux **
** Salidas: **
** ninguna */
1. /* nodo es hijo izquierdo de su padre */
2. Si padreAux.getIzq()=aux
3. padreAux.setIzq(aux.getDer())
4. /* nodo es hijo derecho de su padre */
5. Si padreAux.getDer()=aux
6. padreAux.setDer(aux.getDer())

Algoritmo 97 buscaPredecesor(aux:Nodo):String
/* Elimina un nodo que tiene un sólo hijo derecho **
** de un árbol de búsqueda **
** Entradas: **
** aux:Nodo, nodo a eliminar **
** padreAux:Nodo, padre de aux **
** Salidas: **
** ninguna */
1. padreAux=null
2. Mientras aux.getDer() ≠ null
3. padreAux=aux
4. aux=aux.getDer()
5. apuntadores.add(aux)
6. apuntadores.add(padreAux)
7. Return apuntadores

ITPuebla 171
3.3. ÁRBOLES

Algoritmo 98 recorrerEnorden(raiz:Nodo):Vector<String>
/* Recorre recursivamente en orden un árbol binario **
** Entradas: **
** raiz:Nodo, **
** Salidas: **
** recorrido:Vector<String> */
1. recorrido=new Vector<String>()
2. Si raiz ≠ null
3. recorrido.add(recorrerEnorden(raiz.getIzq()))
4. recorrido.add(raiz.getDato())
5. recorrido.add(recorrerEnorden(raiz.getDer()))
6. Return recorrido

Algoritmo 99 recorrerPreorden(raiz:Nodo):Vector<String>
/* Recorre recursivamente en preorden un árbol binario **
** Entradas: **
** raiz:Nodo, **
** Salidas: **
** recorrido:Vector<String> */
1. recorrido=new Vector<String>()
2. Si raiz ≠ null
3. recorrido.add(raiz.getDato())
4. recorrido.add(recorrerEnorden(raiz.getIzq()))
5. recorrido.add(recorrerEnorden(raiz.getDer()))
6. Return recorrido

ITPuebla 172
3.3. ÁRBOLES

Algoritmo 100 recorrerPostorden(raiz:Nodo):Vector<String>


/* Recorre recursivamente en postorden un árbol binario **
** Entradas: **
** raiz:Nodo, **
** Salidas: **
** recorrido:Vector<String> */

1. recorrido=new Vector<String>()
2. Si raiz ≠ null
3. recorrido.add(recorrerEnorden(raiz.getIzq()))
4. recorrido.add(recorrerEnorden(raiz.getDer()))
5. recorrido.add(raiz.getDato())
6. Return recorrido

3.3.4. Aplicaciones

Un árbol de búsqueda puede utilizarse como ı́ndice, es decir, como una


estructura de datos auxiliar, que mantiene datos ordenados para acceder a
los objetos almacenados en un archivo, tal archivo controlado de tal forma se
llama archivo indexado [16][12][6]. Por ejemplo, en la Figura 3.38 se tiene
un archivo indexado, donde cada renglón representa un objeto que almacena
los datos de un alumno (número de control, nombre y fecha de nacimiento)
y un árbol de búsqueda donde cada nodo almacena un número de control
(clave) y la posición que ocupa en el archivo (dirección relativa). Los ı́ndices
se utilizan cuando los datos que deben almacenarse requieren mucho espacio
de almacenamiento y no caben en memoria primaria. El acceso a la memoria
primaria es muy lento, por lo que sólo las claves y las direcciones relativas se
almacenan en un ı́ndice en memoria primaria. El ı́ndice mantiene las claves
ordenadas, con lo que la búsqueda o acceso a cualquier elemento es más
barato (toma menos tiempo de ejecución).

ITPuebla 173
3.3. ÁRBOLES

Figura 3.38: Árboles de búsqueda de altura máxima y mı́nima que almacenan


los mismos datos

Un problema con el uso de los árboles de búsqueda es que no se puede


garantizar que sean equilibrados, pues el crecimiento de sus ramas depende
del orden en que se introduzcan las claves y este tipo de árboles crecen hacia
abajo; con esto la búsqueda de algunas claves será más rápida que la úsqueda
de otras.

Un árbol binario de n nodos, y en particular uno de búsqueda, tiene


altura máxima de n1 y una altura mı́nima de log2 (n) [11].

Un árbol de búsqueda alcanza su altura máxima cuando el árbol deriva


en una lista ligada (al insertar claves ordenadas se obtiene una estructura
que cumple las caracterı́sticas de un árbol binario pero también las de una
lista simplemente ligada).

Un árbol de búsqueda alcanza su altura mı́nima cuando el árbol está equi-


librado, es decir cuando las longitudes de los caminos desde la raı́z a cualquier
hoja son las mismas o, a lo más, difieren en una unidad. Ver la ilustración
de esto en la Figura 3.39, donde el árbol alcanza su altura máxima porque
los datos se insertaron en orden creciente: 09220001, 09220005, 09220008,
09220010, 09220013, 09220021; mientras que alcanza su altura mı́nima por-
que los datos se insertaron, por ejemplo, en el siguiente orden: 09220010,

ITPuebla 174
3.3. ÁRBOLES

09220013, 09220005, 09220008, 09220021, 09220001; el orden de inserción


puede depender, por ejemplo, del orden en que los alumnos se han inscrito.

Figura 3.39: Árboles de búsqueda de altura máxima y mı́nima que almacenan


los mismos datos

Introducir los árboles de búsqueda como ı́ndices a pesar del problema


de equilibrio que conllevan tiene por objetivo la comprensión del uso de una
estructura de ı́ndice para acceder a un archivo indexado y la programación
de este tipo de árboles no es muy compleja.

ITPuebla 175
Capı́tulo 4

Métodos de ordenamiento y
búsqueda

En este capı́tulo se abordan diversos métodos para resolver dos proble-


mas fundamentales de la programación en el tratamiento de datos: la cla-
sificación u ordenamiento, y la búsqueda. También se introducen medidas
de eficiencia para discernir cuáles de los algoritmos presentados son mejores
que otros, es decir, para identificar los que sean más eficientes.

4.1. Métodos de Ordenamiento

En esta sección se presentan diferentes métodos para resolver el problema


del ordenamiento, algunos mejores que otros en el sentido de la eficiencia,
es decir, con respecto al tiempo que tardan en resolver el problema. Por
ejemplo, para algunos métodos puede ser más eficiente ordenar un conjunto
de datos parcialmente ordenados que uno totalmente desordenado.

El ordenamiento o clasificación es la operación de organizar datos


de acuerdo a cierto criterio, por ejemplo: organización de números crecien-
te o decrecientemente, organización de cadenas alfabéticamente creciente o
decrecientemente, organización de procesos por prioridad, etc [5] [16].

El ordenamiento puede clasificarse en ordenamiento interno y externo. El

176
4.1. MÉTODOS DE ORDENAMIENTO

ordenamiento interno se realiza a nivel memoria principal, se caracteriza


porque se pueden ordenar pocos datos y el acceso a éstos es rápido. Mien-
tras que el ordenamiento externo se realiza a nivel memoria secundaria
(archivos), se caracteriza porque se pueden ordenar muchos datos, pero el
acceso a éstos es lento.

Un archivo es un conjunto de datos almacenados en memoria secun-


daria. . También puede definirse como un conjunto de registros lógicamente
relacionados, donde un registro en un conjunto de campos lógicamente rela-
cionados y un campo es la caracterı́stica o propiedad de un objeto o entidad.
Cuando un archivo se concibe como un conjunto de registros, hay un campo
llamado llave o clave que identifica de manera única a cada registro [16].

Por ejemplo, partiendo del archivo de los alumnos del Tecnológico de


Puebla presentados en la Tabla 4.1 (donde cada renglón es un registro y
cada registro tiene el conjunto de campos número de control, nombre y
fecha de nacimiento, y donde la clave es el número de control), se puede
hacer un ordenamiento externo (a nivel archivo) de acuerdo al número de
control, lo que implica mover fı́sicamente los registros, quedando como en la
Tabla 4.2. También puede hacerse un ordenamiento externo de acuerdo al
apellido, como en la Tabla 4.3.

Otra forma de hacer un ordenamiento de los registros de un archivo sin


moverlos fı́sicamente es mediante un ordenamiento por dirección, es decir,
utilizando estructuras de datos auxiliares que permitan dicho ordenamiento,
como se ve en la Figura 4.1.

Tabla 4.1: Archivo de los alumnos del Tec–Puebla.


Número de Control Nombre Fecha de Nacimiento
09220001 Ana López Ramı́rez 02/04/1989
09220010 Julio Ruı́z Blanco 17/08/1988
09220005 Marı́a Luna Dı́az 30/11/1990
09220013 Nadia Luz Morales 28/09/1987
09220008 Pedro Cruz Pérez 19/12/1989
09220021 José Oropeza Bueno 21/08/1991

ITPuebla 177
4.1. MÉTODOS DE ORDENAMIENTO

Tabla 4.2: Archivo de los alumnos del Tec–Puebla, con registros ordenados
de acuerdo al número de control.
Número de Control Nombre Fecha de Nacimiento
09220001 Ana López Ramı́rez 02/04/1989
09220005 Marı́a Luna Dı́az 30/11/1990
09220008 Pedro Cruz Pérez 19/12/1989
09220010 Julio Ruı́z Blanco 17/08/1988
09220013 Nadia Luz Morales 28/09/1987
09220021 José Oropeza Bueno 21/08/1991

Tabla 4.3: Archivo de los alumnos del Tec–Puebla, con registros ordenados
de acuerdo al apellido.
Número de Control Nombre Fecha de Nacimiento
09220008 Pedro Cruz Pérez 19/12/1989
09220001 Ana López Ramı́rez 02/04/1989
09220005 Marı́a Luna Dı́az 30/11/1990
09220013 Nadia Luz Morales 28/09/1987
09220021 José Oropeza Bueno 21/08/1991
09220010 Julio Ruı́z Blanco 17/08/1988

Figura 4.1: Ordenamiento de los registros de un archivo por dirección, de


acuerdo al número de control y de acuerdo al apellido.

En este capı́tulo se estudian los siguientes métodos de ordenamiento


interno [5][9][16]:

ITPuebla 178
4.1. MÉTODOS DE ORDENAMIENTO

Métodos de ordenamiento por Inserción.


• Inserción Directa.
• Inserción Directa Generalizada o Shell.

Métodos de ordenamiento por Intercambio.


• Ordenamiento de Burbuja o Bubble Sort.
• Ordenamiento Rápido o Quick Sort.
• Ordenamiento por Concatenación o Merge Sort.

Métodos de ordenamiento por Selección.


• Selección Directa.
• Ordenamiento por Montı́culo o Heap Sort.

Para ello se supone que los datos a ordenar se encuentran almacenados


en un arreglo unidimensional de tamaño N .

4.1.1. Métodos de ordenamiento por Inserción

Los métodos de ordenamiento por Inserción se caracterizan por


tomar individualmente cada dato a ordenar e introducirlo en un subconjunto
de datos ya ordenados.

Inserción Directa

Suponer que se tiene el arreglo A de cinco cadenas (N =5) a ordenarse


alfabéticamente de manera ascendente:
0 1 2 3 4
A Mary Ana Pedro Carmen Juan

La inserción directa [5][9][7] comienza considerando que el dato Mary


está en su lugar, es decir, que ya está ordenado.

ITPuebla 179
4.1. MÉTODOS DE ORDENAMIENTO

A continuación, pone a Ana en su lugar, para lo cual:


compara A[1] con A[0] y cambia el contenido del arreglo como sigue:

0 1 2 3 4
A Ana Mary Pedro Carmen Juan

Pone a Pedro en su lugar comparando A[2] con A[1] y no hay cambios


en el arreglo.
Pone a Carmen en su lugar:
comparando A[3] con A[2] y cambiando el contenido del arreglo
como sigue:

0 1 2 3 4
A Ana Mary Carmen Pedro Juan

comparando A[2] con A[1] y cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Mary Pedro Juan

comparando A[1] con A[0] y sin necesidad de cambios en el arreglo.


Pone a Juan en su lugar:
comparando A[4] con A[3] y cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Mary Juan Pedro

comparando A[3] con A[4] sin necesidad de cambiar el contenido del


arreglo.
Al colocar en su lugar al último componente del arreglo se termina el método
de la Inserción Directa, quedando los datos ordenados. Este procedimiento
se ve en el Algoritmo 101.

ITPuebla 180
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 101 insercionDirecta(A:Object[N]):Object[N]


/* Ordena ascendentemente un vector de N objetos con **
** el método de la inserción directa **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para k=1,2,...,N-1
2. dato=A[k]
3. i = k-1
4. Mientras (i≥0) && (dato<A[i])
5. A[i+1]=A[i]
6. i=i-1
7. A[i+1]=dato

Inserción Directa Generalizada o Shell

Nuevamente, suponer que se tiene el arreglo A de cinco cadenas (N =5)


a ordenarse alfabéticamente de manera ascendente:

0 1 2 3 4
A Mary Ana Pedro Carmen Juan

La inserción directa generalizada o Shell [5][9][7], como su nombre lo


indica, es una generalización del método de la inserción directa ya vista.
Comienza calculando un tamaño de paso como paso = N /2 = 2, entonces:
Pone a Pedro en su lugar:
comparando A[2] con A[2 − paso] = A[0] y cambia el contenido del
arreglo como sigue:

0 1 2 3 4
A Pedro Ana Mary Carmen Juan

Pone a Carmen en su lugar:


comparando A[3] con A[3 − paso] = A[1] sin haber necesidad de

ITPuebla 181
4.1. MÉTODOS DE ORDENAMIENTO

cambios en el arreglo.
Pone a Juan en su lugar:
comparando A[4] con A[4 − paso] = A[2] y cambiando el contenido
del arreglo:

0 1 2 3 4
A Pedro Ana Juan Carmen Mary

comparando A[2] con A[2 − paso] = A[0] y cambiando el contenido


del arreglo:

0 1 2 3 4
A Juan Ana Pedro Carmen Mary

Hasta aquı́ se tiene un primer ciclo de ordenamiento, en donde los datos han
quedado ordenados cada dos componentes de A (porque paso = 2), es decir
A[0] < A[2] < A[4] (Juan < Pedro < Mary) y A[1] < A[3] (Ana < Carmen).
A continuación la Inserción Directa Generalizada decrementa el tamaño de
paso con paso = paso/2 = 1, con lo que el método se comporta como el de
la Inserción Directa, de aquı́ su generalización, ya que la Inserción Directa
Generalizada se convierte en la Inserción Directa cuando el tamaño de paso
es 1. Entonces:
Pone a Ana en su lugar:
comparando A[1] con A[0] y cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Juan Pedro Carmen Mary

Pone a Pedro en su lugar:


comparando A[2] con A[1] sin necesidad de cambiar el contenido del
arreglo.
Pone a Carmen en su lugar
comparando A[3] con A[2], cambiando el contenido del arreglo:

ITPuebla 182
4.1. MÉTODOS DE ORDENAMIENTO

0 1 2 3 4
A Ana Juan Carmen Pedro Mary

comparando A[2] con A[1], cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Juan Pedro Mary

Pone a Mary en su lugar:


comparando A[4] con A[3], cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Juan Mary Pedro

comparando A[3] con A[2], sin necesidad de cambiar el contenido


del arreglo.
Con esto, el arreglo queda ordenado. El procedimiento de la Inserción Directa
Generalizada se ve en el Algoritmo 102.

ITPuebla 183
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 102 insercionDirectaGralizada(A:Object[N]):Object[N]

/* Ordena ascendentemente un vector de N objetos con **


** el método de la inserción directa generalizada o **
** shell **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
N −1
1. paso= 2
2. Mientras paso ≥ 1
3. Para k=paso,paso+1,paso+2,...,N-1
4. dato=A[k]
5. i=k-paso
6. Mientras (i≥0)&&(dato<A[i])
7. A[i+paso]=A[i]
8. i=i-paso
9. A[i+paso]=dato
paso
10. paso= 2

4.1.2. Métodos de ordenamiento por Intercambio

Los métodos de ordenamiento por intercambio se caracterizan por


tomar dos datos, compararlos y si es necesario intercambiarlos de posición,
proceso que se repite hasta ordenar todos los datos.

Ordenamiento de la Burbuja

Suponer que se tiene el arreglo A de cinco cadenas (N =5) a ordenarse


alfabéticamente de manera ascendente:

0 1 2 3 4
A Mary Ana Pedro Carmen Juan

El ordenamiento de la burbuja o bubble sort [5][9][7] comienza po-

ITPuebla 184
4.1. MÉTODOS DE ORDENAMIENTO

niendo a la cadena alfabéticamente más grande en el último componente del


arreglo, para ello:
compara A[0] con A[1] y cambia el contenido del arreglo como sigue:

0 1 2 3 4
A Ana Mary Pedro Carmen Juan

compara A[1] con A[2] y no es necesario cambiar los datos del arreglo,
compara A[2] con A[3] y cambiando el contenido del arreglo:

0 1 2 3 4
A Ana Mary Carmen Pedro Juan

compara A[3] con A[4] y cambia el contenido del arreglo:

0 1 2 3 4
A Ana Mary Carmen Juan Pedro

con esto, el método ha dejado al mayor de los datos (Pedro) en el último com-
ponente del arreglo, con lo que el dato queda ordenado. El método continúa
poniendo la siguiente cadena alfabéticamente más grande en el penúltimo
componente del arreglo, para ello:
compara A[0] con A[1] y no es necesario cambiar los datos del arreglo,
compara A[1] con A[2] y cambia el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Mary Juan Pedro

compara A[2] con A[3] y cambia el contenido del arreglo:

0 1 2 3 4
A Ana Carmen Juan Mary Pedro

ITPuebla 185
4.1. MÉTODOS DE ORDENAMIENTO

con esto, el penúltimo componente del arreglo queda ordenado y el méto-


do pone el siguiente dato más grande en el antepenúltimo componente del
arrelo, para ello:
compara A[0] con A[1] sin cambios el contenido del arreglo,
compara A[1] con A[2] sin cambios el contenido del arreglo,
compara A[2] con A[3] sin cambios el contenido del arreglo.
Por último, el método coloca la siguiente cadena alfabéticamente más gran-
de en la posición 1 del arreglo, con lo que los datos quedan completamente
ordenados. Todos estos pasos pueden verse en el Algoritmo 103.

Algoritmo 103 burbuja(A:Object[N]):Object[N]


/* Ordena ascendentemente un vector de N objetos con **
** el método de la burbuja **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para k=1,2,...,N-1
2. Para j=0,1,...,N-1-k
3. Si A[j]>A[j+1]
4. aux=A[j]
5. A[j]=A[j+1]
6. A[j+1]=aux

Ordenamiento Rápido

El método del ordenamiento rápido [5][9][7] se basa en la estrategia


de programación Divide y Vencerás [2], ya que divide el problema de orde-
nar un conjunto de datos en subproblemas idénticos al problema original,
excepto por las cantidades de datos a ordenar, que son menores, y cuyas so-
luciones (que se alcanzan resolviendo cada problema con la misma estrategia
de Divide y Vencerás) se fusionan en la solución del problema original.

Suponer que se tiene el arreglo A de seis cadenas (N =6) a ordenarse


alfabéticamente de manera ascendente:
0 1 2 3 4 5
A Mary Ana Sara Pedro Juan Carmen

ITPuebla 186
4.1. MÉTODOS DE ORDENAMIENTO

El Ordenamiento Rápido o Quick Sort empieza escogiendo un pivote, por


ejemplo Mary de la posición cero del arreglo, y coloca las cadenas menores
que Mary a su izquierda y las mayores que Mary a su derecha, es decir:

0 1 2 3 4 5
A Ana Juan Carmen Mary Sara Pedro

Vuelve a aplicarse el mismo método con los subconjuntos izquierdo (de la


posición 0 a la 2, tomando como pivote Ana) y derecho (de la posición 4 a
la 5, tomando como pivote a Pedro) hasta tener el arreglo ordenado, como
se ve en la Figura 4.2.

Notar que el método del Ordenamiento Rápido va construyendo un árbol


binario, por lo que, como se verá posteriormente, es el método más eficiente
de los vistos hasta ahora cuando el árbol binario es equilibrado.

Para la implementación del método del Ordenamiento Rápido, suponer


que se tienen los siguientes ı́ndices al arreglo de datos a ordenar y sus ini-
cializaciones:

p, ı́ndice del primer dato del conjunto;

u, ı́ndice del último dato del conjunto;

piv, ı́ndice del dato pivote, en esta implementación el pivote es el


primer dato del conjunto de datos a ordenar (piv = p);

i, ı́ndice que recorre el conjunto de datos de derecha a izquierda para


identificar aquellos menores que el pivote y colocarlos a la izquierda de
éste, se inicia indizando al último dato del conjunto a ordenar (i = u);

d, ı́ndice que recorre el conjunto de datos de izquierda a derecha para


identificar aquellos mayores que el pivote y colocarlos a la derecha
de éste, se inicia indizando al segundo dato del conjunto a ordenar
(d = p + 1).

ITPuebla 187
4.1. MÉTODOS DE ORDENAMIENTO

Figura 4.2: Ejemplo de ordenamiento de datos por el Método del Ordena-


miento Rápido.

Entonces, una vez inicializados los ı́ndices, el Ordenamiento Rápido con-


siste de mover i de derecha a izquierda mientras los datos que indice sean
mayores que el pivote y mientras i no indice al primer dato del conjunto
(i > p). Después, mueve d de izquierda a derecha mientras los datos que
indice sean menores que el pivote y mientras d no indice al último dato del
conjunto (d < u).

Si después de mover i y d sucede que d < i, quiere decir que se han


encontrado datos que no estás ordenados con respecto al pivote (es decir,
hay un dato indizado por i que debe ir a la izquierda del pivote por ser mayor
que éste, y hay un dato indizado por d que debe ir a la derecha del pivote
por ser menor que éste) por lo que se intercambian los datos indizados por
i y d, y los ı́ndices avanzan una posición para continuar con sus respectivos
recorridos.

Habiendo terminado i y d con sus recorridos, el último paso es inter-


cambiar los datos indizados por i y piv, quedando el pivote ”enmedio” del
conjunto, a su izquierda los datos menores que él y a su derecha los datos
mayores que él. Con esto se ha ordenado el pivote, quedando por ordenar

ITPuebla 188
4.1. MÉTODOS DE ORDENAMIENTO

los conjuntos de datos de su izquierda y de su derecha con el mismo pro-


cedimiento (es decir, dividiendo el problema en subproblemas idénticos al
original pero de menores tamaños que el original y resolviéndolos con la mis-
ma estrategia). En la Figura 4.3 se tiene un esquema del funcionamiento del
Ordenamiento Rápido y en el Algoritmo 104 los pasos a seguir. En el algo-
ritmo, los pasos 15 y 16 corresponden a llamados recursivos al algoritmo del
Ordenamiento Rápido para aplicar el mismo procedimiento a los problemas
de ordenar subconjuntos de datos que quedaron a la izquierda y a la derecha
del pivote.

Figura 4.3: Ejemplo de implementación de ordenamiento de datos por el


Método del Ordenamiento Rápido.

ITPuebla 189
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 104 quickSort(A:Object[N],p:int,u:int):Object[N]


/* Ordena ascendentemente y recursivamente un vector **
** de N objetos con el método del ordenamiento rápido **
** o quick sort **
** Entradas: **
** A:Object[N], vector a ordenar **
** p:int, ı́ndice del primer dato del vector **
** a ordenar **
** u:int, ı́ndice del último dato del vector **
** a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Si p<u /* resuelve problema original */
2. piv=p
3. d=p+1
4. i=u
5. Mientras d<i
6. Mientras (d<u)&&(A[d]>A[piv])
7. d=d+1 /* mueve d a la derecha */
8. Mientras (i>p)&&(A[i]>A[piv])
9. i=i-1 /* mueve i a la izquierda */
10. Si d<i
11. A[d]↔A[i] /* intercambia datos d, i */
12. d=d+1
13. i=i-1
14. A[piv]↔A[i] /* intercambia datos piv, i */
15. quickSort(A,p,i-1) /* resuelve subproblema izqdo */
16. quickSort(A,i+1,u) /* resuelve subproblema derecho */

Ordenamiento por Concatenación

El método del ordenamiento por concatenación o Merge Sort


[5][9][7] consiste en combinar o concatenar dos conjuntos de datos orde-
nados en uno tercero ordenado. Este método comienza considerando que
cada componente del conjunto de datos está ordenado, después toma pares
de componentes individuales y forma conjuntos de dos componentes orde-
nados. Entonces, toma parejas de pares ordenados y las concatena en un
conjunto de a lo más cuatro componentes ordenados. Este proceso continúa

ITPuebla 190
4.1. MÉTODOS DE ORDENAMIENTO

hasta ordenar todos los datos, como se ve en el ejemplo de la Figura 4.4.

Figura 4.4: Ejemplo de implementación de ordenamiento de datos por el


Método del Ordenamiento por Concatenación.

Como se ve en la Figura 4.4, el Ordenamiento por Concatenación cons-


truye un árbol binario de las hojas a la raı́z que es equilibrado. Se sabe que
un árbol binario equilibrado de N nodos tiene un altura de ⌈log2 N ⌉ [11],
por lo que, para ordenar un conjunto de N datos, se necesita un ciclo de
⌈log2 N ⌉ iteraciones para la construcción del árbol binario. En cada nivel del
árbol se necesita un ciclo que concatene dos conjuntos de datos ordenados
en un tercer conjunto que debe quedar ordenado: en el primer nivel del árbol
k 0
(representado por k = 0) se concatenan conjuntos de un dato (2 = 2 = 1),
k 1
en el nivel dos (k = 1) se concatenan conjuntos de dos datos (2 = 2 = 2), en
k 2
el nivel tres (k = 2) se concatenan conjuntos de cuatro datos (2 = 2 = 4),
k
etc. Entonces, en el nivel k se concatenan conjuntos de 2 datos.

Por otra parte, haciendo referencia a la Figura 4.4, en el nivel 1 del árbol
se concatenan seis parejas de conjuntos de datos: en la primera pareja los

ITPuebla 191
4.1. MÉTODOS DE ORDENAMIENTO

conjuntos corresponden a los ı́ndices [i1 = 0, ..., f1 = 0] e [i2 = 1, ..., f2 = 1]; en


la segunda pareja los conjuntos corresponden a los ı́ndices [i1 = 2, ..., f1 = 2]
e [i2 = 3, ..., f2 = 3]; etc. En el nivel 2 del árbol se concatenan tres parejas
de conjuntos de datos: en la primera pareja los conjuntos corresponden a
los ı́ndices [i1 = 0, ..., f1 = 1] e [i2 = 2, ..., f2 = 3]; en la segunda pareja los
conjuntos corresponden a los ı́ndices [i1 = 4, ..., f1 = 5] e [i2 = 6, ..., f2 = 7];
etc. En el nivel 3 del árbol se concatena una pareja de conjuntos de datos:
cuyos ı́ndices corresponden a [i1 = 0, ..., f1 = 3] e [i2 = 4, ..., f2 = 7]. Este
proceso continúa hasta el último nivel del árbol, como se ve en la Figura
4.5.

Figura 4.5: Ejemplo de implementación de ordenamiento de datos por el


Método del Ordenamiento por Concatenación. Detalles de los rangos de
datos que se concatenan.

ITPuebla 192
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 105 concatenación(A:Object[N]):Object[N]


/* Ordena ascendentemente un vector de N objetos con **
** el método de concatenación o merge sort **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para k=0,1,...,⌊log2 N ⌋
k
2. rango=2
3. i1=0;
4. f1=(i1+1)*rango-1
5. i2=f1+1
6. f2=(i2+1)*rango-1
7. Mientras f2<N
8. concatenar(i1,f1,i2,f2)
9. i1=f2+1
10. f1=(i1+1)*rango-1
11. i2=f1+1
12. f2=(i2+1)*rango-1

ITPuebla 193
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 106 concatenar(A:Object[N],i1,f1,i2,f2:int):Object[N]

/* Concatena dos subconjuntos de un vector. **


** Algoritmo auxiliar del algoritmo de ordenamiento **
** por concatenación **
** Entradas: **
** A:Object[N], vector a concatenar **
** i1:int, inicio del subconjunto 1 **
** f1:int, fin del subconjunto 1 **
** i2:int, inicio del subconjunto 2 **
** f2:int, fin del subconjunto 2 **
** Salidas: **
** A:Object[N], vector concatenado */
1. i=i1
2. j=i2
3. k=0
4. Mientras (i≤f1)&&(j≤f2)
5. Si A[i]<A[j]
6. AUX[k]=A[i]
7. i=i+1
8. Sino
9. AUX[k]=A[j]
10. j=j+1
11. k=k+1
12. Si i>f1
13. Mientras j≤f2
14. AUX[k]=A[j]
15. k=k+1
16. j=j+1
17. Sino
18. Mientras i≤f1
19. AUX[k]=A[i]
20. k=k+1
21. i=i+1
22. A[i1,...,f2]=AUX[0,...,f2-i1]

El proceso descrito en los párrafos anteriores se reúnen en el Algoritmo


105. En el paso 8 se hace un llamado al Algoritmo 106 que concatena or-
denadamente el conjunto de datos de los ı́ndices [i1 , ..., f1 ] con el conjunto

ITPuebla 194
4.1. MÉTODOS DE ORDENAMIENTO

de datos de los ı́ndices [i2 , ..., f2 ], para lo cual se utiliza un arreglo auxiliar
AU X para ir construyendo el conjunto de datos ordenados que resultan de
la concatenación de otros dos conjuntos de datos ordenados.

4.1.3. Métodos de ordenamiento por Selección

Los métodos de ordenamiento por selección se caracterizan por


buscar y seleccionar al dato de mayor o menor valor, y acomodarlo en la
posición que le corresponda.

Selección Directa

Suponer que se tiene el arreglo A de cinco cadenas (N =5) a ordenarse


alfabéticamente de manera ascendente:

0 1 2 3 4
A Mary Ana Pedro Carmen Juan

El ordenamiento de la selección directa [5][9][7] coloca en el primer


componente del arreglo al dato más pequeño del conjunto, para esto compara
el primer componente con el resto de los datos del arreglo y si es necesario
los intercambia, ası́:
compara A[0] con A[1] y cambia el contenido del arreglo como sigue:

0 1 2 3 4
A Ana Mary Pedro Carmen Juan

compara A[0] con A[2], con A[3] y con A[4], sin necesidad de cambiar
los datos del arreglo,
pues Ana es el menor de todos.
El método vuelve a realizar el mismo procedimiento ahora colocando el
siguiente dato menor del conjunto en la posición 1 del arreglo, para esto:
compara A[1] con A[2] y no hay cambios,
compara A[1] con A[3] y cambia los datos del arreglo como sigue:

ITPuebla 195
4.1. MÉTODOS DE ORDENAMIENTO

0 1 2 3 4
A Ana Carmen Pedro Mary Juan

compara A[1] con A[4] y no hay cambios.


Ahora, se coloca el tercer dato más pequeño del conjunto en la posición 2
del arrreglo, para esto:
compara A[2] con A[3] y cambia datos:

0 1 2 3 4
A Ana Carmen Mary Pedro Juan

compara A[2] con A[4] y cambia datos:

0 1 2 3 4
A Ana Carmen Juan Pedro Mary

Por último, en este ejemplo, el método:


compara A[3] con A[4] y cambia datos del arreglo:

0 1 2 3 4
A Ana Carmen Juan Mary Pedro

Este procedimiento puede verse en el Algoritmo 107.

ITPuebla 196
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 107 selecciónDirecta(A:Object[N]):Object[N]


/* Ordena ascendentemente un vector de N objetos con **
** el método de la selección directa **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para i=0,1,...,N-2
2. Para j=i+1,i+2,...,N-1
3. Si A[i]>A[j]
4. A[i]↔A[j] /* intercambia datos */

Ordenamiento por Montı́culo

Un montı́culo es un árbol binario donde para cada nodo se cumple que


su valor es menor o igual que el valor de cualquiera de sus descendientes
[16][5]. Por ejemplo, en la Figura 4.6 se tiene un montı́culo de 13 datos. Se
observa que el dato de la raı́z, a, es alfabéticamente menor que todos sus des-
cendientes (que todos los nodos del montı́culo); el dato c es menor que todos
sus descendientes (f, r, k, h y w); el dato m es menor que sus descendientes
(p); etc. Lo anterior puede verificarse para cada nodo del montı́culo.

Figura 4.6: Ejemplo de un montı́culo.

ITPuebla 197
4.1. MÉTODOS DE ORDENAMIENTO

La representación de un montı́culo mediante un arreglo obedece a las


siguientes reglas:

El nodo k se almacena en el componente k del arreglo.

El hijo izquierdo del nodo k se almacena en el componente 2k + 1 del


arreglo.

El hijo derecho del nodo k se almacena en el componente 2k + 2 del


arreglo.

Entonces, el montı́culo de la Figura 4.6 tiene la siguiente representación en


un arreglo:

0 1 2 3 4 5 6 7 8 9 10 11 12 13
A a e c k m f r n s p k h w

donde, por ejemplo, el nodo a de la posición k=0 del arreglo tiene su hijo
izquierdo e almacenado en la posición 2k + 1 = 2 × 0 + 1 = 1 y su hijo derecho
c almacenado en la posición 2k + 2 = 2 × 0 + 2 = 2; en otro ejemplo, el nodo f
que está almacenado en la posición k=5 del arreglo, tiene su hijo izquierdo
k en la posición 2k + 1 = 2 × 5 + 1 = 11 y su hijo derecho h en la posición
2k + 2 = 2 × 5 + 2 = 12. La forma en que se almacenan los nodos de un
montı́culo en un arreglo corresponde al recorrido por niveles o a lo ancho
del montı́culo.

Ahora bien, el método de ordenamiento por montı́culo [5][9][7] o


heap sort se realiza en dos fases: la primera consiste en tomar los datos del
arreglo a ordenar e insertarlos en un montı́culo; la segunda en ir eliminando,
raı́z a raı́z, los datos del montı́culo, colocándolos de vuelta en el arreglo;
ası́ es como los datos quedan ordenados. Por ejemplo, suponer que se tiene
el siguiente arreglo A de seis cadenas (N =6) a ordenarse alfabéticamente de
manera ascendente:

0 1 2 3 4 5
A Mary Ana Sara Pedro Juan Carmen

ITPuebla 198
4.1. MÉTODOS DE ORDENAMIENTO

La primera fase del Ordenamiento por Montı́culo consiste en recorrer


el arreglo A y tomar cada dato de éste para insertarlo en el montı́culo, en
el último nivel de izquierda a derecha. Por ejemplo, para el arreglo visto
anteriormente, el método toma Mary y lo inserta en un montı́culo vacı́o,
como en la Figura 4.7(a). Después toma el dato Ana y lo inserta en el nivel
uno del montı́culo a la izquierda de Mary, como en la Figura 4.7(b); como
Ana es menor que Mary se intercambian los nodos, con lo que se cumplen las
condiciones para que el árbol binario sea un montı́culo. Continúa el mismo
proceso para insertar el dato Sara en el nivel 1, a la derecha de la raı́z del
montı́culo, como en la Figura 4.7(c), y como Sara es mayor que Ana, no es
necesario intercambiar nodos. Lo mismo sucede con la inserción de Pedro,
en el nivel 2 del montı́culo, a la izquierda de Mary, como en la Figura 4.7(d).
Ya en la Figura 4.7(e), al insertar a Juan como hijo derecho de Mary, como
Juan < Mary, se intercambian nodos, y como Juan > Ana, el montı́culo no
sufre más cambios. Por último, al insertar Carmen a la izquierda de Sara
en el montı́culo (Figura 4.7(f)), como Carmen < Sara, se intercambian los
nodos, y como Carmen > Ana, el montı́culo no sufre más cambios.

Figura 4.7: Ejemplo de inserción de datos en un montı́culo.

La segunda fase del Ordenamiento por Montı́culo consiste en elminar,


raı́z a raı́z, los datos del montı́culo. Cada vez que se elimina una raı́z, en
su lugar se coloca el nodo que está más a la derecha del último nivel del

ITPuebla 199
4.1. MÉTODOS DE ORDENAMIENTO

montı́culo, después se reordenan los datos, en caso de ser necesario, para


que se cumplan las condiciones de un montı́culo. Por ejemplo, si se tiene el
montı́culo que aparece a la derecha de la Figura 4.7(f), se elimina la raı́z
Ana, que es el primer dato ordenado del arreglo original, en lugar de la raı́z
se coloca a Sara, que es el dato que está más a la derecha del último nivel
del montı́culo, esta operación puede verse en la Figura 4.8(a). Entonces,
se compara Sara con Juan y Carmen, y como es mayor que ambos nodos
hijos, se intercambia con el menor de ellos (Carmen), para que se cumplan
las condiciones de que, para un nodo cualquiera del montı́culo, todos sus
descendientes son mayores que el nodo (4.8(b)); como Sara no tiene nodos
hijos, termina el reacomodo de datos, quedando el montı́culo de la Figura
4.8(c).

Figura 4.8: Ejemplo de eliminación de datos de un montı́culo.

Este proceso vuelve a repetirse con la nueva raı́z Carmen, que es el se-
gundo elemento ordenado del arreglo original. En la Figura 4.8(d), la nueva
raı́z es Mary, que se intercambia con el menor de sus hijos, Juan, quedando
el montı́culo de la Figura 4.8(e). Notar que el arreglo A se va llenando con
las raı́ces que se van eliminando del montı́culo, las cuales están ordenadas
alfabéticamente de manera ascendente.

En la Figura 4.8(f) se ha eliminado a Juan, siendo la nueva raı́z Sara


que se intercambia con Pedro. En la Figura 4.8(g) se ha eliminado a Pedro,

ITPuebla 200
4.1. MÉTODOS DE ORDENAMIENTO

siendo la nueva raı́z Sara. Por último se elimina Sara (Figura 4.8(h)) ,
quedando el montı́culo vacı́o y el arreglo A con los datos ordenados.

En el Algoritmo 108 se tienen los llamados a algoritmos de las dos etapas


de las que consiste el Ordenamiento por Montı́culo, los cuales pueden verse
en los Algoritmo 109 y en el Algoritmo 110, respectivamente.

Algoritmo 108 montı́culo(A:Object[N]):Object[N]


/* Ordena ascendentemente un vector de N objetos con **
** el método de ordenamiento por montı́culo o **
** merge sort **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado **
** Variables/Objetos auxiliares: **
** mont:Object[N], montı́culo */
1. mont=insertarEnMontı́culo(A)
2. A=eliminarMonticulo(mont)

ITPuebla 201
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 109 insertarEnMontı́culo(A:Object[N]):Object[N]


/* Inserta los objetos de un vector en un montı́culo. **
** Algoritmo auxiliar del método de ordenamiento **
** por montı́culo **
** Entradas: **
** A:Object[N], vector a insertar **
** Salidas: **
** mont:Object[N], montı́culo */
1. Para i=0,1,...,N-1
2. mont[i]=A[i]
3. hijo=i
4. padre=(hijo-1)/2
5. parar=Falso
6. Mientras (padre≥0)&&(parar=Falso)
7. Si mont[hijo]<mont[padre]
8. mont[hijo]↔mont[padre] /* intercambia datos */
9. hijo=padre
10. padre=(hijo-1)/2
11. Sino
12. parar=Verdadero
13. Return mont

En los Algoritmos 109 y 110, el montı́culo utilizado se representa con


un arreglo del mismo tamaño que A (N ) llamado mont. En el primer al-
goritmo se manejan dos ı́ndices, padre e hijo, que indizan los componentes
correspondientes a los nodos padres e hijos del montı́culo al momento de
reordenar datos (intercambiar nodos padres e hijos), después de insertar un
dato. En el segundo algoritmo se manejan tres ı́ndices, padre, hijoIzquierdo
e hijoDerecho, que indizan a un nodo padre y sus hijos izquierdo y derecho
del montı́culo al momento de reordenar datos para cumplir las condiciones
del montı́culo, después de eliminar un dato.

ITPuebla 202
4.1. MÉTODOS DE ORDENAMIENTO

Algoritmo 110 eliminarMontı́culo(mont:Object[N]):Object[]


/* Elimina los objetos de un montı́culo y los inserta **
** en un vector. Algoritmo auxiliar del método de **
** ordenamiento por montı́culo **
** Entradas: **
** mont:Object[N], montı́culo a eliminar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para i=0,1,...,N-1
2. A[i]=mont[0]
3. mont[0]=mont[N-i-1]
4. padre=0
5. hijoizquierdo=padre×2+1
6. hijoderecho=padre×2+2
7. parar=Falso
8. Mientras((hijoizquierdo<N-i)||(hijoderecho<N-i)) &&
(parar=Falso)
9. /* toma como hijo al mı́nimo entre el
10. izquierdodo y el derecho */
11. Si (hijoderecho<N-i) /* el padre tiene 2 hijos */
12. Si (mont[hijoizquierdo]<mont[hijoderecho])
13. hijo=hijoizquierdo
14. Sino
15. hijo=hijoderecho
16. Sino /* el padre sólo tiene hijo izquierdo */
17. hijo=hijoizquierdo
18. Si mont[hijo]<mont[padre]
19. mont[hijo]↔mont[padre] /* intercambia datos */
20. padre=hijo
21. hijoizquierdo=padre×2+1
22. hijoderecho=padre×2+2
23. Sino
24. parar=Verdadero
25. Return A

ITPuebla 203
4.2. MÉTODOS DE BÚSQUEDA

4.2. Métodos de Búsqueda

Al igual que en el ordenamiento, en esta sección se estudian diversos


métodos para resolver el problema de la búsqueda. Concretamente se es-
tudiarán dos métodos de búsqueda: la búsqueda secuencial y la búsqueda
binaria. Será posible identificar cuál método es mejor al aplicar a ambos
métodos un análisis de eficiencia con base en sus tiempos de ejecución.

Por otra parte, por ejemplo, la búsqueda es más eficiente si el conjunto


de datos donde se aplica está ordenado u organizado de alguna forma es-
pecı́fica, como es el caso de buscar un número de teléfono en un directorio
telefónico, si el directorio está desordenado, la búsqueda de un número es-
pecı́fico tardará mucho tiempo, pero si el directorio está ordenado, la búsque-
da será rápida (eficiente) porque podrán aplicarse criterios para discriminar
conjuntos de datos y evitar la búsqueda entre ellos.

No importa la ı́ndole del problema que se requiera resolver, siempre se


enfrenta el problema de buscar una solución. La búsqueda es una operación
básica al momento de controlar y organizar datos. Por ejemplo, un caso
sencillo es buscar un objeto en un conjunto de objetos, los que pueden estar
desordenados u ordenados. Sea cual fuere el caso, la operación de buscar
determinará si el objeto buscado es hallado o no en el conjunto.

En las siguientes secciones se estudiarán los métodos de búsqueda


[5][9][7]:

Búsqueda Secuencial.

Búsqueda Binaria.

Para ello se supone que el conjunto de datos en donde se hará la búsqueda


se encuentran almacenados en un arreglo unidimensional de tamaño N .

4.2.1. Búsqueda Secuencial

La búsqueda secuencial [5][9][7] consiste en hacer un recorrio de los


elementos de un conjunto de datos, uno a uno, hasta encontrar el dato

ITPuebla 204
4.2. MÉTODOS DE BÚSQUEDA

buscado. Por ejemplo, suponer que se tiene el arreglo A de N =6 cadenas


ordenadas:

0 1 2 3 4 5
A Ana Carmen Juan Mary Pedro Sara

y que se desea buscar el dato Mary. Para ello, se utiliza un ı́ndice que empiece
indizando a la posición 0 del arreglo, y se hace una comparación de si el
dato buscado Mary está en la posición 0, como no es ası́, el ı́ndice avanza a
la posición 1 y vuelve a comparar. Este procedimiento continúa hasta que
el ı́ndice apunte a la posiciı́n 3 y al comparar Mary, el datos buscado, con
el contenido en esa posición, verifica que son iguales, por lo que se puede
concluir que el dato buscado sı́ está en el arreglo.

Otro ejemplo es buscar, sobre el mismo arreglo ordenado, el dato Eliza,


con lo que el ı́ndice ayuda a recorrer los componentes de las posiciones 0,
1 y 2; cuando llega a la posición 2, como Juan es mayor que Eliza, se
puede concluir que el dato buscado no estará en el arreglo, sin necesidad
de terminar la búsqueda hasta el último elemento (esta es una ventaja por
tener los datos ordenados que, de otra forma, obligarı́a a recorrer el arreglo
completo sin éxito y empleando más tiempo de ejecución).

En el Algoritmo 111 se tienen los pasos a seguir por la Búsqueda Secuen-


cial, donde dato es el dato a buscar en el arreglo A de tamaño N .

ITPuebla 205
4.2. MÉTODOS DE BÚSQUEDA

Algoritmo 111 busquedaSecuencial(A:Object[N],dato:Object):int


/* Busca un dato en un arreglo ordenado de objetos, **
** utilizando el método de la búsqueda secuencial **
** Entradas: **
** A:Object[N], vector ordenado **
** dato:Object, dato a buscar en el vector **
** Salidas: **
** i:int, posición del dato en el vector */
1. encontrado=Falso
2. i=0
3. Mientras (i<N)&&(encontrado=Falso)
4. Si A[i]=dato
5. encontrado=Verdadero
6. Sino
7. i=i+1
8. Si encontrado=Verdadero
9. Return i
10.Sino
11. Return -1

4.2.2. Búsqueda Binaria

La búsqueda binaria [5][9][7] se basa en un arreglo de datos ordenados


para buscar un dato en la posición de la mitad del arreglo, en caso de no
encontrarse ahı́, si el dato buscado es menor que el dato de la posición de la
mitad, se aplican los mismos pasos para buscar el dato en la mitad izquierda
del arreglo; análogamente, si el dato requerido es mayor que el dato de la
posición de la mitad, se aplican los mismos pasos para buscar el dato en
la mitad derecha del arreglo. Un ejemplo de este procedimiento se ve en la
Figura 4.9 y en el Algoritmo 112 los pasos que lo conforman.

ITPuebla 206
4.2. MÉTODOS DE BÚSQUEDA

Figura 4.9: Ejemplo de la Búsqueda Binaria.

En la Figura 4.9 se observa que la Búsqueda Binaria forma un árbol


binario donde cada nodo tiene un sólo hijo, ya sea el hijo derecho, como
cuando el rango de búsqueda es del componente i=5 al f =9 del arreglo A; o
ya sea el hijo izquierdo, como cuando el rango de búsqueda es del componente
i=5 al f =6 del arreglo A. Es importante observar este comportamiento, ya
que determinará que la Búsqueda Binaria es un algoritmo eficiente, ya que
construye un árbol binario de altura mı́nima, al contrario de la Búsqueda
Secuencial, que construye un árbol binario de altura máxima en el peor de
los casos, es decir, cuando debe recorrer todo el arreglo para encontrar el
dato buscado. Los detalles de la eficiencia de este algoritmo y los vistos a lo
largo de este capı́tulo se ven en la siguiente sección.

ITPuebla 207
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Algoritmo 112 busquedaBinaria(A:Object[N],dato:Object):int
/* Busca un dato en un arreglo ordenado de objetos, **
** utilizando el método de la búsqueda binaria **
** Entradas: **
** A:Object[N], vector ordenado **
** dato:Object, dato a buscar en el vector **
** Salidas: **
** i:int, posición del dato en el vector */
1. i=0
2. f=N-1
3. m=(i+f)/2
4. encontrado=Falso
5. Mientras (i≤f)&&(encontrado=Falso)
6. Si A[m]=dato
7. encontrado=Verdadero
8. Sino
9. Si A[m]<dato
10. i=m+1
11. Sino
12. f=m-1
13. m=(i+f)/2
14. Si encontrado=Verdadero
15. Return m
16. Sino
17. Return -1

4.3. Medición Teórica del Tiempo de Ejecución de


los Algoritmos de Ordenamiento y Búsqueda

En esta sección se calcula el tiempo de ejecución de algunos algoritmos


representativos de ordenamiento y búsqueda vistos.

Ordenamiento por Inserción Directa

El método de ordenamiento de la Inserción Directa y sus tiempos de


ejecución se ven en el Algoritmo 113 [15][5][9]. Primero se analizan los pasos

ITPuebla 208
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
5–6, que conforman el bloque de sentencias que se ejecutan dentro del ciclo
Mientras del paso 4, este bloque tiene un tiempo de ejecución de:

TbloqueM ientras (N ) = 1,

porque cada sentencia es O(1). El tiempo de ejecución de la sentencia itera-


tiva Mientras no se calcula directamente, ya que no siempre se ejecuta el
mismo número de iteraciones. Entonces hay que analizar el algoritmo supo-
niendo el peor caso, en donde i comienza con el valor k − 1 y se decrementa
hasta tomar el valor 0, es decir, suponiendo que hace el máximo de iteracio-
nes, lo cual se da cuando al insertar un dato ordenadamente en un arreglo de
k componentes se tienen que recorrer los k componentes, por eso el tiempo
de ejecución de la sentencia Mientras es:

TM ientras (N ) = k × TbloqueM ientras (N ) = k × 1 = k.

Ahora, el bloque de sentencias del ciclo Para, es decir, de los pasos 2–7, tiene
un tiempo de ejecución de:

TbloqueP ara (N ) = máx(T2 (N ), T3 (N ), TM ientras (N ), T7 (N )) = máx(1, 1, k, 1) = k,

por lo que la sentencia iterativa Para del paso 1 tiene el siguiente tiempo de
ejecución y orden de complejidad:

TP ara (N ) = ∑ TbloqueP ara (N ) = ∑ k = (N − 1) = O(N ),


N −1 N −1 2
N N N 2
= −
2 2 2
k=1 k=1

como se ha visto en el Algoritmo 113. Por lo tanto:

TInserciónDirecta (N ) = = O(N ).
2
N N 2

2 2

ITPuebla 209
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Algoritmo 113 insercionDirecta(A:Object[N]):Object[N]
/* Tiempos de ejecución del método de la inserción directa **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */

TP ara (N ) = O(N )
2
1. Para k=1,2,...,N-1
2. dato=A[k] T2 (N ) = 1
3. i=k-1 T3 (N ) = 1
4. Mientras (i≥0) && (dato<A[i]) TM ientras (N ) = k
5. A[i+1]=A[i] T5 (N ) = 1
6. i=i-1 T6 (N ) = 1
7. A[i+1]=dato T7 (N ) = 1

Ordenamiento de la Burbuja

El método de Ordenamiento de la Burbuja [15][5][9] con sus tiempos de


ejecución se muestra en el Algoritmo 114. Nuevamente, primero se calculan
los tiempos de ejecución del bloque de sentencias (pasos 4–6) de la sentencia
condicional Si ... Entonces ..., y de la condición de la misma sentencia,
que son:

TbloqueCondicional (N ) = máx(T4 (N ), T5 (N ), T6 (N )) = máx(1, 1, 1) = 1,


Tcondición (N ) = 1,

con lo que:

TSi...Entonces (N ) = máx(TbloqueCondicional (N ), Tcondición (N )) = 1.

Entonces, la sentencia Para del paso 2 tiene el siguiente tiempo de ejecución:

TP ara2 (N ) = ∑ TSi...Entonces (N ) = ∑ 1 = N − 1 − k.
N −1−k N −1−k

j=0 j=0

Con esto se puede calcular el tiempo de ejecución de la sentencia Para del


paso 1, cuyo bloque de ejecución es la sentencia Para del paso 2, entonces

ITPuebla 210
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
se tiene que:

TP ara1 (N ) = ∑ TP ara2 (N ) = ∑ (N − 1 − k) = ∑ N − ∑ 1 − ∑ k =
N −1 N −1 N −1 N −1 N −1

k=1 k=1 k=1 k=1 k=1

= N (N − 1) − (N − 1) − (N − 1) = (N − 1)(N − 1 − ) =
N N
2 2
= (N − 1)( − 1) =
N
2

+ 1 = O(N ),
2
N N 2
= −N −
2 2
por lo que el algoritmo del Ordenamiento de la Burbuja es de orden cuadráti-
co, de igual forma que la Inserción Directa, es decir:

TBurbuja (N ) = − N + 1 = O(N ).
2
N 3 2
2 2

Algoritmo 114 burbuja(A:Object[N]):Object[N]


/* Tiempos de ejecución del método de la burbuja **
** Entradas: **
** A:Object[N], vector a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
TP ara1 (N ) = O(N )
2
1. Para k=1,2,...,N-1
2. Para j=0,1,...,N-1-k TP ara2 (N ) = N − 1 − k
3. Si A[j]>A[j+1] TSi...Entonces (N ) = 1
4. aux=A[j] T4 (N ) = 1
5. A[j]=A[j+1] T5 (N ) = 1
6. A[j+1]=aux T6 (N ) = 1

Ordenamiento Rápido

Ahora se verá que en algunos casos el método del Ordenamiento Rápido


es más eficiente que el de la Inserción Directa y el de la Burbuja, ya que el
Ordenamiento Rápido puede ser de orden logarı́tmico que, de acuerdo a la
jerarquı́a de funciones, es un mejor orden que el polinomial (cuadrático) de
la Inserción Directa y de la Burbuja [15][5][9].

ITPuebla 211
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Como se ha visto en la Figura 4.2, el Ordenamiento Rápido ordena un
componente llamado pivote del arreglo de datos, colocando los datos meno-
res que el pivote a su izquierda y los mayores que el pivote a su derecha,
formando ası́ un árbol binario al ir ordenando con el mismo método ambos
conjuntos. Un caso ideal (el mejor de los casos) sucede cuando los pivotes
quedan ordenados exactamente a la mitad del arreglo de datos en manipula-
ción, pues ası́ se va formando un árbol binario equilibrado, que tiene altura
mı́nima de ⌊log2 N ⌋ con N nodos. El peor caso sucede cuando los pivotes
quedan ordenados en uno de los extremos del arreglo de datos en manipu-
lación, quedando el resto de los datos a su izquierda o bien a su derecha,
formando un árbol binario totalmente desequilibrado (es decir, formando
una lista ligada, que es una estructura lineal), que tiene altura máxima de
N con N nodos.

Además de la altura del árbol binario que se va formando, hay que con-
siderar que el número de comparaciones que se hacen en cada nivel del árbol
es, en términos de orden superior, del orden de N con N datos. Esto puede
deducirse como sigue, considerando que el árbol binario es equilibrado:

En el primer nivel del árbol se compara el pivote con N − 1 = O(N )


datos.
En el segundo nivel del árbol se comparan dos pivotes con dos sub-
conjuntos (el izquierdo y el derecho del pivote de nivel anterior) de:
N −1
−1
2
datos cada uno, es decir, en total se comparan:

− 1 = O (2 ) = O(N )
N −1 N
2
2 2
datos.
En el tercer nivel del árbol se comparan cuatro pivotes con cuatro
subconjuntos (los izquierdos y los derechos de los pivotes del nivel
anterior) de:
N −1
2
−1
−1
2
datos cada uno, es decir, en total se comparan:
N −1
4( − 1) = O (4 ) = O(N )
2
−1 N
2 4

ITPuebla 212
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
datos.

Entonces, en términos de orden superior, el número de comparaciones que


se hacen por cada nivel del árbol es del orden de O(N ) con N datos.

Reescribiendo el Algoritmo recursivo 104 con el Algoritmo iterativo 115,


que es un esquema representativo de la construcción del árbol binario (itera-
ción Para de los pasos 1–5) y del número de comparaciones que se realiza en
cada nivel del árbol (iteración Para de los pasos 3–5), el tiempo de ejecución
del Ordenamiento Rápido es:
TOrdenamientoRápido (N ) = HN,
donde H es la altura del árbol binario. Entonces, si se tiene la altura máxima,
el tiempo de ejecución del algoritmo es:
TOrdenamientoRápido (N ) = (N − 1)N = O(N ),
2

pero si se tiene la altura mı́nima, entonces:


TOrdenamientoRápido (N ) = ⌊log2 N ⌋N = O(N log2 N ),
que es un orden superlineal, que es un mejor orden que el cuadrático del
peor caso o de la Inserción Directa o del Ordenamiento de la Burbuja.

Algoritmo 115 quickSort(A:Object[N],p:int,u:int):Object[N]


/* Tiempos de ejecución del método del ordenamiento **
** rápido o quick sort presentado esquemáticamente **
** Entradas: **
** A:Object[N], vector a ordenar **
** p:int, ı́ndice del primer dato del vector **
** a ordenar **
** u:int, ı́ndice del último dato del vector **
** a ordenar **
** Salidas: **
** A:Object[N], vector ordenado */
1. Para i=1,2,...,H TP ara1 (N ) = O(HN )
/* H es la altura del árbol binario */
2. Seleccionar pivote
3. Para j=1,2,...,N-i TP ara2 (N ) = O(N )
4. Comparar A[pivote] con A[j]
5. Colocar A[j] a la izquierda o derecha de A[pivote]

ITPuebla 213
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Un análisis similiar puede hacerse para el Ordenamiento por Concatena-
ción, ya que construye un árbol binario equilibrado, como se ve en la Figura
4.4, y en cada nivel del árbol se hace un número de comparaciones del orden
de O(N ) para construir el tercer conjunto ordenado de datos, a partir de dos
conjuntos ordenados de datos. De esta forma se puede concluir que el orden
de complejidad del Ordenamiento por Concatenación es:

TOrdenamientoConcatenación (N ) = O(N log2 N ).

Ordenamiento por Montı́culo

Como se ha visto en el Algoritmo 108, el Ordenamiento por Montı́culo


consiste de dos etapas, en la primera se insertan los datos en el montı́culo,
en la segunda se eliminan, raı́z a raı́z, los datos del montı́culo. Al hacer la
eliminación se obtienen los datos ordenados. En el Algoritmo 116 se esque-
matiza el Algoritmo 109, que realiza la primera etapa. En este algoritmo, el
ciclo Mientras del paso 5, en el peor caso, es del orden de O(log2 i), que es el
caso cuando el i–ésimo dato insertado en el último nivel del montı́culo debe
reacomodarse en la raı́z del montı́culo, lo que implica hacer un recorrido de
un árbol binario equilibrado de i nodos, con altura ⌊log2 i⌋. Ası́, el bloque de
los pasos 2–8 tiene un tiempo de ejecución de [15][5][9]:

TbloqueP ara (N ) = O(log2 i).

Ahora, para calcular el tiempo de ejecución del paso 1, se tiene que:

TP ara (N ) = ∑ TbloqueP ara (N ) = ∑ log2 i ≤ ∑ log2 N = N log2 N.


N N N

i=1 i=1 i=1

Por otra parte, la eliminación de datos de un montı́culo requiere de pa-


sos análogos a la inserción, entonces su tiempo de ejecución también es de
O(N log2 N ).

Por lo tanto, el orden de complejidad del Ordenamiento por Montı́culo


es:

TOrdenamientoM ontículo (N ) = O(N log2 N ) + O(N log2 N ) = O(N log2 N ).

ITPuebla 214
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Algoritmo 116 insertarEnMontı́culo(A:Object[N]):Object[N]
/* Tiempos de ejecución de la inserción de los objetos **
** de un vector en un montı́culo presentado esquemáticamente **
** Entradas: **
** A:Object[N], vector a insertar **
** Salidas: **
** mont:Object[N], montı́culo */

1. Para i=1,2,...,N T1 (N ) = O(N log2 N )


2. Insertar A[i] en el último componente de mont T2 (N ) =
1
3. hijo=i T3 (N ) = 1
4. Calcular padre del hijo T4 (N ) = 1
5. Mientras (mont[padre]<mont[hijo])&&(padre≥0) T5 (N ) =
log2 i
6. mont[padre]↔mont[hijo] T6 (N ) = 1
7. hijo=padre T7 (N ) = 1
8. Calcular padre del hijo T8 (N ) = 1

Para terminar con el análisis de los algoritmos de ordenamiento, el Or-


denamiento por Selección Directa también tiene un tiempo de ejecución de
orden cuadrático:
TSelecciónDirecta (N ) = O(N ).
2

Su análisis se hace como el de la Inserción Directa o el Ordenamiento de la


Burbuja.

Búsqueda Secuencial

Los tiempos de ejecución de la Búsqueda Secuencial se ven en el Algo-


ritmo 117 [15][5][9]. El paso 3 con el ciclo Mientras es el más costoso del
algoritmo, ya que, en el peor caso se necesita comparar el dato buscado con
todos los elementos del arreglo, es decir, se necesitan hacer N comparaciones.
Entonces, el tiempo de ejecución y el orden de complejidad son:

TB úsquedaSecuencial (N ) = máx(T1 (N ), T2 (N ), T3 (N ), T6 (N )) = N = O(N ),

que es de orden lineal.

ITPuebla 215
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Algoritmo 117 busquedaSecuencial(A:Object[N],dato:Object):int
/* Tiempos de ejecución del método de la búsqueda **
** secuencial **
** Entradas: **
** A:Object[N], vector ordenado **
** dato:Object, dato a buscar en el vector **
** Salidas: **
** i:int, posición del dato en el vector */
1. encontrado=Falso T1 (N ) = 1
2. i=0 T2 (N ) = 1
3. Mientras (i<N)&&(encontrado=Falso) T3 (N ) = O(N )
4. Si A[i]=dato
5. encontrado=Verdadero
6. Sino
7. i=i+1
8. Si encontrado=Verdadero T8 (N ) = 1
9. Return i
10.Sino
11. Return -1

Búsqueda Binaria

La Búsqueda Binaria es más eficiente que la Secuencial, pues como se ve


en la Figura 4.9 [15][5][9], la Búsqueda Binaria construye un árbol binario
equilibrado. Entonces, el tiempo de ejecución del paso 5 del Algoritmo 118
es:
TM ientras (N ) = O(log2 N ),
ya que, en el peor caso (cuando no se encuentre el dato buscado o cuando
i > f ) se construye completamente el árbol binario equilibrado, que es de
altura ⌊log2 N ⌋ Por lo tanto, la complejidad de tiempo de la Búsqueda Binaria
es:
TB úsquedaBinaria (N ) = O(log2 N ).

ITPuebla 216
4.3. MEDICIÓN TEÓRICA DEL TIEMPO DE EJECUCIÓN DE LOS
ALGORITMOS DE ORDENAMIENTO Y BÚSQUEDA
Algoritmo 118 busquedaBinaria(A:Object[N],dato:Object):int
/* Tiempos de ejecución del método de la búsqueda **
** binaria **
** Entradas: **
** A:Object[N], vector ordenado **
** dato:Object, dato a buscar en el vector **
** Salidas: **
** i:int, posición del dato en el vector */
1. i=0 T1 (N ) = 1
2. f=N-1 T2 (N ) = 1
3. m=(i+f)/2 T3 (N ) = 1
4. encontrado=Falso T4 (N ) = 1
5. Mientras (i≤f)&&(encontrado=Falso)
6. Si A[m]=dato T6 (N ) = 1
7. encontrado=Verdadero
8. Sino
9. Si A[m]<dato T9 (N ) = 1
10. i=m+1
11. Sino
12. f=m-1
13. m=(i+f)/2 T13 (N ) = 1
14. Si encontrado=Verdadero T14 (N ) = 1
15. Return m
16. Sino
17. Return -1

Resumen de los Tiempos de Ejecución Teóricos de los Algoritmos


de Ordenamiento y Búsqueda

En la Tabla 4.4 se tiene un resumen de los tiempos de ejecución y los


órdenes de complejidad de los algoritmos más representativos de ordena-
miento y búsqueda vistos en este capı́tulo [15][5][9]. Se observa que los me-
jores algoritmos de ordenamiento son los de orden súperlineal, tales como:
Ordenamiento Rápido y Ordenamiento por Montı́culo; mientras que el mejor
algoritmo de búsqueda es el de la Búsqueda Binaria, con orden logarı́tmico.

ITPuebla 217
4.4. RECUPERACIÓN DE DATOS

Tabla 4.4: Tiempos de ejecución teóricos y órdenes de complejidad de algo-


ritmos de ordenamiento y búsqueda representativos.
Ordenamiento Tiempo de Complejidad
Ejecución
O(N )
2
N N 2
Inserción Directa 2
− 2
Cuadrática
O(N )
2
N 3 2
De la Burbuja − +1 N Cuadrática
(N − 1)N O(N )
2 2
2
Rápido Cuadrática
en el peor caso
Rápido ⌊log2 N ⌋N O(N log2 N ) Súperlineal
en el mejor caso
Por Montı́culo 2O(N log2 N ) O(N log2 N ) Súperlineal
Búsqueda Tiempo de Complejidad
Ejecución
Secuencial N O(N ) Lineal
Binaria log2 N O(log2 N ) Logarı́tmica

En diversas fuentes, tales como [16][14][7][5][13][2], pueden encontrarse


estos y otros métodos de ordenamiento y búsqueda, ası́ como sus análisis
de complejidad de tiempo de ejecución. En este texto se presentaron los
métodos más representativos y se trató de ilustrar de manera sencilla y
sintetizada el análisis de los correspondientes algoritmos.

4.4. Recuperación de datos

En esta sección se presenta la forma de recuperar datos almacenados en


archivos. Se considera que un archivo es un conjunto de registros lógica-
mente relacionados almacenados en memoria secundaria; que un registro es
un conjunto de campos lógicamente relacionados; que un campo es un atri-
buto o propiedad de un objeto o entidad [16]. En este caso, la recuperación
consiste en acceder a un registro en particular de un archivo, involucrando
un proceso de búsqueda. La recuperación puede ser de tres formas [16][1]:

Recuperación por acceso secuencial. Para acceder al i–ésimo re-


gistro en el archivo, se deben acceder a los (i − 1) registros previos,

ITPuebla 218
4.4. RECUPERACIÓN DE DATOS

es decir, la búsqueda del registro es secuencial y directamente en el


archivo.

Recuperación por acceso indexado. Para acceder a un registro en


el archivo, no se realiza la búsqueda del registro directamente en el
archivo, sino en una estructura de datos auxiliar, llamada ı́ndice, que
indexa los registros de archivo. Un ı́ndice es una estructura de datos
ordenada que almacena las claves de los registros.

Recuperación por acceso directo. Para acceder a un registro en


el archivo también se utiliza una estructura de datos auxiliar llamada
Tabla Hash que también almacena claves pero no necesariamente or-
denadas. Para insertar, buscar y eliminar claves en la Tabla Hash se
utiliza una función llamada Función Hash.

Para aplicar alguna operación a los registros de un archivo, se deben


realizar los siguientes pasos:

Paso 1. Abrir o crear un archivo. Crear un archivo es marcar el comienzo


del tiempo de vida de un archivo, reservando espacio en el almacenamiento
secundario. Abrir un archivo es prepararlo para aplicarle actualizaciones.
En la Figura 4.10 está un ejemplo de un archivo de estudiantes llamado
ArchAlu.bin residente en memoria secundaria vista como un arreglo, donde
cada registro tiene una dirección lógica o dirección relativa al archivo. El
archivo tiene una marca de fin de archivo (End Of File, EOF). En la figura
se ve que al abrir un archivo, el sistema operativo maneja un apuntador,
aquı́ llamado ApArch, que apunta al primer registro.

ITPuebla 219
4.4. RECUPERACIÓN DE DATOS

Figura 4.10: Abrir un archivo

Paso 2. Acceder al archivo. Aplica alguna operación a sus registros.

Paso 3. Cerrar el archivo. Indica que ha terminado el tiempo para aplicar


actualizaciones a un archivo.

Algunas operaciones que se pueden aplicar a los registros de un


archivo son [16][1]:

Consultar o recuperar un registro (operación de lectura de archi-


vo). Accede a un archivo que ha sido abierto para conocer el contenido
de uno, varios o todos los registros. En la Figura 4.11 se ilustra la
operación de leer o recuperar el registro apuntado por ApArch, este
apuntador avanza al siguiente registro tras la lectura.

ITPuebla 220
4.4. RECUPERACIÓN DE DATOS

Figura 4.11: Leer, consultar o recuperar un registro de un archivo

Actualizar un registro (operación de escritura de archivo). Cambia


el contenido de un archivo que ha sido abierto. La actualización puede
significar:

• Insertar un reistro (dar de Alta un registro). Añade un regis-


tro a un archivo que ha sido creado o abierto, incrementando su
tamaño.

ITPuebla 221
4.4. RECUPERACIÓN DE DATOS

• Modificar un registro. Cambia el (los) valor(es) de alguno(s)


de los atributos de un registro.

En la Figura 4.12 se ilustra la escritura de un nuevo registro en el


archivo en el lugar donde apunta ApArch. Luego de la escritura, ApArch
apunta al siguiente registro.

Figura 4.12: Escribir un registro en un archivo

ITPuebla 222
4.4. RECUPERACIÓN DE DATOS

Eliminar un registro (dar de Baja un registro). Quita un registro


de un archivo que ha sido abierto. La eliminación puede ser lógica
(colocando una señal en algún campo sin modificar el tamaño del ar-
chivo) o fı́sica (eliminando los datos del dispositivo de almacenamiento
secundario y decrementando el tamaño del archivo).

Recorrer todos los registros. Accede a cada uno de los registros


del archivo para aplicarles alguna operación especı́fica. Esta operación
suele utilizarse para listar el contenido de un archivo, accediendo a
los registros en el orden que tengan en el archivo, o accediendo a los
registros ordenados de acuerdo a su clave.

Recuperación por acceso secuencial

Un archivo cuya recuperación de registros es por acceso secuencial, se


llama archivo secuencial. . En un archivo secuencial el orden fı́sico en que
fueron escritos los registros es el orden de lectura de los mismos. Utilizar
un archivo secuencial tiene la ventaja del acceso rápido al siguiente registro
cuando los registros están almacenados de acuerdo a un patrón de acceso
dado, además el uso e implementación de un archivo secuencial son senci-
llos. Por otra parte, un inconveniente de utilizar este tipo de archivos es el
acceso lento al siguiente registro cuando los registros no están almacenados
de acuerdo al patrón de acceso [16][1].

Las operaciones más representativas sobre los registros de este tipo de


archivos se ven en los siguientes algoritmos:

Altas. Escribir un registro en un archivo no existente con el


algoritmo 119.

ITPuebla 223
4.4. RECUPERACIÓN DE DATOS

Algoritmo 119 insertarArchSecNuevo(archivo:File,registro:Object):


void
/* Inserta un registro en un archivo secuencial nuevo **
** Entradas: **
** archivo:File **
** registro:Object **
** Salidas: **
** ninguna */
1. Crear el archivo
2. Escribir registro en el archivo
3. Cerrar el archivo

Altas. Al final de un archivo existente con el algoritmo 120, para


ello el algoritmo tiene un ciclo que lee todos los registros del archivo
para colocar el Apuntador de Archivo después del último registro y
escribir en esa posición el nuevo registro, como se ve en la Figura 4.13.

Algoritmo 120 insertarFinalArchSec(archivo:File,registro:Object):


void
/* Inserta un registro al final de un archivo **
** secuencial **
** Entradas: **
** archivo:File, **
** registro:Object **
** Salidas: **
** ninguna */
1. Abrir el archivo
2. reg=Leer un registro del archivo
3. Mientras reg≠EOF
4. reg=Leer un registro del archivo
5. Escribir registro en el archivo
6. Cerrar el archivo

ITPuebla 224
4.4. RECUPERACIÓN DE DATOS

Figura 4.13: Esquema de la ejecución del Algoritmo 120 que da de alta un


registro al final de un Archivo Secuencial. Los pasos del algoritmo se indican
en los cı́rculos.

Altas. Al inicio de un archivo existente, para lo cual se necesita


hacer un corrimiento de todos los registros una posición hacia adelante
en el archivo (llamado archivo fuente) utilizando un archivo auxiliar,
como en el algoritmo 121. Este algoritmo se ilustra en la Figura 4.14.

ITPuebla 225
4.4. RECUPERACIÓN DE DATOS

Algoritmo 121 insertarInicioArchSec(archivoFuente:File,


nuevoRegistro: Object):void
/* Inserta un registro al inicio de un archivo **
** secuencial **
** Entradas: **
** archivoFuente:File, **
** nuevoRegistro:Object **
** Salidas: **
** ninguna */
1. Abrir el archivoFuente
2. Crear el archivoAuxiliar
3. Escribir nuevoRegistro en el archivoAuxiliar
4. reg=Leer un registro del archivoFuente
5. Mientras reg≠EOF
6. Escribir reg en el archivoAuxiliar
7. reg=Leer un registro del archivoFuente
8. Cerrar el archivoAuxiliar
9. Cerrar el archivoFuente
10.Renombrar el archivoAuxiliar con el nombre del
archivoFuente

ITPuebla 226
4.4. RECUPERACIÓN DE DATOS

Figura 4.14: Esquema de la ejecución del Algoritmo 121 que da de alta un


registro al inicio de un Archivo Secuencial. Los pasos del algoritmo se indican
en los cı́rculos.

Altas. Entre dos registros de un archivo existente con el algo-


ritmo 122, que se muestra en la Figura 4.15.

ITPuebla 227
4.4. RECUPERACIÓN DE DATOS

Algoritmo 122 insertarPosiArchSec(archivoFuente:File,


nuevoRegistro: Object, posición:int):void
/* Inserta un registro en una posición dada de **
** un archivo secuencial **
** Entradas: **
** archivoFuente:File, **
** nuevoRegistro:Object **
** posición:int **
** Salidas: **
** ninguna */
1. Abrir el archivoFuente
2. Crear el archivoAuxiliar
3. contador=0
4. reg=Leer un registro del archivoFuente
5. Mientras (reg≠EOF) && (contador<posición)
6. Escribir reg en el archivoAuxiliar
7. reg=Leer un registro del archivoFuente
8. contador=contador+1
9. Escribe nuevoRegistro en el archivoAuxiliar
10. Mientras reg≠EOF
11. Escribir reg en el archivoAuxiliar
12. reg=Leer un registro del archivoFuente
13. Cerrar el archivoAuxiliar
14. Cerrar el archivoFuente
15. Renombrar el archivoAuxiliar con el nombre del
archivoFuente

ITPuebla 228
4.4. RECUPERACIÓN DE DATOS

Figura 4.15: Esquema de la ejecución del Algoritmo 122 que inserta un nuevo
registro en la posición lógica 3 de un Archivo Secuencial. En los cı́rculos se
indican los números de paso del algoritmo.

Bajas. Para eliminar fı́sicamente el primer registro de un archivo


no vacı́o se tiene el algoritmo 123, que se ilustra en la Figura 4.16.
Este algoritmo hace uso de un archivo auxiliar.

ITPuebla 229
4.4. RECUPERACIÓN DE DATOS

Algoritmo 123 eliminarInicioArchSec(archivoFuente:File):Object

/* Elimina el primer registro de un archivo secuencial **


** Entradas: **
** archivoFuente:File, **
** Salidas: **
** registro:Object, registro eliminado */
1. Abrir el archivoFuente
2. Crear el archivoAuxiliar
3. /* Lee el registro que se va a eliminar */
4. registro=Leer un registro del archivoFuente
5. /* Lee el segundo registro del archivo */
6. reg=Leer un registro del archivo fuente
7. Mientras reg≠EOF
8. Escribir reg en el archivoAuxiliar
9. reg=Leer un registro del archivo fuente
10. Cerrar el archivoAuxiliar
11. Cerrar el archivoFuente
12. Renombrar el archivo Auxiliar con el nombre del
archivoFuente
13. Return registro

ITPuebla 230
4.4. RECUPERACIÓN DE DATOS

Figura 4.16: Esquema de la ejecución del Algoritmo 123 que elimina el primer
registro de un Archivo Secuencial. En los cı́rculos se indican los números de
paso del algoritmo.

Bajas. Para eliminar fı́sicamente el último registro de un archivo


no vacı́o se tiene el algoritmo 124, que se ilustra en la Figura 4.17.

ITPuebla 231
4.4. RECUPERACIÓN DE DATOS

Algoritmo 124 eliminarFinalArchSec(archivoFuente:File):Object


/* Elimina el último registro de un archivo secuencial **
** Entradas: **
** archivoFuente:File, **
** Salidas: **
** registro:Object, registro eliminado */
1. Abrir el archivoFuente
2. Crear el archivoAuxiliar
3. regActual=Leer un registro del archivoFuente
4. regSiguiente=Leer un registro del archivoFuente
5. Mientras regSiguiente≠EOF
6. Escribir regActual en el archivoAuxiliar
7. regActual=regSiguiente
8. regSiguiente=Leer un registro del archivoFuente
9. Cerrar el archivoAuxiliar
10. Cerrar el archivoFuente
11. Renombrar el archivoAuxiliar con el nombre del
archivoFuente

Figura 4.17: Esquema de la ejecución del Algoritmo 124 que elimina el último
registro de un Archivo Secuencial.

Bajas. Para eliminar fı́sicamente un registro de cierta posición

ITPuebla 232
4.4. RECUPERACIÓN DE DATOS

de un archivo no vacı́o se tiene el algoritmo 122, cambiando el paso


9: en lugar de escribir un registro, se lee un registro, perdiendo ası́ el
registro a eliminar.

Modificaciones. Para modificar un registro, primero se busca de


acuerdo a su clave, luego se lee para mostrar sus valores y luego se
escriben los nuevos valores, como en el algoritmo 125. El registro a
modificar se especifica mediante su dirección lógica. Para volver a es-
cribir el registro en el archivo se necesita cerrarlo y volver a abrirlo
para que el Apuntador de Archivo vuelva a posicionarse en el primer
registro y se avance nuevamente a la dirección lógica donde debe es-
cribirse el registro modificado. Con este algoritmo se puede modificar
el primer, el último o cualquier otro registro.

ITPuebla 233
4.4. RECUPERACIÓN DE DATOS

Algoritmo 125 modificaPosiArchSec(archivo:File, reg:Object,


posi:int): Object
/* Modifica el registro de una posición dada en **
** un archivo secuencial **
** Entradas: **
** archivo:File **
** reg:Object **
** posi:int **
** Salidas: **
** reg:Object, registro modificado */
1. Abrir el archivo
2. contador=0
3. reg=Leer un registro del archivo
4. Mientras (reg≠EOF)&&(contador<posi)
5. reg=Leer un registro del archivo
6. contador=contador+1
7. Si reg≠EOF
8. Muestra campos de reg
9. Modifica campos de reg
10. Cerrar el archivo
11. Abrir el archivo
12. contador=0
13. regaux=Leer un registro del archivo
14. Mientras contador<posi
15. regaux=Leer un registro del archivo
16. contador=contador+1
17. Escribir reg en el archivo
18. Cerrar el archivo

Consultas.
Para consultar o ver el contenido de un registro de un Archivo Secuen-
cial, se requiere de un proceso de búsqueda del registro en el archivo.
El proceso de búsqueda ya se ha empleado en las operaciones de AL-
TAS, BAJAS y MODIFICACIONES en los casos en que se necesita
localizar en el archivo un registro especı́fico. Con el Algoritmo 126 se
localiza el registro de una dirección lógica concreta en el archivo y se
lee para su consulta. Este algoritmo puede modificarse para localizar
un registro con una clave especı́fica.

ITPuebla 234
4.4. RECUPERACIÓN DE DATOS

Algoritmo 126 consultaPosiArchSec (archivo:File, posi:int):


Object
/* Consulta el registro de una posición dada en **
** un archivo secuencial **
** Entradas: **
** archivo:File **
** posi:int **
** Salidas: **
** reg:Object, registro consultado */
1. Abrir el archivo
2. contador=0
3. reg=Leer un registro del archivo
4. Mientras (reg≠EOF)&&(contador<posi)
5. reg=Leer un registro del archivo
6. contador=contador+1
7. Return reg

Recorrido. En el Algoritmo 127 se recupera cada registro del archivo,


desde el primero hasta el último, y se va mostrando el valor de sus
atributos.

Algoritmo 127 recorreArchSec(archivo:File, posi:int): Object


/* Recorre desde el primero hasta el último **
** registro de un archivo secuencial **
** Entradas: **
** archivo:File **
** Salidas: **
** ninguna */
1. Abrir el archivo
2. reg=Leer un registro del archivo
3. Mientras reg≠EOF
4. Mostrar campos de reg
5. reg=Leer un registro del archivo
6. Cerrar el archivo

ITPuebla 235
4.4. RECUPERACIÓN DE DATOS

Recuperación por acceso indexado

Un archivo cuya recuperación de registros es por acceso indexado, se lla-


ma archivo indexado. . Para acceder a los registros de este tipo de archivo,
se utiliza un ı́ndice, que es una estructura de datos que almacena claves
ordenadas con sus correspondientes direcciones relativas de los registros del
archivo. Utilizar un archivo indexado tiene la ventaja de que las bśquedas
de registros son rápidas al buscar en datos ordenados en memoria principal
(en el ı́ndice) y el acceso al archivo es directo, sin tener que recorrerlo. Por
otra parte, un inconveniente es que se requiere espacio adicional en memoria
para almacenar los ı́ndices [16][1].

La forma de recuperar o acceder a los registros en este tipo de archivo


se ve en el algoritmo 128. Este algoritmo se aplica para las cualquier opera-
ción sobre los registros de un archivo indexado: altas, bajas, modificaciones,
consultas.

Algoritmo 128 accederRegistroArchIndex(archivo:File,clave:


String): Object
/* Accede a un registro con cierta clave, para **
** aplicarle cierta operacién en un archivo indexado **
** Entradas: **
** archivo:File **
** ı́ndice:Índice **
** clave:String **
** Salidas: **
** Ninguna */
1. dirRel=indice.busca(clave)
2. Abrir el archivo
3. Colocar el Apuntador de Archivo en dirRel
4. Acceder (leer/escribir/modificar) al registro apuntado por
dirRel
5. Cerrar el archivo

Una tabla de directorio es un ı́ndice, es un arreglo ordenado de objetos,


cada uno de ellos almacena la clave de un registro y su dirección relativa al
archivo. El orden se establece de acuerdo a la clave de los registros [16][1].
Ver ilustración en la Figura 4.18.

ITPuebla 236
4.4. RECUPERACIÓN DE DATOS

Figura 4.18: Tabla de Directorio para indizar un archivo

Antes de revisar las operaciones básicas sobre registros de un archivo in-


dexado por una tabla de directorio, se muestran los algoritmos que permiten
buscar, insertar y eliminar objetos en una tabla de directorio. En los algo-
ritmos, la tabla de directorio es un vector de objetos denoto con tablaDir,
donde la entrada i contiene una clave (cuyo acceso y asignación de valores se
denotan con tablaDir[i].getClave(), tablaDir[i].setClave(valor)) y
una dirección relativa asociada a la clave (cuyo acceso y asignación de valores
se denotan con tablaDir[i].getDirRel(), tablaDir[i].setDirRel(valor)).

Buscar en una tabla de directorio. En el algoritmo 129 se busca


una clave en una tabla de directorio y regresa la dirección relativa
asociada a esa clave, que indica la posición que ocupa tal registro en
un archivo indexado.

ITPuebla 237
4.4. RECUPERACIÓN DE DATOS

Algoritmo 129 buscar(tablaDir:Vector<Object>, clave:String):


int
/* Aplica el algoritmo de la búsqeuda secuencial para **
** buscar una clave en una tabla de directorio y regresa **
** la dirección relativa del registro correspondiente **
** en un archivo indexado **
** Entradas: **
** tablaDir:Vector<Object> **
** clave:String **
** Salidas: **
** dirRel:int, posición del registro en **
** el archivo */

1. i=0
2. encontrado=Falso
3. Mientras (i<tablaDir.length())&&(encontrado=Falso)
4. Si tablaDir[i].getClave()=clave
5. encontrado=Verdadero
6. Sino
7. i=i+1
8. Si encontrado
9. Return tablaDir[i].getDirRel()
10. Sino
11. Return -1 /* clave no encontrada */

Insertar un componente en una tabla de directorio. En el


algoritmo 130 se insertan en una tabla de directorio una clave y la
dirección relativa de un registro almacenado en un archivo indexado
(ver Figura 4.19, en (a) se hace espacio para en (b) insertar el nuevo
componete).

ITPuebla 238
4.4. RECUPERACIÓN DE DATOS

Algoritmo 130 insertar(tablaDir:Vector<Object>, clave:String,


dirRel:int): boolean
/* Aplica el algoritmo de la inserción directa para **
** insertar ordenadamente una clave y una dirección **
** relativa en una tabla de directorio. Regresa la **
** dirección relativa si inserción existosa, regresa -1 **
** si la clave ya está en la tabla de directorio **
** Entradas: **
** tablaDir:Vector<Object> **
** clave:String **
** dirRel:int **
** Salidas: **
** bandera:boolean */

1. dirRel = tablaDir.buscar(clave)
2. Si dirRel=-1 /* la clave no está en la tablaDir */
3. j=tablaDir.getNumeroClaves()-1
4. parar=Falso
5. Mientras (j≥0)&&(!parar)
6. Si tablaDir[j].getClave()>clave
7. tablaDir[j+1].setClave(tablaDir[j].getClave()
8. tablaDir[j+1].setDirRel(tablaDir[j].getDirRel()
9. j=j-1
10. Sino
11. tablaDir[j+1].setClave(clave)
12. tablaDir[j+1].setDirRel(dirRelativa)
13. parar=Verdadero
14. Return dirRel

ITPuebla 239
4.4. RECUPERACIÓN DE DATOS

Figura 4.19: Ejemplo de la inserción de una pareja {clave, dirRelativa} en


una Tabla de Directorio

Eliminar un componente de una tabla de directorio. En el


algoritmo 131 se elimina un componente de una tabla de directorio
correspondiente a una clave dada. En la Figura 4.20 (a) se elimina
un componente y en (b) se recorren los componentes de la tabla de
directorio.

ITPuebla 240
4.4. RECUPERACIÓN DE DATOS

Algoritmo 131 eliminar(tablaDir:Vector<Object>, clave:String):


int
/* Elimina un componente de una tabla de directorio, **
** correspondiente a una clave dada. Regresa la **
** dirección relativa asociada a la clave **
** Entradas: **
** tablaDir:Vector<Object> **
** clave:String **
** Salidas: **
** dirRel:int */
1. dirRel=tablaDir.buscar(clave)
2. Si dirRel≠-1
3. n=tablaDir.getNumeroClaves()-1
5. Para j=i,i+1,...,n-1
6. tablaDir[j].getClave(tablaDir[j+1].getClave()
7. tablaDir[j].getDirRel(tablaDir[j+1].getDirRel())
8. tablaDir[n].setClave(null)
9. tablaDir[n].setDirRel(-1)
10. Return dirRelativa /* eliminación exitosa */
11. Sino
12. Return -1 /* clave no existe en tablaDir */

Figura 4.20: Ejemplo de la eliminación de una pareja {clave, dirRelativa}


en una Tabla de Directorio.

Los puntos a considerar antes de introducir los algoritmos de las opera-


ciones con un archivo indexado son:

ITPuebla 241
4.4. RECUPERACIÓN DE DATOS

La posición que ocupan los registros en el archivo es irrelevante, pues


su control se da mediante el ı́ndice, que es ordenado.

El ı́ndice considerado será una Tabla de Directorio, a la que se lla-


mará T ablaDirectorio, donde T abla[i].clave denota la clave almace-
nada en la entrada i de la Tabla de Directorio y T abla[i].dirRelativa
denota la dirección relativa asociada a la clave de la entrada i de la
Tabla de Directorio.

Las bajas de registros se consideran bajas lógicas, por lo que las di-
recciones relativas que se van liberando tras las bajas se almacenan
en una lista ligada llamada ListaLibres. A través de ListaLibres se
controlan las posiciones del archivo que van quedando disponibles para
nuevas altas de registros.

Las bajas de registros se consideran bajas lógicas, por lo que las di-
recciones relativas que se van liberando tras las bajas se almacenan
en una lista ligada llamada ListaLibres. A través de ListaLibres se
controlan las posiciones del archivo que van quedando disponibles para
nuevas altas de registros.

El apuntador de archivo se denota con apArch.

La operación que permite posicionar el apuntador de archivo en un


registro concreto del archivo para accederlo se denota con SEEK.

Ahora se presetan las operaciones de altas, bajas, modificaciones, con-


sultas y recorrido de un archivo indexado por una tabla de directorio:

Altas. Algoritmo 132.

Bajas. Algoritmo 133.

Modificaciones. Algoritmo 134.

Consultas. Algoritmo 135.

Recorrido. Algoritmo 136.

ITPuebla 242
4.4. RECUPERACIÓN DE DATOS

Algoritmo 132 insertar(archivo:File,tablaDir:Vector<Object>,


ListaLibres:ListaLigada, reg:Object): Object
/* Inserta un registro en un archivo indexado por una **
** tabla de directorio. Regresa el registro insertado **
** Entradas: **
** archivo:File **
** tablaDir:Vector<Object> **
** ListaLibres:ListaLigada **
** reg:Object **
** Salidas: **
** reg:Object */
1. Si !ListaLibres.esVacia() /* toma la dirección de un
registro libre */
2. dirRel=ListaLibres.eliminarIni()
3. dirReldeListaLibres=Verdadero
4. Sino /* tomar la dirección del final del archivo */
5. dirRel=archivo.length()
6. dirReldeListaLibres=Falso
7. otraDirRel=tablaDir.insertar(reg.getClave(),dirRel)
8. Si otradirRel=-1 /* reg no está en el archivo */
9. archivo.abrir()
6. archivo.seek(dirRel) /* coloca apArch en dirRel del
archivo */
7. archivo.escribir(reg,apArch)
8. archivo.cerrar()
10. Return reg
11.Sino /* reg está en el archivo */
12. Return null

ITPuebla 243
4.4. RECUPERACIÓN DE DATOS

Algoritmo 133 eliminar(archivo:File,tablaDir:Vector<Object>,


clave:String): Object
/* Elimina un registro en un archivo indexado por una **
** tabla de directorio. Regresa el registro eliminado **
** Entradas: **
** archivo:File **
** tablaDir:Vector<Object> **
** clave:String **
** Salidas: **
** reg:Object, registro eliminado */
1. dirRel=tablaDir.eliminar(clave)
2. Si dirRel≠-1 /* clave encontrada */
3. ListasLibres.insertarFinal(dirRel)
4. archivo.abrir()
5. archivo.seek(dirRel)
6. reg=archivo.leerRegistro()
7. archivo.cerrar()
8. Return reg /* Eliminación exitosa */
9. Sino /* clave no encontrada */
10. Return null /* Eliminación no exitosa */

ITPuebla 244
4.4. RECUPERACIÓN DE DATOS

Algoritmo 134 modificar(archivo:File,tablaDir:Vector<Object>,


reg:Object): Object
/* Modifica un registro en un archivo indexado por una **
** tabla de directorio. Retorna el registro modificado **
** Entradas: **
** archivo:File **
** tablaDir:Vector<Object> **
** reg:Object, nuevo registro **
** Salidas: **
** regm:Object, registro modificado */
1. dirRel=tablaDir.buscar(reg.getClave())
2. Si dirRel≠-1 /* clave encontrada */
3. archivo.abrir()
4. archivo.seek(dirRel)
5. regm=archivo.leerRegistro()
6. archivo.seek(dirRel)
7. archivo.escribirRegistro(reg)
8. archivo.cerrar()
9. Return regm /* modificación exitosa */
10. Sino
11. Return null /* modificación no exitosa */

ITPuebla 245
4.4. RECUPERACIÓN DE DATOS

Algoritmo 135 consultar(archivo:File,tablaDir:Vector<Object>):


Object
/* Dada una clave, consulta o recupera el registro **
** correspondiente de un archivo indexado por una **
** tabla de directorio **
** Entradas: **
** archivo:File **
** tablaDir:Vector<Object> **
** clave:String **
** Salidas: **
** reg:Object, registro recuperado */
1. dirRel=tablaDir.buscar(clave)
2. Si dirRel≠-1 /* clave encontrada */
3. archivo.abrir()
4. archivo.seek()
5. reg=archivo.leerRegistro()
6. archivo.cerrar()
7. Return Verdadero /* consulta exitosa */
8. Sino
9. Return Falso /* consulta no exitosa */

Algoritmo 136 recorrer(archivo:File,tablaDir:Vector<Object>):


void
/* Dado un archivo indexado por una tabla de **
** directorio, recorre todos los registros en orden **
** de acuerdo a sus claves **
** Entradas: **
** archivo:File **
** tablaDir:Vector<Object> **
** Salidas: **
** Ninguna */
1. archivo.abrir()
2. Para i=0,1,...,tablaDir.length()
3. dirRel=tablaDir[i].getDirRel()
4. archivo.seek()
5. reg=archivo.leerRegistro()
6. reg.aplicaProcesamiento()
7. archivo.cerrar()

ITPuebla 246
4.4. RECUPERACIÓN DE DATOS

Recuperación por acceso directo

Un archivo cuya recuperación de registros es por acceso directo, se llama


archivo directo, en el que cada registro se almacena de forma que pue-
da ser accedido mediante la transformación de su llave o clave [16][1]. . En
un archivo directo también se utiliza una estructura de datos auxiliar para
acceder a sus registros, llamada Tabla Hash o Tabla de Dispersión o
tabla hash. A diferencia de una tabla de directorio, la tabla de dispersión
no está ordenada. El acceso a los elementos de una tabla de dispersión se
realiza mediante una Función Hash o Función de Dispersión. La fun-
ción de dispersión opera sobre una clave y dá como resultado una dirección
relativa y tiene la forma:

f (clave) = dirRel,

donde, dada la clave de un registro, la función de transformación f indi-


cará la dirección relativa dirRel donde el registro se almacena en el archivo.

Cuando f (clave) = clave, se tiene un direccionamiento absoluto y estric-


tamente un archivo directo (ver Figura 4.21). Algunos inconvenientes de este
direccionamiento son que pueden quedar huecos libres en el archivo y que los
valores de las claves podrı́an ser muy grandes, desaprovechando memoria.

ITPuebla 247
4.4. RECUPERACIÓN DE DATOS

Figura 4.21: Ejemplo de organización de registros en un Archivo Directo con


transformación de claves mediante direccionamiento absoluto

Cuando f (clave) = dirRel, se tiene un direccionamiento relativo y un ar-


chivo directo también conocido como archivo relativo [16][1]. En la Figura
4.22) se ilustra esto. Aunque se mejora la distribución de los registros en el
archivo, sigue habiendo desperdicio de espacio. Para evitar esto, se utilizan
las tablas de dispersión, también llamadas tablas de acceso directo (ver
Figura 4.23).

ITPuebla 248
4.4. RECUPERACIÓN DE DATOS

Figura 4.22: Ejemplo de organización de registros en un Archivo Directo con


transformación de claves mediante direccionamiento relativo.

ITPuebla 249
4.4. RECUPERACIÓN DE DATOS

Figura 4.23: Ejemplo de uso de una Tabla de Acceso Directo como estructura
de datos auxiliar para el acceso a un Archivo Directo.

Cuando se utilizan funciones de dispersión, es probable que para dos


claves ci y cj , con i ≠ j, suceda que f (ci ) = f (cj ), es decir que dos claves
diferentes se dispersen al mismo componente de la tabla de dispersión. A
este evento se le denomina colisión. En vista de esto, dos de las propiedades
que debe tener una buena función de dispersión son [16]:

La función de dispersión debe poder calcularse rápidamente, es decir,


debe requerir pocas operaciones simples.

La función de dispersión debe minimizar colisiones, es decir, no de-


be mostrar un sesgo hacia ningún hueco en particular de la tabla de
dispersión.

Al igual que en un Archivo Indexado, para acceder a un registro en un

ITPuebla 250
4.4. RECUPERACIÓN DE DATOS

Archivo Directo se hace una búsqueda de la clave del registro en la Tabla


de Dispersión, que es una búsqueda eficiente; una vez localizada la clave
en la Tabla de Dispersión, se puede conocer la dirección relativa asociada
para acceder al registro en el archivo. Un algoritmo genérico para el acceso
a un registro de un Archivo Directo consiste de los mismos pasos que el
algoritmo 128, sustituyendo el paso 1 de acceso a Índice por acceso a Tabla
de Dispersión, como en el algoritmo 137.

Algoritmo 137 accederRegistroArchDirec(archivo:File,clave:


String): Object
/* Accede a un registro con cierta clave, para **
** aplicarle cierta operacién en un archivo indexado **
** Entradas: **
** archivo:File **
** ı́ndice:Índice **
** clave:String **
** Salidas: **
** Ninguna */
1. dirRel=tablaDispersion.busca(clave)
2. Abrir el archivo
3. Colocar el Apuntador de Archivo en dirRel
4. Acceder (leer/escribir/modificar) al registro apuntado por
dirRel
5. Cerrar el archivo

Para poder presentar diferentes formas de la función de disperción (méto-


dos de dispersión), considerar que:

M es el tamaño de la tabla de dispersión, es decir el número de com-


ponentes de un arreglo denominado TD.

N es el número de claves a dispersar en TD.

M>N, es decir, el número de componentes de TD debe ser mayor que el


número de claves a dispersar.

El factor de carga, definido como la relación del número de claves a


N
dispersar contra el número de componentes de TD, F actorCarga = M ,
que mide la saturación de una tabla de dispersión con M componentes,

ITPuebla 251
4.4. RECUPERACIÓN DE DATOS

debe ser, a lo más, igual que 0.7, es decir, TD no debe llenarse con claves
más allá del 70 % de su capacidad [16].

c denota una clave arbitraria.

C denota el universo de claves. En la mayorı́a de los casos C es un


subconjunto de los números naturales.

Si C es un conjunto de cadenas, las claves se pueden representar como


un entero tomando la ”notación desarrollada”de la cadena. Por ejem-
plo, si c = ”ptr”, su ”notación desarrollada”, tomando como base 128
(el número de sı́mbolos del código ASCII), es:

p × 1282 + t × 1281 + r × 1280 =


= 112 × 1282 + 116 × 1281 + 114 × 1280 = 1849970,

por lo tanto, en lugar de utilizar c = ”ptr” se utiliza c = 1849970.

Ahora, dos de los métodos de dispersión representativos son:

Método de la División, se define como [13]:

f (c) = c mod M,

donde f mapea la clave c con el ı́ndice que se obtiene del residuo de


la división de la clave con el número de componentes de la TD M . Se
recomienda [16] [13] que M sea número primo, y que no sea potencia
de 2 ni de 10, para minimizar colisiones. Por ejemplo, para dispersar
los N = 6 registros del archivo de la Figura 4.24, tomando M = 11,
que no es potencia de 2, no es potencia de 10 y es número primo,
además de que el factor de carga es N /M = 6/11 =0.54, es decir, la TD
estará llena hasta un 54 %, se obtienen los siguientes resultados (con
una colisión entre las claves 09220010 y 09220021):
f (09220001) = 9220001 mod 11 = 10
f (09220010) = 9220010 mod 11 = 8
f (09220005) = 9220005 mod 11 = 3
f (09220013) = 9220013 mod 11 = 0
f (09220008) = 9220008 mod 11 = 6
f (09220021) = 9220021 mod 11 = 8

ITPuebla 252
4.4. RECUPERACIÓN DE DATOS

Figura 4.24: Ejemplo de uso de una Tabla Hash como estructura de datos
auxiliar para el acceso a un Archivo Directo utilizando el Método de la
División

Método de la Multiplicación, se define como [13]:

f (c) = f loor (M × (c × K − f loor (c × K))) ,


donde 0 < K < 1 y un valor recomendado en [13] es K = 0.61803399;
el operador f loor realiza el redondeo hacia abajo de su parámetro. Por
ejemplo, al dispersar las N = 6 claves de la Figura 4.25, con M = 11
y K=0.61803399, se obtienen los siguientes resultados (con colisiones
entre 09220021 y 09220013):
f (09220001) = f loor (11 × (9220001 × 0.61803399 − [9220001 × 0.61803399])) =
= f loor (11 × (5698274.00583399 − 5698274)) =
= f loor (11 × 0.00583399) = 0

f (09220010) = f loor (11 × (9220010 × 0.61803399 − [9220010 × 0.61803399])) =


= f loor (11 × (5698279.56813990 − 5698279)) =
= f loor (11 × 0.56813990) = 6

f (09220005) = f loor (11 × (9220005 × 0.61803399 − [9220005 × 0.61803399])) =


= f loor (11 × (5698276.47796995 − 5698276)) =
= f loor (11 × 0.47796995) = 5

f (09220013) = f loor (11 × (9220013 × 0.61803399 − [9220013 × 0.61803399])) =


= f loor (11 × (5698281.42224187 − 5698281)) =
= f loor (11 × 0.42224187) = 4

ITPuebla 253
4.4. RECUPERACIÓN DE DATOS

f (09220008) = f loor (11 × (9220008 × 0.61803399 − [9220008 × 0.61803399])) =


= f loor (11 × (5698278.33207192 − 5698278)) =
= f loor (11 × 0.33207192) = 3

f (09220021) = f loor (11 × (9220021 × 0.61803399 − [9220021 × 0.61803399])) =


= f loor (11 × (5698286.36651379 − 5698286)) =
= f loor (11 × 0.36651379) = 4

Figura 4.25: Ejemplo de uso de una tabla de dispersión como estructura de


datos auxiliar para el acceso a un Archivo Directo utilizando el Método de
la Multiplicación

Método de la dispersión abierta, es un método para resolver coli-


siones mediante la contrucción de una lista ligada en la entrada de la
TD que contenga todas aquellas claves que se colisionan en tal entrada.
Ası́, los componentes de la TD ya no serán direcciones relativas al archi-
vo, sino que serán cabezas de listas ligadas que apuntan a nodos que
almacenan: una clave dispersada, la dirección relativa asociada a la
clave y un apuntador al siguiente nodo. Por ejemplo, para resolver las
colisiones de la Figura 4.24, las claves que se dispersan en la entrada
8, quedan en la misma lista ligada, como se ve en la Figura 4.26.

ITPuebla 254
4.4. RECUPERACIÓN DE DATOS

Figura 4.26: Ejemplo del Método de la Dispersión Abierta para resolver


colisiones cuando se utiliza una Tabla Hash como estructura de datos auxiliar
para el acceso a un Archivo Directo utilizando el Método de la División

Las operaciones para buscar, insertar y eliminar una clave en una TD,
con el método de la dispersión abierta, están en los algoritmos 138, 139
y 140, correspondientemente, donde FuncionHash(clave) es un llamado a
un algoritmo que puede aplicar algún método de dispersión, como el de la
división o la multiplicación.

ITPuebla 255
4.4. RECUPERACIÓN DE DATOS

Algoritmo 138 buscarClaveTD(TD:Nodo[],clave:String):int


/* Busca una clave en una TD con el método de la **
** dispersión abierta **
** Entradas: **
** TD:Nodo[] **
** clave:String **
** Salidas: **
** clave:String */
1. i=FuncionHash(clave)
2. nodoAux=TD[i]
3. encontrado=Falso
4. Mientras (nodoAux≠null)&&(encontrado=Falso)
5. Si nodoAux.getClave()=clave
6. encontrado=Verdadero
7. Sino
8. nodoAux=nodoAux.getSig()
9. Si encontrado
10. Return nodoAux.getDirRel()
11. Sino
12. Return -1 /* dirección relativa no válida */

ITPuebla 256
4.4. RECUPERACIÓN DE DATOS

Algoritmo 139 insertarClaveTD(TD:Nodo[], clave:String,


dirRel:int): Object
/* Inserta una clave y dirección relativa asociada, en **
** una TD con el método de la dispersión abierta **
** Entradas: **
** TD:Nodo[] **
** clave:String **
** dirRel:int **
** Salidas: **
** clave:String */
1. i=TD.buscar(clave)
2. Si i=-1 /* clave no ya existe en TD */
3. nuevoNodo=new NuevoNodo()
4. nuevoNodo.setClave(clave)
5. nuevoNodo.setDirRel(dirRel)
6. nuevoNodo.setSig(TD[i])
7. TD[i]=nuevoNodo
8. Return clave /* inserción exitosa */
9. Sino
10. Return null /* clave ya existe en TD */

ITPuebla 257
4.4. RECUPERACIÓN DE DATOS

Algoritmo 140 eliminarClaveTD(TD:Nodo[],clave:String):int


/* Elimina una clave y dirección relativa asociada, de **
** una TD con el método de la dispersión abierta. **
** Regresa la dirección relativa **
** Entradas: **
** TD:Nodo[] **
** clave:String **
** Salidas: **
** dirRel:int */
1. nodoAnterior=null
2. i=FuncionHash(clave)
3. nodoAux=TD[i]
4. encontrado=Falso
5. Mientras (nodoAux≠null)&&(encontrado=Falso)
6. Si nodoAux.getClave=clave
7. encontrado=Verdadero
8. Sino
9. nodoAnterior=nodoAux
10. nodoAux=nodoAux.getSig()
11. Si encontrado=Verdadero
12. Si nodoAnterior=null /* clave encontrada en la cabeza
de la lista ligada */
13. TD[i]=nodoAux.getSig()
14. Sino /* clave encontrada en un nodo interior de la
lista ligada */
15. nodoAnterior.setSig(nodoAux.getSig())
16. Return nodoAux.getDirRel() /* eliminación exitosa */
17. Sino
18. Return -1 /* la clave no existe en TD */

Antes de introducir los algoritmos de las operaciones sobre archivos di-


recto, considerar las siguientes suposiciones:

La posición que ocupan los registros en el archivo es irrelevante, pues


su control se da mediante la tabla de dispersión (TD).

Para la resolución de colisiones se condiera el Método de la Dispersión


Abierta, con lo que TD[i] representa un apuntador al primer nodo de
una lista ligada.

ITPuebla 258
4.4. RECUPERACIÓN DE DATOS

Cada nodo de cualquier lista ligada de TD, tiene tres campos: una clave
dispersada, la dirección relativa asociada a la clave, y un apuntador al
siguiente nodo.

Las bajas de los registros se consideran lógicas, por lo que las direccio-
nes relativas que se liberan se almacenan en una lista ligada llamada
ListaLibres, a través de la cual se controlan las posiciones del archi-
vo que van quedando disponibles para nuevas altas de registros. Esto
también significa que, fı́sicamente, los registros eliminados permanecen
en el Archivo Directo, sin embargo podrán ser reutilizados al dar de
alta nuevos registros.

Las operacione sobre los registros de un archivo directo se ven en los


siguientes algoritmos:

Altas. Algoritmo 141.

Bajas. Algoritmo 142.

Modificaciones. Algoritmo 143.

Consultas. Algoritmo 144.

Recorrido. Algoritmo 145. En este algoritmo, el recorrido se realiza


de acuerdo a cómo aparecen las claves en la TD desde la primera,
hasta la última entrada. El recorrido no resulta ordenado con respecto
a las claves ni a ningún otro atributo de los registros, porque la TD
no es ordenada. Este recorrido es equivalente a recorrer directamente
el archivo sin hacer uso de la TD (podrı́a utilizarse el algoritmo que
recorre un archivo secuencial).

ITPuebla 259
4.4. RECUPERACIÓN DE DATOS

Algoritmo 141 insertar(archivo:File, TD:Nodo[], reg:Object):


Object
/* Inserta un registro en un archivo directo. Regresa **
** el registro insertado **
** Entradas: **
** archivo:File **
** TD:Nodo[] **
** reg:Object **
** Salidas: **
** reg:Object */
1. Si !ListaLibres.esVacia() /* tomar dirRel de un registro
libre */
2. dirRel=ListaLibres.eliminarIni()
3. dirReldeListaLibres=Verdadero
4. Sino /* tomar la dirRel del final del archivo */
5. dirRel=archivo.size()/reg.size() /* número de registros
en el archivo */
6. dirReldeListaLibres=Falso
7. InsercionExitosa=TD.insertarClaveTD(clave,dirRel)
8. Si InsercionExitosa=Verdadero /* clave no duplicada */
9. /* Insertar el registro en el archivo */
10. archivo.abrir()
11. archivo.seek(dirRel)
12. archivo.escribir(reg)
13. archivo.cerrar()
14. Return reg
15. Sino /* clave duplicada, no se inserta reg en archivo */
16. Si dirReldeListaLibres=Verdadero
17. ListaLibres.insertarIni(dirRel)
18. Return null

ITPuebla 260
4.4. RECUPERACIÓN DE DATOS

Algoritmo 142 eliminar(archivo:File, TD:Nodo[], clave:String):


Object
/* Elimina lógicamente un registro en un archivo **
** directo con clave dada. Regresa el registro **
** eliminado **
** Entradas: **
** archivo:File **
** TD:Nodo[] **
** clave:String **
** Salidas: **
** reg:Object */
1. dirRel=TD.eliminarClaveTD(clave)
2. Si dirRel≠-1 /* clave encontrada */
3. ListaLibres.insertarIni(dirRel)
4. archivo.abrir()
5. archivo.seek(dirRel)
6. reg=archivo.leerRegistro()
7. Return reg
8. Sino /* clave no encontrada */
9. Return null /* Eliminación no exitosa */

ITPuebla 261
4.4. RECUPERACIÓN DE DATOS

Algoritmo 143 modificar(archivo:File, TD:Nodo[], reg:Object):


Object
/* Modifica un registro en un archivo directo. Regresa **
** el registro modificado **
** Entradas: **
** archivo:File **
** TD:Nodo[] **
** reg:Object, nuevo registro **
** Salidas: **
** regm:Object, registro modificado */
1. dirRel=TD.buscarClaveTD(clave)
2. Si dirRel≠-1 /* clave encontrada */
3. archivo.abrir()
4. archivo.seek(dirRel)
5. regm=archivo.leerRegistro()
7. archivo.seek(dirRel)
8. archivo.escribirRegistro(reg)
9. archivo.cerrar()
10. Return regm /* modificación exitosa */
11. Sino
12. Return null /* modificación no exitosa */

ITPuebla 262
4.4. RECUPERACIÓN DE DATOS

Algoritmo 144 consultar(archivo:File, TD:Nodo[], clave:String):


Object
/* Dada una clave, consulta o recupera un registro de un **
** archivo directo. Regresa el registro **
** Entradas: **
** archivo:File **
** TD:Nodo[] **
** clave:String **
** Salidas: **
** reg:Object, registro consultado */

1. dirRel=TD.buscarClaveTD(clave)
2. Si dirRel≠-1 /* clave encontrada */
3. archivo.abrir()
4. archivo.seek(dirRel)
5. reg=archivo.leerRegistro()
6. archivo.cerrar()
7. Return reg /* consulta exitosa */
8. Sino
9. Return null /* consulta no exitosa */

ITPuebla 263
4.4. RECUPERACIÓN DE DATOS

Algoritmo 145 recorrer(archivo:File, TD:Nodo[]): Object


/* Recorre todos los registros de un archivo directo, **
** de acuerdo al orden en que aparecen en la TD **
** Entradas: **
** archivo:File **
** TD:Nodo[] **
** Salidas: **
** Ninguna */
1. archivo.abrir()
2. Para i=0,1,...,TD.length()
3. Si TD[i]≠null
4. apAux=TD[i]
5. Mientras apAux≠null /* recorre lista */
6. dirRel=apAux.getDirRelativa()
7. apAux=apAux.getSig()
8. archivo.seek(dirRel)
9. reg=archivo.leerRegistro()
10. reg.aplicarOperacion()
10. archivo.cerrar()

El ordenamiento de archivos no suele darse como tal, ya que es muy


costoso acceder a almacenamiento secundario. En su lugar, se utilizan es-
tructuras de datos auxiliares residentes en almacenamiento primario, donde
el acceso es menos costoso, y al estar estas estructuras ordenadas, la búsque-
da es más rápida.

ITPuebla 264
Índice alfabético

Árbol, 3, 149 Analizar, 9, 12


Altura, 151 Criterios, 10
Aridad, 152 Definición, 9
Binario, 153 Diseñar, 9
Binario ordenado, 153 Recursivo, 108
De búsqueda, 153 Algoritmo de Dijkstra, 145
Representación, 155 Análisis de algoritmos
Definición, 149, 152 O grande, 14
Equilibrado, 153 Complejidad asintótica, 14
Hojas, 149 Complejidad en el espacio, 28
Nivel, 150 Complejidad en el tiempo, 14
Nodos interiores, 149 De un algoritmo no recursivo,
Operaciones básicas, 157 29
Raı́z, 149 De un algoritmo recursivo, 29
Recorrido a lo ancho y largo, 155 Ejemplo, 29
Recorrido en orden, preorden y Criterios para analizar un algo-
postorden, 160 ritmo, 10
Recorrido por niveles y a lo pro- Definición, 9
fundo, 155 Eficiencia, 12
Subárbol, 151 Jerarquı́a de funciones, 18
Índice, 236 Medición teórica de tiempo de eje-
Tabla de directorio, 236 cución, 19
Buscar, 237 Mejor caso, 13
Eliminar, 240 Paso, 19
Insertar, 238 Peor caso, 13
O grande, 14 Reglas generales, 21
Definición, 14 Tamaño de un problema, 12
Ejemplo, 14 Tiempo de ejecución, 13
De un algoritmo que invoca a
Algoritmo otro algoritmo, 23

265
ÍNDICE ALFABÉTICO

De un bloque de sentencias, 21 Bajas, 229, 231, 232


De una sentencia, 21 Consultass, 234
De una sentencia decisiva, 22 Modificaciones, 233
De una sentencia iterativa, 22 Recorrido, 235
Definición, 20 Secuencial–indexado
Ejemplos, 23 Bajas, 242
Regla de la suma, 21 Consultas, 242
Regla del producto, 21 Modificiaciones, 242
Suma de matrices, 25 Recorrido, 242
Suma de vectores, 24 Arreglo, 2
Archivo
Campo, 177 Búsqueda, 204
Clave o llave, 177 Métodos de búsqueda, 204
Definición, 177, 218 Búsqueda binaria, 206
Directo, 247 Búsqueda secuencial, 204
Altas, 259 Backtracking, 111
Bajas, 259 Bicola, 88
Consultas, 259
Cola
Función de dispersión o fun-
Aplicaciones, 103
ción hash, 247
Bicola, 88
Modificaciones, 259
Implementada con un vector,
Recorrido, 259
90
Tabla de dispersión o tabla hash,
Circular, 81
247
Implementada con un vector,
Indexado, 173, 236
81
Altas, 242
Cola doble, 88
Operaciones sobre registros, 220
Definición, 69
Actualizar, 221
Dinámica, 77
Consultar o recuperar, 220
Estática, 73
Eliminar, 223
Implementada con un vector, 73
Recorrer todos los registros, 223
Implementada con una lista liga-
Recuperación por acceso directo,
da simple, 77
247
Operaciones básicas, 71
Recuperación por acceso indexa-
Desencolar, 72
do, 236
Encolar, 72
Recuperación por acceso secuen-
Ver final, 72
cial, 223
Ver frente, 72
Registro, 177
Verificar si es vacı́a, 72
Secuencial, 223
Representación en memoria, 69
Altas, 223–225, 227

ITPuebla 266
ÍNDICE ALFABÉTICO

Simple, 69 Colisión, 250


TDA, 70
Colision, 250 Grafo, 3
Complejidad asintótica, 14 Adyascencia, 120
Complejidad en el espacio, 28 Algoritmo de Dijkstra, 145
Complejidad en el tiempo, 14 Aplicaciones, 143
De un algoritmo no recursivo, 29 Camino, 120
De un algoritmo recursivo, 29 Conexo, 120
Ejemplo, 29 De pesos, 144
Definición, 119
Divide y vencerás, 112 Dirigido, 143
Grado de un nodo, 121
Estructura de datos Grado interno y externo, 143
Índice, 236 Incidencia, 120
Tabla de directorio, 236 Matriz de adyascencia, 121
Clasificación, 3 Matriz de incidencia, 122
Definición, 2 Matriz de pesos, 144
Dinámica, 5 No dirigido
Ejemplos, 2 Definición, 119
Estática, 5 Operaciones básicas, 123
Función de dispersión o función Ponderado, 144
hash, 247 Representación en memoria, 121
Caracterı́sticas deseables, 250 Simple
Colisión, 250 Definición, 119
Heterogénea, 4 TDA, 127
Homogénea, 4 Valencia de un nodo, 121
Lineal, 4
Manejo de memoria, 5 Jerarquı́a de funciones, 18
Montı́culo, 197
No lineal, 4 Lista
Operaciones básicas, 2 Aplicaciones, 96
Tabla de directorio, 236 Circular, 52
Buscar, 237 Doble, 51
Eliminar, 240 Representación en memoria, 32
Insertar, 238 Simple
Tabla de dispersión o tabla hash, Definición, 31
247 Nodo, 31
Operaciones básicas, 36
Función de dispersión o función hash, TDA, 33
247 Lista ligada, 3
Caracterı́sticas deseables, 250

ITPuebla 267
ÍNDICE ALFABÉTICO

Métodos de dispersión, 252 Ver tope, 59


Método de la dispersión abierta, Verificar si es vacı́a, 59
254 Representación en memoria, 57
Método de la división, 252 TDA, 58
Método de la multiplicación, 253 Pila de registros de activación, 111
Manejo de memoria, 5
Estática y dinámica, 5 Recuperación
Ejemplos, 6 De datos, 218
Matriz Por acceso directo, 219
De adyascencia, 121 Por acceso indexaco, 219
De incidencia, 122 Por acceso secuencial, 218
De pesos, 144 Recursividad
Mejor caso, 13 Algoritmo recursivo, 108
Montı́culo, 197 Backtracking, 111
Definición, 108
Notación asintótica, 14 Divide y vencerás, 112
Ejemplos, 111
Ordenamiento, 176 Pila de registrod de activación,
clasificación, 176 111
Externo, 177 Procedimiento recursivo, 111
Interno, 177 Vuelta atrás, 111
Métodos de ordenamiento, 178 Registro, 2
Ordenamiento por inserción, 179 Regla de la suma, 21
Ordenamiento por intercambio, Regla del producto, 21
184
Ordenamiento por selección, 195 Subárbol, 151
Ordenamiento Shell, 181
Tabla de directorio, 236
Paso, 19 Buscar, 237
Peor caso, 13 Eliminar, 240
Pila Insertar, 238
Aplicaciones, 96 Tabla de dispersión o tabla hash, 247
Definición, 56 Tamaño de un problema, 12
Dinámica, 66 Tiempo de ejecución, 13
Estática, 61 De un algoritmo que invoca a otro
Implementada con un vector, 61 algoritmo, 23
Implementada con una lista liga- De un bloque de sentencias, 21
da simple, 67 De una sentencia, 21
Operaciones básicas, 59 De una sentencia decisiva, 22
Pop, 59 De una sentencia iterativa, 22
Push, 59 Definición, 20

ITPuebla 268
ÍNDICE ALFABÉTICO

Ejemplos, 23
Suma de matrices, 25
Suma de vectores, 24
Regla de la suma, 21
Regla del producto, 21
Tipo de dato
Clasificación, 2
Definición, 2
Estructurado, 2
Clasificación, 3
Dinámico, 5
Ejemplos, 2
Estático, 5
Heterogéneo, 4
Homogéneo, 4
Lineal, 4
No lineal, 4
Operaciones básicas, 2
Simple, 2
Tipo de dato abstracto (TDA)
Definición, 7
Esquema general, 8

Vuelta atrás, 111

ITPuebla 269
Bibliografı́a

[1] Luis Joyanes Aguilar. Estructuras de datos en Java. McGraw Hill,


2008.

[2] Alfred V. Aho, John E. Hopcroft, and Jeffrey D. Ullman. Estructuras


de Datos y Algoritmos. Addison–Wesley, 1988.

[3] Sandra Andersen. Data Structures In Java: A Laboratory Course. Jones


& Bartlett Learning, 2001.

[4] Sara Baase and Allen Van Gelder. Algoritmos computacionales. Intro-
ducción al análisis y diseño. Pearson Educación, 2002.

[5] Osvaldo Cairó. Estructuras de Datos. Segunda Edición. McGraw Hill,


2001.

[6] Bruce Eckel. Thinking in Java (4th Edition). Prentice Hall, 2006.

[7] Michael T. Goodrich and Roberto Tamassia. Data Structures and Al-
gorithms in JAVA. John Wiley & Sons, Inc., 2006.

[8] Ralph P. Grimaldi. Matemática discreta y combinatoria. Prentice Hall,


1998.

[9] Silvia Guardati. Estructura de datos orientada a objetos. Algoritmos


con C++. Prentice Hall, 2007.

[10] Ellis Horowitz and Sartaj Sahni. Fundamentals of Computer Algoritms.


Computer Science Press, Inc, 1978.

[11] Richard Johnsonbaugh. Matemáticas Discretas. Cuarta Edición. Pren-


tice Hall, 2000.

270
BIBLIOGRAFÍA

[12] Elliot B. Koffman and Paul A.T. Wolfgang. Estructura de datos con
C++. Objetos, abstracciones y diseño. McGraw Hill, 2008.

[13] Yedidyah Langsam, Moshe J. Augenstein, and Aaron M. Tenenbaum.


Data Structures Using C and C++. Prentice Hall, 2003.

[14] Yedidyah Langsam, Moshe J. Augenstein, and Aaron M. Tenenbaum.


Data Structures Using Java. Prentice Hall, 2003.

[15] R.C.T. Lee, S.S. Tseng, R.C. Chang, and Y.T. Tsai. Introducción al
diseño y análisis de algoritmos. Un enfoque estratégico. McGraw Hill,
2007.

[16] Mary E. S. Loomis. Estructura de Datos y Organizacion de Archivos.


Segunda Edición. Prentice Hall, 1991.

[17] Guillermo Peris. Vistiendo a Batman. Orden topológico en grafos diri-


gidos acı́clicos. https://medium.com/el-blog-de-melquiades/vistiendo-
a-batman-14e9a5a24c25, 2017.

[18] James Roberge. A Laboratory Course in C++ Data Structures, Second


Edition. Jones and Bartlett Publishers, Inc, 2003.

[19] Kennet H. Rosen. Matemática discreta y sus aplicaciones. McGraw


Hill, 2005.

[20] Robert W. Sebesta. Concepts of Programming Languages. The Benja-


min/Cummings Publishing Company, 2005.

ITPuebla 271

También podría gustarte