Sunteți pe pagina 1din 557

Algoritmos y Programacin o en Pascal

Cristbal Pareja Flores o Manuel Ojeda Aciego Angel Andeyro Quesada Carlos Rossi Jimnez e

Algoritmos y Programacin o en Pascal

A nuestros compaeros y alumnos n

Indice

Presentacin o Tema I
Cap tulo 1 1.1 1.2

xix 1
3 3 5 6 8 11 11 14 15 16 18 20 20 21 23 23 24 24 25 27 28

Algoritmos e introduccin a Pascal o


Problemas, algoritmos y programas

Solucin de problemas mediante programas . . . . . . . . . . . . o Concepto de algoritmo . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 1.2.2 Una denicin de algoritmo . . . . . . . . . . . . . . . . . o Una denicin formal de algoritmo . . . . . . . . . . . . . o Computabilidad . . . . . . . . . . . . . . . . . . . . . . . Correccin de algoritmos o . . . . . . . . . . . . . . . . . . Complejidad de algoritmos . . . . . . . . . . . . . . . . .

1.3

Aspectos de inters sobre los algoritmos . . . . . . . . . . . . . . e 1.3.1 1.3.2 1.3.3

1.4 1.5 1.6 1.7 1.8

Lenguajes algor tmicos y de programacin . . . . . . . . . . . . . o Desarrollo sistemtico de programas . . . . . . . . . . . . . . . . a Conclusin o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Referencias bibliogrcas a

Cap tulo 2 2.1 2.2 2.3 2.4

El lenguaje de programacin Pascal o

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o Otros detalles de inters . . . . . . . . . . . . . . . . . . . . . . . e Origen y evolucin del lenguaje Pascal . . . . . . . . . . . . . . . o Pascal y Turbo Pascal . . . . . . . . . . . . . . . . . . . . . . . . Tipos de datos bsicos a

Cap tulo 3 3.1

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o

viii 3.2 3.3 3.4 3.5 3.6 3.7 3.8

Indice El tipo integer . . . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . Observaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo de una expresin . . . . . . . . . . . . . . . . . . . . . . . o Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Elementos bsicos del lenguaje a 28 32 35 36 39 43 43 47 47 48 52 52 52 54 57 59 59 60 62 63 63 67 68 68 69 69 71 73 73 78 79 81

Cap tulo 4 4.1 4.2 4.3

Un ejemplo introductorio . . . . . . . . . . . . . . . . . . . . . . Vocabulario bsico . . . . . . . . . . . . . . . . . . . . . . . . . . a 4.2.1 4.3.1 4.3.2 4.3.3 Constantes y variables . . . . . . . . . . . . . . . . . . . . Asignacin . . . . . . . . . . . . . . . . . . . . . . . . . . o Instrucciones de escritura . . . . . . . . . . . . . . . . . . Instrucciones de lectura . . . . . . . . . . . . . . . . . . . Encabezamiento . . . . . . . . . . . . . . . . . . . . . . . Declaraciones y deniciones . . . . . . . . . . . . . . . . . Cuerpo del programa . . . . . . . . . . . . . . . . . . . . . Conclusin: estructura general de un programa . . . . . . o Instrucciones bsicas . . . . . . . . . . . . . . . . . . . . . . . . . a

4.4

Partes de un programa . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 4.4.2 4.4.3 4.4.4

4.5

Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Primeros programas completos Dibujo de la letra C . . . . . . . . . . . . . . . . . . . . Suma de dos nmeros . . . . . . . . . . . . . . . . . . . . u

Cap tulo 5 5.1 5.1.1 5.1.2 5.2 5.3 5.4

Algunos programas sencillos . . . . . . . . . . . . . . . . . . . . .

Programas claros programas de calidad . . . . . . . . . . . . . Desarrollo descendente de programas . . . . . . . . . . . . . . . . Estado de los cmputos . . . . . . . . . . . . . . . . . . . o Desarrollo descendente con especicaciones . . . . . . . . Desarrollo de programas correctos . . . . . . . . . . . . . . . . . 5.4.1 5.4.2

5.5 5.6

Observaciones nales . . . . . . . . . . . . . . . . . . . . . . . . . Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Indice

ix

Tema II
Cap tulo 6 6.1 6.2

Programacin estructurada o
Instrucciones estructuradas

83
85 86 88 88 92 94 94 98

Composicin de instrucciones . . . . . . . . . . . . . . . . . . . . o Instrucciones de seleccin . . . . . . . . . . . . . . . . . . . . . . o 6.2.1 6.2.2 La instruccin if-then-else . . . . . . . . . . . . . . . . . o La instruccin case . . . . . . . . . . . . . . . . . . . . . o La instruccin while . . . . . . . . . . . . . . . . . . . . . o La instruccin repeat . . . . . . . . . . . . . . . . . . . . o

6.3

Instrucciones de iteracin . . . . . . . . . . . . . . . . . . . . . . o 6.3.1 6.3.2 6.3.3

La instruccin for . . . . . . . . . . . . . . . . . . . . . . 100 o Eleccin de instrucciones iterativas . . . . . . . . . . . . . 103 o Terminacin de un bucle . . . . . . . . . . . . . . . . . . . 105 o Uso correcto de instrucciones estructuradas . . . . . . . . 106 Mtodo de biparticin . . . . . . . . . . . . . . . . . . . . 113 e o Mtodo de Newton-Raphson . . . . . . . . . . . . . . . . 115 e Inversin de funciones . . . . . . . . . . . . . . . . . . . . 117 o

6.4

Diseo y desarrollo de bucles . . . . . . . . . . . . . . . . . . . . 103 n 6.4.1 6.4.2 6.4.3

6.5

Dos mtodos numricos iterativos . . . . . . . . . . . . . . . . . . 113 e e 6.5.1 6.5.2 6.5.3

6.6

Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Programacin estructurada o 123

Cap tulo 7 7.1 7.2

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 o Aspectos tericos . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 o 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.2.6 Programas y diagramas de ujo . . . . . . . . . . . . . . . 125 Diagramas y diagramas propios . . . . . . . . . . . . . . . 126 Diagramas BJ (de Bhm y Jacopini) . . . . . . . . . . . . 130 o Equivalencia de diagramas . . . . . . . . . . . . . . . . . . 135 Teoremas de la programacin estructurada . . . . . . . . 137 o Recapitulacin . . . . . . . . . . . . . . . . . . . . . . . . 138 o Seudocdigo . . . . . . . . . . . . . . . . . . . . . . . . . . 139 o Diseo descendente . . . . . . . . . . . . . . . . . . . . . . 141 n

7.3

Aspectos metodolgicos . . . . . . . . . . . . . . . . . . . . . . . 139 o 7.3.1 7.3.2

7.4

Renamiento correcto de programas con instrucciones estructuradas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146

x 7.4.1 7.4.2 7.5 7.6 7.7 Un ejemplo detallado

Indice . . . . . . . . . . . . . . . . . . . . 147

Recapitulacin . . . . . . . . . . . . . . . . . . . . . . . . 150 o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 . . . . . . . . . . . . . . . . . . . . . . 153

Conclusin o

Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Referencias bibliogrcas a

Tema III
Cap tulo 8 8.1 8.2

Subprogramas
Procedimientos y funciones

155
157

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 o Subprogramas con parmetros . . . . . . . . . . . . . . . . . . . . 162 a 8.2.1 8.2.2 8.2.3 8.2.4 Descripcin de un subprograma con parmetros . . . . . . 162 o a Parmetros formales y reales . . . . . . . . . . . . . . . . 165 a Mecanismos de paso de parmetros . . . . . . . . . . . . . 165 a Consistencia entre denicin y llamada o . . . . . . . . . . 168

8.3 8.4 8.5

Estructura sintctica de un subprograma . . . . . . . . . . . . . . 169 a Funcionamiento de una llamada . . . . . . . . . . . . . . . . . . . 170 Ambito y visibilidad de los identicadores . . . . . . . . . . . . . 174 8.5.1 8.5.2 8.5.3 8.5.4 Tipos de identicadores segn su mbito . . . . . . . . . . 174 u a Estructura de bloques . . . . . . . . . . . . . . . . . . . . 175 Criterios de localidad . . . . . . . . . . . . . . . . . . . . 181 Efectos laterales . . . . . . . . . . . . . . . . . . . . . . . 181 Parmetros por valor y por referencia . . . . . . . . . . . 183 a Parmetros por referencia y funciones . . . . . . . . . . . 183 a Funciones con resultados mltiples . . . . . . . . . . . . . 184 u

8.6

Otras recomendaciones sobre el uso de parmetros . . . . . . . . 183 a 8.6.1 8.6.2 8.6.3

8.7 8.8

Desarrollo correcto de subprogramas . . . . . . . . . . . . . . . . 184 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Aspectos metodolgicos de la programacin con o o subprogramas

Cap tulo 9 9.1 9.2 9.3

189

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 o Un ejemplo de referencia . . . . . . . . . . . . . . . . . . . . . . . 190 Metodolog de la programacin con subprogramas . . . . . . . . 192 a o 9.3.1 Diseo descendente con subprogramas . . . . . . . . . . . 193 n

Indice 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.5 9.6 9.7

xi Programa principal y subprogramas . . . . . . . . . . . . 194 Documentacin de los subprogramas . . . . . . . . . . . . 195 o Tamao de los subprogramas . . . . . . . . . . . . . . . . 196 n Renamiento con subprogramas y con instrucciones estructuradas . . . . . . . . . . . . . . . . . . . . . . . . . 197

Estructura jerrquica de los subprogramas . . . . . . . . . . . . . 199 a Ventajas de la programacin con subprogramas . . . . . . . . . . 201 o Un ejemplo detallado: representacin de funciones . . . . . . . . 203 o Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 211

Cap tulo 10 Introduccin a la recursin o o

10.1 Un ejemplo de referencia . . . . . . . . . . . . . . . . . . . . . . . 212 10.2 Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . . . . . 213 a 10.3 Otros ejemplos recursivos . . . . . . . . . . . . . . . . . . . . . . 216 10.3.1 La sucesin de Fibonacci . . . . . . . . . . . . . . . . . . 216 o 10.3.2 Torres de Hanoi . . . . . . . . . . . . . . . . . . . . . . . 216 10.3.3 Funcin de Ackermann . . . . . . . . . . . . . . . . . . . . 219 o 10.4 Correccin de subprogramas recursivos . . . . . . . . . . . . . . . 219 o 10.4.1 Principios de induccin . . . . . . . . . . . . . . . . . . . 220 o 10.5 Recursin mutua . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 o 10.6 Recursin e iteracin . . . . . . . . . . . . . . . . . . . . . . . . . 226 o o 10.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 10.8 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 228

Tema IV

Tipos de datos denidos por el programador

231
233

Cap tulo 11 Tipos de datos simples y compuestos

11.1 Tipos ordinales denidos por el programador . . . . . . . . . . . 234 11.1.1 Tipos enumerados . . . . . . . . . . . . . . . . . . . . . . 235 11.1.2 Tipo subrango . . . . . . . . . . . . . . . . . . . . . . . . 238 11.2 Denicin de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . 240 o 11.2.1 Observaciones sobre la denicin de tipos . . . . . . . . . 242 o 11.3 Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 11.3.1 Operaciones sobre el tipo conjunto . . . . . . . . . . . . . 245 11.3.2 Observaciones sobre el tipo conjunto . . . . . . . . . . . . 247

xii

Indice 11.3.3 Un ejemplo de aplicacin . . . . . . . . . . . . . . . . . . 248 o 11.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250

Cap tulo 12 Arrays

253

12.1 Descripcin del tipo de datos array . . . . . . . . . . . . . . . . . 253 o 12.1.1 Operaciones del tipo array y acceso a sus componentes . . 257 12.1.2 Caracter sticas generales de un array . . . . . . . . . . . . 260 12.2 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 12.3 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 12.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 Cap tulo 13 Registros 271

13.1 Descripcin del tipo de datos registro . . . . . . . . . . . . . . . . 271 o 13.1.1 Manejo de registros: acceso a componentes y operaciones . 273 13.1.2 Registros con variantes . . . . . . . . . . . . . . . . . . . . 276 13.2 Arrays de registros y registros de arrays . . . . . . . . . . . . . . 279 13.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 Cap tulo 14 Archivos 285

14.1 Descripcin del tipo de datos archivo . . . . . . . . . . . . . . . . 285 o 14.2 Manejo de archivos en Pascal . . . . . . . . . . . . . . . . . . . . 286 14.2.1 Operaciones con archivos . . . . . . . . . . . . . . . . . . 288 14.3 Archivos de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 14.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Cap tulo 15 Algoritmos de b squeda y ordenacin u o 301

15.1 Algoritmos de bsqueda en arrays . . . . . . . . . . . . . . . . . 301 u 15.1.1 Bsqueda secuencial . . . . . . . . . . . . . . . . . . . . . 302 u 15.1.2 Bsqueda secuencial ordenada u . . . . . . . . . . . . . . . 304 15.1.3 Bsqueda binaria . . . . . . . . . . . . . . . . . . . . . . . 304 u 15.2 Ordenacin de arrays . . . . . . . . . . . . . . . . . . . . . . . . . 306 o 15.2.1 Seleccin directa . . . . . . . . . . . . . . . . . . . . . . . 307 o 15.2.2 Insercin directa . . . . . . . . . . . . . . . . . . . . . . . 309 o 15.2.3 Intercambio directo . . . . . . . . . . . . . . . . . . . . . . 310 15.2.4 Ordenacin rpida (Quick Sort) . . . . . . . . . . . . . . 312 o a 15.2.5 Ordenacin por mezcla (Merge Sort) . . . . . . . . . . . . 316 o

Indice

xiii 15.2.6 Vectores paralelos . . . . . . . . . . . . . . . . . . . . . . 318

15.3 Algoritmos de bsqueda en archivos secuenciales . . . . . . . . . 320 u 15.3.1 Bsqueda en archivos arbitrarios . . . . . . . . . . . . . . 321 u 15.3.2 Bsqueda en archivos ordenados . . . . . . . . . . . . . . 321 u 15.4 Mezcla y ordenacin de archivos secuenciales . . . . . . . . . . . 322 o 15.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 15.6 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 330

Tema V

Memoria dinmica a

333
335

Cap tulo 16 Punteros

16.1 Introduccin al uso de punteros . . . . . . . . . . . . . . . . . . . 336 o 16.1.1 Denicin y declaracin de punteros . . . . . . . . . . . . 337 o o 16.1.2 Generacin y destruccin de variables dinmicas . . . . . 338 o o a 16.1.3 Operaciones bsicas con datos apuntados . . . . . . . . . 339 a 16.1.4 Operaciones bsicas con punteros . . . . . . . . . . . . . . 341 a 16.1.5 El valor nil . . . . . . . . . . . . . . . . . . . . . . . . . . 343 16.2 Aplicaciones no recursivas de los punteros . . . . . . . . . . . . . 344 16.2.1 Asignacin de objetos no simples . . . . . . . . . . . . . . 345 o 16.2.2 Funciones de resultado no simple . . . . . . . . . . . . . . 346 16.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348 Cap tulo 17 Estructuras de datos recursivas 351

17.1 Estructuras recursivas lineales: las listas enlazadas . . . . . . . . 351 17.1.1 Una denicin del tipo lista . . . . . . . . . . . . . . . . . 352 o 17.1.2 Insercin de elementos . . . . . . . . . . . . . . . . . . . . 353 o 17.1.3 Eliminacin de elementos . . . . . . . . . . . . . . . . . . 355 o 17.1.4 Algunas funciones recursivas . . . . . . . . . . . . . . . . 355 17.1.5 Otras operaciones sobre listas . . . . . . . . . . . . . . . . 358 17.2 Pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 17.2.1 Denicin de una pila como lista enlazada . . . . . . . . . 363 o 17.2.2 Operaciones bsicas sobre las pilas . . . . . . . . . . . . . 363 a 17.2.3 Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . 365 17.3 Colas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 17.3.1 Denicin del tipo cola o . . . . . . . . . . . . . . . . . . . 371

xiv

Indice 17.3.2 Operaciones bsicas . . . . . . . . . . . . . . . . . . . . . 371 a 17.3.3 Aplicacin: gestin de la caja de un supermercado . . . . 374 o o 17.4 Arboles binarios . . . . . . . . . . . . . . . . . . . . . . . . . . . 376 17.4.1 Recorrido de un rbol binario . . . . . . . . . . . . . . . . 378 a 17.4.2 Arboles de bsqueda . . . . . . . . . . . . . . . . . . . . . 379 u 17.4.3 Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . 383 17.5 Otras estructuras dinmicas de datos . . . . . . . . . . . . . . . . 387 a 17.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 17.7 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 391

Tema VI

Aspectos avanzados de programacin o

393
395

Cap tulo 18 Complejidad algor tmica

18.1 Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . . . . . 396 a 18.2 Medidas del comportamiento asinttico . . . . . . . . . . . . . . 402 o 18.2.1 Comportamiento asinttico . . . . . . . . . . . . . . . . . 402 o 18.2.2 Notacin O mayscula (una cota superior) o u . . . . . . . . 404 18.2.3 Notacin mayscula (una cota inferior) . . . . . . . . . 405 o u 18.2.4 Notacin mayscula (orden de una funcin) . . . . . . 405 o u o 18.2.5 Propiedades de O, y . . . . . . . . . . . . . . . . . . 406 18.2.6 Jerarqu de rdenes de frecuente aparicin . . . . . . . . 407 a o o 18.3 Reglas prcticas para hallar el coste de un programa . . . . . . . 408 a 18.3.1 Tiempo empleado . . . . . . . . . . . . . . . . . . . . . . 408 18.3.2 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 18.3.3 Espacio de memoria empleado . . . . . . . . . . . . . . . 417 18.4 Utiles matemticos . . . . . . . . . . . . . . . . . . . . . . . . . . 418 a 18.4.1 Frmulas con sumatorios . . . . . . . . . . . . . . . . . . 419 o 18.4.2 Sucesiones de recurrencia lineales de primer orden . . . . 419 18.4.3 Sucesiones de recurrencia de orden superior . . . . . . . . 421 18.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 18.6 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 425 427

Cap tulo 19 Tipos abstractos de datos

19.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428 o 19.2 Un ejemplo completo . . . . . . . . . . . . . . . . . . . . . . . . . 429

Indice

xv 19.2.1 Desarrollo de programas con tipos concretos de datos . . 430 19.2.2 Desarrollo de programas con tipos abstractos de datos . . 431 19.2.3 Desarrollo de tipos abstractos de datos . . . . . . . . . . . 434

19.3 Metodolog de la programacin de TADs . . . . . . . . . . . . . 440 a o 19.3.1 Especicacin de tipos abstractos de datos . . . . . . . . 440 o 19.3.2 Implementacin de tipos abstractos de datos . . . . . . . 441 o 19.3.3 Correccin de tipos abstractos de datos . . . . . . . . . . 443 o 19.4 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 19.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 19.6 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 448 449

Cap tulo 20 Esquemas algor tmicos fundamentales

20.1 Algoritmos devoradores . . . . . . . . . . . . . . . . . . . . . . . 450 20.1.1 Descripcin . . . . . . . . . . . . . . . . . . . . . . . . . . 450 o 20.1.2 Adecuacin al problema . . . . . . . . . . . . . . . . . . . 451 o 20.1.3 Otros problemas resueltos vorazmente . . . . . . . . . . . 452 20.2 Divide y vencers . . . . . . . . . . . . . . . . . . . . . . . . . . . 453 a 20.2.1 Equilibrado de los subproblemas . . . . . . . . . . . . . . 454 20.3 Programacin dinmica . . . . . . . . . . . . . . . . . . . . . . . 455 o a 20.3.1 Problemas de programacin dinmica . . . . . . . . . . . 455 o a 20.3.2 Mejora de este esquema . . . . . . . . . . . . . . . . . . . 457 20.3.3 Formulacin de problemas de programacin dinmica . . 460 o o a 20.4 Vuelta atrs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462 a 20.4.1 Mejora del esquema de vuelta atrs a . . . . . . . . . . . . 466 20.5 Anexo: algoritmos probabilistas . . . . . . . . . . . . . . . . . . . 468 20.5.1 Bsqueda de una solucin aproximada . . . . . . . . . . . 468 u o 20.5.2 Bsqueda de una solucin probablemente correcta . . . . 469 u o 20.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470 20.7 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 473

Apndices e
Apndice A Aspectos complementarios de la programacin e o

475
477

A.1 Subprogramas como parmetros . . . . . . . . . . . . . . . . . . . 477 a A.1.1 Ejemplo 1: derivada . . . . . . . . . . . . . . . . . . . . . 479

xvi

Indice A.1.2 Ejemplo 2: biparticin . . . . . . . . . . . . . . . . . . . . 480 o A.1.3 Ejemplo 3: transformacin de listas . . . . . . . . . . . . 482 o A.2 Variables aleatorias . . . . . . . . . . . . . . . . . . . . . . . . . . 482 A.2.1 Generacin de nmeros aleatorios en Turbo Pascal . . . . 483 o u A.2.2 Simulacin de variables aleatorias . . . . . . . . . . . . . . 484 o A.2.3 Ejemplos de aplicacin . . . . . . . . . . . . . . . . . . . . 486 o A.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488 A.4 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 490 491

Apndice B El lenguaje Turbo Pascal e

B.1 Elementos lxicos . . . . . . . . . . . . . . . . . . . . . . . . . . . 492 e B.2 Estructura del programa . . . . . . . . . . . . . . . . . . . . . . . 492 B.3 Datos numricos enteros . . . . . . . . . . . . . . . . . . . . . . . 492 e B.4 Datos numricos reales . . . . . . . . . . . . . . . . . . . . . . . . 493 e B.5 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 494 B.5.1 Declaracin de cadenas . . . . . . . . . . . . . . . . . . . 494 o B.5.2 Operadores de cadenas . . . . . . . . . . . . . . . . . . . . 495 B.5.3 Funciones de cadenas . . . . . . . . . . . . . . . . . . . . 496 B.5.4 Procedimientos de cadenas . . . . . . . . . . . . . . . . . 496 B.6 Tipos de datos estructurados . . . . . . . . . . . . . . . . . . . . 498 B.7 Instrucciones estructuradas . . . . . . . . . . . . . . . . . . . . . 498 B.8 Paso de subprogramas como parmetros . . . . . . . . . . . . . . 499 a B.9 Archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500 B.10 Memoria dinmica . . . . . . . . . . . . . . . . . . . . . . . . . . 501 a B.11 Unidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501 B.11.1 Unidades predenidas de Turbo Pascal . . . . . . . . . . . 502 B.11.2 Unidades denidas por el usuario . . . . . . . . . . . . . . 503 B.11.3 Modularidad incompleta de Turbo Pascal . . . . . . . . . 505 Apndice C El entorno integrado de desarrollo e C.2 Desarrollo completo de un programa en Turbo Pascal 507 . . . . . . 508

C.1 Descripcin del entorno . . . . . . . . . . . . . . . . . . . . . . . 507 o C.2.1 Arranque del entorno . . . . . . . . . . . . . . . . . . . . 508 C.2.2 Edicin del programa fuente . . . . . . . . . . . . . . . . . 510 o C.2.3 Grabar el programa fuente y seguir editando . . . . . . . 510

Indice C.2.4 Compilacin o

xvii . . . . . . . . . . . . . . . . . . . . . . . . . 512

C.2.5 Ejecucin . . . . . . . . . . . . . . . . . . . . . . . . . . . 514 o C.2.6 Depuracin . . . . . . . . . . . . . . . . . . . . . . . . . . 514 o C.2.7 Salida de Turbo Pascal . . . . . . . . . . . . . . . . . . . . 516 C.3 Otros mens y opciones . . . . . . . . . . . . . . . . . . . . . . . 517 u C.3.1 Search (Bsqueda) . . . . . . . . . . . . . . . . . . . . . . 517 u C.3.2 Tools (Herramientas) . . . . . . . . . . . . . . . . . . . . . 517 C.3.3 Options (Opciones) . . . . . . . . . . . . . . . . . . . . . . 517 C.3.4 Window (Ventana) . . . . . . . . . . . . . . . . . . . . . . 519 C.3.5 Help (Ayuda) . . . . . . . . . . . . . . . . . . . . . . . . . 519 C.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519 C.5 Referencias bibliogrcas a . . . . . . . . . . . . . . . . . . . . . . 520

Bibliograf a Indice alfabtico e

521 527

Presentacin o
Este libro trata sobre mtodos de resolucin de problemas mediante el desae o rrollo de algoritmos y estructuras de datos, desde el principio y paso a paso, y su materializacin en programas de computador. o Desde luego, no es el primer libro sobre este tema; de hecho, ha habido en los ultimos quince aos un gran aluvin de textos sobre algoritmos y sobre n o programacin. La razn para ello ha sido sin lugar a dudas doble: por un o o lado, la difusin que estos temas han tenido y siguen teniendo, integrndose o a en los estudios ms diversos; por otro, la evolucin que est experimentando el a o a desarrollo de algoritmos y programas, pasando de ser un arte (reinventado por cada programador a base de tcnicas personales, estrechamente vinculadas con e su lenguaje de programacin) a una actividad ms cient o a ca, metodolgica y o disciplinada. Por consiguiente, resulta necesario aclarar cul es el enfoque adoptado en este a libro. Examinando la bibliograf existente actualmente sobre programacin a a o un nivel introductorio permite armar las siguientes conclusiones: Una parte importante de los libros existentes han adoptado un enfoque prctico puro, no metodolgico, que es el ms tradicional, y an subsiste a o a u en demasiados libros. Se confunde la enseanza de la programacin con n o la de un lenguaje concreto, ofreciendo muchas veces un mero manual de referencia del lenguaje elegido. Bajo el atractivo de los llamativos resultados inmediatos (programas que funcionan), este enfoque ignora la base conceptual y metodolgica necesaria, y propicia los peores hbitos de o a programacin, que son adems dif o a ciles de erradicar. Otra postura extrema se centra en el anlisis y desarrollo de soluciones a algor tmicas puras, de forma independiente de cualquier lenguaje de programacin. Esta independencia permite ignorar las peculiaridades de los o lenguajes reales, yendo a los conceptos; sin embargo, esa independencia de los lenguajes de programacin es a nuestro entender innecesaria e inconveo niente en los primeros pasos, ya que obliga al aprendiz de la programacin o a estudiar aparte los detalles concretos del lenguaje de programacin con o que necesariamente debe desarrollar sus prcticas. a

xx

Presentacion En cambio, encontramos este enfoque interesante en niveles superiores de la enseanza de la programacin, donde interesa concentrarse en los conn o ceptos, ms dif a ciles y donde ya no supone obstculo alguno expresar las a ideas en cualquier lenguaje de programacin. o

El enfoque adoptado en este libro recoge ambos aspectos: por un lado, viene a cubrir la necesidad de un enfoque metodolgico en el aprendizaje y en el ejercicio o de la programacin, pero tambin la necesidad de experimentar con programas o e concretos, expresarlos en un lenguaje real y hacerlos funcionar con un traductor concreto. En resumen, intentamos compaginar las ventajas de los enfoques anteriores, presentando la base conceptual y metodolgica necesaria para desao rrollar los algoritmos de forma razonada y disciplinada, sin olvidar por ello la conveniencia de expresarlos en un lenguaje de programacin, materializndolos o a y experimentando con ellos, y que el lector ha de ser instruido tambin en esta e tarea. En relacin con el enfoque metodolgico que se impone actualmente, se cono o sidera necesario atender a la correccin de los programas. El tratamiento que o se le da en la literatura ha llevado nuevamente a dos posturas articialmente extremas: Algunos autores ignoran completamente el estudio de la correccin, cono tentndose con algunas comprobaciones para deducir que un programa es a correcto. En cambio, otros adoptan un tratamiento exhaustivo, utilizando tcnicas e formales de especicacin o vericacin. o o A nuestro entender, es incuestionable la importancia de garantizar que los programas desarrollados funcionarn de la forma deseada. Sin embargo, la vea ricacin formal de los programas de cierto tamao es impracticable. Por ello, o n asumimos de nuevo una posicin intermedia y realista consistente en los siguieno tes planteamientos: Plantear el desarrollo de programas correctos con el empleo de tcnicas e semiformales. Limitar el estudio de la correccin a los elementos que resulten delicados, o bien por su dicultad o por su novedad. Atender a la correccin de los algoritmos durante su desarrollo en lugar de o a posteriori. Esta idea resulta ser una ayuda esencial en el aprendizaje de la programacin. o

Presentacion

xxi

En resumidas cuentas, este libro va dirigido a aqullos que desean introdue cirse en la programacin, con una base slida, con una buena metodog de o o a diseo y desarrollo de programas correctos y con hbitos disciplinados desde una n a perspectiva realista y pragmtica. Se presentan las tcnicas con un cierto nia e vel de abstraccin para identicar los conceptos esenciales e independientes del o lenguaje de programacin empleado, y al mismo tiempo se aterriza expresando o estas tcnicas en un lenguaje concreto. e El lenguaje escogido para estas implementaciones ha sido Pascal. Esta eleccin se debe a que este lenguaje es simple y tiene una sintaxis sencilla, que o hace que sea fcil de aprender, y al mismo tiempo es lo bastante completo como a para plasmar las diferentes tcnicas y mtodos necesarios en programas de come e plejidad media-alta. Esto lo hace una herramienta pedaggica idnea para el o o aprendizaje de la programacin. A todo esto hay que sumar las numerosas imo plementaciones existentes y su accesibilidad, as como su evolucin y continua o puesta al d para permitir tcnicas de programacin actuales (por ejemplo, moa e o dular u orientada a los objetos) y su gran difusin y aceptacin en el mbito o o a acadmico. e

Organizacin del libro o


El libro est estructurado en siete partes. En cada una de ellas se estudian a las tcnicas y mecanismos nuevos, conceptualmente primero, detallando luego e su tratamiento en Pascal y, nalmente, compaginando ambas facetas con el aspecto metodolgico. Cada tema se ha dividido en varios cap o tulos para evitar una excesiva fragmentacin. En cada cap o tulo se ha incluido una lista de ejercicios propuestos de dicultad aproximadamente creciente. Al nal de cada tema se desarrolla un ejemplo completo pensado para mostrar a la vez los aspectos ms destacados del mismo, as como unas pocas referencias comentadas que se a sugieren como lecturas complementarias o de consulta.

Contenido
El contenido se ha seleccionado partiendo de las directrices sealadas en n [DCG+ 89] y [Tur91]. Incluye los contenidos cursos CS1 y CS2 [GT86, KSW85] salvo los aspectos de organizacin de computadores, que se estudian en [PAO94], o de los mismos autores que este libro. En el primer tema se presentan, entre otros, los conceptos esenciales de algoritmo, dato y programa. Se introduce el lenguaje Pascal y la estructura de los programas escritos en l, as como los elementos bsicos del lenguaje. Se incluyen e a

xxii

Presentacion

algunos programas sencillos, y se adelantan la tcnica descendente de diseo de e n programas y algunos apuntes sobre la correccin. o El segundo tema se dedica a la programacin estructurada. Se pone espeo cial nfasis en el diseo descendente o por renamientos sucesivos partiendo de e n especicaciones escritas en pseudocdigo, y se muestra cmo compaginar esta o o tcnica con la derivacin de programas correctos. e o En el tercer tema se estudian los subprogramas. Al igual que en el tema anterior, se detalla cmo enfocar la correccin en el uso de esta tcnica. Se o o e concluye con un cap tulo de introduccin a la recursin. o o En la mayor de los programas no basta con los tipos de datos bsicos, sino a a que es necesario que el programador dena otros ms complejos. A ello se dedica a el cuarto tema. El quinto tema estudia las tcnicas propias de la gestin de memoria dinmica. e o a Se justica su necesidad, y se presenta su principal aplicacin, que es la denicin o o de estructuras de datos recursivas. El sexto tema introduce tres aspectos avanzados: la programacin con tipos o abstractos de datos, el coste de los algoritmos y los principales esquemas de diseo de algoritmos. Aunque, ciertamente, su estudio en profundidad rebasa un n primer curso, es frecuente introducir o siquiera mencionar sus ideas bsicas. a Por supuesto, siempre es altamente recomendable consultar otras referencias (nosotros mismos las seleccionamos para cada tema), pero tambin es cierto e que el alumno se ve obligado con frecuencia a usar varios textos bsicos para a cubrir diferentes partes de la materia. Justamente, estos ultimos cap tulos se incluyen para que el lector interesado se pueda asomar a ellos sin verse obligado a consultar los cap tulos introductorios de otros libros. Finalmente se incluyen tres apndices: en el primero se introducen un par e de aspectos complementarios para un primer curso de programacin (el paso o de subprogramas como parmetros y el uso de variables aleatorias); el segundo a es un prontuario de uso del entorno integrado de desarrollo Turbo Pascal; y el tercero indica algunos detalles de Turbo Pascal en que se separa del estndar, a pero que son de uso frecuente.

Notacin empleada o
En la lectura de este texto se encontrarn fragmentos escritos en distintos a lenguajes: En castellano puro, donde se ha usado este tipo de letra. En Pascal, para lo que se ha elegido el teletipo, salvo las palabras reservadas, que van en negrita.

Presentacion

xxiii

Tambin se ha empleado el teletipo para indicar las salidas y entradas de e datos, porque es el tipo de letra ms parecido al que aparece en el monitor. a En seudocdigo, que se expresa con letra cursiva. o En lenguaje matemtico, en que se usan los s a mbolos usuales. Otros s mbolos especiales: ; El espacio en blanco (vase la pgina 206) e a Uno o ms pasos al evaluar una expresin (vase la pgina 30) a o e a Fin de archivo (vase la pgina 57) e a Fin de l nea (vase la pgina 57) e a

Advertencia para el alumno


No existe un mtodo general para resolver problemas mediante algoritmos; e por ello, es de gran importancia estudiar las tcnicas de forma gradual, yendo e de lo fcil a lo dif y vinculndolas con situaciones ampliamente conocidas. a cil a Nosotros pretendemos haber cumplido con este objetivo, proporcionando ejemplos de fcil comprensin y ejercicios a la medida de lo explicado. Y eso es a o lo bueno. Lo malo es que el alumno puede percibir la sensacin de comprenderlo o todo a la velocidad que lee. . . y aqu reside el peligro. Porque una mera lectura del texto, o incluso una lectura atenta, no basta para asimilarlo, adquiriendo las tcnicas necesarias para resolver otros problemas de dicultad similar a la de los e planteados. Es preciso adoptar una actitud cr tica durante la lectura, trabajar minuciosamente con los problemas propuestos e incluso tatar de imaginar soluciones alternativas a los ejemplos dados. Y cuanto antes se acepte esa realidad, mejor que mejor.

Agradecimientos
Muchas personas han contribuido de diferentes maneras a que este libro sea lo que es. En primer lugar, debemos a nuestros alumnos de estos aos su ayuda, n aun sin saberlo, porque ellos han sido la razn de que emprendiramos este o e trabajo, y porque sus preguntas y comentarios, d a d tienen una respuesta a a, en las pginas que siguen. En segundo lugar, debemos a muchos de nuestros a compaeros su aliento y apoyo, tan necesario cuando uno se enfrenta a un trabajo n de esta envergadura. Y por ello, lo dedicamos a ambos, alumnos y compaeros. n De un modo muy especial, deseamos expresar nuestro agradecimiento a Juan Falgueras Cano, Luis Antonio Galn Corroto y Yolanda Ortega y Malln por a e su cuidadosa lectura de la primera versin completa del manuscrito, y por sus o

xxiv

Presentacion

valiosos comentarios y sugerencias. No podemos olvidar tampoco la ayuda de Manuel Enciso Garc a-Oliveros en los ultimos retoques, as como su proximidad y apoyo durante todo el tiempo que nos ha ocupado este trabajo. Por ultimo, deseamos expresar nuestra gratitud y cario a Jos Luis Galn n e a Garc que ha dejado en este libro muchas horas. a,

Tema I

Algoritmos e introduccin a o Pascal

Cap tulo 1

Problemas, algoritmos y programas

1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8

Solucin de problemas mediante programas . . . . . . o Concepto de algoritmo . . . . . . . . . . . . . . . . . . . Aspectos de inters sobre los algoritmos . . . . . . . . e Lenguajes algor tmicos y de programacin . . . . . . . o Desarrollo sistemtico de programas . . . . . . . . . . a Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . o Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . Referencias bibliogrcas . . . . . . . . . . . . . . . . . . a

3 5 11 16 18 20 20 21

En este primer cap tulo se trata la resolucin de problemas por medio de o un computador. Puesto que ste no es ms que un mero ejecutor de tareas, es e a fundamental conocer un mtodo de resolucin del problema en cuestin, esto e o o es, un algoritmo. La escritura de este algoritmo como un conjunto de rdenes o comprensibles por el computador es lo que se llama programa.

1.1

Solucin de problemas mediante programas o

Los computadores desempean una gran variedad de tareas, liberando as n al hombre de tener que realizarlas personalmente. Para ello, es preciso ensear n al computador cul es su trabajo y cmo llevarlo a cabo, esto es, programarlo, a o

Cap tulo 1. Problemas, algoritmos y programas

dndole instrucciones precisas (programas) en un lenguaje que comprenda (Pasa cal, Modula-2, C, etc.). Una vez aprendido el programa, el computador seguir a ciegamente sus instrucciones cuantas veces sea requerido. Precisamente, la tarea de la programacin consiste en describir lo que debe o hacer el computador para resolver un problema concreto en un lenguaje de programacin. Sin embargo, el programa es solamente el resultado de una serie de o etapas que no se pueden pasar por alto. Hablando en trminos muy amplios, se e identican de momento las siguientes fases: 1. Anlisis del problema, estableciendo con precisin lo que se plantea. a o 2. Solucin conceptual del problema, describiendo un mtodo (algoritmo) que o e lo resuelva. 3. Escritura del algoritmo en un lenguaje de programacin. o En la primera fase es corriente partir de un problema denido vagamente, y el anlisis del mismo consiste en precisar el enunciado, identicando los datos a de partida y los resultados que se desean obtener. La descripcin precisa de un o problema se llama especicacin. Con frecuencia, el lenguaje natural no basta o para lograr la precisin deseada, por lo que se recurre en mayor o menor medida o a lenguajes formales, como la lgica o las matemticas. Supongamos por ejemplo o a que se plantea el problema de dividir dos nmeros. En primer lugar, se necesita u saber si se trata de la divisin entera o de aproximar el resultado con decimales o y, en ese caso, hasta dnde. Pongamos por caso que interesa la divisin eucl o o dea. Una descripcin precisa deber tener en cuenta que los datos son dos enteros o a (llammosles dividendo y divisor, como es usual), de los que el segundo es no e nulo. El resultado es tambin un par de enteros (llammosles cociente y resto, e e como siempre) tales que dividendo = divisor cociente + resto Pero eso no es todo: si nos contentamos con ese enunciado, para todo par de enteros (dividendo, divisor), el par (0, dividendo) siempre es una solucin. Por o eso, hay que aadir que resto debe ser adems tal que 0 resto < divisor. n a

Con este pequeo ejemplo se pretende resaltar la importancia de analizar n bien el problema planteado, deniendo con precisin los requisitos que deben o vericar los datos y las condiciones en que deben estar los resultados.

Sin embargo, en esta fase slo se ha estudiado qu se desea obtener, y no o e cmo lograrlo. Este es el cometido de la segunda etapa: describir un mtodo o e (algoritmo) tal que partiendo de datos apropiados lleve sistemticamente a los rea sultados descritos en la especicacin. Del concepto de algoritmo nos ocupamos o

1.2. Concepto de algoritmo

1 Principio 2 4 Comprar impresos de matriculacin o 5 7 Rellenar el sobre y pagar en el banco 8 Est abierto el a plazo de matr cula? s 4 no 3

2 Esperar a maana n 2

5 Leer instrucciones. Tengo alguna duda? s 6 no 7 8 Entregar el sobre 9

6 Preguntar dudas en Secretar a 7 9 Fin

Figura 1.1.

en el siguiente apartado. Es evidente que slo se puede conar en un algoritmo o si ha superado con xito determinado control de calidad: la primera e inexcusae ble exigencia es que sea correcto; esto es, que resuelva el problema especicado. Cuando un problema admita varios algoritmos como solucin, convendr dispoo a ner de criterios para escoger; y cuando un problema no tenga solucin, no resulta o sensato buscar un algoritmo para resolverlo. Estos aspectos los estudiamos en el apartado 1.3. Finalmente, para que un computador resuelva problemas hay que escribir el algoritmo en un lenguaje de programacin; de ello hablaremos en el apartado o 1.4.

1.2

Concepto de algoritmo

Los conceptos de algoritmo y de mtodo son parecidos: los mtodos para e e efectuar procesos forman parte de las costumbres o rutinas que el hombre aprende un d y luego repite de manera inconsciente, sin reparar ya en las acciones, ms a a sencillas, que integran el proceso (por ejemplo andar, leer o conducir). Por eso el concepto de algoritmo suele compararse a otros como mtodo o rutina de e acciones. La secuencia de pasos de la gura 1.1 describe un mtodo para efectuar la e matr cula en la universidad. Sin embargo, el trmino algoritmo tiene connotaciones ms formales que e a cualquier otro debido a su origen: se trata de una acomodacin al castellano del o

Cap tulo 1. Problemas, algoritmos y programas

nombre de Muhammad ibn Ms al-Jwr , matemtico persa que populariz ua a zm a o . su descripcin de las cuatro reglas (algoritmos) de sumar, restar, multiplicar y o dividir.

1.2.1

Una denicin de algoritmo o

Hablando informalmente, un algoritmo es la descripcin precisa de los pasos o que nos llevan a la solucin de un problema planteado. Estos pasos son, en geneo ral, acciones u operaciones que se efectan sobre ciertos objetos. La descripcin u o de un algoritmo afecta a tres partes: entrada (datos), proceso (instrucciones) y salida (resultados).1 En este sentido, un algoritmo se puede comparar a una funcin matemtica: o a + : Z Z Z Z Z Z (algoritmo) (entrada) (proceso) (salida) Incluso en algoritmos no matemticos es fcil identicar las tres partes: entraa a da, proceso y salida. As ocurre, por ejemplo, en las instrucciones para hacer la declaracin de la renta. o Caracter sticas de los algoritmos La descripcin de algoritmo que se ha dado es algo imprecisa. Una caracteo rizacin ms completa deber incluir adems los siguientes requisitos: o a a a 1. Precisin o Un algoritmo debe expresarse de forma no ambigua. La precisin afecta o por igual a dos aspectos: (a) Al orden (encadenamiento o concatenacin) de los pasos que han de o llevarse a cabo. (b) Al contenido de las mismas, pues cada paso debe saberse realizar con toda precisin, de forma automtica. o a Por lo tanto, una receta de cocina puede ser considerada como un mtodo, e pero carece de la precisin que requieren los algoritmos debido al uso de o expresiones como aadir una pizca de sal, porque qu debe entenderse por n e una pizca?
Es habitual llamar datos a la entrada y resultados a la salida, aunque el concepto de dato es ms amplio, y abarca a toda la informacin que maneja un algoritmo, ya sea inicialmente o a a o su trmino, as como tambin durante el transcurso de su utilizacin. e e o
1

1.2. Concepto de algoritmo

Principio
1

a
2

a = 0

NO
4

a a-1

b b+1

6 SI
b
7

Fin

Figura 1.2. Diagrama de ujo de la suma lenta.

2. Determinismo Todo algoritmo debe responder del mismo modo ante las mismas condiciones. Por lo tanto, la accin de barajar un mazo de cartas no es un algoritmo, o ya que es y debe ser un proceso no determinista. 3. Finitud La descripcin de un algoritmo debe ser nita. o Un primer ejemplo Consideremos el ejemplo que, expresado grcamente,2 aparece en la a gura 1.2. El algoritmo descrito tiene por objetivo sumar dos cantidades enteras. Si se anotan esas cantidades inicialmente en sendas casillas (a las que llamaremos a y b para abreviar), este mtodo consiste en ir pasando de a a b una unidad e cada vez, de forma que, cuando a = 0, el resultado ser el valor de b. a Vemos un ejemplo de su funcionamiento con los datos 2 y 3 en la gura 1.3. (Los nmeros se han incluido para seguir mejor la evolucin de los clculos.) u o a Se observa que la descripcin del algoritmo que se ha dado es, en efecto, o precisa (cada paso est exento de ambigedad, as como el orden en que se debe a u efectuar) y determinista (el efecto de cada paso es siempre el mismo para unos datos concretos cualesquiera). Estas dos caracter sticas son una consecuencia del lenguaje escogido para expresar el algoritmo.
El lenguaje empleado es el de los diagramas de ujo, que estudiaremos en el cap tulo 7, apartado 7.2.1. Esperamos que, debido a su sencillez, el funcionamiento de este ejemplo resulte comprensible directamente.
2

8 Posicin o 1 2 3 4 5 3 4 5 3 6 7

Cap tulo 1. Problemas, algoritmos y programas Datos pendientes [2, 3] [3] [] [] [] [] [] [] [] [] [] Resultados emitidos [] [] [] [] [] [] [] [] [] [] [5] Var a ? 2 2 2 1 1 1 0 0 0 0 Var b ? ? 3 3 3 4 4 4 5 5 5

Figura 1.3. Ejecucin de la suma lenta con los datos 2 y 3. o

En cambio, si bien es cierto que, con los datos del ejemplo, se ha obtenido una solucin, si se examina con detenimiento se ver que ese algoritmo no o a siempre termina para dos cantidades enteras cualesquiera (v. g. 3 y 1). De hecho, la terminacin es una caracter o stica escurridiza de la que hablaremos ms tarde (vase el apartado 1.3.1). a e

1.2.2

Una denicin formal de algoritmo o

La caracterizacin hecha hasta ahora de los algoritmos es satisfactoria a efeco tos prcticos. Ms concreta an resulta a la vista de un lenguaje algor a a u tmico como el de los diagramas de ujo, que deja entrever dos aspectos: El mantenimiento de unas variables (a y b) y de unas posiciones (1, . . . , 7), a lo que llamamos estado. Por ejemplo, cada la de la tabla de la gura 1.3 representa un estado en la evolucin del algoritmo 1.2. o La descripcin de transiciones entre estados, que vienen dadas por el propio o diagrama. Estos aspectos de los algoritmos estn ausentes de la primera denicin, a o por lo que puede resultar an algo incompleta; surge pues la necesidad de una u denicin ms formal: o a Denicin: o mentos: Un algoritmo es una cudrupla que comprende los siguientes elea

1. El conjunto de los estados (llammosle E) que pueden presentarse en todo e momento durante el clculo. a

1.2. Concepto de algoritmo Un estado viene dado por una tupla, incluyendo: los valores de las variables que entran en juego; los datos sin leer y los resultados emitidos, y

la marca, identicando la posicin del algoritmo en la que se da este o estado de los clculos. a

Es decir, un estado se puede expresar as : <Datos por leer; Resultados emitidos; Variables; Posicin> o 2. La identicacin de los estados iniciales, I E, o estados posibles al o comienzo del algoritmo. 3. La identicacin de los estados nales, F E, como posibles estados al o terminar el algoritmo. 4. Una funcin de transicin entre estados, o o t : E E que describe el efecto de cada paso del cmputo asociado al algoritmo. o Esta funcin deber: o a Estar denida dentro de E, esto es, para cualquier e E debemos tener que t(e) E. As las transiciones entre estados son precisas y , deterministas. A partir de cualquier estado inicial, la funcin de transicin t debe o o llevar a un estado nal en un nmero nito de pasos, formalmente: u para cualquier e I existe un k IN tal que
t se aplica k veces

t(t( t(e) )) F y, adems, no tiene efecto alguno sobre los estados nales, es decir: a para cualquier e F ocurre que t(e) = e. De aqu se obtiene la caracter stica de nitud. Siguiendo con el algoritmo del diagrama de ujo de la gura 1.2, identicamos los siguientes estados: E = { < 1 ; [d1 , d2 ] ; [ ] ; < 2 ; [d2 ] [] [] ; [] ; < p ; < 7 ; a a0 > > > }

; [r] ;

; [ ] ; a a 0 , b b0 >

10

Cap tulo 1. Problemas, algoritmos y programas

donde d1 , d2 , a, b, r IN, p {3, . . . , 6}, y siendo I = {< 1; [d1 , d2 ]; [ ]; >} y F = {< 7; [ ]; [r]; >}. La funcin de transicin t es la siguiente: o o t(< 1; [d1 , d2 ]; [ ]; >) t(< 2; [d2 ]; [ ]; a d1 >) = < 2; [d2 ]; [ ]; a d1 >

= < 3; [ ]; [ ]; a d1 , b d2 > < 6; [ ]; [ ]; b b0 > si a = 0 t(< 3; [ ]; [ ]; a a0 , b b0 >) = < 4; [ ]; [ ]; a a0 , b b0 > si a = 0 t(< 4; [ ]; [ ]; a a0 , b b0 >) = < 5; [ ]; [ ]; a a0 1, b b0 > t(< 5; [ ]; [ ]; a a0 , b b0 >) = < 3; [ ]; [ ]; a a0 , b b0 + 1 > t(< 6; [ ]; [ ]; b b0 >) t(< 7; [ ]; [r]; >) = < 7; [ ]; [b0 ]; > = < 7; [ ]; [r]; >

Desde el punto de vista del usuario de un algoritmo, se puede considerar un algoritmo como una caja opaca cuyos detalles internos se ignoran, aorando slo o la lectura de los datos y la escritura de los resultados. Estos aspectos observables desde el exterior se llaman frecuentemente la interfaz externa. No obstante, el mecanismo interno interesa al autor de algoritmos, esto es, al programador. Para atender esta necesidad, algunos entornos de programacin permiten trazar o programas, con lo que el programador puede ver evolucionar el estado interno durante la marcha de su programa con el grado de detalle que desee (vanse e los apartados 5.4.1 y C.2.6). Esta posibilidad es interesante para depurar los programas, esto es, para buscar los posibles errores y subsanarlos. Para terminar este apartado, debemos decir que el esquema de funcionamiento descrito a base de transiciones entre estados se conoce con el nombre de modelo de von Neumann (vase el apartado 7.2.1 de [PAO94]). Se dice que este e modelo es secuencial, en el sentido de que los pasos se efectan uno tras otro. De u hecho, no es posible acelerar el proceso efectuando ms de un paso a un tiempo, a porque cada paso tiene lugar desde el estado dejado por el paso anterior. Este cuello de botella es un defecto propio de las mquinas de von Neumann. a Cualidades deseables de un algoritmo Es muy importante que un algoritmo sea sucientemente general y que se ejecute ecientemente. Veamos con ms detalle qu se entiende por general y a e por eciente:

1.3. Aspectos de interes sobre los algoritmos 1. Generalidad

11

Es deseable que un algoritmo sirva para una clase de problemas lo ms a amplia posible. Por ejemplo, la clase de problemas resolver una ecuacin o de segundo grado, a + bx + cx2 = 0 es ms general que la consistente a en resolver ecuaciones de primer grado, a + bx = 0. 2. Eciencia Hablando en trminos muy generales, se considera que un algoritmo es e tanto ms eciente cuantos menos pasos emplea en llevar a cabo su comea tido. Por ejemplo, para hallar la suma de dos nmeros naturales, la regla u tradicional que se aprende en enseanza primaria es ms eciente que el n a rudimentario procedimiento de contar con los dedos, de uno en uno. Este tema se revisar en el apartado 1.3.3, y ser tratado con mayor detalle en a a el cap tulo 18. Estas dos cualidades no son siempre conciliables, por lo que frecuentemente hay que optar por una solucin en equilibrio entre ambas cuando se disea un o n algoritmo. Por ejemplo, un algoritmo que estudia y resuelve sistemas de ecuaciones es ms general que uno que resuelve sistemas de ecuaciones lineales, y a ste a su vez es ms general que otro que slo considera sistemas de dos ecuae a o ciones lineales con dos incgnitas. Sin embargo, a mayor generalidad se tiene o tambin una mayor complejidad puesto que hay que tratar ms casos y no se e a pueden aplicar algoritmos espec cos. Por consiguiente, si un buen nmero de u situaciones puede resolverse con un algoritmo rpido aunque poco general, es a preferible adoptar ste. e

1.3
1.3.1

Aspectos de inters sobre los algoritmos e


Computabilidad

Con el aumento de potencia y el abaratamiento de los computadores, cada vez se plantean aplicaciones ms sorprendentes y de mayor envergadura, capaces de a resolver problemas ms generales. Da la impresin de que cualquier problema que a o se plantee ha de ser computable (esto es, ha de tener una solucin algor o tmica), sin otra limitacin que la potencia de la mquina ejecutora. o a Sin embargo, esto no es as Por rotunda que pueda parecer esta armacin, . o se debe aceptar que hay problemas no computables La cuestin que se plantea es algo delicada: advirtase que, si no se conoce o e algoritmo que resuelva un problema planteado, slo se puede asegurar que no o

12

Cap tulo 1. Problemas, algoritmos y programas

se conoce algoritmo que resuelva ese problema; en cambio, establecer que un problema no es computable requiere demostrar que nunca se podr encontrar a ningn algoritmo para resolver el problema planteado. u Los siguientes son ejemplos de problemas no computables: Dcimo problema de Hilbert. e

Resolver una ecuacin diofntica con ms de una incgnita. o a a o Esto signica encontrar soluciones enteras de una ecuacin de la forma o P (x1 , x2 , . . .) = 0, donde P es un polinomio con coecientes enteros.

Problema de la parada.

Determinar si un algoritmo a naliza o no cuando opera sobre una entrada de datos d: si a(d) S Stop(a, d) = No si a(d)

donde a(d) (resp. a(d) ) expresa que el algoritmo a, aplicado al dato d, s para (resp. no para).

Examinaremos con ms atencin el problema de la parada, y despus veremos a o e cun escurridizo puede resultar determinar si un algoritmo particular para o no a considerando un sencillo ejemplo. Claro est que esto no demuestra nada salvo, a en todo caso, nuestra incapacidad para analizar este algoritmo en concreto. Por ello, incluimos seguidamente una demostracin de que es imposible hallar un o algoritmo capaz de distinguir entre los algoritmos que paran y los que no. Obsrvese que se plantea aqu estudiar algoritmos que operan sobre algorite mos, vistos como datos. Por extrao que parezca al principio, en Computacin n o se desarrollan frecuentemente programas que manejan otros programas con diversos nes. El problema de la parada no es computable El problema de la parada, denido ms arriba, se puede expresar como sigue: a se puede examinar cualquier algoritmo y decidir si para? Demostraremos que no existe, ni puede existir, el algoritmo Stop que distinga si un algoritmo a para cuando se aplica a los datos d. Procederemos por reduccin al absurdo: Suponiendo que existe ese algoritmo o (descrito ms arriba como Stop), a partir de l es posible construir otro similar, a e Stop, que averige si un algoritmo a para cuando se aplica a su propio texto: u Stop(p) = Stop(p, p) = S No si p(p) si p(p)

1.3. Aspectos de interes sobre los algoritmos

13

Y entonces, tambin se puede denir el siguiente algoritmo a partir del ane terior: Raro(p) = No si Stop(p) = S si Stop(p) = No = No si p(p) si p(p)

Veamos que el algoritmo Raro tiene un comportamiento verdaderamente extrao cuando se aplica a s mismo: n No No si Stop(Raro) = S si Stop(Raro) = No si Raro(Raro) si Raro(Raro)

Raro(Raro) = =

lo que resulta obviamente imposible. La contradiccin a que hemos llegado nos lleva a rechazar la hiptesis inicial o o (la existencia de un algoritmo para el problema de la parada), como quer amos demostrar. N meros pedrisco u Como ejemplo de la dicultad de examinar un algoritmo y decidir si concluir a tarde o temprano, consideremos la siguiente funcin t: IN IN: o 3n + 1 si n es impar t(n) = n/2 si n es par Partiendo de un nmero natural cualquiera, le aplicamos t cuantas veces sea u necesario, hasta llegar a 1. Por ejemplo, 3 10 5 16 8 4 2 1 Esta sencilla sucesin genera nmeros que saltan un nmero de veces impreo u u decible, de manera que cabe preguntarse si todo natural n alcanza el 1 tras una cantidad nita de aplicaciones de t, o por el contrario existe alguno que genera trminos e 3 que saltan indenidamente. Se ha comprobado, por medio de computadores, que la sucesin llega a 1 si o 40 12 , pero a n no se se comienza con cualquier nmero natural menor que 2 u 10 u ha podido demostrar para todo n.
3

27 1

t111

Este problema tambin se conoce como problema 3n+1. e

14

Cap tulo 1. Problemas, algoritmos y programas

1.3.2

Correccin de algoritmos o

El aspecto de la correccin es de crucial importancia para quien desarrolla un o algoritmo. Sin embargo, es imposible detectar los posibles errores de una forma sistemtica. Con frecuencia, la bsqueda de errores (depuracin) consiste en la a u o comprobacin de un algoritmo para unos pocos juegos de datos. Sin embargo, eso o no garantiza nada ms que el buen funcionamiento del algoritmo para esos juegos a de datos y no para todos los posibles. Volviendo al algoritmo de la suma lenta, la comprobacin con los juegos de datos [2, 3] y [3, -1] ofrecer resultados correctos, o a y sin embargo el algoritmo unicamente termina cuando el primer sumando es (un entero) positivo. Frente a la comprobacin, la vericacin consiste en la demostracin del buen o o o funcionamiento de un algoritmo con respecto a una especicacin. Esto es, la o vericacin trata de garantizar que, para todos los datos considerados (descritos o en la especicacin), el algoritmo lleva a los resultados (tambin descritos en la o e especicacin) deseados. Por eso, aunque frecuentemente se habla de correccin o o de un algoritmo, sin ms, en realidad hay que decir correccin de un algoritmo a o con respecto a una especicacin. o Por lo general, la vericacin se basa en establecer aserciones (propiedades) o del estado de la mquina en cada paso del algoritmo. Se profundizar en esta a a idea a lo largo de todo el texto. Por ejemplo, para vericar el funcionamiento del algoritmo de suma lenta, consideremos que se hace funcionar sobre dos enteros genricos m y n. Claramente, al llegar a la posicin 3 por vez primera, a = m y e o b = n. Por otra parte, en la posicin 3 se tiene invariablemente la propiedad o a+b=m+n independientemente de las vueltas que se den y de las modicaciones que se efecten sobre a y b,4 ya que cada unidad que se reste a a se le suma a b. Ahora u bien, cuando se pasa de la posicin 3 a la 6 es por ser a = 0, con lo que se tiene, o simultneamente, el invariante a + b = m + n y a = 0, es decir: a b=m+n lo que asegura que la salida es correcta. . . cuando sta se produzca. Un algoritmo e que ofrece una solucin correcta cuando para, pero del que no sepamos si para o o no, se dice parcialmente correcto. Ese es el caso de la suma lenta de nmeros u enteros. Adems, si inicialmente a es un nmero natural, es seguro que el algoritmo a u para, ya que la operacin a a 1 lo llevar a cero, precisamente en a vueltas o a del bucle. Si se asegura que un algoritmo es parcialmente correcto y que para
4

Por ello, esta propiedad se conoce como invariante.

1.3. Aspectos de interes sobre los algoritmos

15

en un tiempo nito, se dice que el algoritmo es (totalmente) correcto. Ese es el caso de la suma lenta de pares de nmeros, siendo el primero de ellos entero y u positivo. La vericacin de algoritmos no es una tarea fcil. Al contrario, vericar o a completamente un algoritmo involucra el uso de lgica matemtica, y con freo a cuencia resulta incluso ms complicado que desarrollarlo. De ah el inters que a e tiene esmerarse, desde el principio, en escribir algoritmos correctos, adquiriendo un buen estilo y esforzndose en emplear metodolog apropiadas para ello. a as

1.3.3

Complejidad de algoritmos

Resulta de gran inters poder estimar los recursos que un algoritmo necesita e para resolver un problema. En mquinas secuenciales, estos recursos son el a tiempo y la memoria.5 Muchas veces, un algoritmo tarda tanto en ofrecer el resultado que resulta, en realidad, intil. Un ejemplo de esta situacin se da en sistemas de control u o de procesos, en donde la respuesta a determinadas circunstancias debe disparar mecanismos de seguridad en un tiempo cr tico (por ejemplo, en centrales nucleares). Anlogamente para el espacio, es posible que la mquina en que ha a a de funcionar un programa disponga de una capacidad de memoria limitada y nos veamos obligados a elegir algoritmos que usen poca memoria. Por lo tanto, existen situaciones en las que si disponemos de varios algoritmos para un mismo problema, deberemos decidir cul es el ms rpido o el que menos cantidad de a a a memoria requiere. En el cap tulo 18 se ahondar en esta idea. a Como el tiempo requerido por los programas depende en gran medida de la potencia de la mquina ejecutora, es frecuente medir en pasos (de coste jo) a el coste del algoritmo correspondiente. Para concretar un poco, y siguiendo con el ejemplo de la suma lenta, consideremos que cada bloque o caja del diagrama es un paso y, por tanto, cuesta una unidad. Entonces, es fcil deducir que sumar a los naturales m y n lleva 3m + 4 pasos. En efecto, llamando tm al coste de sumar m y n, se tiene que t0 = 4 pasos tm = tm1 + 3, si n 1 pues para sumar 0 y n hay que realizar cuatro pasos (vase la gura 1.2) y si e m = 0 hay que realizar tres pasos para reducir m en una unidad; resumiendo t0 = 4
En los ultimos aos, tambin est interesando medir el nmero de procesadores en mquinas n e a u a de procesamiento en paralelo (vase el apartado 3.5 de [PAO94]). e
5

16

Cap tulo 1. Problemas, algoritmos y programas t1 = t0 + 3 = 4 + 3 1

t2 = t1 + 3 = 4 + 3 2 . . . . . . . . .

tm = tm1 + 3 = 4 + 3 m independientemente de n. Por otra parte, es frecuente concentrar el inters en el coste para datos grane des estudiando el comportamiento asinttico. De este modo, es posible redono dear el tiempo despreciando trminos dominados por otros: e 3m + 4 3m Tambin es frecuente despreciar factores de proporcionalidad, absorbidos por e las velocidades de proceso de mquinas con distinta potencia, con lo cual a 3m m de manera que, en resumen, se dir que el algoritmo estudiado tiene coste lia neal. Naturalmente, podemos encontrarnos algoritmos con coste constante, logar tmico, lineal, cuadrtico, exponencial, etc. a Hallar el coste de un algoritmo no es en general una tarea fcil. Con frea cuencia requiere una fuerte base matemtica. Sin embargo, tambin es util a e comprender, desde el principio, que la potencia de los computadores no es ilimi tada, y que la ejecucin de los programas consume sus recursos. Ese es el inters o e de que merezca la pena esforzarse por estudiar y encontrar algoritmos ecientes, aunque ello requiere muchas veces sacricar su simplicidad.
m

1.4

Lenguajes algor tmicos y de programacin o

La descripcin de un algoritmo incluye organizar los datos que intervienen o en el mismo, as como las acciones que se deben llevar a cabo sobre ellos. Una vez ideado el algoritmo, el modo ms natural e inmediato (y tambin el menos a e formal) de expresar esa organizacin es redactndolo con palabras y frases del o a lenguaje cotidiano. En el extremo opuesto se sitan, por su rigidez, los lenguajes u de programacin. o Entre la libertad, exibilidad y ambigedad de los lenguajes naturales y la u precisin, rigidez y limitaciones de expresividad de los lenguajes de programacin o o se sitan los lenguajes algor u tmicos. Estos tienen las siguientes cualidades: 1. Tienden un puente entre la forma humana de resolver problemas y su resolucin mediante programas de computador. o

1.4. Lenguajes algor tmicos y de programacion

17

2. Tienen cierta independencia de los lenguajes de programacin particulares, o de modo que estn libres de sus limitaciones y as los algoritmos escritos a en ellos se pueden traducir indistintamente a un lenguaje de programacin o u otro. Por ello, los lenguajes algor tmicos constituyen una herramienta expresiva con una libertad y exibilidad prxima a la de los naturales y con el rigor de los o de programacin. o En realidad, las unicas restricciones que deber imponerse a estos lenguajes an proceden de las caracter sticas que tienen los algoritmos: expresar sus acciones (qu deben realizar y cundo) con la precisin necesaria, y que estas acciones e a o sean deterministas. Por consiguiente, todo lenguaje algor tmico debe poseer mecanismos con que expresar las acciones as como el orden en que han de llevarse a cabo. Las acciones, se expresan mediante instrucciones (tambin llamadas rdenes o sene o tencias), que son comparables a verbos en innitivo: asignar. . , leer. . , escribir. . . y otras. La concatenacin de las instrucciones expresa en qu orden deben suceo e derse las acciones; esto es, cmo se ensamblan unas tras otras. Los modos ms o a usados para ensamblar rdenes son la secuencia, la seleccin y la repeticin, y o o o los estudiaremos en el cap tulo 7. A la vista de las observaciones anteriores es lgico cuestionar que los lenguajes o naturales sean algor tmicos (debido sobre todo a la ambigedad que es inherente u a ellos). Adems, resulta ms prctico utilizar medios de expresin normalizados, a a a o facilitndose la comunicacin entre diseadores y usuarios de los algoritmos y a o n las personas que los desarrollan. Los diagramas de ujo constituyen uno de los lenguajes algor tmicos que mayor difusin han alcanzado, aunque su uso est menguando drsticamente en o a a los ultimos aos en favor de otros mtodos ms apropiados desde el punto de n e a vista formativo.6 Entre ellos, debe citarse el seudocdigo, que aportar valiosas o a ideas para la adquisicin de un buen estilo de programacin y, en denitiva, para o o aprender cmo enfocar la resolucin de problemas complicados. Seguidamente, o o describimos el algoritmo de la suma lenta mediante seudocdigo: o
Sean a, b Z Z Leer a y b Mientras a = 0, hacer Escribir b
6

aa1 bb+1

De hecho, nosotros desaconsejamos su uso como lenguaje de desarrollo de algoritmos, limitando su estudio a mostrar, precisamente, lo que no es la programacin estructurada (vase el o e apartado 7.2.1).

18

Cap tulo 1. Problemas, algoritmos y programas

En la prctica, el seudocdigo se usa como un medio expresivo de camino a o hacia un lenguaje de programacin concreto. Por tanto, es lgico que los algorito o mos seudocodicados tengan un parecido notorio con los programas escritos en ese lenguaje. En concreto, comprese por ejemplo el seudocdigo anterior con el a o programa siguiente, escrito en Pascal:
Program SumaLenta (input, output); {Se suman dos enteros, pasando unidades de uno a otro} {PreC.: input = [m n], enteros} var a, b: integer; begin ReadLn (a, b); {Inv.: a + b = m + n} while a <> 0 do begin a:= a-1; b:= b+1 end; {while} WriteLn(b) end. {SumaLenta}

1.5

Desarrollo sistemtico de programas a

En los ultimos aos, las aplicaciones comerciales se han hecho cada vez ms n a grandes y complejas y, por tanto, su desarrollo resulta cada vez ms caro, lento a y sujeto a numerosos errores. Este problema fue tremendamente importante hacia los aos sesenta (se hablaba entonces de crisis del software), y para n tratar de solucionarlo surgi entonces la Ingenier del software, que considera o a el desarrollo de programas y aplicaciones como un proceso productivo donde se aplican tcnicas de ingenier El desarrollo del software se organiza en fases, e a. que en conjunto se conocen como el ciclo de vida.7 Son las siguientes: Planicacin: En esta fase inicial hay que constatar la verdadera necesidad del o producto que se va a desarrollar y valorar los recursos humanos y tcnicos e que precisa su desarrollo. Esta valoracin se traduce en coste econmico y o o tiempo de elaboracin. Si se aprueba el proyecto, se pasa a la fase siguiente. o
La organizacin de estas fases da lugar a diversas variantes del ciclo de vida, siendo uno de o los ms aplicados el conocido como ciclo de vida clsico. Existen cr a a ticas importantes a ste, e bsicamente por su carcter secuencial y por la dicultad de establecer inicialmente todas las a a especicaciones. En respuesta a estas cr ticas se han creado otros modelos de desarrollo como son la utilizacin de prototipos y de lenguajes de cuarta generacin (4GL) y el llamado modelo en o o espiral.
7

1.5. Desarrollo sistematico de programas

19

Anlisis: En la fase de anlisis se establecen cules deben ser la funciones que a a a debe cumplir la aplicacin y cmo debe realizarse el trabajo conjunto de los o o diferentes mdulos en que se va a dividir. Dentro de esta fase se determina o un sistema de pruebas que permita detectar los posibles errores y asegurar el funcionamiento correcto de la aplicacin y las condiciones para unir los o mdulos de forma able. Como resultado de esta fase se redactan las o especicaciones detalladas del funcionamiento general del software. Dise o: En esta fase se disea el conjunto de bloques, se dividen en partes y n n se asignan a los equipos de programadores. Cada equipo elabora su parte, escribindola en un lenguaje algor e tmico y probndola de forma manual. a Como resultado de esta fase se obtienen algoritmos escritos en lenguaje algor tmico. Codicacin: La fase de codicacin se confunde muchas veces con la proo o gramacin y consiste en escribir los algoritmos en un lenguaje de prograo macin. Es un proceso casi automtico. o a Validacin: La fase de validacin consiste en aplicar el sistema de pruebas a los o o mdulos, a las conexiones entre ellos (prueba de integracin) y, por ultimo, o o a la totalidad de la aplicacin (prueba de validacin). Como resultado o o de esta fase se genera la aplicacin habiendo corregido todos los errores o detectados en las pruebas. Mantenimiento: En la fase de mantenimiento se redacta la documentacin o actualizada, tanto para el programador como para el usuario. Se inicia la explotacin de la aplicacin y se detectan y corrigen los errores y deo o ciencias no advertidas en las fases anteriores, lo que puede suponer un coste aadido importante. El resultado de esta fase es una aplicacin en n o explotacin. o Evidentemente, en un curso como ste, los programas que vamos a desarroe llar no requieren, por su reducido tamao, la ejecucin de todas estas fases. Sin n o embargo, conviene advertir que la tarea de la programacin no consiste en la o codicacin (escritura de un programa) directa, ni siquiera para programas seno cillos, sino que incluye otras fases: es necesario comprender primero el problema que se plantea; si se puede dividir en subproblemas, esto reduce la complejidad, aunque en ese caso hace falta acoplar los diferentes mdulos (por el momento, o digamos que se trata de fragmentos de programa); y, por supuesto, es ineludible que el programa sea correcto, de forma que este requisito necesario merece toda nuestra atencin. o

20

Cap tulo 1. Problemas, algoritmos y programas

1.6

Conclusin o

La importancia de los algoritmos y de los lenguajes algor tmicos reside en su capacidad para expresar procesos, encaminados a ofrecer la solucin a proo blemas planteados, con independencia de los lenguajes de programacin, y aun o de la mquina, siendo as posible el desarrollo de algoritmos para computadoa res todav no creados: de hecho, muchos de los programas actuales responden a a algoritmos concebidos mucho antes de que apareciera el primer computador: considrese por ejemplo el mtodo que ide Euclides en el siglo iv a. C. para e e o hallar el mximo comn divisor de dos nmeros naturales. Los lenguajes de a u u programacin son tambin importantes, ya que permiten materializar los algoo e ritmos, razn por la que se ha avanzado tanto en el estudio de stos en las ultimas o e dcadas. e Por otra parte, tratar el mtodo (algoritmo) como objeto de estudio supone e un salto de nivel en la resolucin de problemas, ya que ahora no slo se requiere la o o aplicacin de un determinado procedimiento de resolucin, sino que cobra imporo o tancia la invencin y descripcin del mismo. Es decir, para resolver un problema o o habr que identicar qu acciones conducen a la solucin, y cmo describirlas y a e o o organizarlas. En otras palabras, en este cap tulo, nuestro modus operandi ha consistido en: 1. Disponer de un problema y un mtodo. e 2. Resolverlo; es decir, aplicar el mtodo al problema. e mientras que, a partir de ahora, slo disponemos de un problema y no del mtodo o e que debemos aplicar, por lo que deberemos disear y desarrollar el mismo para n poder aplicarlo al problema planteado. En resumidas cuentas, la resolucin de o un problema general requerir: a 1. Disear un algoritmo apropiado. n 2. Escribir el programa correspondiente en un lenguaje de computador. 3. Ejecutar el programa correspondiente para el juego de datos del problema.

1.7

Ejercicios
(i) Resolver una ecuacin de primer grado de la forma a + bx = 0. o (ii) Sumar dos fracciones. (iii) Interpretar una partitura al viol n.

1. Considere la relacin de problemas: o

1.8. Referencias bibliograficas


(iv) Hacer la cuenta atrs, desde 10 hasta 0. a Para cada uno de ellos, se pide que: (a) Identique cules son los datos y los resultados. a (b) Describa un problema ms general y, si se puede, otro menos general. a

21

(c) Distinga cules de esos problemas pueden resolverse mediante algoritmos y a cules no. a (d) Esboce, con sus propias palabras o en seudocdigo, un algoritmo para los o problemas (i), (ii) y (iv). 2. El problema de restar dos enteros positivos se puede resolver por un procedimiento anlogo al de la suma lenta: en vez de pasar unidades de una cantidad a la otra, a (2, 3) (1, 4) (0, 5) 5 se van restando unidades a ambas a la vez: (9, 2) (8, 1) (7, 0) 7 Se pide que: (a) Escriba un diagrama de ujo para este problema. (b) Examine cmo evoluciona para el clculo de 5 2. o a (c) Estudie su complejidad. (d) Estudie su correccin. o (e) Exprese el algoritmo en seudocdigo. o (f) Redacte el programa correspondiente en Pascal basndose en el programa a presentado para la suma lenta.

1.8

Referencias bibliogrcas a

Muchos de los conceptos que se estudian en este cap tulo (y en algunas otras partes de este libro) pueden consultarse tambin en [GL86], que ofrece una introduccin clara e o y amena a diversos aspectos tericos y prcticos sobre los algoritmos y programas, su o a desarrollo y ejecucin, as como sus implicaciones sociales. La formalizacin de los o o algoritmos dada aqu es una adaptacin de la presentada en [FS87]. o El apartado de los lenguajes algor tmicos se ampl un poco en el cap a tulo 7 de este mismo libro, donde se introducen el seudocdigo y los diagramas de ujo. Debe o advertirse sin embargo que esta referencia no anima al lector a estudiar los diagramas de ujo ahora, sino ms bien a esperar su momento y limitar su estudio al cometido con a que all se introducen. A pesar del t tulo del ep grafe lenguajes algor tmicos y de programacin, no se ha o dicho mucho sobre estos ultimos: en realidad, la idea es establecer un primer v nculo entre ellos. Para ampliar lo dicho aqu sobre los lenguajes de programacin, remitimos o al cap tulo 5 de [PAO94] y a la bibliograf que all se reere. a

Cap tulo 2

El lenguaje de programacin Pascal o

2.1 2.2 2.3 2.4

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . o Otros detalles de inters . . . . . . . . . . . . . . . . . . e Origen y evolucin del lenguaje Pascal . . . . . . . . . o Pascal y Turbo Pascal . . . . . . . . . . . . . . . . . . . .

23 24 24 25

En este breve cap tulo se presenta el lenguaje de programacin Pascal, resalo tando algunas de sus caracter sticas ms importantes, como la sencillez de sus a instrucciones, la estructuracin de stas y su capacidad expresiva, que permite o e implementar algoritmos de muy diversa ndole. Estas caracter sticas, entre otras, son las que justican que Pascal sea el lenguaje de programacin utilizado en o este libro.

2.1

Introduccin o

Pascal es un lenguaje de alto nivel : se parece ms al lenguaje natural hablado, a o al matemtico, que al lenguaje de mquina (vase el cap. 5 de [PAO94]). Este a a e nivel se alcanza gracias a una pequea coleccin de mecanismos simples pero n o de una gran potencia: unos permiten estructurar acciones (secuencia, seleccin e o iteracin), otros datos (arrays, registros, cheros), y otros hacen posible extender o el lenguaje, dotndolo en general con conceptos (datos y operaciones) semejantes a a los empleados en el razonamiento humano sobre los problemas, como se mostr o en el apartado 1.4.

24

Cap tulo 2. El lenguaje de programacion Pascal

La gran expresividad debida a la particular estructuracin de sus datos y de o su pequea coleccin de instrucciones evita la necesidad de recurrir a otros mecan o nismos (probablemente enrevesados, dif ciles de expresar y de analizar, como por ejemplo la instruccin de bifurcacin incondicional goto); por todo esto decimos o o que es un lenguaje estructurado (vase el cap e tulo 7). Por otra parte, permite crear mdulos que extienden el lenguaje (por lo que se dice que es modular ; ver o el cap tulo 9); esta caracter stica permite desarrollar programas dividindolos en e partes (mdulos o subprogramas) ms pequeas, independientes, que se pueden o a n desarrollar por separado. Una caracter stica positiva de Pascal es su reducido conjunto de instrucciones, lo que lo hace relativamente compacto y fcil de aprender. En resumen, a el lenguaje Pascal facilita la adquisicin de buenos hbitos de programacin y o a o proporciona los instrumentos que permiten adaptarse a los principales mtodos e de desarrollo de algoritmos: programacin estructurada y modular y denicin y o o uso de tipos de datos. Estas tcnicas han convertido en las ultimas dcadas a la e e programacin en una actividad disciplinada y guiada por una cierta metodolog o a, y el lenguaje Pascal ha contribuido enormemente a su difusin y utilizacin. Por o o ello, es adems idneo como primer lenguaje de programacin, facilitando el a o o aprendizaje posterior de otros. As lo prueba su fuerte implantacin. o

2.2

Otros detalles de inters e

La mayor de los traductores del Pascal son compiladores (vase el apara e tado 5.3.1 de [PAO94]): el programa en Pascal se traduce de una sola vez a lenguaje mquina antes de ser ejecutado, y en ese proceso se detectan gran cana tidad de errores de forma automtica, permitiendo al programador enmendarlos a antes de la ejecucin. Como ejemplo de las vericaciones que se efectan durante o u la compilacin, una de las ms importantes consiste en la compatibilidad de los o a tipos de los objetos. Antes de la aparicin de Pascal exist lenguajes dirigidos a la programacin o an o cient ca (como Fortran) y otros dirigidos a la de gestin (como Cobol). El o lenguaje Pascal trata de conciliar los dos tipos de programacin, por lo que suele o decirse que Pascal es un lenguaje de propsito general. o

2.3

Origen y evolucin del lenguaje Pascal o

Es obvio decir que Pascal toma el nombre del matemtico francs Blaise a e Pascal (16231662) que en 1642 invent la primera mquina de calcular para o a ayudar a su padre en su trabajo de tasador de impuestos.

2.4. Pascal y Turbo Pascal

25

El lenguaje Pascal fue concebido por Niklaus Wirth en 1968 y denido en 1970 en el Instituto Politcnico de Zurich para ensear la programacin a sus e n o alumnos. Desde que comenz a utilizarse (1971), ha tenido un enorme desarrollo o y difusin, adaptndose a la mayor de los computadores, grandes y pequeos. o a a n Actualmente es uno de los lenguajes ms usados en las universidades de muchos a pa ses del mundo. Gracias a esta difusin, junto con los compiladores de este o lenguaje, se han desarrollado potentes entornos de programacin de gran calidad o y bajo precio. Algunas de las implementaciones del lenguaje son Turbo Pascal c (que funciona en computadores compatibles PC, bajo el sistema operativo DOS y bajo Windows), Macintosh Pascal c , VAX Pascal c , Microsoft Pascal c y Quick Pascal c . Es un lenguaje estandarizado, estando recogido en el Pascal User Manual and Report de K. Jensen y N. Wirth [JW85]. Por lo general, las distintas versiones se adaptan al estndar y lo extienden. Por lo tanto, un programa escrito en a Pascal estndar (segn el Pascal User Manual and Report) debe funcionar en la a u mayor de las versiones; en cambio, si una versin contiene extensiones, lo ms a o a probable es que no funcione en las otras. En cualquier caso, es ciertamente comprensible que las caracter sticas presentadas aqu sin conocer el lenguaje, pueden sonar a hueco, ya que el momento , apropiado para una valoracin cabal es a posteriori , despus de un conocimiento o e ms completo de este lenguaje e incluso otros: slo as puede apreciarse su elea o gancia conceptual, la enorme inuencia que ha tenido en el desarrollo de otros, en la enseanza de la programacin y en la metodolog de desarrollo de programas n o a y, naturalmente, tambin sus limitaciones. e Quienes ya conozcan este lenguaje en mayor o menor medida, o quienes deseen ampliar el contenido de este libro, pueden encontrar en [Wir93] una visin o panormica, escrita por el propio Wirth. a

2.4

Pascal y Turbo Pascal

La posicin que se adopta en este libro acerca del lenguaje de programacin o o utilizado es intermedia, entre el estndar ideado inicialmente (lo que es cona veniente para que los programas sean transportables) y un compilador real (lo que tiene la ventaja de permitir la prctica en el desarrollo de programas). El a compilador concreto adoptado es Turbo Pascal, potente, rpido, de amplia dia fusin y dotado con un entorno muy bien desarrollado que facilita la tarea del o programador. El inconveniente consiste en que Turbo Pascal, como la mayor de los coma piladores existentes, incluye diferencias con respecto a Pascal estndar, aunque a

26

Cap tulo 2. El lenguaje de programacion Pascal

stas son m e nimas: debe decirse que casi no tiene limitaciones, y que en cambio ofrece una gran cantidad de extensiones. Nuestra decisin ha sido trabajar con este compilador1 para que sea posible o al alumno desarrollar sus prcticas, haciendo uso de las m a nimas herramientas extendidas del mismo y sealando en todo caso las diferencias con respecto al n estndar. Adems se incluye un apndice sobre Turbo Pascal donde se presentan a a e las caracter sticas y diferencias ms importantes con Pascal estndar. a a

Para nuestros propsitos es vlida cualquier versin de Turbo Pascal desde la 4.0 hasta la 7.0, o a o as como Turbo Pascal para Windows y Borland Pascal.

Cap tulo 3

Tipos de datos bsicos a

3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . o El tipo integer . . . . . . . . . . . . . . . . . . . . . . . . El tipo real . . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . El tipo boolean . . . . . . . . . . . . . . . . . . . . . . . . Observaciones . . . . . . . . . . . . . . . . . . . . . . . . . El tipo de una expresin . . . . . . . . . . . . . . . . . . o Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . .

28 28 32 35 36 39 43 43

Es preciso adquirir desde el principio el concepto de dato, puesto que los algoritmos (y por lo tanto los programas) los manejan constantemente,1 desde la entrada (que puede considerarse como la coleccin inicial de datos) hasta la o salida (o conjunto de datos resultantes), pasando frecuentemente por un sinf de n resultados provisionales que intervienen en los clculos intermedios (tales como a las cifras de acarreo en una operacin de multiplicar). o Este cap tulo se dedica al estudio de los tipos de datos elementales de Pascal, explicando sus dominios y operaciones, as como la construccin de expresiones o con datos de dichos tipos.
1

De hecho, en un algoritmo se organizan dos aspectos inseparables: acciones y datos.

28

Cap tulo 3. Tipos de datos basicos

3.1

Introduccin o

Un dato es simplemente la representacin de un objeto mediante s o mbolos manejables por el computador. El uso de nmeros (enteros y reales), caracteres y valores lgicos es corriente u o en programacin. De hecho, casi todos los lenguajes disponen de ellos a priori, o por lo que se les llama tambin tipos de datos predenidos o tambin estndar. e e a Ciertamente, es posible ampliar nuestro lenguaje con otros objetos a la medida de los problemas que se planteen, tales como vectores o conjuntos (vanse e los cap tulos 11 y 12). Estos se construyen usando los datos predenidos como las piezas ms elementales, por lo que tambin se les llama tipos de datos bsicos a e a a los tipos de datos predenidos. Sin embargo, lo cierto es que, en principio, slo se puede contar con los tipos o de datos mencionados, que en Pascal se llaman respectivamente integer, real, char y boolean, y que estudiamos a continuacin.2 o Los tipos de datos se caracterizan mediante sus dominios (conjuntos de valores) y las operaciones y funciones denidas sobre esos dominios.

3.2

El tipo integer

Dominio Su dominio, denotado Z, incluye valores enteros positivos o negativos. Debido a las limitaciones de representacin (vase el apartado 2.2.3 de [PAO94]), o e el dominio de integer est acotado por la constante predenida MaxInt, propia a de cada versin: en Turbo Pascal por ejemplo, MaxInt vale 32767, y el dominio o de integer es {-32768,..., 32767}. Los nmeros integer se escriben liteu ralmente sin espacios ni puntos entre sus cifras, posiblemente precedidos por el signo ms o menos, por ejemplo: 174, -273, +17, etc. El diagrama sintctico a a (vase apartado 5.2.1 de [PAO94]) de la gura 3.1 resume estos detalles. e

Operaciones y funciones Asociadas al tipo integer, se tienen las siguientes operaciones aritmticas: e
En adelante, usaremos letra de molde para escribir el texto en Pascal, excepto las palabras reservadas (vase el apartado 4.2) que aparecern en negrilla. e a
2

3.2. El tipo integer

29

Nmero Entero + Entero sin signo -Entero sin signo Dgito

Dgito ... 0 1 ... ... 9

Figura 3.1.

30 + * div mod

Cap tulo 3. Tipos de datos basicos suma resta multiplicacin o divisin entera o resto de la divisin entera o

Su funcionamiento es similar al que tienen en Matemticas: son operaciones a binarias (tienen dos argumentos, salvo la operacin -, usada tambin para hallar o e el opuesto de un entero) e injas (en las expresiones, se sitan entre sus dos u argumentos). Emplearemos la notacin matemtica o a div : Z Z Z para expresar que los dos argumentos son de tipo entero y su resultado tambin lo e es. Todas las operaciones presentadas responden a este esquema. Las operaciones div y mod expresan respectivamente el cociente y el resto de la divisin entera:3 o 7 7 -7 -7 div 2 ; 3 div -2 ; -3 div 2 ; -3 div -2 ; 3 7 7 7 7 mod 2 ; 1 mod 2 ; 1 mod 2 ; 1 mod 2 ; 1

Obsrvese que verican la conocida regla: e dividendo = divisor cociente + resto Adems, existen las siguientes funciones predenidas: a Abs Sqr Pred Succ valor absoluto del entero cuadrado del entero entero predecesor entero sucesor

que son monarias (se aplican a un argumento unico) y se aplican en forma preja (preceden a su argumento, dado entre parntesis). e Estas operaciones y funciones son internas, puesto que convierten argumentos integer en integer: Abs : Z Z Sin embargo, debe observarse que las limitaciones que pesan sobre el tipo integer afectan a las operaciones y funciones, cuyo resultado ser correcto slo a o cuando no rebase los l mites del dominio.
3

Usaremos el s mbolo ; para expresar la evaluacin de una operacin o funcin. o o o

3.2. El tipo integer Expresiones

31

Al igual que ocurre en Matemticas, es posible escribir expresiones combia nando los nmeros con esas operaciones y funciones: u 2 + 3 * Sqr(2) Entonces, tiene sentido preguntarse en qu orden se reducirn las operaciones e a involucradas, ya que el resultado puede depender de ello. Para deshacer la ambigedad se conviene en evaluar las operaciones *, div y mod antes que + y -, u como se hace en Matemticas; esta idea se expresa habitualmente diciendo que a la precedencia de unas operaciones (*, div y mod) es mayor que la de otras. Por ejemplo, la expresin anterior se calcular en tres pasos: o a 2 + 3 * Sqr(2) ; 2 + 3 * 4 ; 2 + 12 ; 14 Similarmente, una expresin puede resultar ambigua cuando sea posible aplio car dos operaciones con la misma precedencia. Por ejemplo: 7 div 2 * 2 En este caso, se efectuar la valoracin de las operaciones asumiendo la asoa o ciatividad a la izquierda: 7 div 2 * 2 ; 3 * 2 ; 6 Como ocurre en Matemticas, es posible forzar un orden de evaluacin disa o tinto del convenido mediante el uso de parntesis: e (2 + 3) * 4 ; 5 * 4 ; 20 7 div (2 * 2) ; 7 div 4 ; 1 Aunque las operaciones anteriores resultan sencillas, combinndolas aproa piadamente es posible lograr expresiones que resuelven directamente un gran nmero de problemas. Por ejemplo, si n representa una cierta cantidad (entera) u de dinero, en pesetas, n div 25 expresar su cambio en monedas de cinco duros; a n mod 25 el nmero de pesetas restantes, que se podr cambiar por n mod u an 25 div 5 duros y n mod 5 pesetas. Con el uso de las funciones, las expresiones adquieren una gran potencia. Como ejemplo, la expresin o (a + b + 1) div 2 + Abs(a - b) div 2 calcula el mximo de dos cantidades enteras representadas por a y b. a Por otra parte, el orden de aplicacin de las funciones est exento de ambio a gedad debido al uso obligatorio de los parntesis. u e

32

Cap tulo 3. Tipos de datos basicos

3.3

El tipo real

Dominio Su dominio, en adelante R, incluye valores numricos con parte decimal. Las e limitaciones en la representacin de cantidades reales afectan ahora al dominio o en dos aspectos (vase el apartado 2.2.3 de [PAO94]): la magnitud de los valores e incluidos (en Turbo Pascal por ejemplo, el dominio de real est comprendido a 38 ) y la precisin (hacia 10 cifras signicativas aproximadamente entre 1.7 10 o en Turbo Pascal). Hay dos modos de escribir los nmeros reales en Pascal: usando el punto u decimal (que debe tener algn d u gito a su izquierda y a su derecha), o bien usando la llamada notacin cient o ca o exponencial. A continuacin tenemos o ejemplos de nmeros reales en notacin decimal y cient u o ca, respectivamente: 3.1415926 6.023E+23

El diagrama sintctico de la expresin de nmeros reales es el de la gura 3.2. a o u

Operaciones y funciones Existen las siguientes operaciones aritmticas relacionadas con el tipo real: e + * / suma resta multiplicacin o divisin o

y las siguientes funciones predenidas: Abs Sqr SqRt Sin Cos ArcTan Ln Exp valor absoluto cuadrado ra cuadrada z seno coseno arcotangente logaritmo neperiano funcin exponencial o

En las funciones trigonomtricas, el ngulo se expresa en radianes. Las fune a ciones logar tmica y exponencial son de base e.

3.3. El tipo real

33

Nmero Real + Real sin signo -Real sin signo .

cifras

cifras

+
E cifras

--

Cifras Dgito

Figura 3.2.

34 Reales y enteros

Cap tulo 3. Tipos de datos basicos

Se observa que los s mbolos de las operaciones +, -, y *, as como las funciones Abs y Sqr, coinciden con las correspondientes a los nmeros enteros, por lo que u las siguientes descripciones resultan ambas correctas, + : Z Z Z + : R R R as como las siguientes para el caso de las funciones: Abs : Z Z Abs : R R Este multiuso de ciertas operaciones y funciones se llama sobrecarga de operadores, y de l aparecern nuevos casos ms adelante. e a a Otro aspecto en que el lenguaje se muestra exible consiste en reconocer la inclusin Z R, y obrar en consecuencia: as toda operacin o funcin que o , o o requiera expresamente una cantidad real aceptar una de tipo entero, realizando a automticamente la correspondiente conversin. La descripcin siguiente a o o + : Z R R es una consecuencia de ello, producida al convertir el primer argumento en un real. En cambio, la conversin en sentido inverso no se realiza automticamente. o a Para ello, el lenguaje est dotado con funciones para convertir reales en enteros: a Trunc Round Por ejemplo, Round(-3.6) Trunc(-99.9) -Round(99.9) -Round(-99.9)
4

truncamiento (eliminacin de la parte decimal) o redondeo al entero ms prximo4 a o

; ; ; ;

-4 -99 -100 100

Inferior o superior.

3.4. El tipo char Expresiones

35

La precedencia de las operaciones aditivas (+ y -) es menor que la de las multiplicativas (* y /), igual que en Matemticas. a El juego de operaciones y funciones predenidas en Pascal tiene carencias notables, tales como la potencia, la tangente, el arcoseno o los logaritmos de base decimal o arbitraria. Sin embargo, combinando las presentadas anteriormente, se logra fcilmente expresar las operaciones mencionadas, entre otras muchas. a Por ejemplo, en la siguiente tabla se dan algunas expresiones equivalentes (salvo errores de precisin)5 a algunas funciones usuales: o funcin o xy tg(x) arcsen(x) logb (x) expresin equivalente o Exp(y * Ln(x)) Sin(x)/Cos(x) ArcTan(x/SqRt(1 - Sqr(x)) Ln(x)/Ln(b) para x > 0 para x =
2

+ k

para 0 < x < 1 para b > 1, x > 0

Como ejemplo nal de la potencia de las operaciones y funciones presentadas, damos la expresin o Trunc (Ln(n)/Ln(10)) + 1 que halla el nmero de cifras de un entero n > 0. u

3.4

El tipo char

Dominio El dominio de este tipo de datos, abreviadamente C, incluye el juego de caracteres disponibles en el computador, que var entre las diferentes versiones a desarrolladas, todas tienen en comn el respeto al orden alfabtico ingls y al u e e orden entre d gitos; por ejemplo, en Turbo Pascal se utiliza una codicacin en o ASCII de 8 bits, por lo que existen hasta 256 posibles caracteres que se recogen en la correspondiente tabla de cdigos (vase el apartado 2.2.4 de [PAO94]). o e Los valores de tipo char se escriben entre apstrofes (por ejemplo, A) o excepto el carcter apstrofe (), que se escribe mediante . a o
Debe tenerse en cuenta que la imprecisin cometida al tratar con nmeros reales puede hacerse o u ms importante a medida que se suceden las operaciones. a
5

36 Funciones

Cap tulo 3. Tipos de datos basicos

No existen operaciones internas entre caracteres; en cambio, s se tienen las siguientes funciones internas predenidas: Pred Succ carcter anterior a carcter sucesor a

Adems, existen las siguientes funciones de conversin entre char e integer: a o Ord Chr nmero de orden del carcter en el juego adoptado u a carcter asociado a un nmero de orden dado a u

El esquema de estas funciones es el siguiente: Pred, Succ Ord Chr Por ejemplo, Pred(Z) Pred(z) Succ(7) Chr(Ord(1) + 4) ; ; ; ; Y y 8 5 : : : C C Z C Z C

3.5

El tipo boolean

Dominio Este tipo proviene del lgebra de Boole, como indica su nombre. Su dominio, a denotado mediante B en adelante, contiene solamente los dos valores lgicos o predenidos: False y True. El tipo boolean es particularmente util en tareas de control de bucles y selecciones. Operaciones y funciones internas Asociadas al tipo boolean, se tienen las siguientes operaciones: not and or negacin lgica (con la precedencia ms alta) o o a conjuncin lgica (precedencia multiplicativa) o o disyuncin lgica (predecencia aditiva) o o

3.5. El tipo boolean Son internas (convierten valores lgicos en valores lgicos): o o not and or : : : B B B B B B B B

37

y su funcionamiento viene dado por la siguiente tabla de verdad: A B A and B A or B not A False False False False True False True False True True True False False True False True True True True False Adems, se tienen predenidas las funciones sucesor, predecesor y orden, que a operan como sigue: Succ(False) ; True Pred(True) ; False Ord(False) ; 0 Ord(True) ; 1 Operadores relacionales Son las operaciones binarias de comparacin siguientes: o = <> < <= > >= igual distinto menor menor o igual mayor mayor o igual

Estas operaciones estn sobrecargadas: permiten la comparacin entre dos a o valores de cualquiera de los tipos bsicos, resultando de ello un valor lgico: a o Es decir, si representamos los cuatro tipos introducidos mediante Z, R, C y B respectivamente, se tiene >= >= >= >= : : : : Z R C B Z R C B B B B B

para cada una de las operaciones relacionales.

38

Cap tulo 3. Tipos de datos basicos

Con respecto a la comparacin de nmeros reales, debemos remitirnos al o u apartado 2.2.3 de [PAO94], de donde se deduce la escasa abilidad de estas operaciones cuando los argumentos estn muy prximos, como consecuencia de a o las limitaciones de precisin en los sistemas de representacin de estos nmeros. o o u En cuanto a la comparacin de datos no numricos, el s o e mbolo < signica anterior y no menor que, obviamente, no tiene sentido en tales casos. En el caso concreto de char, esta anterioridad viene dada por sus posiciones en la tabla adoptada (v.g. ASCII), y en el de boolean, se considera False anterior a True. Algunas operaciones relacionales reciben nombres especiales cuando trabajan sobre valores booleanos: = <> <= equivalencia lgica o o exclusivo implicacin o

El funcionamiento de estas operaciones viene dado por las siguientes tablas de verdad: A False False True True B False True False True A = B True False False True A <> B False True True False A <= B True True False True

Las operaciones relacionales tienen menor precedencia que las aditivas. Por ejemplo: ; ; ; 3 + 1 >= 7 - 2 * 3 3 + 1 >= 7 - 6 4 >= 1 False {ops. multiplicativos} {ops. aditivos} {ops. relacionales}

Finalmente, se tiene la funcin de conversin que indica si un entero es impar: o o Odd : Z B Odd(n) ;
True

si n es impar

False en otro caso

Aunque la funcin Odd(n) es equivalente a n mod 2 = 1, su uso es prefeo rible al de sta, ya que opera ms ecientemente. e a

3.6. Observaciones Circuito largo-corto

39

Sea la expresin booleana P and Q. Un modo de valorarla consiste en hallar o primero el valor de P, luego el valor de Q, y entonces deducir el valor de la expresin de las tablas de la verdad. No obstante, si P hubiera resultado valer o False, el valor de la expresin ser False, tanto si Q resulta ser True como o a si resulta ser False, de manera que podr amos haber evitado hallar Q. Estos dos modos de evaluar las expresiones booleanas se llaman respectivamente con circuito largo y con circuito corto. Su importancia no radica unicamente en la eciencia conseguida al evitar evaluar ciertos trminos, sino que a veces el modo de evaluacin tiene nes algo e o ms sutiles. Consideremos la valoracin de la expresin siguiente, donde den a o o resulta valer 0: (den <> 0) and (num/den = 0) Si el modo de evaluacin es con circuito corto, el resultado nal es False, o para lo cual slo se necesita hallar el valor de o den <> 0 En cambio, si se valora con circuito largo, el clculo del segundo trmino a e produce un error (divisin por cero), y la ejecucin del programa fracasa. o o Pascal estndar establece que la evaluacin se produce con circuito largo, pero a o Turbo Pascal ofrece la posibilidad de escoger entre ambos modos (vase el apare tado C.3).

3.6

Observaciones

Tipos ordinales En los cuatro tipos de datos simples predenidos existe una ordenacin, de o manera que todos ellos son comparables con los operadores relacionales. Por otra parte, es posible enumerar fcilmente los dominios Z, C y B, asignando a a sus elementos su nmero de posicin: se es el cometido de la funcin Ord, que u o e o est denida para estos tres tipos de datos, y no para R. a Por ello, decimos que los tipos integer, char y boolean son ordinales, mientras que real no lo es. Adems de Ord, las funciones Succ y Pred son exclusivas a de los tipos ordinales, por lo que no es posible ni razonable obtener el siguiente (o el anterior) de un nmero real dado. u

40

Cap tulo 3. Tipos de datos basicos

Resumen de expresiones con los tipos de datos bsicos a Las expresiones en programacin son similares a las empleadas en Matemtio a cas, aunque sus elementos constituyentes no slo son nmeros, como hemos visto. o u Las expresiones pueden consistir en una constante, una variable, una funcin o aplicada a una expresin o una operacin entre expresiones. Aunque en princio o pio puede resultar extrao denir expresiones en trminos de expresiones, debe n e observarse que estas ultimas son componentes (esto es, ms pequeas), con lo a n que nalmente, una expresin est compuesta por constantes o variables, piezas o a bsicas de las expresiones. En las guras 3.3 y 3.4 se completa lo explicado hasta a ahora sobre la estructura sintctica que tienen las expresiones vlidas6 en Pascal. a a Como se indic anteriormente, las operaciones se aplican en las expresiones o por orden, segn su precedencia, como se indica en la tabla de la gura 3.5. Si u coinciden en una expresin dos o ms operaciones de la misma precedencia se o a asocian de izquierda a derecha. El orden de aplicacin de precedencias puede o alterarse mediante el parntesis, igual que en Matemticas. e a Las funciones se aplican a sus argumentos entre parntesis, por lo que no e existe ambigedad posible. u

Cadenas de caracteres Si se observa la sintaxis de los literales, se ve que una posibilidad consiste en escribir una cadena de ningn carcter, uno o ms. Aunque este literal no u a a pertenece a ninguno de los tipos de datos bsicos presentados, esta posibilidad a nos permitir desde el principio emitir mensajes claros (vase 4.3.2). a e Es vlido por tanto escribir literales como los siguientes: a Yo tengo un to en Amrica e Carlos ODonnell para representar las frases encerradas entre apstrofes, sin stos, que actan slo o e u o como delimitadores, y expresando el apstrofe simple mediante dos de ellos. o As por ejemplo, los literales anteriores representan respectivamente la frase Yo tengo un to en Amrica, la cadena vac y Carlos ODonnell. e a
En realidad, se consideran slo las expresiones vlidas por el momento. A medida que se o a introduzcan posibilidades nuevas, se completarn los diagramas sintcticos. a a
6

3.6. Observaciones

41

Expresin Miembro

Op. relacional Miembro


+

Miembro

Trmino
--

Trmino Trmino Factor

Op. aditivo

Factor Factor

Op. multiplicativo

Literal sin signo


Identificador Id. de funcin ( Expresin , ( Expresin ) )

not

Factor

Figura 3.3. Diagrama sintctico de las expresiones (1). a

42
Literal sin signo

Cap tulo 3. Tipos de datos basicos

Real sin signo

Entero sin signo

Carcter

Op. relacional

<

<=

<>

>

>=

Op. aditivo

or

Op. multiplicativo

div

mod

and

Figura 3.4. Diagrama sintctico de las expresiones (2). a

op. monarios op. multiplicativos op. aditivos ops. de relacin o

cambio de signo, not *, /, div, mod, and +, -, or =, <>, <, >, <=, >=

Figura 3.5. Cuadro-resumen de prioridades.

3.7. El tipo de una expresion

43

3.7

El tipo de una expresin o

El resultado de cada expresin tiene un tipo determinado, independiente o de su valor, y que puede conocerse aun ignorando los valores de sus elementos componentes, teniendo en cuenta slo sus tipos. As por ejemplo, pueden o existir expresiones numricas, bien enteras (como 2 + 3) o reales (como 3.14 e * Sqr(2.5) por ejemplo), booleanas (como 2 + 2 = 5) y de caracteres (como Succ(a)). La informacin sobre el tipo de una expresin (y el de sus subexpresiones o o componentes) juega en Pascal un importante papel: una vez escrito un programa, el compilador comprueba que las expresiones que intervienen en l son e correctas sintcticamente y que los tipos de las componentes de las mismas son a consistentes. Por ejemplo, la expresin siguiente podr ser analizada as o a : ( ( ( ( (6 (Z + + Z 8) Z) R * * * 3.14 R R B < < < < B Ord ( Ord ( a C Z Z ) ) ) ) ) ) or or or or or True B B B B

aceptndose la comparacin siguiente: a o True = ((6 + 8) * 3.14 < Asc(a)) or False y rechazndose en cambio esta otra: a 2 = ((6 + 8) * 3.14 < Asc(a)) or True

3.8

Ejercicios

1. De las siguientes expresiones en Pascal, detecte las errneas; en las que son coo rrectas, indique qu tipo deben tener los identicadores, y deduzca su tipo y su e valor resultante, si es posible. (a) (c) (e) (g) x = y p = True p > Succ(p) Odd(n * (n - 1)) (b) (d) (f) (h) Odd(k) or Odd(Succ(k)) 10 div 3 = 10 / 3 P = Q or R Ord(b) - Ord(a) > 0

2. Sea un ngulo, dado en grados. Escriba una expresin en Pascal que halle: a o (a) el nmero de vueltas completas que da, u (b) el ngulo entre 0 y 359 al que equivale, a

44

Cap tulo 3. Tipos de datos basicos


(c) el nmero del cuadrante en que se encuentra (numerados stos en sentido u e inverso al de las agujas del reloj), (d) el ngulo en radianes al que equivale, a (e) su tangente. 3. Evale las siguientes expresiones en el dominio de integer: u (2 * 2) div 4 = 2 * (2 div 4) (10000 * 4) div 4 = 10000 * (4 div 4) 4. Usando el operador mod, escriba en Pascal una expresin que, para un entero n, o d el nmero correspondiente al d de la semana; esto es, para n = 1, 2, . . . , 7, e u a 8, 9, . . . esa expresin resulte valer 1, 2, . . . , 7, 1, 2, . . . o 5. Halle el valor de la expresin Ord(C) - Ord(0), donde C es de tipo char y o representa, sucesivamente, los valores 0, 1, . . . , 9. 6. Considere la correspondencia siguiente entre los caracteres alfabticos y los nmeros e u naturales: A ... Z 1 26

D expresiones que pasen del carcter C, supuesto que es una mayscula, al nmero e a u u correspondiente, y del nmero N, supuestamente entre 1 y 26, a su carcter asou a ciado. 7. Exprese la condicin que deben cumplir las coordenadas de un punto del plano o (x, y) para: (a) que su distancia al punto (1, 1) sea inferior a 5 unidades, (b) estar en el primer cuadrante del plano, (c) estar por debajo de la recta x + y = 6, (d) cumplir simultneamente los apartados anteriores. a 8. Dados los catetos c1 y c2 de un tringulo rectngulo, escriba una expresin que a a o sirva para hallar la correspondiente hipotenusa. 9. Se sabe que la relacin entre la temperatura, expresada en grados Farenheith (F ) o y cent grados (C) viene expresada por la frmula F = 1 8C + 32. o (a) Escriba una expresin que sirva para calcular F a partir de C. Deducir el o tipo de la misma suponiendo primero que C es integer y luego que es real. (b) Escriba una expresin que sirva para calcular C a partir de F ofreciendo un o resultado integer. 10. Encuentre una expresin en Pascal para cada uno de los siguientes apartados: o (b) log10 (x), para x IR, x > 0 (a) 10x , para x IR

3.8. Ejercicios
(c) (1)n , para n Z Z
5

45

(d) xy , para x, y IR (e)


1 n

, para n IR

(f) Dado un entero n, quitarle las c ultimas cifras (g) Dado un entero n, dejar de l slo sus ultimas c cifras e o (h) Dado un entero n, hallar la cifra c-sima e 11. Sea f : IR IR una funcin derivable. La expresin o o f (x + ) f (x) proporciona una aproximacin de la derivada de f en x, para valores pequeos de o n . Tomando = 103 , escriba expresiones en Pascal que aproximen las derivadas de las siguientes funciones (a) f (x) = sen(x), en x =
3

(b) g(x) = 2x2 + 3x 4, en x = e

Cap tulo 4

Elementos bsicos del lenguaje a

4.1 4.2 4.3 4.4 4.5

Un ejemplo introductorio Vocabulario bsico . . . . . a Instrucciones bsicas . . . a Partes de un programa . . Ejercicios . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

47 48 52 59 63

En este cap tulo se presentan los rudimentos m nimos necesarios para poder construir programas elementales en Pascal. El lector debe esforzarse aqu por tener al trmino del mismo una visin global de las partes de un programa, e o as como del modo en que los programas captan los datos, los manipulan y, nalmente, dan los resultados.

4.1

Un ejemplo introductorio

Consideremos el problema de hallar el rea de un c a rculo a partir de su radio. Un procedimiento sencillo para ello consiste en aplicar la conocida frmula o 2 , donde A y r son dos n meros reales que representan el rea y el radio A = r u a del c rculo respectivamente. Para llevar a cabo el procedimiento, necesitaremos conocer el valor de la constante , a la que llamaremos pi (si no se necesita una gran precisin, puede bastar con 3.14). Entonces, el clculo consiste en o a averiguar el valor del radio r y aplicar la frmula para hallar A, que se ofrece o como el resultado.

48

Cap tulo 4. Elementos basicos del lenguaje Estas ideas pueden organizarse como se indica a continuacin: o
Manera de hallar el rea de un c a rculo: Sean: pi = 3.14 r, A IR (radio y rea resp.) a Pasos que hay que llevar a cabo: Averiguar el valor del radio r Hallar el valor del rea A, que es pi r2 a El rea resulta ser el valor de A a

La transcripcin de este algoritmo en un programa en Pascal es casi directa: o


Program AreaCirculo (input, output); {Se halla el rea de un crculo conociendo su radio} a const Pi = 3.14; var r, A: real; {radio y rea} a begin Write(Cul es el radio?: ); a ReadLn(r); A:= Pi * Sqr(r); WriteLn(rea = , A) A end. {AreaCirculo}

4.2

Vocabulario bsico a

En castellano las letras se agrupan para formar palabras, y stas se combinan e entre s y con los signos de puntuacin para construir frases; anlogamente, en o a Pascal, se parte de un juego de caracteres bsico (ASCII por ejemplo) para a componer los diferentes elementos de su vocabulario: las palabras reservadas, los identicadores, los s mbolos especiales, los literales y los comentarios. Palabras reservadas Las palabras reservadas son componentes con signicado jo usadas en los constructores del lenguaje. Se suelen escribir en negrita, facilitando as la lectura de los programas. Las palabras reservadas de Pascal estndar son las siguientes: a and, array, begin, case, const, div, do, downto, else, end, le, for, forward, function, goto, if, in, label, mod, nil, not, of, or,

4.2. Vocabulario basico packed, procedure, program, record, repeat, set, then, to, type, until, var, while, with.

49

Adems, en este libro se usarn las siguientes, aadidas en los compiladores a a n de Turbo Pascal: implementation, interface, string, unit, uses Cada palabra reservada tiene un cometido espec co que es inalterable; dicho de otro modo, las palabras reservadas no son redenibles. Identicadores Los identicadores desempean un papel similar al de los sustantivos (ren presentando objetos), adjetivos (representando tipos, que calican los objetos) y verbos (representando acciones) en las oraciones. Los identicadores que estn disponibles antes de empezar a escribir un proa grama se llaman predenidos; damos la siguiente clasicacin: o 1. Archivos estndar de entrada/salida: a input, output. 2. Constantes: False, MaxInt, True. 3. Tipos: boolean, char, integer, real, text. 4. Funciones: Abs, ArcTan, Chr, Cos, EoF, EoLn, Exp, Ln, Odd, Ord, Pred, Round, Sin, Sqr, SqRt, Succ, Trunc. 5. Procedimientos: Dispose, Get, New, Pack, Page, Put, Read, ReadLn, Reset, Rewrite, Unpack, Write, WriteLn. La posibilidad de extensin del lenguaje permite la creacin de identicadores o o (denidos por el programador) para representar archivos, constantes, variables, tipos, funciones y procedimientos a la medida de nuestras necesidades (vase la e gura 4.1).

50
Identificador

Cap tulo 4. Elementos basicos del lenguaje

LetraAZ LetraAZ Dgito

LetraAZ ... a A ... ... z Z

Figura 4.1.

Cualquier cadena de caracteres no resulta vlida como identicador: exisa ten razones para limitarlas. Los identicadores debern estar formados por las a letras1 y los d gitos,2 empezando por letra y sin espacios en blanco. En los identicadores, las letras maysculas y minsculas son indistinguibles para el u u compilador. Ejemplos de identicadores admitidos son: max, LimSup, anno, MaxCoorY, EsPrimo, EsValido, Seat600D En cambio, no son correctos: primero-F, Primero F, 600D Por otra parte, se recomienda la eleccin de identicadores mnemotcnicos, o e que facilitan su uso al estar relacionados con el objeto que se nombra adems a de aumentar la legibilidad de nuestros programas. Por ello, no es conveniente que los identicadores sean excesivamente cortos (probablemente sin signicado) ni excesivamente largos (lo que incomoda su escritura e interpretacin, y puede o
En Pascal estndar (y tambin en Turbo Pascal) se excluyen la ~ y las vocales acentuadas. a e n En Turbo Pascal tambin se permite el carcter de subrayado, luego son vlidos los identie a a a cadores Max x o Area circulo. No obstante, se evitar su uso en este libro.
2 1

4.2. Vocabulario basico

51

provocar errores). A propsito de esto, en el apartado 5.2 se ampl estas o an recomendaciones. El lenguaje permite tambin dotar a los identicadores predenidos con un e nuevo signicado; sin embargo, este cambio es en general poco recomendable, por lo que no consideraremos esta posibilidad. S mbolos especiales Son similares a los signos de puntuacin de las oraciones: o + ( ) * [ / ] := (* . *) , { ; } : (. = < > <= >= <>

.) ..

Los que estn formados por varios caracteres deben escribirse sin espacios en a blanco entre ellos. Literales En el curso de un programa, con frecuencia necesitamos escribir directamente elementos del dominio de un tipo bsico para expresar cantidades numricas a e enteras (como 365) o reales (como 3.141592), caracteres (como &) o cadenas de caracteres (como 1 a~o = 365 das, en las que es posible intercalar espacios n en blanco, as como el carcter ~, las vocales acentuadas y otros s a n mbolos, pero sin poderse partir una cadena entre ms de una l a nea). Esos valores escritos directamente se llaman literales. Entre los tipos bsicos, los valores extremos del dominio de integer no a se suelen expresar directamente con literales, sino mediante identicadores con un valor constante (por ejemplo MaxInt). Por otra parte, los literales que expresan cadenas de caracteres no pertenecen obviamente a ninguno de los tipos bsicos, aunque existe la posibilidad de construir objetos apropiados (vanse los a e cap tulos 11 y siguientes), y su uso es tan frecuente que algunas versiones concretas de Pascal proporcionan ese tipo ya predenido (vase el apndice B sobre e e Turbo Pascal). Comentarios Para facilitar la lectura de los programas, es una buena costumbre intercalar comentarios en castellano. Se distinguen del texto en Pascal por los delimitadores { y }, o bien (* y *). Por ejemplo:

52

Cap tulo 4. Elementos basicos del lenguaje


{Programa que halla la hipotenusa de un tringulo rectngulo} a a {Autor: Pitgoras, presumiblemente} a {Fecha: hacia el s. VI a. C.}

Cuando se encuentra un s mbolo { (*, todos los caracteres que lo siguen o hasta el primer s mbolo } *), respectivamente, son ignorados, emparejndose o a { con } y (* con *). El texto recorrido entre esos delimitadores es un comentario, y se interpreta como un espacio en blanco. Por consiguiente, no ser a correcto interrumpir una palabra reservada, un identicador o un literal con un comentario. Los comentarios en Pascal no pueden anidarse, es decir, no se puede poner un comentario dentro de otro.

4.2.1

Constantes y variables

En el ejemplo del apartado 4.1, se deni el valor de Pi: decimos que Pi o es una constante para expresar que su valor no ser alterado en el curso del a programa; tambin se dice que es una constante con nombre para diferenciarla e de las constantes expresadas literalmente, a las que tambin se llama constantes e annimas. En contraposicin, los objetos r (radio) y A (rea) pueden representar o o a diferentes valores, por lo que se llaman variables. En los objetos constantes y variables mencionados se pueden considerar los siguientes aspectos: el identicador (Pi, r y A), que es el trmino con el que e pueden referirse; su tipo (real, en los tres casos) y el valor (3.14 constantemente para pi, y desconocidos de antemano para r y A, variables en el transcurso del programa). Para resaltar estos aspectos de los objetos constantes (con nombre) y variables, es corriente representarlos as : pi 3.14 entendiendo que el tipo determina el espacio en donde reside el valor, de forma que slo son aceptables valores del correspondiente dominio. o

4.3
4.3.1

Instrucciones bsicas a
Asignacin o

La instruccin de asignacin se utiliza para dar un valor inicial a las variables o o o para modicar el que ya tienen.

4.3. Instrucciones basicas

53

En algunos compiladores, una variable declarada presenta un valor indenido al iniciarse el programa; en efecto, se trata de un valor basura, representado por el contenido de la memoria reservado cuando se declar la variable. Lgicamente, o o un programa que depende de valores indenidos tiene un comportamiento indeterminado; por ello es necesario evitar el operar con tales variables, asignndoles a valores iniciales. Una variable con valor indeterminado se puede representar as : x1 ? La asignacin graba un valor en la memoria y destruye su valor previo, tanto o si es un valor concreto como si es indeterminado. Consideremos la siguiente sentencia de asignacin: o x1:= (-b + SqRt(Sqr(b) - 4 * a * c))/(2 * a) Consta de un identicador de variable (x1), el s mbolo de la asignacin (que o es :=) y una expresin. El proceso de asignacin se produce de la siguiente o o forma: en primer lugar se evala la expresin, calculndose el valor nal, y a u o a continuacin se almacena el valor en la memoria. o Si asignamos un valor (1.74, por ejemplo) a la variable, sta pasar a repree a sentarse as : x1 1.74 Ejemplos de instrucciones de asignacin: o base:= 10.0 altura:= 20.0 area:= base * altura / 2 contador:= contador + 1 acumulador:= acumulador + valor La sintaxis de una instruccin de asignacin viene dada por el diagrama de o o la gura 4.2. Subrayamos que, semnticamente, el identicador debe representar a una variable, y que el resultado de la expresin debe ser del mismo tipo que la o variable.3
Salvo que la expresin sea integer y la variable real. (Vase lo comentado en el aparo e tado 3.3.)
3

54

Cap tulo 4. Elementos basicos del lenguaje

Identificador

:=

Expresin

Figura 4.2. Instruccin de asignacin. o o ee r r

Obsrvese la gran diferencia que existe entre una asignacin (que es una e o accin y tiene el efecto de alterar el valor de una variable) y una igualdad, o que es una proposicin que arma una relacin entre objetos. Por ejemplo, o o la asignacin o contador:= contador + 1 es una instruccin que tiene por objeto incrementar en una unidad el valor o de la variable contador, mientras que la igualdad contador = contador + 1 es una expresin booleana de una relacin que, por cierto, es falsa cualquiera o o que sea el valor de contador.

4.3.2

Instrucciones de escritura

En todo programa se tienen entradas y salidas de datos con las que el programa se comunica con el exterior. La lectura o entrada de datos se realiza a travs de dispositivos tales como el teclado, una unidad de disco, o chas perfoe radas en los computadores antiguos, etc. La escritura o salida de resultados se realiza a travs de dispositivos como la pantalla o la impresora. e Es habitual asumir un medio de entrada y uno de salida impl citos, que se utilizan mientras no se indique otro distinto. Frecuentemente en los computadores personales se adopta la consola como medio estndar, de manera que los a datos se introducen a travs del teclado y los resultados se escriben en el monitor. e La salida de resultados se expresa en Pascal con las rdenes Write y WriteLn, o que pueden tener varios argumentos consistentes en expresiones de diferentes tipos: Write(1 + 2 + 3) WriteLn(Un tigre, dos tigres, tres tigres, ...) WriteLn(1234, 56, 7) WriteLn(El doble de , n , es , 2 * n) El efecto de ambas se lleva a cabo en dos pasos sucesivos para cada uno de sus argumentos: en primer lugar se evala la expresin; en segundo se escribe el u o

4.3. Instrucciones basicas

55

Write WriteLn ( Expresin , )

Figura 4.3. Instruccin de escritura. o

resultado en el dispositivo de salida estndar. Los resultados de sus expresiones a se escriben sin espacio de separacin, a no ser que se d expl o e citamente. Estas instrucciones se diferencian en que la orden WriteLn genera un salto de l nea, situando el cursor en el principio de la l nea siguiente, listo para seguir la siguiente instruccin de escritura. Por ejemplo, si se efectuaran las cuatro o instrucciones del ejemplo consecutivamente, la salida ser as a : 6Un tigre, dos tigres, tres tigres, ... 1234567 El doble de 15 es 30 suponiendo que n es el entero 15. El cursor salta y se queda en la cuarta l nea, listo para continuar la escritura. Ambas instrucciones pueden utilizarse sin argumentos: la instruccin Write o no produce efecto alguno, mientras que WriteLn provoca un salto de l nea. Por lo tanto, la secuencia de instrucciones Write; Write(Hola); WriteLn equivale a la instruccin o WriteLn(Hola) La sintaxis de estas instrucciones se describe en la gura 4.3. Parmetros de formato de salida a Con datos de tipo integer:

La salida de resultados mediante Write y WriteLn est bastante limitada: a incluso mediante el espaciado, los nmeros quedan desalineados. Para u resolver este problema se utilizan las salidas con formato aadiendo un n

56

Cap tulo 4. Elementos basicos del lenguaje nmero entero a cada una de las expresiones por escribir, que indica al u procedimiento Write o WriteLn en qu espacio debe justicar (por la dee recha) cada uno de los valores numricos. Por ejemplo, las instrucciones e siguientes sitan sus resultados correctamente sangrados:4 u WriteLn(1234:5,56:5,7:5) WriteLn(12:5,345:5,67:5) La salida puede rebasar el espacio reservado: WriteLn(12345:3) Con datos reales: 12345 1234 12 56 345 7 67

Mientras no se indique lo contrario, la salida de valores reales se escribe en notacin cient o ca, que es bastante ilegible. Por ejemplo: 2.7315190000E+02 Como primera mejora de esta presentacin, podemos justicar el resultado o a la derecha, como se ha hecho con los datos integer: Write(a:15) 2.73151900E+02

aadiendo a la izquierda los espacios en blanco necesarios. n An mejor es aadir un doble formato, mostrndose el real en notacin u n a o decimal: el primer parmetro indica las posiciones totales, como se ha a visto, y el segundo el nmero de decimales u Write(a:10:3) redondeando las cifras visibles si es preciso. Con caracteres y cadenas de caracteres: 273.152

Los valores de tipo carcter pueden justicarse mediante un parmetro de a a formato que expresa el espacio m nimo total, justicando la salida a la derecha: WriteLn(A:8) WriteLn(AEIOU:8) A AEIOU

Indicamos de este modo las instrucciones (a la izquierda) junto con la salida que producen en el output (derecha). Usaremos el s mbolo para precisar el espacio ocupado por el carcter a blanco.

4.3. Instrucciones basicas Con datos de tipo boolean:

57

Se puede aadir un parmetro de salida a las expresiones booleanas que n a justica el resultado por la derecha: WriteLn(ok:5) TRUE

donde se ha supuesto que el valor de ok es True. El diagrama sintctico de la gura 4.3 se puede completar trivialmente para a que admita la posibilidad de incluir parmetros de formato. a El archivo output Los resultados de un programa se escriben en el output, que frecuentemente es el monitor. En realidad, el archivo output consiste en una secuencia de caracteres (vase el apartado 14.3), por lo que los resultados numricos se convierten e e en los caracteres que representan el correspondiente valor. Entre esos caracteres existe una marca especial que representa el salto de l nea (que suele representarse mediante ), as como otra para indicar el nal del archivo (que representaremos mediante ). Por ejemplo, el nal del output de los ejemplos anteriores puede representarse as : ... A AEIOU TRUE . . .

En los dispositivos usuales el carcter se interpreta como un retorno de carro a y un avance de l nea, conrindole a la salida el aspecto global de una sucesin de e o l neas, de interpretacin visual mucho ms fcil que una sucesin de caracteres o a a o sin ms. a

4.3.3

Instrucciones de lectura

Las operaciones de entrada se realizan en Pascal mediante los procedimientos Read y ReadLn, cuya sintaxis se muestra en la gura 4.4. Como ejemplo de estas instrucciones tenemos: Read(x,y,z) ReadLn(u) que actan sobre una o ms variables, estando separadas por comas cuando se u a trata de ms de una. a Al llegar a esta instruccin, el computador lee los valores introducidos y los o asigna por orden a las variables argumento indicadas. Debe sealarse que cada n valor le debe tener un tipo compatible con el de la variable en la que se do almacena.

58

Cap tulo 4. Elementos basicos del lenguaje

Read ReadLn ( Id.Variable , )

Figura 4.4. Instruccin de lectura. o

El archivo input Los datos del programa se leen del input, que frecuentemente es el teclado. Surgen ahora tres cuestiones que deben aclararse. En primer lugar, el archivo input tambin consiste en realidad en una secuencia de l e neas de caracteres (vase el apartado 14.3), que deben convertirse en nmeros cuando las variables e u correspondientes sean de tipo numrico. Cuando haya varias variables numricas, e e se pueden escribir en una sola l nea separadas con espacios en blanco. En el ejemplo5 ReadLn(var1, var2, var3) 123 456 789

se asignar 123 a var1, 456 a var2 y 789 a var3, manteniendo el orden de a lectura. Por lo tanto, la introduccin de datos, sean del tipo que sean, se realiza a o travs de una secuencia de caracteres. Es posible que esta conversin no se pueda e o llevar a cabo por incompatibilidad de tipos, producindose un error de ejecucin. e o As ocurrir en el ejemplo a Read(var1) si la variable var1 fuera de tipo numrico. e En segundo lugar, el efecto de la sentencia ReadLn consiste en captar los datos del input y avanzar hasta rebasar el siguiente salto de n de l nea. As por ejemplo, siendo las variables a, b y c de tipo numrico, en la siguiente e situacin o ReadLn(a, b); Read(c)
5

1 2 3 4 5 6 7 8

Ahora, la parte de la derecha representa el input.

4.4. Partes de un programa

59

las variables a, b y c tomar los valores 1, 2 y 6, respectivamente. Cuando an se leen variables numricas se saltan los blancos anteriores, que as actan como e u separadores; en cambio, los caracteres se leen de uno en uno, sin separacin de o ningn tipo. u Subrayamos que, en realidad, el input consiste en una unica tira de caracte res: 1 2 3 4 5 6 7 8 . . . Finalmente, usaremos en adelante el s mbolo para expresar el n del archivo de entrada de datos. Conviene indicar que, cuando se trabaja en el sistema operativo DOS, el s mbolo representa a la vez el avance de l nea (A. L.) y el retorno de carro (R. C.), tal como reeja su s mbolo usual: A. L.

R. C. Sin embargo, en algunos traductores de Pascal para computadores personales, a veces se le atribuye el papel adicional de la cesin del control al computador o (desde el teclado) para que reanude su actividad (vase el apndice C). Esta coine e cidencia de papeles diculta a veces observarlos aisladamente cuando el input es el teclado.

4.4

Partes de un programa

En este apartado se renen las ideas expuestas en los anteriores, presentndou a se las partes o secciones componentes de los programas en Pascal: encabezamiento, declaraciones y bloque o cuerpo de acciones. Al n disponemos de todos los elementos necesarios para escribir los primeros programas, aunque se trata de programas muy simples, carentes por supuesto de muchos mecanismos del lenguaje. Por otra parte, adems de reunir las ideas introducidas hasta ahora, el estua diante deber en este punto conseguir poner a punto sus primeros programas en a un entorno de programacin real. o

4.4.1

Encabezamiento

El encabezamiento de un programa establece una identicacin del mismo. o En cierto modo equivale al t tulo de un libro e incluye informacin sobre los o

60
Program

Cap tulo 4. Elementos basicos del lenguaje


Identificador Identificador ,

Figura 4.5. Encabezamiento de un programa.

objetos, externos al programa, con que ste intercambia informacin: la inclusin e o o de stos en el encabezamiento establece la comunicacin correspondiente desde e o el programa. En los primeros programas estos objetos son slo los archivos estndar: el o a de entrada de datos (input) y el de salida (output), que en los computadores personales representan a la consola (teclado y monitor respectivamente); ambos archivos se incluirn siempre que el programa deba realizar operaciones de a entrada y salida respectivamente, aunque conviene que el archivo output est e siempre presente, para indicar al computador dnde comunicar las eventuales o situaciones de error. Ms adelante, se ver cmo el programa podr recibir ina a o a formacin de otra procedencia (por ejemplo, una tabla estad o stica situada en un archivo de disco), o dirigir su salida a otros dispositivos (tales como la impresora). El encabezamiento es obligatorio en Pascal estndar pero optativo en Turbo a Pascal y en otros traductores; sin embargo, es recomendable utilizarlo siempre, para que los programas sean ms claros y se puedan usar en otros entornos. a El encabezamiento empieza con la palabra reservada Program, seguida del nombre del programa, que debe ser un identicador vlido de Pascal y, entre a parntesis, la lista de parmetros del programa. El encabezamiento se separa de e a las siguientes secciones con un punto y coma (;). Por ejemplo:
Program AreaCirculo (input, output); Program DeclaracRenta (input, output, tablaRetenciones);

As pues, la sintaxis del encabezamiento responde al diagrama de la gura 4.5.

4.4.2

Declaraciones y deniciones

Adems de los identicadores predenidos, el usuario casi siempre va a nea cesitar el uso de otros nuevos, en cuyo caso debe introducirlos y describirlos (excepto el identicador del programa) antes de usarlos; este protocolo se corresponde conceptualmente con la sentencia siguiente, de uso comn en Matemticas u a Sean n Z x IR, p = 3.14 y f : IR IR tal que f (x) = pxn Z,

4.4. Partes de un programa


literal const Identificador ;
+ --

61

= Identificador

Figura 4.6. Denicin de constantes. o

legitimando el posterior uso de los identicadores n, x, p y f . En Pascal, esta informacin de partida permite al compilador hacer las correso pondientes asignaciones de memoria y vericar que el uso de todos esos objetos se ajusta a sus caracter sticas, avisando al programador en caso contrario. La obligacin de incluir declaraciones slo se da cuando el programador neceo o site incluir objetos nuevos para usarlos en el programa. Estos objetos responden a varias categor etiquetas, constantes, tipos, variables, procedimientos y funas: ciones. Veremos con detalle cada una de ellas en su momento; por ahora, basta con introducir la denicin de constantes y la declaracin de variables. o o Denicin de constantes o El diagrama sintctico de la denicin de constantes aparece en la gura 4.6. a o En el apartado 4.2.1 se explic la posibilidad de usar constantes literalmente, o escribiendo su valor; a menudo interesa expresar constantes mediante identicadores (v.g. Pi). Para utilizar constantes con nombre, hay que denirlas previamente usando la palabra reservada const, del modo siguiente:
const TempCongelaAgua = 0; TempHierveAgua = 100; Pi = 3.14; MenosPi = - Pi; PrimeraLetra = A; Vocales = aeiou;

Una vez denidas las constantes, pueden intervenir en expresiones al igual que los literales. Por ejemplo: Pi * Sqr(7.5). En general, siempre que en un programa existan valores constantes, se recomienda su presentacin mediante constantes con nombre. De este modo, o resulta el programa ms legible (v.g. Pi, E, AnchoPantalla, AltoPantalla). a

62

Cap tulo 4. Elementos basicos del lenguaje


Identificador , Tipo

var

Figura 4.7. Declaracin de variables. o

Adems, cuando una constante se repita en varios puntos de un programa, basa tar con escribir una sola vez su valor, siendo as muy fcil modicar el programa a a adaptndolo a nuevos valores de las constantes. a Declaracin de variables o La declaracin de variables sigue el diagrama sintctico de la gura 4.7, donde o a la palabra Tipo es (por el momento) un identicador de entre integer, real, char y boolean. Como ya se ha dicho, las variables son objetos cuyo valor no se conoce a priori, o bien puede cambiar a lo largo del programa. En cambio, el tipo de las variables permanece inalterable desde que se establece al principio del programa; los traductores de Pascal utilizan esta informacin para determinar la cantidad o de espacio reservado en la memoria para cada objeto y la forma en que se har a la representacin, as como realizar las vericaciones aludidas en el apartado 3.7. o Para poder utilizar una variable es preciso declararla:
var indice, contador, edad: integer; altura, peso: real; esPrimo, hayDatos: boolean; inicial: char;

Deben recordarse las recomendaciones para elegir identicadores mnemotce nicos que guarden relacin con el objeto al que dan nombre y que no sean exceo sivamente largos ni cortos.

4.4.3

Cuerpo del programa

En el cuerpo del programa es donde se relacionan las sucesivas sentencias o instrucciones ejecutables que componen el programa. Va precedido por la palabra reservada begin y termina con la palabra reservada end y un punto nal, y las instrucciones se separan con puntos y comas (vase la gura 4.8). e

4.5. Ejercicios
Instruccin ;

63

begin

end

Figura 4.8. Cuerpo de un programa.

Encabezamiento

Declaraciones

Cuerpo

Figura 4.9. Estructura bsica de un programa a

Los principales tipos de sentencias ejecutables son los siguientes: instrucciones de asignacin, de entrada o salida, estructuradas y llamadas a procedimieno tos. Estas dos ultimas las estudiaremos en cap tulos posteriores.

4.4.4

Conclusin: estructura general de un programa o

Sintetizando lo dicho hasta ahora, un programa tiene tres partes: encabezamiento, declaraciones y cuerpo, segn la gura 4.9 u 1. El encabezamiento del programa se considerar obligatorio. a 2. Tngase en cuenta que la seccin de declaraciones, en realidad, slo tiene e o o componentes optativas, por lo que puede haber programas sin declaracin o alguna. 3. Finalmente, el cuerpo o bloque es tambin obligatorio. e

4.5

Ejercicios
(a) Seale en l algunas palabras reservadas, s n e mbolos especiales, identicadores predenidos y denidos por el programador, literales y comentarios. (b) Localice las constantes que aparecen, ya sea con nombre o literales. (c) Delimite asimismo las tres secciones: encabezamiento, declaraciones y cuerpo.

1. Considerando el primer programa del cap tulo,

2. Considrese el programa inicial de este cap e tulo. En el cuerpo de instrucciones, es posible cambiar entre s los identicadores Pi y r?

64

Cap tulo 4. Elementos basicos del lenguaje


3. De las siguientes instrucciones de asignacin, detecte las errneas; averige el o o u efecto de realizar las correctas y d el tipo que deben tener las variables que e intervienen en ellas para que sean aceptables. (a) (c) (e) (g) (i) (k) a:= 5 1:= a b:= 5 a:= a p:= 2 x:= (b) (d) (f) (h) (j) (l) l:= 2 < 1 p:= Sqr(2) = Sqr(2.0) MaxInt:= 32767 c:= 2 <= 7 c1:= c2 p:= q = r = s

* a + 1 * r y

4. Cul es el efecto de llevar a cabo la siguiente lista de instrucciones sucesivamente? a (a)a := 2 (b)b := 3 (c)a := b (d)b := a

5. Efecte lo siguiente, paso a paso, con varios pares a y b de tipo integer. u (a) a:= a + b (b) b:= a - b (c) a:= a - b

qu conclusin podemos extraer? e o 6. Considrese la siguiente frmula (debida a Hern de Alejandr que expresa el e o o a), valor de la supercie S de un tringulo cualquiera en funcin de sus lados, a, b y a o c: a+b+c a+b+c a+b+c a+b+c S= a b c 2 2 2 2 D una secuencia de instrucciones para obtenerla, evitando el clculo repetido del e a semiper metro, sp = a+b+c y almacenando el resultado nalmente en la variable 2 S. 7. Escriba una instruccin tal que, siendo i una variable integer con valor 10 y x o una variable real con valor 1 234567 103 , ofrezca la salida v(10) = 1234.57 haciendo referencia a i y x. 8. Sean a, b, c, d, e y f variables reales. Escriba instrucciones de escritura que ofrezcan los valores de las siguientes variables y su suma: + a b a+b c d c+d e f e+f

donde los valores de a y b estn comprendidos entre 0 y 105, y su columna debe a mostrarse redondeada, sin decimales; c y d estn comprendidos entre 0 y 3, y su a columna debe mostrarse redondeada con 2 decimales; e y f estn comprendidos a entre 0 y 3, y su columna debe mostrarse con un d gito, despreciando la parte decimal. Obsrvese que el resultado ofrecido puede parecer errneo, debido al redondeo o e o al truncamiento. D un ejemplo de ello. e

4.5. Ejercicios
9. Sean las instrucciones de lectura: ReadLn(i1, c1, r1) ReadLn(i2)

65

donde i1, i2 son variables integer, c1 es de tipo char y r1 es real. Dados los inputs (a) 1A1 2 3 . . . (b) 1 A1 2 . . .

diga cules son correctos y en su caso cules son los valores recibidos por las distina a tas variables. Explique por qu se producen los errores y la forma de subsanarlos. e 10. Sean las variables m y n de tipo integer. (a) Analice la compatibilidad de tipos en la siguiente expresin: o m < n or m = n (b) Coloque los parntesis necesarios para que sea correcta. e (c) Puede ser correcta si m y n son de algn otro tipo? u

(c) 1.2 A1.2 1.2 . . . (d) -1123 0.5 8 13 . . .

Cap tulo 5

Primeros programas completos

5.1 5.2 5.3 5.4 5.5 5.6

Algunos programas sencillos . . . . . . . . . . . . . . . . Programas claros programas de calidad . . . . . . Desarrollo descendente de programas . . . . . . . . . . Desarrollo de programas correctos . . . . . . . . . . . . Observaciones nales . . . . . . . . . . . . . . . . . . . . Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . .

68 69 71 73 79 81

Con el conocimiento que ya se tiene de las instrucciones bsicas de Pascal a es posible desarrollar algunos programas completos que servirn para resaltar a aspectos importantes que hay que tener en cuenta desde que se empieza a programar. A continuacin se presentan algunos programas sencillos, con los que se o podrn efectuar las primeras prcticas, depurar errores y poner a punto los pria a meros programas, integrando las distintas fases que intervienen en la resolucin o de problemas mediante programas. Los problemas presentados como ejemplo y propuestos como ejercicios nos permitirn descubrir la necesidad de desarrollar a los programas con naturalidad, afrontando las dicultades que planteen una a una y con una metodolog que nos permita garantizar que el programa desarroa llado es correcto. A pesar de la simplicidad conceptual de los problemas resueltos en este cap tulo, no debe desdearse una lectura sosegada del mismo para adquirir bien n la base que se necesita para los cap tulos posteriores.

68

Cap tulo 5. Primeros programas completos

5.1
5.1.1

Algunos programas sencillos


Dibujo de la letra C

El siguiente ejemplo es de lo ms simple, aunque su efecto no produce tres a l neas de asteriscos, como parecen indicar los identicadores denidos, sino una sola. Por la misma razn, los identicadores son inapropiados, ya que inducen a o pensar que el programa escribe tres l neas distintas.
Program LetraC (output); {Dibujo de la letra C} const {Definicin de constantes, que puede mejorarse} o linea1 = ***; linea2 = *; linea3 = ***; begin {Cuerpo del programa} Write(linea1); Write(linea2); Write(linea3) end. {LetraC}

Es fcil modicarlo para que produzca efectivamente tres l a neas (en vez de una sola) con 3, 1 y 3 asteriscos respectivamente, sustituyendo las instrucciones Write por WriteLn. Una mejora trivial consiste en dejarlo con slo dos constano tes, evitando repetirlas. Ms an, un programa tan sencillo no requiere denir a u esas constantes, pudiendo usarse directamente los literales.
ee r r Disposicin clara de los programas. El programa anterior podr hao a berse escrito igualmente como sigue: Program LetraC (output); {Dibujo de la letra C} const {Definicin de constantes, que puede mejorarse} o Linea1 = ***; Linea2 = *; Linea3 = ***; begin {Cuerpo del programa} Write(Linea1); Write(Linea2); Write(Linea3) end. Sin embargo, la presentacin inicial tiene ventajas indiscutibles: el proo grama inicial est dispuesto con claridad, con lo que leer, revisar y analizar a el programa resulta ms fcil. Asimismo, permite distinguir las componena a tes del programa y su estructura.

Como se ve, Pascal es muy exible en cuanto a la disposicin del texto de los o programas, por lo que decimos que es un lenguaje de formato libre.

5.2. Programas claros programas de calidad

69

La misma nalidad tiene la colocacin adecuada de los comentarios, que no o tienen efecto alguno en el funcionamiento del programa y nos permiten en cambio incluir explicaciones sobre la nalidad del programa o sobre su funcionamiento. En el mismo sentido, no est de ms volver a recordar que se recomienda a a introducir identicadores mnemotcnicos, que sugieren el papel que juegan, fae cilitando tambin la lectura del programa. e El uso apropiado de una clara disposicin del texto, la inclusin de comeno o tarios apropiados, y una buena eleccin de los identicadores, se conoce como o autodocumentacin, y no es ningn lujo superuo. Por el contrario, se considera o u preciso adquirir desde el principio el hbito de desarrollar programas claros y a bien autodocumentados.

5.1.2

Suma de dos n meros u

El siguiente programa halla la suma de dos nmeros enteros: u


Program Suma (input, output); {Pide dos enteros y halla su suma} var a, b: integer; {los sumandos} begin {Lectura de los datos:} Write(Primer nmero: ); u ReadLn(a); Write(Segundo nmero: ); u ReadLn(b); {Clculos y resultados:} a WriteLn(a, + ,b, = ,a + b) end. {Suma} ee r r Entradas y salidas claras. Adems de la documentacin interna del a o programa, otra recomendacin que debe tenerse en cuenta desde el principio o es que las lecturas de los datos y la salida de los resultados sean claras, incluyendo para ello los mensajes necesarios y ofreciendo comprobaciones de que los datos se han le correctamente. do

5.2

Programas claros programas de calidad

Una recomendacin de gran importancia para lograr que los programas sean o correctos consiste en habituarse a escribirlos de forma clara, diferenciando bien sus distintos fragmentos para que sean fcilmente identicables y legibles. Otra a

70

Cap tulo 5. Primeros programas completos

razn para que los programas sean claros es que un programa se escribe una vez, o pero se lee muchas, bien para depurar sus posibles errores o para efectuar en l modicaciones. Se ha dicho que un programa bien escrito debe poderse leer e tan fcilmente como una novela y, aunque esto puede resultar excesivo a veces, a conviene esmerarse desde el principio en intentarlo antes incluso que perseguir la eciencia. En este sentido, conviene indicar que, de cara a mejorar la legibilidad de un programa, tambin es esencial una buena estructuracin y organizacin de las e o o acciones que lo componen, como se explica en los cap tulos 6 y 7. En la documentacin de un programa se pueden observar, entre otros, los o aspectos siguientes: El sangrado o encolumnado,1 facilitando la identicacin de fragmentos o con distinto cometido o subordinados a otros, etc. Los comentarios, aclarando los siguientes detalles: El cometido de los objetos introducidos. El funcionamiento del programa. Las condiciones requeridas o garantizadas en un determinado punto del programa. A este respecto, vase el apartado 5.4. e La eleccin adecuada de identicadores, de forma que reejen su contenido. o Las siguientes indicaciones contribuyen a su rpida interpretacin: a o Como las constantes, variables y funciones representan objetos, suelen nombrarse con sustantivos (Pi, x, sucesor) o sintagmas nominales (MaxInt mximo entero), excepto cuando representan valores a lgicos, en que desempean el papel de sentencias (esPrimo o n es primo), posiblemente abreviadas (primo, Odd, ok, EoLn). Los procedimientos representan acciones, por lo que se les suele nombrar con verbos en innitivo (Escribir, Write) Por otra parte, no debe escatimarse la longitud de los identicadores (aunque tampoco debe abusarse), cuando ello aclare el objeto identicado (AreaTotal, PagaExtra, DistTierraSol) incluso usando varias palabras para ello. En este caso, resulta aconsejable escribir con mayscula la iniu cial de cada palabra. Esta recomendacin es vlida para los identicadores o a predenidos (WriteLn, SqRt).
1

En algunos manuales puede leerse indentado, palabra que no existe en castellano.

5.3. Desarrollo descendente de programas

71

Tambin se suelen usar las maysculas y las minsculas con un criterio e u u uniforme, para que resulte sencillo interpretar la entidad de un identicador. Concretamente, la tipograf que seguimos para cada identicador es a la siguiente: Constantes denidas, empezando con mayscula: Pi, N, Maximo. u Variables, empezando con minscula: x, miEdad. u Funciones y procedimientos, empezando con mayscula: SqRt, Write. u Tipos, empezando con minscula. Los denidos por el programador, u empezarn por t y luego seguir una mayscula. a a u Otra importante cualidad de los programas consiste en que las entradas de los datos y las salidas de resultados se efecten tambin de forma clara, con u e mensajes concisos y apropiados, conrmando los datos capturados cuando su lectura sea delicada, haciendo uso de los parmetros de formato, etc. a

5.3

Desarrollo descendente de programas

En este apartado desarrollaremos un programa que tiene por objeto hallar la hipotenusa de un tringulo rectngulo a partir de las longitudes de sus catetos. a a Procederemos en tres pasos: 1. Obtencin de los catetos. o 2. Clculo de la hipotenusa. a 3. Escritura del resultado. Esta primera aproximacin puede expresarse en un estilo muy prximo a Pascal: o o
Program Clculo de hipotenusa a begin Obtener los catetos, catA , catB Hallar la hipotenusa, hipo Escribir el resultado, hipo end.

Ahora, en una primera fase se desarrollan un poco estas acciones. Algunas son tan sencillas que pueden transcribirse directamente en Pascal, aunque pueden mantenerse los comentarios para indicar el cometido de cada segmento de programa:

72

Cap tulo 5. Primeros programas completos


Program Hipotenusa (input, output); begin {Obtencin de datos} o Write(Catetos: ); ReadLn(catA, catB); Hallar la hipotenusa, hipo {Escritura de resultados} WriteLn( Hipotenusa = , hipo) end. {Hipotenusa}

aadiendo entonces los identicadores que van surgiendo: n


var catA, catB, {catetos} hipo : real; {hipotenusa}

Otras instrucciones en cambio son algo ms complicadas, pudiendo descoma ponerse en varias ms sencillas. As por ejemplo, el paso Hallar la hipotenusa a puede llevarse a cabo en dos:
Hallar la suma de los cuadrados de los catetos (SumCuadr) Hallar la ra de SumCuadr, que es ya la hipotenusa z

que pueden escribirse directamente como las instrucciones siguientes:


sumCuadr:= Sqr(catA) + Sqr(catB) hipo:= SqRt(sumCuadr)

requirindose aadir la variable sumCuadr, de tipo real. e n Finalmente, este desarrollo desemboca en un programa en Pascal:
Program Hipotenusa (input, output); {Este programa pide las longitudes de los catetos de un tringulo rectngulo y halla la correspondiente hipotenusa} a a var catA, catB, {longitudes de los catetos} sumCuadr, {para guardar CatA2 + CatB2 } hipo : real; {longitud de la hipotenusa} begin {Prog. hipotenusa} {Obtencin de los datos y su comprobacin:} o o Write (Introduce las longitudes de los catetos: ); ReadLn (catA, catB); WriteLn (un cateto mide , catA:8:4, y el otro , catB:8:4);

5.4. Desarrollo de programas correctos


{Clculos:} a sumCuadr:= Sqr(catA) + Sqr(catB); hipo:= SqRt(sumCuadr); {Resultados:} WriteLn (La hipotenusa mide: , hipo:8:4) end. {Prog. Hipotenusa}

73

Resumen El programa Hipotenusa se ha desarrollado en fases sucesivas, a partir de un boceto a grandes rasgos del mismo. En esta primera versin aparecen acciones o y datos escritos en los trminos del propio problema. e Entonces empieza un proceso de renamiento por pasos sucesivos, desarrollando esas acciones en cada fase: las ms sencillas podrn escribirse directamente a a en Pascal; otras requerirn varios pasos en esa direccin. En ambos casos pueden a o aparecer nuevos objetos que ser preciso incluir en las secciones de constantes o a variables. Este modo de proceder se llama diseo descendente y renamiento por pasos n sucesivos, ya que el desarrollo de un programa se lleva a cabo identicando primero grandes acciones y descendiendo a sus detalles progresivamente. En el apartado 7.3 se desarrollan estas ideas ampliamente.

5.4

Desarrollo de programas correctos

En este apartado se establece la base necesaria para razonar sobre la correccin de los programas durante su desarrollo, en vez de hacerlo a posteriori. o Por el momento, atendemos al efecto que tienen las instrucciones elementales sobre los datos manejados por el programa. De hecho, se puede garantizar que un programa es correcto cuando el efecto que produce a partir de unos datos genricos consiste en desembocar en los resultados deseados. En suma, un e programa es correcto si cumple con su cometido para unos datos cualesquiera, o sea, genricos. e Nuestra propuesta consiste en habituarse, desde el principio, a concentrar la dosis de atencin necesaria para estar seguro de que un programa es correcto. o Para ello, se debe seguir este principio durante todas las fases del desarrollo.

5.4.1

Estado de los cmputos o

El modelo de programacin adoptado en este libro es el imperativo (vase o e el apartado 5.1.3 de [PAO94]). En l, el efecto concreto de una instruccin en e o

74

Cap tulo 5. Primeros programas completos

un momento dado puede variar dependiendo del conjunto de valores asociados a los objetos (constantes y variables) en ese instante. Esos valores (as como el conjunto de los datos de entrada y los resultados producidos) constituyen la nocin de estado de un programa, que se altera mediante cualquiera de las o sentencias bsicas a medida que avanza la ejecucin del programa. a o Por ejemplo, si consideramos declaradas a, b: integer, el efecto de la instruccin Write(a + b) depende del estado (valor) de ambas variables, a y o b, por lo que su interpretacin en los puntos del programa donde aparezca o
... ReadLn(a, b); WriteLn(a + b); a:= a + b; WriteLn(a + b); b:= Sqr(a); WriteLn(a + b); ...

requiere una interpretacin histrica basada en los sucesivos estados precedeno o tes. En concreto, si el input consiste en una l nea con los nmeros 2 3 , las u instrucciones WriteLn(a + b) producen tres salidas distintas: 5, 8 y 30. Trazado y depuracin de un programa o Una forma de comprobar el comportamiento de un programa para un juego concreto de datos de entrada consiste en simular su funcionamiento a mano y seguir la evolucin de los estados por los que atraviesa. T o picamente, esos estados incluyen informacin sobre el input por leer, el output emitido, los valores de o las variables que intervienen y el punto en que estamos en un momento dado (vase el apartado 1.2.2). e Siguiendo con el fragmento de programa anterior, el estado de los cmputos o se puede mantener en una tabla como la de la gura 5.1, en la que las posiciones representan los estados sucesivos entre las instrucciones. As resulta fcil analizar cmo las instrucciones modican el valor de las , a o variables. Sin embargo, este sencillo mtodo se vuelve inviable a poco que un e programa se complique. Por ello, algunos entornos de desarrollo de programas incorporan facilidades para efectuar este seguimiento de forma automtica, pera mitiendo establecer las variables o expresiones de nuestro inters, as como las e posiciones en que deseamos que se produzca una suspensin momentnea de los o a cmputos para examinar su estado. Entre esos entornos se encuentra Turbo o Pascal (vase el apartado C.2.6). e

5.4. Desarrollo de programas correctos

75

Posicin o

Input ...

Output

1 2 3 4 5

[2 3 ] [] [] [] []

[] [] [5 ]

? 2 2 5 5

? 3 3 3 3

[]

[]

8 5 ...
8

8 5

[5 ]

5 25

30

5 25

Figura 5.1.

76

Cap tulo 5. Primeros programas completos

Precondiciones, postcondiciones y especicaciones Otro inconveniente del seguimiento descrito es que slo nos permite examinar o el funcionamiento de un programa para un juego de datos concreto, de donde no podemos concluir que un programa es correcto para cualquier juego de datos de entrada. Para examinar la correccin de un programa, debemos caracterizar en o general los puntos delicados, aportando una descripcin (ms o menos formal) o a del estado de los cmputos en ese punto. o En general, si tras la instruccin de lectura los valores de las variables x e y o son x0 e y0 respectivamente, tenemos:
...; {x =?, y =?} Write(Nmeros: ); u ReadLn(x, y); {x = x0 , y = y0 } x:= x + y; {x = x0 + y0 , y = y0 } WriteLn(x,y); {x = x0 + y0 , y = y0 } y:= Sqr(x); {x = x0 + y0 , y = (x0 + y0 )2 } WriteLn(x,y); {x = x0 + y0 , y = (x0 + y0 )2 } ...

Los comentarios insertados ahora constituyen armaciones sobre el estado de los clculos en un momento dado, y tienen una funcin doble: a o Nos permiten analizar con detalle el funcionamiento de un programa o un fragmento de programa. Por ejemplo, consideremos el programa siguiente, cuyo objeto consiste en intercambiar el valor de dos variables de tipo char entre s :
Program Intercambio (input, output); var c1, c2: char; {los dos caracteres} begin Write (Caracteres: ); ReadLn(c1, c2); c1:= c2; c2:= c1; WriteLn (Invertidos: , c1, c2) end. {Intercambio}

5.4. Desarrollo de programas correctos

77

Si llamamos a y b a los caracteres le dos del input, se pueden insertar los siguientes predicados:
{c1 = a, c2 = b} c1:= c2; {c1 = b, c2 = b} c2:= c1; {c1 = b, c2 = b}

con lo que no se obtiene el resultado deseado. En cambio, el siguiente programa s consigue llevar a cabo el intercambio de dos caracteres cualesquiera. La prueba de ello se da en el propio programa:
Program Intercambio (input, output); var c1, c2, {los dos caracteres} aux : char; {variable auxiliar} begin Write (Caracteres: ); ReadLn(c1, c2); {c1 = a, c2 = b} aux:= c1; {c1 = a, c2 = b, aux = a} c1:= c2; {c1 = b, c2 = b, aux = a} c2:= aux; {c1 = b, c2 = a, aux = a} WriteLn (Invertidos: , c1, c2) end. {Intercambio}

En el razonamiento anterior, se ha partido de un programa y, para vericar su funcionamiento, se han incluido aserciones sobre el estado de los clculos, a averiguando as el efecto que tienen una o varias instrucciones sobre los mismos. Rec procamente, se puede partir del efecto que un (fragmento de) programa debe producir para buscar instrucciones que lo logren. Por ejemplo, se puede plantear la bsqueda de un fragmento de programa I que modique u el valor de las variables x, y, z: integer del siguiente modo: {x = x0 , y = y0 , z = z0 } I {x = y0 , y = z0 , z = x0 }

78

Cap tulo 5. Primeros programas completos Las situaciones del estado inmediatamente anterior a la ejecucin de una o instruccin e inmediatamente posterior a la misma se llaman precondicin o o y postcondicin respectivamente. o El planteamiento anterior es la especicacin formal de un problema en o forma de ecuacin, que puede leerse as se pide un algoritmo I tal que, o : si se ejecuta cuando x = x0 , y = y0 , z = z0 , a su trmino se obtiene e x = y0 , y = z0 , z = x0 . Para interpretar correctamente una especicacin o deben tenerse en cuenta adems las declaraciones de los objetos involucraa dos.

En resumen, pueden entenderse las instrucciones como funciones que convierten un estado en otro. Las condiciones por las que atraviesa la ejecucin de o un programa pueden especicarse ms o menos formalmente como comentarios, a ayudndonos a comprender el funcionamiento de un algoritmo y a vericar su a correccin. o Rec procamente, es posible plantear un problema especicando las condiciones inicial y nal que el algoritmo solucin debe vericar. Este modo de proceder o nos lleva a desarrollar algoritmos correctos, ya que el razonamiento sobre su funcionamiento no surge a posteriori, sino durante el proceso de desarrollo. Por supuesto, encontrar algoritmos que veriquen una especicacin dada requiere o cierta experiencia en el desarrollo de los mismos; precisamente en ello consiste la programacin. o Un medio aconsejable para adquirir el hbito de desarrollar algoritmos disa ciplinadamente consiste en esforzarse por razonar sobre la correccin de los proo gramas desde el principio, como se ha indicado en el punto primero, incluyendo posteriormente la especicacin de las condiciones necesarias en el desarrollo de o los algoritmos.

5.4.2

Desarrollo descendente con especicaciones

Incorporando aserciones en el proceso descendente de programacin, resulta o que tambin las especicaciones van renndose, indicando el cometido de cada e a parte del programa y las condiciones iniciales en que se ejecuta cada pieza del programa y las nales a su trmino. As por ejemplo, el paso Obtener los e catetos, catA y catB, tiene por objeto cambiar el valor de las variables catA y catB, que es desconocido, y anotar en ellas los valores (digamos que a y b son los valores verdaderos dados en el input), lo que se expresa as :
{CatA =?, catB =?} Obtener los catetos, CatA y catB {CatA = a, catB = b }

5.5. Observaciones finales

79

De hecho, la frase Obtener los catetos, CatA y catB reeja precisamente el cometido de su especicacin. o De esta forma, la primera fase del desarrollo del programa anterior puede escribirse as :
Program Clculo de hipotenusa a begin {CatA =?, catB =?} Obtener los catetos, CatA y catB {CatA = a, catB = b } Hallar la hipotenusa, Hipo {Hipo = a2 + b2 } Escribir el resultado, Hipo {Output = a2 + b2 } end.

En las fases sucesivas se ir renando el algoritmo, que se va convirtiendo a poco a poco en un programa.

5.5
ee r r

Observaciones nales
Limitaciones del tipo integer. Se ha escrito y ejecutado el programa siguiente en un compilador en que MaxInt es 32767. La ejecucin del mismo o est representada en la columna de la derecha: a Program LimitacionesDeInteger (output); {se asume la constante MaxInt = 32767} var n: integer; begin n:= 10000; WriteLn(n); {10000} n:= n*4; WriteLn(n); {-25536} n:= n div 4; WriteLn(n) {-6384} end. {LimitacionesDeInteger} Se observa aqu que al ser Z, el dominio de integer, distinto de Z las Z, operaciones correspondientes tambin tienen sus limitaciones. En el ejeme plo anterior, se produce un desbordamiento en la segunda instruccin del o programa.

80
ee r r

Cap tulo 5. Primeros programas completos


Limitaciones del tipo real. El siguiente programa tiene un resultado imprevisto: aunque la expresin Ln(Exp(1)) = 1 es cierta, su evaluacin o o produce el valor False en el siguiente programa: Program LimitacionesDeReal (output); begin ... WriteLn(Ln(Exp(1)) = 1) { False} ... end. {LimitacionesDeInteger} Ello se debe a que los nmeros del tipo real se representan slo aproxiu o madamente: a t tulo de ejemplo, la cantidad 0.1 es en binario un decimal peridico, por lo que se truncan cifras en su representacin (vase el aparo o e tado 2.2.3 de [PAO94]). En esta referencia se identican los peligros ms a frecuentes que surgen al trabajar con nmeros reales, como es en concreto u la comparacin de cantidades reales. o

ee r r

Variables sin valor inicial. En el siguiente programa se hace uso de variables a las que no se ha dado valor alguno. Program VariablesSinValorInicial (output); var x, y: real; begin {x =?, y =?} WriteLn(x); WriteLn(y); WriteLn(x+y) end. {VariablesSinValorInicial} Por lo tanto, su funcionamiento produce resultados arbitrarios, como se muestra a continuacin, donde cada columna representa una ejecucin diso o tinta. 3.4304031250E+04 9.2923334062E+18 2.6086154722E-04 2.0689620625E-36 1.6805384925E+24 -0.0000000000E+00 0.0000000000E+00 8.5444437667E+37 1.9225485289E-17

La conclusin es sta: si no se da a las variables valor inicial, stas toman o e e valores arbitrarios.

5.6. Ejercicios

81

5.6

Ejercicios

1. Escriba programas que den cada una de las siguientes salidas: ***** ***** ***** ***** ***** * * * * * * *** ***** ******* *

2. Enmiende los errores cometidos en la escritura del siguiente programa: program Par-o-impar (imput); {Este programa pide un entero: si es par contesta "TRUE", y si no, "FALSE" } beguin {datos} Write(Dame un entero y averigar si es par); ReadLn(n); u e {clculos} esPar:= not odd n {resultados} a WriteLn(Solucin: esPar); end o 3. Escriba un programa para cada uno de los siguientes problemas, documentndolo a debidamente. Incluir adems bajo el encabezamiento la informacin concerniente a o al autor (cdigo, grupo, nombres, etc.), as como la fecha de realizacin. Una vez o o acabados, obtngase una copia escrita de ellos. e (a) Solucin de una ecuacin de la forma ax + b = 0, supuesto a=0. o o (b) Lectura de los coecientes a, b y c de una ecuacin de segundo grado o ax2 + bx + c = 0 y clculo de sus ra a ces, en los siguientes casos: i. Suponiendo que las ra son reales. Ejecutarlo con los siguientes juegos ces de datos: 1 2 1 1 0 -1 1 0 0 ii. Suponiendo que son imaginarias. Ejecutarlo con el siguiente juego de datos: 1 1 1 (c) Desarrolle un programa que, para una cierta cantidad de dinero (en pesetas), da el cambio apropiado, en billetes de mil, monedas de quinientas, de cien, de veinticinco, de duro y de peseta. bill1000 mon100 cantidad resto1000 resto100 . . . 3817 segundos = 1 horas, 3 minutos y 37 segundos

(d) Convierta una cantidad de tiempo (en segundos, Z en la correspondiente Z), en horas, minutos y segundos, con arreglo al siguiente formato:

82

Cap tulo 5. Primeros programas completos


4. Escriba un programa que, en primer lugar, lea los coecientes a2 , a1 y a0 de un polinomio de segundo grado a2 x2 + a1 x + a0 y escriba ese polinomio. Y, en segundo, lea el valor de x y escriba qu valor toma e el polinomio para esa x. Para facilitar la salida, se supondr que los coecientes y x son enteros. Por a ejemplo, si los coecientes y x son 1, 2, 3 y 2, respectivamente, la salida puede ser 1x^2 + 2x + 3 p(2) = 9 5. Razone, informalmente, la correccin o falsedad de los siguientes fragmentos de o programa, cuyo cometido se indica a continuacin. o (a) Intercambio del valor de dos variables enteras. x:= x+y; y:= x-y; x:= x-y (b) Dado un entero n, hallar el menor m n que es par. m:= n div 2 * 2 6. Los trminos de la sucesin de Fibonacci2 se denen as e o : Los dos primeros son unos.

Cada trmino es igual a la suma de los dos anteriores e

Es decir: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, . . . Suponiendo que a y b son dos trminos consecutivos de esa sucesin, razone la e o correccin de los siguientes fragmentos de programa desarrollados para hacer avano zar un paso esos trminos: e (a) Siendo aux: integer una variable auxiliar para hacer el trasvase de valores: aux:= a; a:= b; b:= aux + a (b) Sin usar variable auxiliar alguna: b:= a+b; a:= b-a

Descubierta por Leonardo da Pisa (1180-1250) y publicada en su Liber Abacci, en 1202.

Tema II

Programacin estructurada o

Cap tulo 6

Instrucciones estructuradas

6.1 6.2 6.3 6.4 6.5 6.6

Composicin de instrucciones . . . . . . . . . . . . . . . o Instrucciones de seleccin . . . . . . . . . . . . . . . . . o Instrucciones de iteracin . . . . . . . . . . . . . . . . . o

86 88 94

Dise o y desarrollo de bucles . . . . . . . . . . . . . . . 103 n Dos mtodos numricos iterativos . . . . . . . . . . . . 113 e e Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

En el cap tulo anterior se ha visto una breve introduccin a Pascal donde se o han presentado algunos tipos de datos e instrucciones bsicas. Hasta ahora todos a los ejemplos estudiados han sido de estructura muy simple: cada instruccin se o ejecuta una sla vez y adems en el mismo orden en el que aparecen en el listado o a del programa. Para escribir programas que traten problemas ms arduos es necesario coma binar las acciones primitivas para producir otras acciones ms complejas. Este a tipo de acciones combinadas se componen a partir de otras, ms sencillas, mea diante tres mtodos fundamentales: la secuencia o composicin, la seleccin y e o o la repeticin. Estos tres mtodos se describen informalmente como sigue: o e - La forma ms simple de concatenar acciones es la composicin, en ella se a o describe una tarea compleja como una sucesin de tareas ms elementales. o a - La seleccin de una alternativa tras valorar una determinada circunstancia o se reeja mediante las instrucciones if (en sus dos formas) y case.

86

Cap tulo 6. Instrucciones estructuradas - Finalmente, las instrucciones repetitivas (while, for y repeat) permiten expresar en Pascal la repeticin de acciones, ya sea un nmero de veces o u prejado o no.

En los siguientes apartados estudiamos cada una de las construcciones anteriores junto con mtodos que permiten estudiar su correccin. e o

6.1

Composicin de instrucciones o

En bastantes ocasiones una tarea concreta se especica como una serie de tareas que se ejecutan secuencialmente. Por ejemplo, si algn d alguien nos u a pregunta cmo llegar a algn sitio, la respuesta podr ser parecida a sta: o u a e 1. Tuerza por la segunda a la derecha. 2. Siga caminando hasta un quiosco. 3. Tome all el autobs. u En el cap tulo anterior se us, an impl o u citamente, la composicin de instruco ciones simples para obtener una accin ms compleja; en este apartado se preo a senta su estudio completo. En Pascal la composicin de instrucciones se realiza concatenando las instruco ciones y separndolas con el carcter punto y coma (;). La construccin de una a a o instruccin compleja como una sucesin de instrucciones simples se muestra en o o el siguiente segmento de programa, que intercambia los valores de dos variables numricas a y b sin hacer uso de ninguna variable auxiliar: e
begin a:= a + b ; b:= a - b ; a:= a - b end

Una composicin de instrucciones indica que las instrucciones citadas son o ejecutadas secuencialmente siguiendo el mismo orden en el que son escritas. El diagrama sintctico de una instruccin compuesta aparece en la gura 6.1, y su a o descripcin usando notacin EBNF (vase [PAO94], pg. 132134) es la siguiente: o o e begin instruccion {; instruccion} end

6.1. Composicion de instrucciones


begin
Instruccin

87
end

Figura 6.1. Diagrama sintctico de una instruccin compuesta. a o ee r r

Tngase en cuenta que la interpretacin del punto y coma es la de nexo o e o separador de sentencias; por lo tanto no debe aparecer despus de la ultima e sentencia de la sucesin. o Obsrvese adems que el signicado de las palabras reservadas begin y end e a es el de principio y n de la composicin, esto es, actan como delimitadores o u de la misma. Despus de esta interpretacin es sencillo deducir que la e o agrupacin de una sola instruccin, por ejemplo begin x:= x + 1 end, es o o redundante, y equivalente a la instruccin simple x:= x + 1. Asimismo, o resulta superuo anidar pares begin. . . end como en el programa de la izquierda (que resulta ser equivalente al de la derecha). begin Read(x); Read(y); begin x:= x + 1; y:= y + 2 end; WriteLn(x * y) end

begin Read(x); Read(y); x:= x + 1; y:= y + 2; WriteLn(x * y) end

Para facilitar la legibilidad del programa es aconsejable mantener el sangrado dentro de cada par begin-end, as como la inclusin de comentarios que o informen sobre el cometido de cada segmento de cdigo, como se indic en el o o apartado 5.2. Por ejemplo, en el programa anterior podr haberse incluido an comentarios indicando los segmentos de lectura de datos, clculo y resultado del a programa:
begin {Lectura de datos:} Read(x); Read(y); {Clculos:} a x:= x + 1; y:= y + 2;

88

Cap tulo 6. Instrucciones estructuradas

if

Expr. bool.

then

Instruccin

else

Instruccin

Figura 6.2. Diagrama sintctico de if-then-else. a {Resultados:} WriteLn(x * y) end

6.2
6.2.1

Instrucciones de seleccin o
La instruccin if-then-else o

Esta instruccin es el equivalente en Pascal a una expresin condicional del o o tipo si apruebo entonces ir de vacaciones y si no tendr que estudiar en verano, e e con la cual se indica que dependiendo del cumplimiento o no de una condicin o se har una cosa u otra. a En Pascal, la instruccin if-then-else (en adelante, if) es la ms importante o a instruccin de seleccin. Su diagrama sintctico aparece en la gura 6.2. o o a La interpretacin de la sentencia de seleccin genrica o o e if expresion booleana then instruccion-1 else instruccion-2 se puede deducir directamente de la traduccin del ingls de sus trminos:1 si la o e e expresin booleana es evaluada a True entonces se ejecuta la instruccion-1 y en o caso contrario (se evala a False) se ejecuta la instruccion-2. u Un ejemplo t pico en el que aparece una instruccin de seleccin podr ser o o a el siguiente segmento de programa que calcula el mximo de dos nmeros, x e a u y, y lo almacena en la variable max:
if x > y then max:= x else max:= y

Es importante sangrar adecuadamente el texto del programa para mantener la legibilidad del cdigo obtenido. Por otra parte, nada nos impide que las accioo nes que se emprendan tras evaluar el predicado sean acciones compuestas; en tal
Hay que advertir que, si bien en ingls se preere el uso de otherwise al de else, aqul resulta e e ciertamente ms propenso a ser escrito incorrectamente. a
1

6.2. Instrucciones de seleccion

89

caso la instruccin compuesta se pondr entre las palabras begin y end como o a se seal en el apartado anterior. n o Como muestra considrese el renamiento del cdigo anterior: e o
if x > y then begin max:= x; WriteLn(El mximo es , x) a end else begin max:= y; WriteLn(El mximo es , y) a end

Es aconsejable evitar la introduccin de cdigo redundante dentro de las o o posibles alternativas, ya que se facilita en gran manera el mantenimiento del programa. El segmento anterior es un agrante ejemplo de redundancia que puede ser evitada fcilmente colocando el WriteLn una vez realizada la seleccin: a o
if x > y then max:= x else max:= y; {Fin del if} WriteLn(El mximo es , max) a ee r r

A efectos de la colocacin de los puntos y comas debe tenerse en cuenta o que toda la construccin if-then-else corresponde a una sola instruccin, o o y no es una composicin de las instrucciones if, then y else; en particular, o la aparicin de un punto y coma justo antes de un then o de un else dar o a como resultado un error sintctico (bastante frecuente, por cierto). a

Una particularidad de esta instruccin es que la rama else es opcional; en o caso de no ser incluida se ha de interpretar que cuando la expresin booleana o resulta ser falsa entonces no se realiza ninguna accin. Por esta razn, la forma o o if-then es util como sentencia para controlar excepciones que pudieran afectar el procesamiento posterior. Por ejemplo, en el siguiente fragmento de programa se muestra el uso de la forma if-then como sentencia de control.
ReadLn(year); feb:= 28; {No siempre, ya que puede ser a~o bisiesto} n if year mod 4 = 0 then feb:= 29; WriteLn(Este a~o Febrero tiene ,feb, das) n

90

Cap tulo 6. Instrucciones estructuradas

El programa asigna a la variable feb el nmero de d del mes febrero, en u as 2 general este nmero es 28 salvo para aos bisiestos. u n
ee r r Obsrvese el uso de los puntos y comas en el ejemplo anterior: la instruccin e o de seleccin acaba tras la asignacin feb:= 29 y, al estar en una secuencia o o de acciones, se termina con punto y coma.

Aunque, en principio, la instruccin if slo permite seleccionar entre dos o o alternativas, es posible usarla para realizar una seleccin entre ms de dos opo a ciones: la idea consiste en el anidamiento, esto es, el uso de una instruccin if o dentro de una de las ramas then o else de otra instruccin if. Como ejemplo o supngase que se quiere desarrollar un programa que asigne a cada persona una o etiqueta en funcin de su altura (en cm), el siguiente fragmento de cdigo realiza o o la seleccin entre tres posibilidades: que la persona sea de estatura baja, media o o alta.
if altura < 155 then WriteLn(Estatura Baja) else if altura < 185 then WriteLn(Estatura Media) else WriteLn(Estatura Alta)

En el segmento anterior se asigna la etiqueta de estatura baja a quien mida menos de 155 cm, de estatura media a quien est entre 156 y 185 cm y de estatura alta e a quien mida 186 cm o ms. a El anidamiento de instrucciones if puede dar lugar a expresiones del tipo if C1 then if C2 then I2 else I3 (6.1)

que son de interpretacin ambigua en el siguiente sentido: a cul de las dos o a instrucciones if pertenece la rama else? En realidad, la ambigedad slo existe u o en la interpretacin humana, ya que la semntica de Pascal es clara: o a El convenio que se sigue para eliminar la ambigedad consiste en u emparejar cada rama else con el then soltero ms prximo. a o Siguiendo el convenio expuesto, la expresin anterior se interpreta sin ambio gedad como se indica a continuacin: u o if C1 then begin if C2 then I2 else I3 end
El criterio empleado para detectar si un ao es o no bisiesto ha sido comprobar si el ao es n n mltiplo de 4; esto no es del todo correcto, ya que de los aos mltiplos de 100 slo son bisiestos u n u o los mltiplos de 400. u
2

6.2. Instrucciones de seleccion

91

Si, por el contrario, se desea forzar esa construccin de modo que sea intero pretada en contra del convenio, entonces se puede usar un par begin-end para aislar la instruccin if anidada del siguiente modo: o if C1 then begin if C2 then I2 end else I3 Otro modo de lograr la misma interpretacin consiste en aadir la rama else o n con una instruccin vac esto es o a,
e e En la explicacin del convenio sobre la interpretacin del anidamiento de r r o o instrucciones if se ha escrito el cdigo linealmente, en lugar de usar un foro mato vertical (con sangrado), para recordar al programador que la semntica a de Pascal es independiente del formato que se d al cdigo. Es conveniente e o recordar que el sangrado slo sirve para ayudar a alguien que vaya a leer el o programa, pero no indica nada al compilador.

if C1 then if C2 then I2 else else I3

Por ejemplo, en relacin con la observacin anterior, un programador poco o o experimentado podr escribir la instruccin (6.1) dentro de un programa del a o siguiente modo
if C1 then if C2 then I2 else I3 {Cuidado!!}

interpretando errneamente que la rama else est ligada con el primer if. Como o a consecuencia, obtendr un programa sintcticamente correcto que arrojar rea a a sultados imprevisibles debido a la interpretacin incorrecta, por parte del proo gramador, del anidamiento de instrucciones if. La solucin del problema reside o en forzar la interpretacin del anidamiento para que el compilador entienda lo o que el programador ten en mente, esto ser escribir a a if C1 then begin if C2 then I2 end else I3 if C1 then if C2 then I2 else else I3

o bien

Con frecuencia, aunque no siempre, puede evitarse el anidamiento para elegir entre ms de dos opciones, pues para ello se dispone de la instruccin de seleccin a o o mltiple case, que permite elegir entre un nmero arbitrario de opciones con una u u sintaxis mucho ms clara que la que se obtiene al anidar instrucciones if. a

92

Cap tulo 6. Instrucciones estructuradas

case

Expresin

of

Constante

Instruccin

end

Figura 6.3. Diagrama sintctico de la instruccin case. a o

6.2.2

La instruccin case o

La instruccin case permite la seleccin entre una cantidad variable de poo o sibilidades, es decir, es una sentencia de seleccin mltiple. Un ejemplo de esta o u seleccin en lenguaje natural podr ser el siguiente men semanal: segn sea o a u u el d de la semana, hacer lo siguiente: lunes, mircoles y viernes tomar pescado, a e martes, jueves y sbado tomar carne, el domingo comer fuera de casa. a Esta instruccin consta de una expresin (llamada selector ) y una lista de o o sentencias etiquetadas por una o varias constantes del mismo tipo que el selector; al ejecutarse esta instruccin se evala el valor actual del selector y se ejecuta o u la instruccin que tenga esa etiqueta, si no existe ninguna instruccin con esa o o etiqueta se produce un error.3 El diagrama sintctico de la instruccin case a o aparece en la gura 6.3.
ee r r La expresin selectora de una instruccin case as como las etiquetas deben o o ser de un tipo ordinal (vase el apartado 3.6). e

Como ejemplo de aplicacin de la instruccin case considrese el siguiente o o e segmento de cdigo que asigna la calicacin literal segn el valor almacenado o o u en la variable nota de tipo integer:
var nota: real; ... ReadLn(nota); case Round(nota) of 0..4: WriteLn(SUSPENSO); 5,6: WriteLn(APROBADO);
Esto es lo que ocurre en Pascal estndar; en Turbo Pascal no se produce ningn error, sima u plemente se pasa a la siguiente instruccin. o
3

6.2. Instrucciones de seleccion


7,8: WriteLn(NOTABLE); 9: WriteLn(SOBRESALIENTE); 10: WriteLn(MATRICULA de HONOR) end {case}

93

Otra situacin en la que es frecuente el uso de la instruccin case es cuando o o algunos programas se controlan mediante mens, es decir, aparecen en pantalla u las diferentes acciones que se pueden ejecutar dentro del programa y el usuario elige, mediante un nmero o una letra, aqulla que quiere utilizar. u e Por ejemplo, supongamos un programa de gestin de una biblioteca. Tal o programa proporcionar en pantalla un men con las siguientes acciones: a u B. Bsqueda. u P. Peticin prstamo. o e D. Devolucin prstamo. o e S. Salir. En un primer nivel de renamiento, el programa podr escribirse de la sia guiente forma:
var opcion: char; ... Mostrar el men u Leer opcion case opcion of B: Bsqueda. u P: Peticin Prstamo. o e D: Devolucin Prstamo. o e S: Salir. end

Si las acciones son complejas, pueden aparecer submens donde se seleccionan u ciertas caracter sticas de la accin. Por ejemplo, al elegir la opcin de bsqueda o o u puede aparecer un segundo men con las distintas opciones disponibles: u A. Bsqueda por Autores. u M. Bsqueda por Materias. u I. Bsqueda por ISBN. u S. Salir.

94

Cap tulo 6. Instrucciones estructuradas

Se deduce fcilmente que este fragmento de programa debe repetir las accioa nes de bsqueda, peticin y devolucin hasta que se elija la opcin de salida. u o o o El cometido de este fragmento consiste en mostrar el men al usuario y leer un u valor, que se asigna a la variable opcin. Este valor determina la opcin elegida o o y se utiliza en una instruccin case para activar las acciones correspondientes. o Por lo tanto, la instruccin case abunda en este tipo de programas al determinar o las acciones que hay que ejecutar en cada opcin. o

6.3

Instrucciones de iteracin o

Las instrucciones iterativas permiten especicar que ciertas acciones sean ejecutadas repetidamente; esto es lo que se llama usualmente un bucle. Se dispone en Pascal de tres construcciones iterativas (while, repeat y for), no obstante se puede demostrar que todas ellas pueden ser especicadas slo o con la instruccin while (vase el apartado 7.2). En los siguientes apartados o e se estudia detenidamente cada una de las instrucciones de iteracin y se realiza o una comparacin entre las caracter o sticas de cada una de ellas para ayudarnos a escoger la que ms se adecua al bucle que se desea desarrollar. a

6.3.1

La instruccin while o

En algunas ocasiones es necesario especicar una accin que se repite siemo pre que se cumpla una determinada condicin; una frase en lenguaje natural o tal como mientras haga calor usar manga corta es un ejemplo de este tipo de construcciones. En Pascal esta construccin se hace mediante la instruccin while. Su diao o grama sintctico aparece en la gura 6.4, que se corresponde con el esquema a while Expresion booleana do Instruccion cuya interpretacin es: mientras que la expresin booleana sea cierta se ejecutar o o a la instruccin, que se suele llamar cuerpo del bucle, indicada tras el do. o A continuacin tenemos un fragmento de programa que calcula la suma de o los n primeros nmeros naturales: u
ReadLn(n); suma:= 0; contador:= 1; while contador <= n do begin suma:= suma + contador; contador:= contador + 1 end; {while} WriteLn(suma)

6.3. Instrucciones de iteracion


Expresin Booleana
Instruccin

95

while

do

Figura 6.4. Diagrama sintctico de la instruccin while. a o

La ejecucin de una instruccin while comienza con la comprobacin de la o o o condicin (por esto a los bucles while se les llama bucles preprobados); si sta es o e falsa entonces se naliza la ejecucin, esto es, se salta la sentencia que aparece o tras el do; si la condicin es verdadera entonces se ejecuta la instruccin, se o o vuelve a comprobar la condicin y as sucesivamente. o Para una correcta utilizacin de la instruccin while es necesario que la o o instruccin modique las variables que aparecen en la condicin, ya que en caso o o contrario, si la condicin es verdadera siempre permanecer as y el bucle no o a terminar nunca. a Una situacin en que se puede producir este error surge cuando el cuerpo o del bucle es una secuencia de instrucciones y se olvida utilizar los delimitadores begin y end. Por ejemplo, el siguiente segmento de cdigo no calcula la suma o de los enteros desde el 1 hasta el n:
ReadLn(n); suma:= 0; contador:= 0; while contador <= n do suma:= suma + contador; contador:= contador + 1; WriteLn(suma)

{OJO: Fin de while}

Al olvidar delimitar el cuerpo del bucle, la instruccin por iterar termina o antes de actualizar el valor del contador, con lo cual el bucle se repite sin cesar y el programa se cuelga. La correccin de tal error se reduce a incluir un par o begin-end para delimitar la sentencia interior del bucle.
ee r r La instruccin while admite slo una instruccin tras el do, con lo que para o o o iterar una accin mltiple se ha de emplear la composicin de instrucciones o u o con su correspondiente par begin-end. La sintaxis de Pascal permite escribir un punto y coma inmediatamente despus del do. Sin embargo, cuando se entre en el bucle, esta construccin e o dar lugar a un bucle innito, puesto que se interpreta que el interior del a bucle es la instruccin vac que, obviamente, no modica los parmetros de o a a la condicin del bucle. Por lo tanto, a efectos prcticos no se debe escribir o a un punto y coma detrs del do. a

96

Cap tulo 6. Instrucciones estructuradas

Antes de continuar con ms ejemplos de bucles while vamos a introducir a un par de funciones booleanas que aparecen muy frecuentemente en el uso de bucles: EoLn y EoF.4 La funcin EoLn se hace verdadera cuando se alcanza una o marca de n de l nea y falsa en otro caso, mientras que la funcin EoF se hace o verdadera cuando se alcanza una marca de n de archivo y falsa en otro caso. As el siguiente fragmento de programa cuenta y escribe los caracteres de una , l nea:
var c: char; numCar: integer; ... numCar:= 0; while not EoLn do begin Read(c); numCar:= numCar + 1 end; {while} WriteLn(numCar)

y este otro fragmento cuenta el nmero de l u neas del input


var numLin: integer; ... numLin:= 0; while not EoF do begin ReadLn; numLin:= numLin + 1 end; {while} WriteLn(numLin) ee r r

Obsrvese cmo se usa la caracter e o stica de preprobado en los ejemplos anteriores para asegurarse de que no ha terminado la l nea (resp. el archivo) antes de leer el siguiente carcter (resp. l a nea).5

Las instrucciones while se pueden anidar y obtener instrucciones del siguiente tipo

Estas funciones sern estudiadas en mayor profundidad en el apartado 14.3. a En la versin 7.0 de Turbo Pascal se puede marcar el n de la entrada de datos con la o combinacin de teclas [Control] + [z]. o
5

6.3. Instrucciones de iteracion


while condicin 1 do begin o Instrucciones while condicin 2 do o Instruccin; o Instrucciones end {while}

97

simplemente escribiendo el while interior como una instruccin ms dentro del o a cuerpo de otro bucle while. Si el bucle while exterior no llega a ejecutarse, por ser falsa su condicin, tampoco lo har el while interior. Si, por el contrario, el o a while exterior se ejecutara por ser su condicin verdadera, entonces se evaluar o a la condicin del while interior y, si tambin es verdadera, se ejecutarn sus o e a instrucciones interiores hasta que su condicin se vuelva falsa, tras lo cual el o control vuelve al while exterior. Un ejemplo de frecuente aplicacin de anidamiento de instrucciones while o puede ser la gestin de cheros de texto, en el siguiente fragmento de cdigo o o se cuenta el nmero de caracteres del input, que est compuesto a su vez por u a varias l neas
var c:char; numCar:integer; ... numCar:= 0; while not EoF do begin while not EoLn do begin Read(c); numCar:= numCar + 1 end; {while not EoLn} ReadLn end; {while not EoF} WriteLn(numCar)

Las propiedades principales de la instruccin while que se deben recordar o son las siguientes: 1. La condicin se comprueba al principio del bucle, antes de ejecutar la o instruccin; por eso se le llama bucle preprobado. o 2. El bucle termina cuando la condicin deja de cumplirse. o 3. Como consecuencia de los puntos anteriores la instruccin se ejecuta cero o o ms veces; por lo tanto puede no ejecutarse. a

98

Cap tulo 6. Instrucciones estructuradas

repeat

Instruccin

until

Expresin Booleana

Figura 6.5. Diagrama sintctico de la instruccin repeat. a o

6.3.2

La instruccin repeat o

Comenzamos este apartado retomando el ejemplo en lenguaje natural con el que se present la instruccin while: mientras haga calor usar manga corta. La o o caracter stica de preprobado de while hace que este consejo slo sea vlido para o a gente previsora que comprueba el tiempo que hace antes de salir de casa. Cmo se podr modicar el ejemplo anterior para que fuera vlido tambin o a a e para quien no sabe qu tiempo hace fuera hasta que ya es demasiado tarde? e Una forma ser llevar un jersey puesto hasta que haga calor ; de este modo se a evitarn bastantes enfriamientos indeseados. a La instruccin repeat permite la construccin de bucles similares al de este o o ultimo ejemplo, con caracter sticas ligeramente distintas a la del bucle while. El diagrama sintctico de la instruccin repeat aparece en la gura 6.5. La forma a o general de la instruccin repeat obedece al esquema o repeat Lista de instrucciones until Expresion booleana donde
Lista de instrucciones:= instruccion {; instruccion }

por lo tanto, la interpretacin de una instruccin repeat es: repetir las instruco o ciones indicadas en el cuerpo del bucle hasta que se verique la condicin que o aparece tras until.
ee r r En este tipo de bucles las palabras reservadas repeat y until funcionan como delimitadores, no siendo necesario usar begin-end para delimitar la lista de instrucciones.

En la ejecucin de una instruccin repeat se comienza ejecutando la lista de o o instrucciones y despus se comprueba si se cumple la condicin (por eso el bucle e o es postprobado); si la condicin an no se cumple entonces se repite el bucle, o u ejecutando la lista de instrucciones y comprobando la condicin. La iteracin o o

6.3. Instrucciones de iteracion

99

termina cuando la condicin se hace verdadera, en cuyo caso se pasa a la siguiente o instruccin externa al bucle. o Como ejemplo de utilizacin del bucle repeat, se incluye otra versin de la o o suma de los n primeros nmeros naturales. u
ReadLn(n); {Supuesto que n >= 1} suma:= 0; contador:= 0; repeat contador:= contador + 1; suma:= suma + contador until contador = n

Obsrvese que la condicin n >= 1 es imprescindible para que el resultado e o nal sea el esperado. En general, siempre es conveniente comprobar el comportamiento del bucle en valores extremos; en este ejemplo, para n = 0 se generar a un bucle innito, lo cual se evitar sustituyendo la condicin contador = n por a o contador >= n. En este caso, dada la caracter stica de postprobado del bucle repeat, las instrucciones interiores se ejecutarn al menos una vez, por lo que a la suma valdr al menos 1 y el resultado arrojado ser incorrecto para n <= 0. a a Un caso frecuente de utilizacin de repeat se produce en la lectura de datos: o
{lectura de un nmero positivo:} u repeat WriteLn(Introduzca un nmero positivo); u ReadLn(numero) until numero > 0

donde si alguno de los datos introducidos no es positivo entonces la condicin o resultar ser falsa, con lo cual se repite la peticin de los datos. a o Podemos mejorar el ejemplo de aplicacin a la gestin de una biblioteca o o mostrado en el apartado 6.2.2 usando la instruccin repeat para controlar el o momento en el que se desea terminar la ejecucin. o
Mostrar el men u {Elegir una accin segn la opcin elegida:} o u o WriteLn(Elija su opcin: ); o ReadLn(opcion); repeat case opcion of B: Bsqueda. u P: Peticin Prstamo. o e D: Devolucin Prstamo. o e

100

Cap tulo 6. Instrucciones estructuradas


S: Salir. end until (opcion = S) or (opcion = s)

El anidamiento de instrucciones repeat se realiza de la forma que cabe esperar. Como ejemplo se introduce un programa que determina el mximo de una a secuencia de nmeros positivos procedentes del input terminada con el cero. u
Program MaximoDelInput (input, output); {Calcula el mximo de una secuencia de nmeros terminada en 0} a u var max, n: integer; begin max:= 0; repeat {Lee un nmero positivo, insistiendo hasta lograrlo:} u repeat Write(Introduzca un nmero positivo: ); u ReadLn(n) until n >= 0; if n > max then max:= n until n = 0; WriteLn(El mximo es: ,max) a end. {MaximoDelInput}

Las propiedades principales de la instruccin repeat son las siguientes: o 1. La instruccin repeat admite una lista de instrucciones interiores, no o siendo necesario utilizar los delimitadores begin-end. 2. Este bucle se llama postprobado; es decir, la condicin se comprueba o despus de ejecutar la lista de instrucciones, por lo que sta se ejecuta e e al menos una vez. 3. El bucle termina cuando se cumple la condicin. o 4. Como consecuencia de los puntos anteriores la lista de instrucciones siempre se ejecuta una o ms veces. a

6.3.3

La instruccin for o

La instruccin de repeticin for se utiliza para crear bucles con un nmero o o u predeterminado de repeticiones. Un ejemplo sencillo en lenguaje natural podr a o u ser para los bloques desde el A hasta el K hacer la inspeccin del ascensor, segn

6.3. Instrucciones de iteracion

101

to

for

Identif.

:=

Expresin downto

Expresin

do

Instruccin

Figura 6.6. Diagrama de ujo de las instrucciones for.

el cual se especica una tarea repetitiva (la inspeccin) que ha de realizarse o exactamente en 11 ocasiones (para los bloques A,. . . , K ). La sentencia for admite dos variantes: la for-to-do (instruccin for asceno dente) y la for-downto-do (instruccin for descendente). El diagrama sintctico o a de estas sentencias aparece en la gura 6.6. De otro modo: for variable:= expresion ordinal (to|downto) expresion ordinal do instruccion donde se acostumbra a llamar variable de control o ndice del bucle a la variable variable. El funcionamiento del bucle for es el siguiente: primero se comprueba si el ndice rebasa el l mite nal, con lo que es posible que el cuerpo del bucle no llegue a ejecutarse ninguna vez, en caso positivo se le asigna el valor inicial a la variable de control vble, se ejecuta la instruccin interior una vez y se incrementa o (o decrementa, segn se trate de to o downto respectivamente) una unidad el u valor de vble, si este nuevo valor est comprendido entre el valor inicial y el valor a nal, entonces se vuelve a ejecutar la instruccin interior, y as sucesivamente o hasta que vble alcanza el valor nal. En particular, si en una instruccin for-to-do el valor inicial de la variable o es posterior al valor nal entonces no se ejecutan las instrucciones interiores y se sale del bucle. La instruccin for-downto-do tiene un comportamiento anlogo o a cuando el valor inicial de la variable es anterior al valor nal.
ee r r En teor nada impide que en el cuerpo de un bucle for se modique a, el valor de la variable de control o las expresiones inicial y nal del bucle; sin embargo, debe ponerse el mayor cuidado en evitar que esto ocurra. En particular, conviene recordar que la variable de control se actualiza automticamente. El siguiente fragmento de cdigo es un ejemplo a o sintcticamente correcto a for i:= 1 to 5 do begin Write(i); i:= i - 1 end {for}

102

Cap tulo 6. Instrucciones estructuradas


pero genera un bucle innito dando como resultado una sucesin innita de o unos.6

Como ejemplo de aplicacin de la instruccin for podemos considerar, una o o vez ms, la suma de los primeros nmeros naturales 1, 2, . . . , n. a u
var n, i, suma: integer; ... ReadLn(n); suma:= 0; for i:= 1 to n do suma:=suma + i; WriteLn(suma)

Otro ejemplo interesante es el siguiente, con el que se halla una tabulacin o de la funcin seno para los valores 0 , 5 , . . . , 90 . o
const Pi = 3.1416; var r: real; n: integer; ... r:= 2 * Pi/360; {El factor r pasa de grados a radianes} for n:= 0 to 18 do WriteLn(Sin(5 * n * r))

Es conveniente recordar que la variable de control puede ser de cualquier tipo ordinal; por ejemplo, la siguiente instruccin imprime, en una l o nea, los caracteres desde la A a la Z:
for car:= A to Z do Write(car)

Como ejemplo de anidamiento de bucles for podemos considerar el siguiente fragmento que escribe en la pantalla los elementos de la matriz de tamao n m n i + j: denida por aij = 2
const N = 3; M = 5;
6

En realidad, se es el comportamiento en Turbo Pascal. e

6.4. Diseno y desarrollo de bucles


var i,j: integer; ... for i:= 1 to N do for j:= 1 to M do WriteLn(El elemento (,i,,,j,) es ,(i + j)/2)

103

Las siguientes caracter sticas de la instruccin for merecen ser recordadas: o 1. Las expresiones que denen los l mites inicial y nal se evalan una sola u vez antes de la primera iteracin. o 2. El bucle se repite un nmero predeterminado de veces (si se respeta el valor u del ndice en el cuerpo del bucle). 3. El valor de la variable de control se comprueba antes de ejecutar el bucle. 4. El incremento (o decremento) del ndice del bucle es automtico, por lo a que no se debe incluir una instruccin para efectuarlo. o 5. El bucle termina cuando el valor de la variable de control sale fuera del intervalo de valores establecido.

6.4
6.4.1

Dise o y desarrollo de bucles n


Eleccin de instrucciones iterativas o

Para poder elegir la instruccin iterativa que mejor se adapta a una situacin o o particular es imprescindible conocer las caracter sticas ms importantes de cada a instruccin iterativa, as como las similitudes y diferencias entre ellas. o El primero de todos los criterios para elegir una u otra instruccin iterativa o es la claridad : se ha de elegir aquella instruccin que exprese las acciones por o repetir con la mayor naturalidad. Adems, la eleccin de la instruccin adecuada depende de las caracter a o o sticas del problema. En el caso en que se conozca previamente el nmero de repeticiones u que van a ser necesarias, es recomendable usar la instruccin for. Por ejemplo, o el siguiente fragmento de cdigo calcula la media aritmtica de 5 nmeros le o e u dos del input:
Program Media5 (input, output); {Calcula la media de cinco nmeros} u var entrada, total, media: real;

104

Cap tulo 6. Instrucciones estructuradas


begin total:= 0; {Entrada de datos y clculo de la suma total:} a for i:= 1 to 5 do begin ReadLn(entrada); total:= total + entrada end; {for} {Clculo de la media:} a media:= total / 5; {Salida de datos:} WriteLn(La media es , media:10:4) end. {Media5}

Si no se conoce previamente cuntas repeticiones se necesitarn entonces se a a usar bien while o bien repeat; para saber cundo conviene usar una u otra a a ser conveniente recordar sus similitudes y diferencias. a 1. Si no se sabe si se ha de ejecutar el cuerpo del bucle al menos una vez entonces el bucle ha de ser preprobado, con lo cual se usar la instruccin a o while. 2. Si, por el contrario, el cuerpo del bucle se ha de ejecutar al menos una vez entonces se usar repeat, pues nos basta con un bucle postprobado. a Por ejemplo, supngase que estamos desarrollando un programa de gestin o o de un cajero automtico, la primera tarea que se necesita es la de identicar a al usuario mediante su nmero personal; si tenemos en cuenta la posibilidad u de error al teclear el nmero lo mejor ser colocar este fragmento de cdigo u a o dentro de un bucle. Puesto que, obviamente, es necesario que el usuario teclee su nmero de identicacin al menos una vez, se usar la instruccin repeat. u o a o
var codigo, intentos: integer; ... intentos:= 0; repeat Read(codigo); intentos:= intentos + 1 until Cdigo correcto or (intentos > 3) o

donde se ha expresado en seudocdigo la comprobacin de la validez del nmero o o u tecleado y, adems, se incluye un contador para no permitir ms de tres intentos a a fallidos. En caso de duda, si no se sabe muy bien si el cuerpo del bucle se ha de repetir al menos una vez o no, se ha de usar while, pero debemos asegurarnos de que

6.4. Diseno y desarrollo de bucles

105

la condicin est denida en la primera comprobacin. El siguiente ejemplo o a o muestra un caso en el que esto no ocurre: supongamos que dada una l nea de caracteres se desea averiguar el primer carcter que es una letra minscula, el a u siguiente fragmento de programa es errneo o
var car: char; ... while not ((a <= car) and (car <= z)) do Read(car)

en el supuesto de que a car no se le haya dado un valor inicial, ya que entonces el valor de car es desconocido en la primera comprobacin. o

6.4.2

Terminacin de un bucle o

El buen diseo de un bucle debe asegurar su terminacin tras un nmero n o u nito de repeticiones. Los bucles for no crean problemas en este aspecto siempre que se respete la variable de control dentro del cuerpo del bucle. Precisamente el uso de for est a indicado cuando se conoce previamente el nmero de repeticiones necesarias; sin u embargo, para los bucles condicionales (while y repeat) se ha de comprobar que en su cuerpo se modican algunas de las variables que aparecen en su condicin o P de entrada (resp. salida) de manera que, en las condiciones supuestas antes del bucle, P llega a ser falsa (resp. cierta) en un nmero nito de iteraciones. u Considrese el siguiente fragmento de programa: e
var n: integer; ... Read(n); while n <> 0 do begin WriteLn(n); n:= n div 2 end {while}

se observa que en el cuerpo del bucle se modica el valor de la variable, n, que aparece en su condicin. En este caso, para cualquier n entero se puede demostrar o que el bucle termina siempre en log2 |n| + 1 pasos;7 de hecho, estos clculos a forman parte del algoritmo para pasar a base dos (vase [PAO94], pgina 32). e a A continuacin se muestra un ejemplo de bucle que, a pesar de modicar en o su cuerpo la variable de la condicin del bucle, no siempre termina. o
7

La notacin x representa el mayor entero menor que x. o

106
var n: integer; ... ReadLn(n); repeat WriteLn(n); n:= n - 2 until n = 0

Cap tulo 6. Instrucciones estructuradas

Obviamente, este programa slo termina en el caso de que el valor proporcionado o para n sea par y positivo, dando n/2 vueltas, y no termina en caso contrario.

6.4.3

Uso correcto de instrucciones estructuradas

No basta con construir un programa para desempear una tarea determinada, n hay que convencerse de que el programa que se ha escrito resuelve correctamente el problema. El anlisis de la correccin de un programa puede hacerse a posa o teriori, como se explic en el apartado 5.4, aplicando la llamada vericacin o o de programas. Sin embargo, es mucho ms recomendable usar una tcnica de a e programacin que permita asegurar que el programa construido es correcto. o En este apartado se indica cmo probar la correccin de un fragmento de o o cdigo en el que aparecen instrucciones estructuradas. El estudio de la secuencia, o la seleccin y la iteracin se realiza por separado en los siguientes apartados. o o En general, se expresa un fragmento de programa mediante
Precondicin o Instrucciones Postcondicin o

para expresar que, si la precondicin es cierta al comienzo de las instrucciones, o entonces a su trmino la postcondicin se verica. e o La precondicin (abreviadamente PreC.) representa los requisitos para que o trabajen las instrucciones, y la postcondicin (abreviadamente PostC.) repreo senta los efectos producidos por las mismas. As pues, decir que las instrucciones son correctas equivale a decir que, si en su comienzo se verican sus requisitos, a su trmino se han logrado sus objetivos; en suma, cumplen correctamente con e su cometido. Uso correcto de una secuencia de instrucciones Para estudiar la correccin de una secuencia de instrucciones se incluyen asero ciones entre ellas y se analiza la correccin de cada instruccin individualmente. o o

6.4. Diseno y desarrollo de bucles

107

Como ejemplo vamos a comprobar la correccin del fragmento de cdigo que o o altera los valores de las variables a, b y c segn se indica: u
{PreC.: a = X, b = Y, c = Z} a:= a + b + c; b:= a - b; c:= a - c; a:= 2 * a - b - c {PostC.: a = Y + Z, b = X + Z, c = X + Y}

Para la vericacin de la secuencia de instrucciones hemos de ir incluyendo o aserciones que indiquen el efecto de cada instruccin, tal como se present en el o o apartado 5.4:
{PreC.: a = X, b = Y, c = Z} a:= a + b + c; {a = X+Y Z, b = Y, c = Z} b:= a - b; {a = X+Y+Z, b = (X+Y+Z) - Y = X+Z, c = Z} c:= a - c; {a = X+Y+Z, b = X+Z, c = (X+Y+Z) - Z = X+Y} a:= 2 * a - b - c {a = 2*(X+Y+Z) - (X+Z) - (X+Y) = Y+Z, b = X+Z, c = X+Y} {PostC.: a = Y+Z, b = X+Z, c = X+Y}

En el desarrollo anterior se aprecia cmo, partiendo de la precondicin, va camo o biando el estado de las variables a, b y c hasta llegar a la postcondicin. o Uso correcto de una estructura de seleccin o Las estructuras de seleccin incluyen dos tipos de instrucciones, if y case; en o ambos casos el proceso de vericacin es el mismo: se ha de vericar cada una o de las posibles opciones individualmente, haciendo uso de la precondicin y de o la expresin que provoca la eleccin de tal opcin. o o o Puesto que la vericacin se realiza de modo similar tanto para if como para o case consideraremos slo el siguiente ejemplo, con el que se pretende calcular el o mximo de las variables a y b: a
{PreC.: a = X, b = Y} if a >= b then m:= a else m:= b {PostC.: a = X, b = Y, m = max(X,Y)}

108

Cap tulo 6. Instrucciones estructuradas

Para vericar este fragmento se estudiar separadamente cada una de las dos a ramas (la then y la else)
{PreC.: a = X, b = Y} if a >= b then {a = X, b = Y y X >= Y } m:= a {a = X, b = Y, X >= Y y m = X = mx(X,Y)} a else {a = X, b = Y y X < Y} m:= b {a = X, b = Y, X < Y y m = Y = mx(X,Y)} a {Postc.: a = X, b = Y y m = mx(X,Y)} a

Se observa que cada una de las ramas verica la postcondicin, con lo cual o la seleccin descrita es correcta. o Uso correcto de estructuras iterativas Despus de conocer las instrucciones de Pascal que permiten codicar bucles, e en particular tras estudiar los distintos ejemplos incluidos, se puede tener la sensacin de que construir bucles correctamente necesita grandes dosis de inspio racin, ya que un bucle escrito a la ligera puede dar ms o menos vueltas de lo o a necesario, no detenerse nunca o, cuando lo hace, dar un resultado incorrecto. En este apartado se pretende demostrar que no es necesario apelar a las musas para escribir bucles correctos, ya que basta con disear el bucle mediante n una metodolog adecuada que se presenta a continuacin. a o Como primer ejemplo se considerar el algoritmo de la divisin entera mea o diante restas sucesivas; este algoritmo se esboza a continuacin mediante un o sencillo ejemplo: Para hallar el cociente de la divisin de 7 entre 2 hay que ver cuntas veces o a cabe 2 dentro de 7 (en este caso 7 = 2 + 2 + 2 + 1 con lo que el cociente ser a 3 y el resto 1) y, para ello, a 7 (el dividendo) se le va restando 2 (el divisor) repetidamente hasta que se obtenga un nmero menor que 2 (el divisor); este u nmero ser el resto de la divisin, y el nmero de repeticiones realizadas ser u a o u a el cociente. No es conveniente comenzar a escribir directamente el bucle, aun habiendo comprendido perfectamente cmo trabaja el algoritmo. Antes conviene meditar o qu tipo de bucle usar, especicar las variables m e nimas necesarias, expresar el resultado deseado en trminos de variables declaradas y especicar qu se espera e e que el bucle realice en cada repeticin. o

6.4. Diseno y desarrollo de bucles ddo dsor coc resto 7 2 0 7 7 2 1 5 7 2 2 3 7 2 3 1

109

Paso Paso Paso Paso

0: 1: 2: 3:

7 7 7 7

= = = =

7 2+5 2+5 2+2+2+1

= = = =

20+7 21+5 21+5 23+1


Figura 6.7.

1. Para decidir qu tipo de bucle usar debemos observar que, en principio, e no sabemos cuntas repeticiones van a ser necesarias en cada caso (no a nos sirve for); por otro lado, no siempre va a ser necesaria al menos una repeticin del cuerpo del bucle, como por ejemplo al dividir 5 entre 7 (no o nos sirve repeat). En consecuencia tendremos que usar un bucle while. 2. Las variables que necesitaremos para codicar el bucle deben ser al menos cuatro: para el dividendo, el divisor, el cociente y el resto, que llamaremos respectivamente ddo, dsor, coc y resto. 3. Dados ddo y dsor, el resultado que deseamos obtener son valores para coc y resto tales que ddo = dsor * coc + resto vericando que 0 resto dsor. 4. Por ultimo, qu se realiza en cada iteracin? Sencillamente, reducir resto e o (en dsor unidades) e incrementar coc (en 1 unidad): o sea, tantear que ha cabido una vez ms el divisor en el dividendo. Si detallamos la divisin a o de 7 entre 2 (vase la gura 6.7) podemos ver cmo cambian las variables e o coc y resto y observar que: (a) En cada iteracin del bucle existe una relacin que permanece conso o tante: ddo = dsor * coc + resto. (b) Se naliza cuando resto<dsor. Al escribir el programa usaremos esta asercin para asegurar la correccin del cdigo. o o o Ya podemos dar una primera aproximacin al programa usando seudocdigo o o y aserciones:
var ddo, dsor, coc, resto: integer; ... {Entrada de datos:} Leer los valores de dividendo y divisor (positivos no nulos) {Clculos:} a {PreC.PreC.: ddo > 0 y dsor > 0} Dar valor inicial a las variables cociente y resto

110

Cap tulo 6. Instrucciones estructuradas


{La relacin ddo = dsor * coc + resto debe cumplirse siempre} o Comenzar el bucle {PostC.: ddo = dsor * coc + resto y 0 <= resto < dsor} {Salida de datos:} Imprimir el resultado

Las tareas de entrada y salida de datos no presentan demasiada dicultad, pero hay que tener cuidado al codicar los clculos, especialmente el bucle. Con a la informacin obtenida al estudiar el bucle se observa que ste debe repetirse o e mientras que la variable resto sea mayor que el divisor: el valor de resto comienza siendo el del dividendo; en cada iteracin, del resto se sustrae el dsor o hasta que, nalmente, se obtenga un valor para resto menor que el dsor; al mismo tiempo, la variable coc aumenta una unidad cada vez que se ejecuta una iteracin. o
ee r r Un vistazo a la tabla de la gura 6.7 basta para convencerse de que hay que operar sobre la variable resto y no sobre la que contiene el dividendo. Por otra parte, es importante no confundir los conceptos resto y cociente con los valores de las variables resto y coc, ya que slo al nal del proceso o los valores de estas variables son realmente el resto y el cociente de la divisin entera. o

Este razonamiento nos permite escribir el siguiente programa:


Program Cociente (input, output); var ddo, dsor, coc, resto: integer; begin {Entrada de datos:} repeat Write(Introduzca el dividendo: ); ReadLn(ddo); Write(Introduzca el divisor: ); ReadLn(dsor) until (ddo > 0) and (dsor > 0); {Se tiene ddo > 0 y dsor > 0} {Clculos:} a coc:= 0; resto:= ddo; {Inv.: ddo = dsor * coc + resto y resto 0} while resto >= dsor do begin resto:= resto - dsor; coc:= coc + 1 end; {while} {PostC.: ddo = dsor * coc + resto y 0 <= resto < dsor}

6.4. Diseno y desarrollo de bucles


{Salida de datos:} WriteLn(El cociente es, coc, y el resto es , resto) end. {Cociente}

111

En el programa anterior se ha destacado una asercin que permanece conso tante antes, durante y tras la ejecucin del bucle; tal asercin recibe el nombre o o de invariante del bucle (abreviadamente Inv.). En general, para la construccin o de cualquier bucle conviene buscar un invariante que reeje la accin del bucle o en cada iteracin, pues esto facilitar la programacin y la vericacin posterior. o a o o El invariante de un bucle debe vericarse en cuatro momentos: Comienzo: El invariante debe cumplirse justo antes de ejecutar el bucle por primera vez. Conservacin: Si el invariante y la condicin del bucle se cumplen antes de o o una iteracin y se ejecuta el cuerpo del bucle, entonces el invariante seguir o a siendo cierto tras su ejecucin. o Salida: El invariante, junto con la falsedad de la condicin (que se tiene a la o salida del bucle), nos permitir deducir el resultado, que es la postcondicin a o del bucle. Terminacin: El cuerpo del bucle deber avanzar hacia el cumplimiento o a de la condicin de terminacin del bucle, de forma que se garantice la o o nalizacin del mismo. o Para vericar cualquier bucle hay que comprobar estas tres etapas, donde la parte generalmente ms dif es la comprobacin de conservacin del invariante. a cil o o Para el bucle anterior tenemos que 1. Las asignaciones a las variables coc y resto antes del bucle hacen que el invariante se cumpla antes de la primera iteracin. o 2. Supuesto que el invariante se cumple antes de una iteracin, esto es ddo = o dsor * coc + resto, hay que demostrar que el cuerpo del bucle conserva el invariante. En nuestro caso, debemos comprobar que los nuevos valores para coc y resto siguen cumpliendo el invariante, pero esto es trivial ya que dsor * (coc + 1) + (resto - dsor) = dsor * coc + dsor + resto - dsor = dsor * coc + resto = ddo

112

Cap tulo 6. Instrucciones estructuradas

3. La terminacin del bucle la tenemos asegurada, ya que en el cuerpo del buo cle se va disminuyendo el valor de resto, con lo que la condicin de entrada o resto < dsor siempre se va a alcanzar tras un nmero nito de iteraciou nes. La correccin del bucle se deduce del invariante y de la condicin de o o entrada del bucle: tras la ultima iteracin, segn el invariante, tenemos o u ddo = dsor * coc + resto y adems, segn la condicin del bucle, se a u o tiene que resto < dsor con lo cual el bucle es correcto. Dependiendo del programa en particular, aparecen distintos tipos de bucles; no todos los invariantes tienen por qu ser expresables como relaciones numricas e e entre variables. En el siguiente ejemplo tenemos que localizar la posicin del primer carcter o a blanco (un espacio) que aparece en una frase terminada por un punto. La idea consiste en recorrer la frase carcter por carcter teniendo en cuenta a a la posicin del carcter rastreado. Las variables necesarias son dos: car y pos, o a para almacenar el carcter le y su posicin. El cuerpo del bucle, en cada a do o iteracin, debe leer el siguiente carcter y actualizar la posicin; y se volver a o a o a ejecutar a menos que se haya le un blanco o un punto, con lo cual, el invariante do ha de ser que Pos contiene la posicin del ultimo carcter le o a do. El recorrido se va a realizar leyendo caracteres del input, y se supone que ste e contiene algn carcter blanco o algn punto. En estas condiciones tendremos u a u el siguiente bucle:
var car: char; pos: integer; ... pos:= 0; {Inv.: pos indica la posicin del ltimo carcter rastreado} o u a repeat Read(car); pos:= pos + 1 until (car = ) or (car = .) ...

La correccin de este bucle se deja como ejercicio para el lector. o


ee r r Se acaba de introducir, de manera informal, el concepto de invariante de un bucle para analizar su correccin. Sin embargo, no debe pensarse que o los invariantes son herramientas para vericar bucles a posteriori, esto es, despus de haberlos escrito: es conveniente extraer el invariante antes de e escribir nada, pues de esta manera la tarea de programacin del bucle se o facilita enormemente.

6.5. Dos metodos numericos iterativos

113

c3 a

c4 b c2 c1

Figura 6.8. Aproximacin por biparticin. o o

6.5

Dos mtodos numricos iterativos e e

Dada una funcin f : IR IR, se considera el problema de hallar aproximao damente un cero de la misma, esto es, un valor x IR tal que f (x) = 0. En un computador no es posible representar todos los nmeros reales, por lo cual ser u a necesario conformarse con aproximaciones a un cero de f , esto es, con un x tal que f (x) 0. Los siguientes mtodos son ampliamente conocidos por su fcil e a aplicacin y eciencia. El tercer apartado no es ms que una aplicacin directa o a o de los mismos.

6.5.1

Mtodo de biparticin e o

En este primer mtodo, aceptaremos un valor x como aproximacin aceptable e o de un cero x0 de f si | x x0 |< , para una cierta tolerancia prejada (por ejemplo, = 106 ). Este mtodo se basa en el teorema de Bolzano que dice que, cuando f es e continua en un intervalo [a, b] y los signos de f (a) y de f (b) son distintos, entonces existe algn cero de f en ese intervalo. u Aunque no hay modo de hallarlo en general, una posibilidad consiste en hallar el signo de f (c), siendo c = a+b el punto central del intervalo [a, b] y, 2 segn sea igual al de f (a) o al de f (b), quedarnos con el intervalo [c, b] o [a, c], u respectivamente. Iterando este proceso, tendremos un intervalo tan pequeo n como deseemos, y siempre con un cero de f encerrado en l. En concreto, e bastar con repetir el proceso hasta que el ancho del intervalo sea menor que 2 a para que su punto medio se pueda considerar una aproximacin aceptable. o En la gura 6.8 se muestra cmo se va aproximando el cero de la funcin f o o mediante la biparticin sucesiva del intervalo. o

114

Cap tulo 6. Instrucciones estructuradas

La codicacin de este algoritmo es bastante simple: En primer lugar, conoo cida la funcin a la que queremos calcular un cero, debemos solicitar el mximo o a error permitido, epsilon, y los extremos del intervalo donde buscar el cero de la funcin, a y b. Despus habr que reducir el intervalo hasta que sea menor o e a que 2 * epsilon; en la reduccin del intervalo se irn cambiando los valores de o a los extremos del intervalo, para lo cual se usarn las variables izda y dcha. La a primera aproximacin en seudocdigo es la siguiente: o o

Program Biparticion (input, output); var epsilon, a, b, izda, dcha: real; begin Leer el error permitido, epsilon; Leer los extremos del intervalo, a,b; izda:= a; dcha:= b; Reducir el intervalo Imprimir el resultado end. {Biparticion}

La lectura y salida de datos no presenta mayor dicultad, por su parte la tarea reducir el intervalo requiere la utilizacin de una variable adicional, c, o para almacenar el valor del punto central del intervalo. Podemos renar esta tarea mediante un bucle while, ya que no sabemos si ser necesario ejecutar al a menos una reduccin del intervalo de partida (aunque es lo previsible): o
while (dcha - izda) > 2 * epsilon do begin {Inv.: signo(f (izda)) = signo(f (dcha))} c:= (dcha + izda) / 2; if f (dcha) * f (c) < 0 then {El cero se encuentra en [c,dcha]} izda:= c else {El cero se encuentra en [izda,c]} dcha:= c; end {while} {PostC.: c es la aproximacin buscada} o

Lo unico que queda por completar, dejando aparte la entrada y salida de datos, consiste en la comprobacin de la condicin de la instruccin if-then-else, o o o es decir f (dcha) * f (c) < 0, cuya codicacin se realizar una vez conocida o a la funcin f . o El programa nal del mtodo es el siguiente: e

6.5. Dos metodos numericos iterativos

115

Program Biparticion (input, output); var epsilon, a, b, c, izda, dcha: real; begin {Entrada de datos} WriteLn(Error permitido?); ReadLn(epsilon); WriteLn(Extremos del intervalo?); ReadLn(a,b); izda:= a; dcha:= b; gb o egin {Reduccin del intervalo} fwhile{dedeReduddos029 15.6917 0 0 Td /R452+0 1 gWrUparticion deRedu6264 TfTd ((dchag)Tj (on)Tj b;def f52 (dchag(dchag Td (f5Tj /R453 9.9626ag)Tj /R45255220.9219 0 b;26.1517Td (on)Tj31.48 g5 -99.1283 -11.9cTd (dc(del)Tj Td (f)Tj /R453 9.95.94968 15.6 while fb,dede(valob; gdcha:=real;f

116

Cap tulo 6. Instrucciones estructuradas

x2

x1

x0

Figura 6.9. Aproximacin por el mtodo de Newton-Raphson. o e

En la gura 6.9 se muestra cmo se va aproximando el cero de la funcin f o o mediante la sucesin x0 , x1 , x2 , . . . , xn , . . . o La codicacin del mtodo de Newton-Raphson no diere demasiado de la de o e biparticin, ya que, esencialmente, ambas consisten en construir iterativamente o una sucesin de aproximaciones a un cero de la funcin; la unica diferencia estriba o o en la forma de construir la sucesin: en biparticin simplemente se calculaba el o o punto medio del intervalo de trabajo, mientras que en el mtodo de Newtone Raphson, conocido xn , el siguiente trmino viene dado por e xn+1 = xn f (xn ) f (xn )

Para este algoritmo disponemos, al menos, de dos posibilidades que se enumeran a continuacin: o 1. Codicar directamente cada paso de iteracin usando expl o citamente la expresin de la funcin derivada f (puesto que f es conocida es posible o o hallar f usando las reglas de derivacin), o bien o 2. Usar el ejercicio 11 del cap tulo 3 para aproximar el valor de f cada vez que sea necesario. Desde el punto de vista de la correccin del resultado obtenido resulta conveo niente usar directamente la expresin de f para evitar el posible efecto negativo o causado por los errores acumulados tras cada aproximacin de f (xi ). La impleo mentacin de este algoritmo se deja como ejercicio. o

6.6. Ejercicios

117

6.5.3

Inversin de funciones o

Una aplicacin del clculo de ceros de funciones consiste en la inversin puno a o tual de funciones, esto es, dada una funcin g y un punto a en su dominio calcular o g 1 (a). Un ejemplo en el que se da esta necesidad podr ser el siguiente: Supngaa o se que se quiere poner en rbita un satlite de comunicaciones a una altura de o e 25 kilmetros, supngase tambin que se conoce h(t), la altura de la lanzadera o o e espacial en metros en cada instante de tiempo t, entonces, el instante de tiempo preciso t0 en el que se debe soltar el satlite de la lanzadera es tal que h(t0 ) = e 25000, esto es, t0 = h1 (25000). La inversin puntual de funciones consiste simplemente en un pequeo ardid o n que permite expresar la inversa puntual como el cero de cierta funcin: el mtodo o e se basa en que calcular un valor8 de g 1 (a) equivale a hallar un cero de la funcin o f denida como f (x) = g(x) a.

6.6

Ejercicios
(a) Pedir los dos trminos de una fraccin y dar el valor de la divisin correse o o pondiente, a no ser que sea nulo el hipottico denominador, en cuyo caso se e avisar del error. a (b) Pedir los coecientes de una ecuacin de segundo grado y dar las dos soo luciones correspondientes, comprobando previamente si el discriminante es positivo o no. (c) Pedir los coecientes de la recta ax + by + c = 0 y dar su pendiente y su ordenada en el origen en caso de que existan, o el mensaje apropiado en otro caso. (d) Pedir un nmero natural n y dar sus divisores. u

1. Escriba un programa apropiado para cada una de las siguientes tareas:

2.

(a) Escriba un programa que estudie la naturaleza de las soluciones del sistema de ecuaciones siguiente: a 1 x + b1 y = c 1 a 2 x + b2 y = c 2 y lo resuelva en el caso de ser compatible determinado. (b) Apl quelo a los siguientes sistemas: 2x + 3y 3x 2y = = 5 1 6x + 3y 2x + y = = 12 1 4x + 2y 6x + 3y = = 8 12

Pues puede haber varios.

118

Cap tulo 6. Instrucciones estructuradas

3. Escriba un programa que lea un carcter, correspondiente a un d a gito hexadecimal: 0, 1,..., 9, A, B,..., F y lo convierta en el valor decimal correspondiente: 0, 1,..., 9, 10, 11,..., 15 4. Para hallar en qu fecha cae el Domingo de Pascua de un anno cualquiera, basta e con hallar las cantidades a y b siguientes: a:= (19 * (anno mod 19) + 24) mod 30 b:= (2 * (anno mod 4) + 4 * (anno mod 7) + 6 * a + 5) mod 7 y entonces, ese Domingo es el 22 de marzo + a + b d que podr caer en abril. as, a Escriba un programa que realice estos clculos, produciendo una entrada y salida a claras. 5. Considere el siguiente fragmento de programa vlido en Pascal: a ... if P then if Q then if x < 5 then WriteLn(a) else WriteLn(b) else if x < y then WriteLn(c) else WriteLn(d) else if x < 0 then if Q then WriteLn(e) else WriteLn(f) else if Q then WriteLn(g) else WriteLn(h) ... siendo P, Q: boolean y x, y: integer. (a) Reescr balo usando una disposicin ms clara. o a (b) Siendo P x < 3 y Q y < x, detecte y suprima las condiciones redundantes. (c) Detecte y suprima las condiciones redundantes, asumindose que en la entrae da la siguiente sentencia es cierta: (P = Q) (x < y < 0) 6. El cuadrado de todo entero positivo impar se puede expresar como 8k + 1, donde k es entero positivo; por ejemplo ..., 32 = 8 1 + 1, 52 = 8 3 + 1, ...

Sin embargo, algunos valores de k no producen cuadrados, como k = 2 que da 17. Se pide un programa que lea un nmero entero y lo clasique segn las u u posibilidades de los siguientes ejemplos,

6.6. Ejercicios
4 es par 5 es impar, pero no es de la forma 8k+1 17 es impar y de la forma 8k+1 (para k = 2), pero no es un cuadrado perfecto. 25 es impar, de la forma 8k+1 (para k = 3), y cuadrado perfecto de 5 dando una salida como las indicadas. 7. Qu hace el siguiente programa? e Program Letras (output); var fila, col: char; begin for fila:= A to Z do begin for col:= A to fila do Write(fila); WriteLn end {for} end. {Letras} 8. Considrese la funcin e o 3n + 1 P (n) = n/2 si n es par y sea N un nmero natural arbitrario. La sucesin numrica u o e {N, P (N ), P (P (N )), P (P (P (N ))), . . . , P (P (. . . P (N ) . . .)), . . .} si n es impar

119

son los llamados nmeros pedrisco (vase el apartado 1.3.1) generados por N . Por u e ejemplo, para N = 5 su sucesin de nmeros pedrisco es {5, 16, 8, 4, 2, 1, 4, 2, 1, . . .} o u donde, a partir del sexto trmino, los valores 4, 2 y 1 se repiten indenidamente. e Construya un programa que, dado un natural N , escriba su sucesin de nmeros o u pedrisco y cuente las iteraciones necesarias para llegar a 1 (y entrar en el bucle 1, 4, 2, 1, . . . ). Aplicarlo a todos los enteros menores que 30. Se observa algo especial para N = 27? 9. Escriba un programa cuyos datos son dos nmeros naturales y una operacin u o (suma, resta, multiplicacin o divisin), y cuyo resultado es la cuenta correspono o diente en el siguiente formato: * 1234 25 -----30850

Complete el programa anterior de manera que, antes de efectuar la operacin o seleccionada, verique que va a ser calculada sin conicto.

120

Cap tulo 6. Instrucciones estructuradas


n k

10. Escriba un programa para hallar (a) mediante la frmula o

, donde n y k son datos enteros positivos,

n! (n k)!k! n(n 1) . . . (k + 1) (b) mediante la frmula o (n k)! Qu ventajas presenta la segunda con respecto a la primera? e 11. Integracin denida. Sea la funcin f : IR IR. Se puede dar un clculo o o a aproximado de su integral denida dividiendo el intervalo [a, b] en n trozos, de base = ba , y sumando las reas de los n rectngulos de base y alturas a a n f (x + i):
b n

f (x)dx
a i=1

f (x + i)

Escriba un programa que utilice esta expresin para calcular una aproximacin o o de 0 sen(x)dx y compruebe el grado de precisin del programa, comparando su o resultado con el ofrecido por los mtodos anal e ticos. 12. Los dos primeros trminos de la sucesin de Fibonacci (vase el ejercicio 6 del e o e tema 5) valen 1, y los dems se hallan sumando los dos anteriores: 1, 1, 2, 3, 5, 8, a 13, 21, . . . Confeccione un programa que lea una cota entera positiva, N , y genere trminos de la citada sucesin hasta superar la cota jada. e o 13. Cifras de n meros. u (a) Escriba un programa que averige cuntos d u a gitos tiene un nmero natural u n. Concrete en qu condiciones debe estar ese dato para que el programa e funcione correctamente. (b) Escriba el programa pasa pasa, que lee dos nmeros enteros positivos, a u y b, y transere la ultima cifra de a a b: (1234, 5678) (123, 56784) (c) Escriba un programa que invierta un nmero natural, dato, transriendo u todas sus cifras a otro, resultado, que inicialmente vale 0: (1234, 0) (123, 4) (12, 43) (1, 432) (0, 4321) (d) Escriba un programa que lea del input un literal entero positivo, expresado en el sistema de numeracin hexadecimal, y halle la cantidad que representa o expresndola en el sistema decimal usual. a 14. Primos y divisores. Escriba un programa que realice las siguientes tareas: (a) Que pida un nmero entero estrictamente mayor que uno y, una vez comu probada esa condicin, busque su mayor divisor distinto de l. o e (b) Que averige si un nmero n es primo, tanteando divisores a partir de 2 u u hasta dar con uno, o deteniendo la bsqueda al llegar al propio n. u

6.6. Ejercicios

121

(c) Que averige si un nmero n es primo, pero parando la bsqueda (a lo ms) u u u a al llegar a n . 2 (d) Que averigsi un nmero n es primo, pero parando la bsqueda (a lo ms) ue u u a n . al llegar a 15. Nmero perfecto es el que es igual a la suma de sus divisores, excluido l mismo. u e Ejemplo: Contraejemplo: 6=1+2+3 12 = 1 + 2 + 3 + 4 + 6

(a) Escriba un programa que d los nmeros perfectos de 1 a 200. e u (b) Escriba un programa que busque el primer nmero perfecto a partir de 200 u (supuesto que, en efecto, hay alguno). 16. Escriba un programa que aplique el procedimiento de biparticin en el clculo de o a un cero de la funcin f denida como: o f (x) = x4 en [0, 3], con una tolerancia menor que una millonsima. e (Obsrvese que el resultado es una aproximacin de 4 .) e o
1 17. Aplicar el mtodo de biparticin en el clculo aproximado de arcsen( 5 ), a travs e o a e de un cero de la funcin f , denida as o :

f (x) = sen(x) con la misma tolerancia.

1 5

18. Aplicar el mtodo de biparticin en el clculo aproximado de f (x) = ex 10. e o a Obsrvese que el resultado es una aproximacin de loge (10). e o 19. Usar el mtodo de Newton-Raphson para cada uno de los apartados anteriores. e Qu mtodo requiere menos iteraciones? e e

Cap tulo 7

Programacin estructurada o

7.1 7.2 7.3 7.4 7.5 7.6 7.7

Introduccin . . . . . . . . . . . . . . . . o Aspectos tericos . . . . . . . . . . . . . o Aspectos metodolgicos . . . . . . . . o Renamiento correcto de programas Conclusin . . . . . . . . . . . . . . . . . o Ejercicios . . . . . . . . . . . . . . . . . . Referencias bibliogrcas . . . . . . . . a

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

123 125 139 146 151 151 153

En este cap tulo se explican los aspectos necesarios para aplicar de una forma adecuada las instrucciones estructuradas presentadas en el cap tulo anterior, de forma que los programas obtenidos sean claros, correctos y ecientes. En particular, se presentan los resultados tericos que fundamentan la programacin o o estructurada, y la metodolog que permite la construccin de programas segn a o u este estilo de programacin. o

7.1

Introduccin o

Durante la corta historia de los computadores, el modo de programar ha sufrido grandes cambios. La programacin era en sus comienzos todo un arte o (esencialmente cuestin de inspiracin); posteriormente diversas investigaciones o o tericas han dado lugar a una serie de principios generales que permiten conforo mar el ncleo de conocimientos de una metodolog de la programacin. Esta u a o

124

Cap tulo 7. Programacion estructurada

consiste en obtener programas de calidad. Esto se puede valorar a travs de e diferentes caracter sticas que se exponen a continuacin, no necesariamente en o orden de importancia: La correccin del programa que, obviamente, es el criterio indispensable, o en el sentido de que se desean obtener programas correctos que resuelvan el(los) problema(s) para los que estn diseados. a n La comprensibilidad, que incluye la legibilidad y la buena documentacin, o caracter sticas que permiten una mayor facilidad y comodidad en el mantenimiento de los programas. La eciencia, que expresa los requerimientos de memoria y el tiempo de ejecucin del programa. o La exibilidad o capacidad de adaptacin del programa a variaciones del o problema inicial, lo cual permite la utilizacin del programa durante mayor o tiempo. La transportabilidad , que es la posibilidad de usar el mismo programa sobre distintos sistemas sin realizar cambios notables en su estructura. Teniendo en cuenta que un programa, a lo largo de su vida, es escrito slo una o vez, pero le do, analizado y modicado muchas ms, cobra una gran importancia a adquirir tcnicas de diseo y desarrollo adecuadas para obtener programas con e n las caracter sticas reseadas en la introduccin. En este libro estudiamos dos n o tcnicas, conocidas como programacin estructurada y programacin con sube o o programas. El objetivo que las tcnicas anteriores se proponen es que los programas sean e comprensibles, correctos, exibles y transportables. Ambas tcnicas no son e excluyentes; ms bien al contrario, un buen estilo de programacin las integra, a o enfocando los problemas desde los dos puntos de vista simultneamente. Para a evitar la confusin que podr surgir entre ellas, en este cap o a tulo nos centraremos en los principios de la programacin estructurada, dejando los de la programacin o o con subprogramas para los cap tulos 8, 9 y 10. Las ideas que dieron lugar a la programacin estructurada ya fueron expueso tas por E.W. Dijkstra en 1965, aunque el fundamento terico (teoremas de la o programacin estructurada) est basado en los trabajos de Bhm y Jacopini o a o publicados en 1966. La programacin estructurada es una tcnica de programacin cuyo objetivo o e o es, esencialmente, la obtencin de programas ables y fcilmente mantenibles. Su o a estudio puede dividirse en dos partes bien diferenciadas: por un lado su estudio conceptual terico, y por otro su aplicacin prctica. o o a

7.2. Aspectos teoricos

125

Por una parte, el estudio conceptual se centra en ver qu se entiende por proe grama estructurado para estudiar con detalle sus caracter sticas fundamentales. Por otra parte, dentro del enfoque prctico se presentar la metodolog de a a a renamientos sucesivos que permite construir programas estructurados paso a paso, detallando cada vez ms sus acciones componentes. a

7.2

Aspectos tericos o

En este apartado se introducen los diagramas de ujo como medio para explicar lo que no es la programacin estructurada. Estos diagramas han sido profuo samente utilizados hasta hace bien poco. Un par de ejemplos nos demostrarn a el caos que puede producir la falta de una organizacin adecuada. o Un uso ms racional de los diagramas de ujo exige introducir condiciones a que nos permitan hablar de programas razonables1 y cules son los mecanismos a cuya combinacin permite expresar de forma ordenada cualquier programa que o satisfaga estas condiciones. Adems se expone cmo se pueden reescribir en forma estructurada algunos a o programas razonables no estructurados. Ello permitir deducir la gran apora tacin de esta metodolog o a.

7.2.1

Programas y diagramas de ujo

Una prctica muy comn de programacin ha sido la utilizacin de diagraa u o o mas de ujo (tambin llamados organigramas) como una descripcin grca del e o a algoritmo que se pretende programar. Sin embargo, esta popularidad ha ido menguando debido al dbil (o nulo) soporte riguroso de su utilizacin; nosotros e o presentaremos los diagramas de ujo precisamente para mostrar lo que no es programacin estructurada. o Para comprender mejor los problemas que surgen del uso incorrecto de los diagramas de ujo es necesario conocerlos un poco. Un diagrama de ujo se compone de bloques (que representan las acciones y las decisiones) y de l neas (que indican el encadenamiento entre los bloques). Los bloques de un diagrama de ujo pueden ser de cuatro clases distintas: S mbolos terminales, que indican el principio y el nal del algoritmo. Se representan usando valos, como se indica a continuacin: o o
Principio
1

fin

El sentido de este adjetivo se explicar en el apartado 7.2.2. a

126

Cap tulo 7. Programacion estructurada

S mbolos de entrada y salida de datos. Respectivamente, signican lectura y escritura, y se representan como se indica:

Bloques de procesamiento de datos, que realizan operaciones con los datos le dos o con datos privados. Se representan mediante rectngulos que a encierran la especicacin del proceso, como por ejemplo: o

a - b

Nudos de decisin, en los que se elige entre dos o ms alternativas. Segn o a u las alternativas sean dos (generalmente dependiendo de una expresin lgica) o o o ms de dos se usa uno u otro de los siguientes s a mbolos:

Cierto

Falso
A modo de ejemplo, en la gura 7.1 se muestra un sencillo diagrama de ujo que indica el procedimiento de multiplicar dos nmeros enteros positivos u mediante sumas sucesivas. Sin embargo, no todos los diagramas de ujo son tan claros como el anterior. Como muestra considrese el diagrama de ujo de la gura 7.2: si un diagrama e de ujo se escribe de cualquier manera, aun siendo correcto desde el punto de vista de su funcionamiento, puede resultar engorroso, cr ptico, ilegible y casi imposible de modicar. Por otra parte, en la gura 7.3 se observa una disposicin mucho ms clara o a del mismo programa que favorece su comprensin y facilita su codicacin. o o

7.2.2

Diagramas y diagramas propios

El ejemplo anterior resalta la necesidad de una metodolog que sirva para a evitar diagramas tan confusos como el de la gura 7.2. Para formalizar esta

...

7.2. Aspectos teoricos

127

Principio

s0

a=0
NO

S I

fin

a a-1 s s+b

Figura 7.1. Diagrama de ujo para el producto de nmeros naturales. u

Principio

fin

S I

a = b

NO
a < b

NO

a a - b

S I
bb - a

S I

a = b

NO

Figura 7.2. Un diagrama de ujo algo confuso.

128

Cap tulo 7. Programacion estructurada

Principio

S I
a = b

b b - a

NO

a < b
aa - b

S I

NO

fin

Figura 7.3. Versin bien organizada del diagrama anterior. o

7.2. Aspectos teoricos

129

metodolog ser necesario disponer de una cierta clase de diagramas permitidos a a a partir de los cuales construir la teor Entre stos se destaca la subclase de los a. e diagramas propios, que representan, desde cierto punto de vista, a los programas correctamente estructurados. En este apartado se restringe el concepto de diagrama, que ser utilizado a ms adelante en la denicin de programa estructurado. Consideraremos que a o un diagrama se construye usando como elementos bsicos unicamente las tres a siguientes piezas: Accin, que sirve para representar una instruccin (por ejemplo de lectura, o o escritura, asignacin. . . ). o
A

Condicin, que sirve para bifurcar el ujo del programa dependiendo del o valor (verdadero o falso) de una expresin lgica. o o
Cierto

p
Falso

Agrupamiento, que sirve, como su nombre indica, para agrupar l neas de ujo con distintas procedencias.

A continuacin se denen y se dan ejemplos de diagramas propios, que ser o a lo que consideraremos como programa razonable. El lector puede juzgar tras leer la denicin y los ejemplos lo acertado del calicativo. o Denicin: Se dice que un diagrama, construido con los elementos citados o arriba, es un diagrama propio (o limpio) si rene las dos condiciones siguientes: u 1. Todo bloque posee un unico punto de entrada y otro unico punto de salida. 2. Para cualquier bloque, existe al menos un camino desde la entrada hasta l y otro camino desde l hasta la salida. e e

130

Cap tulo 7. Programacion estructurada

Cierto

Cierto

A B

p
Falso

p
B
Falso

Figura 7.4. Un diagrama no propio y un diagrama propio.

C
Cierto Cierto

A
Falso

p
Falso

Figura 7.5. Un diagrama no propio.

Estas condiciones restringen el concepto de diagrama de modo que slo se pero mite trabajar con aqullos que estn diseados mediante el uso apropiado del e a n agrupamiento y sin bloques superuos o formando bucles sin salida. En el diagrama de la izquierda de la gura 7.4 se muestra un ejemplo de un diagrama que no es propio por no tener una unica salida. Agrupando las salidas se obtiene un diagrama propio (a la derecha de la gura). En la gura 7.5 se observa otro diagrama que no es propio, ya que existen bloques (los A y C y q) que no tienen un camino hasta la salida; si el programa llegara hasta esos bloques se colapsar pues no es posible terminar la ejecucin. Finalmente, en a, o la gura 7.6 aparece un diagrama que contiene bloques inaccesibles desde la entrada del diagrama. Algunos autores exigen una condicin adicional a un diagrama para consideo rarlo propio: no contener bucles innitos. Sin embargo esta propiedad est ms a a estrechamente relacionada con la vericacin que con el diseo de programas. o n

7.2.3

Diagramas BJ (de Bhm y Jacopini) o

Los diagramas BJ son tres diagramas especialmente importantes; esta importancia se debe a que pueden considerarse esquemas de acciones muy naturales y completamente expresivos, en el sentido de que cualquier programa razonable

7.2. Aspectos teoricos

131

B p
Cierto

Falso

Figura 7.6. Un diagrama con elementos inaccesibles.

se puede reorganizar de forma ordenada combinando slo estos esquemas. Esta o caracter stica tiene bastante utilidad a la hora del diseo de programas, ya que n arma que cualquier programa razonable (por ejemplo, el de la gura 7.2) puede escribirse de forma ordenada (como en la gura 7.3). Estas ideas se desarrollarn a con ms precisin en el apartado 7.2.4. a o Denicin: Un diagrama se dice que es un diagrama BJ (diagrama de Bhm o o y Jacopini o diagrama privilegiado), si est construido a partir de los siguientes a esquemas: 1. La secuencia de dos acciones A y B, ya sean simples o compuestas:
A B

(7.1) La secuencia se suele denotar como Bloque(A,B). El equivalente en Pascal de este diagrama es la composicin de instruccioo nes. 2. La seleccin entre dos acciones A y B dependiendo de un predicado p. o Los subprogramas, como es obvio, pueden consistir en acciones simples o compuestas (obsrvese que el agrupamiento posterior es esencial). e
Cierto

p
Falso

B
(7.2)

El signicado de esta construccin es si p es cierto entonces se ejecuta A o y si no se ejecuta B.

132

Cap tulo 7. Programacion estructurada En Pascal este diagrama se corresponde con la instruccin if-then-else. o

3. La iteracin repite una accin A dependiendo del valor de verdad de un o o predicado de control p.
A Cierto

Falso

(7.3) El signicado de este tipo de iteracin es mientras que p es cierto hacer A o y se denota mediante DoWhile(p,A). En esta construccin, el predicado p acta como un control sobre la iteo u racin, esto es, si se verica p entonces se ejecuta A. o Observando el diagrama (7.3) se observa que se comprueba el valor del predicado antes de ejecutar la accin (el bucle es preprobado), con lo cual o en Pascal este diagrama de iteracin se corresponde con la instruccin o o while. Otros diagramas de uso frecuente Ya se ha comentado antes que es posible expresar todo diagrama propio usando solamente los tres esquemas anteriores (esto es consecuencia de los resultados matemticos de Bhm y Jacopini); sin embargo, con vistas a obtener una a o representacin ms agradable, se pueden considerar tambin algunos tipos adio a e cionales de esquemas que son versiones modicadas de la secuencia, la seleccin o y la repeticin. o As el esquema de la secuencia se puede generalizar para representar una , secuencia de n subprogramas A1, . . . , An:
A1 A2 ... An

Si hacemos uso de la accin vac (que se corresponde con una instruccin o a o que no hace nada) se puede considerar la siguiente variante de la seleccin: o
Cierto p Falso A

(7.4)

7.2. Aspectos teoricos

133

que se representa con la frmula IfThen(p,A), y que en Pascal se corresponde o con la instruccin if-then-else cuando se omite la rama opcional else. o Una generalizacin interesante de la estructura de seleccin consiste en una o o ramicacin en n ramas (en lugar de dos) tras evaluar una condicin. Esta o o generalizacin se representa mediante el diagrama o
a

fa fb

s . . .
n

. . .

fn

(7.5)

que se suele leer como:


seg n s valga u a hacer fa . . . n hacer fn

y se corresponde con la instruccin case en Pascal. o Este esquema permite en muchas ocasiones expresar situaciones que slo o podr especicarse usando un anidamiento mltiple de instrucciones de sean u leccin. Este hecho se hace patente en la gura 7.7. o Por otra parte, a veces resulta conveniente usar un esquema de iteracin o alternativo al preprobado, en el que el predicado no acte como condicin de u o entrada a la iteracin sino como una condicin que tiene que ser cierta para salir o o de la iteracin. Esta construccin puede expresarse as o o :
Cierto

Falso

(7.6) y se denota como DoUntil(p,A). La interpretacin de esta estructura consiste o en repetir la accin A hasta que la condicin p se haga falsa. En Pascal este o o diagrama se corresponde con los bucles repeat.

134

Cap tulo 7. Programacion estructurada

S I
fa

s = a

NO

S I
fb

s = b

NO . . .

S I
fn

s = n

NO

. . .

Figura 7.7. Expresin de CaseOf en funcin de IfThenElse. o o

Programas estructurados Denicin: Diremos que un diagrama representa a un programa estructurado o si est formado combinando los diagramas privilegiados de secuencia (7.1), sea leccin (7.2) y/o repeticin (7.3). o o Como consecuencia de lo expuesto en el apartado anterior, un diagrama representa un programa estructurado si se puede expresar haciendo uso de cualesquiera de los diagramas (7.1) . . . (7.6); por lo tanto, todo programa estructurado presenta una descomposicin arborescente en la que cada nodo se corresponde o directamente con una instruccin de Pascal o con una condicin. o o Cualquier accin (instruccin o subprograma) de un programa estructurado o o puede ser sustituida por su descomposicin arborescente y viceversa. Esta proo piedad simplica el razonamiento sobre el programa al hacerlo mucho ms legia ble, adems de facilitar su mantenimiento (lo ms probable es que slo haya que a a o realizar modicaciones en subrboles de la estructura general). a Segn la denicin, un programa estructurado P no tiene por qu estar expreu o e sado como diagrama privilegiado, sin embargo, es obvio que precisamente esta expresin es la realmente importante. Para obtener programas estructurados se o introduce la metodolog de diseo descendente de programas en la cual se hace a n bastante uso del seudocdigo; todo esto se ver en el apartado 7.3. o a

7.2. Aspectos teoricos

135

7.2.4

Equivalencia de diagramas

Tras denir lo que se entiende por programa estructurado cabe plantearse si un programa dado en forma no estructurada puede expresarse de forma equivalente mediante un programa estructurado. En este apartado se detalla el concepto de equivalencia de diagramas, que ser usado ms adelante al enunciar a a los teoremas de la programacin estructurada. o

Dos diagramas propios se dice que son equivalentes si designan los mismos clculos; esto es, si para una misma entrada de datos las l a neas de ujo llevan a bloques idnticos. e

Como ejemplo, considrense los diagramas de las guras 7.2 y 7.3: no resulta e dif comprobar que, para una misma entrada de datos, los clculos especicil a cados por cada diagrama son exactamente los mismos y, por lo tanto, ambos diagramas son equivalentes.

Los teoremas de la programacin estructurada (vase el apartado 7.2.5) aro e man que todo diagrama propio se puede expresar equivalentemente como un diagrama privilegiado. El problema es la construccin del diagrama privilegiado o equivalente.

Para realizar esta transformacin se pueden usar, entre otras, las operacioo nes de agrupamiento, inversin de predicados y desdoblamiento de bucles. A o continuacin se describirn estas operaciones y veremos algunos ejemplos que o a aclararn estos conceptos. a

Mediante el agrupamiento podemos evitar la multiplicidad de salidas de un programa, o que un bloque tenga ms de una echa de entrada. En el ejemplo de a la gura 7.4 se mostr un programa no propio porque tiene dos salidas; hacieno do uso del agrupamiento se convierte en un programa estructurado. Un caso similar se muestra en el siguiente ejemplo, en el que tambin se hace uso de un e

136

Cap tulo 7. Programacion estructurada

agrupamiento tras una seleccin. o


Cierto

f h g

p
Falso

Cierto

f h g

p
Falso

La inversin de un predicado consiste en negar la condicin de una seleccin o o o de modo que se intercambien las etiquetas de las ramas.
Cierto

A no p B

Falso

Falso

Cierto

Esta transformacin es especialmente util en casos de iteracin: a veces es neo o cesario invertir el bucle para que aqulla se realice slo cuando la condicin es e o o cierta.2 Por ejemplo, el diagrama de la izquierda se puede modicar mediante la inversin del predicado p y expresarlo como aparece a la derecha o
f Falso p Cierto f Cierto no p DoWhile(no p,f) Falso

que es un diagrama estructurado del tipo DoWhile(no p,f).


2

Vase la denicin del bloque de iteracin. e o o

7.2. Aspectos teoricos

137

Finalmente, las dos equivalencias siguientes de desdoblamiento de bucles pueden hacer ms compacto el diagrama con el que se est trabajando: a e
f

Cierto p Cierto
f

Cierto p Falso

Falso

Falso

Cierto

Cierto

Falso

Falso

7.2.5

Teoremas de la programacin estructurada o

En este apartado se enuncian los resultados ms importantes de la prograa macin estructurada y se comentan sus consecuencias. El primero de todos ellos o es el teorema de estructura, que dice que todo programa propio admite una expresin estructurada. Ms formalmente, en trminos de diagramas, se enuncia o a e as : Teorema 7.1 (de estructura) Todo diagrama propio es equivalente a un diagrama privilegiado. Puesto que todos los diagramas privilegiados admiten una expresin arboreso cente, como consecuencia de este teorema se obtiene que todo programa propio es equivalente a un programa que tiene alguna de las siguientes formas: - Bloque(A,B), - IfThenElse(p,A,B), - DoWhile(p,A), donde p es un predicado del programa original y las acciones A y B son bien instrucciones o bien (sub)programas privilegiados.

138

Cap tulo 7. Programacion estructurada

e e Teniendo en cuenta que todo diagrama propio se puede codicar mediante r r instrucciones estructuradas, del enunciado del teorema se deduce que IfThen, DoUntil, CaseOf y DoFor se pueden expresar en trminos de las construce ciones Bloque, IfThenElse y DoWhile.

El segundo teorema de la programacin estructurada es el teorema de coo rreccin (o validacin). o o Teorema 7.2 (de correccin) La correccin de un programa estructurado se o o puede estudiar mediante pasos sucesivos, examinando cada esquema (nodo) de su estructura arborescente y validando localmente la descomposicin realizada en o ese nodo. La importancia de este teorema reside en que permite, al menos tericameno te, validar (o comprobar la correccin de) un programa a la vez que ste se o e est construyendo. La tcnica de diseo descendente facilita la vericacin, ya a e n o que basta con validar cada uno de los renamientos realizados; esta tcnica se e muestra en el apartado 7.3.2.

7.2.6

Recapitulacin o

Una vez presentados los aspectos tericos de la programacin estructurada, o o merece la pena extraer algunas consecuencias de utilidad prctica de lo visto a hasta ahora, en especial de los diagramas privilegiados. En el primer prrafo del apartado 7.2.3 se adelantaban aproximadamente las a siguientes ideas: Los diagramas BJ representan acciones muy naturales; tanto, que se reejan en frases corrientes de cualquier lenguaje natural, como: Hacer primero esto, luego eso y luego aquello (secuencia). Si llueve ir en coche, si no caminando (seleccin). e o Mientras tenga fuerzas seguir luchando (iteracin). e o Esta naturalidad permite construir diagramas con organizacin clara y o sencilla que facilita el estudio de la correccin de los programas. o Los diagramas BJ son sucientemente expresivos como para, combinndose a entre s expresar cualquier programa razonable. , Por lo tanto, resulta ser altamente recomendable habituarse a desarrollar programas estructurados. Precisamente, en el siguiente apartado se comienzan a estudiar las repercusiones de la programacin estructurada en la metodolog de la programacin. o a o

7.3. Aspectos metodologicos

139

7.3

Aspectos metodolgicos o

Hasta ahora se ha estudiado la programacin estructurada, pero apenas se o han incluido las implicaciones de esta teor en el proceso de programacin. En a o este apartado comienza la exposicin de la tcnica de diseo descendente, que o e n permite aplicar la teor introducida para construir programas estructurados. a La tcnica de diseo descendente (en ingls, top-down) est basada en un e n e a proceso de aproximacin sucesiva a la solucin del problema planteado. El apeo o lativo de diseo descendente surge del hecho de que se parte de una especicacin n o abstracta del problema por resolver para, mediante renamientos sucesivos, ir descendiendo hasta cubrir todos los detalles y describir el programa en un lenguaje de programacin. o Tambin existe la tcnica contraria, llamada diseo ascendente (en ingls, e e n e bottom-up), que parte de soluciones concretas disponibles para diferentes partes del problema y las integra para generar la solucin total. o En los cap tulos anteriores ya se ha usado el seudocdigo en varias ocasiones; o este apartado comienza con un repaso de sus cualidades y su papel dentro del diseo descendente de programas. n

7.3.1

Seudocdigo o

El seudocdigo es un lenguaje intermedio que sirve de puente entre un leno guaje natural (como el espaol, por ejemplo) y ciertos lenguajes de programacin n o (como Pascal). Es util como primera aproximacin, y es muy apropiado para o describir renamientos progresivos de un programa, permitiendo al programador prescindir de los detalles y limitaciones que tienen los lenguajes espec cos y as poder concentrarse slo en la lgica del programa. o o Puesto que el seudocdigo no es un lenguaje formal, existe una gran libertad o en el uso de recursos, lo cual permite muchas veces dejar sin detallar algunos fragmentos de programa. Esta caracter stica hace que el seudocdigo facilite el o abordar un problema mediante el diseo descendente (vase el apartado 7.3.2). n e A continuacin se describe un fragmento de seudocdigo con la potencia o o expresiva suciente para expresar las instrucciones estructuradas principales: la secuencia, la seleccin y la iteracin, con sus principales variantes. o o La secuencia de bloques de programa se denota mediante una lista de acciones consecutivas. Por ejemplo, la secuencia de tareas que hay que seguir para realizar una llamada desde un telfono pblico se puede expresar en seudocdigo del e u o modo siguiente:
Buscar una cabina libre

140
Insertar monedas Marcar el nmero deseado u

Cap tulo 7. Programacion estructurada

Para expresar una seleccin del tipo IfThenElse(p,A,B) o IfThen(p,A) se o usar su equivalente en espaol. En el ejemplo anterior pudiera ocurrir que a n no recordemos el nmero al que deseamos marcar, en ese caso se presenta una u seleccin: si se recuerda el nmero se marca, y si no se pide informacin. o u o
Buscar una cabina libre Insertar monedas si se recuerda el nmero entonces u Marcar el nmero deseado u si no Pedir informacin y marcar o

En este caso, la tarea de pedir informacin se descompone como una secueno cia: marcar el 003 y pedir el nmero deseado. Esta secuencia de tareas puede u interferir en la secuencia primitiva (la de realizar la llamada); para evitar este posible conicto se hace uso de un nivel ms de anidamiento, con su correspona diente sangrado, como se muestra a continuacin: o
Buscar una cabina libre Insertar monedas si se recuerda el nmero entonces u Marcar el nmero deseado u si no Marcar el 003 Pedir el nmero deseado u Marcar el nmero obtenido u

Las iteraciones del tipo DoUntil(p,A) se ilustran a continuacin con el o mtodo de estudio ms perfecto que existe para aprender una leccin de forma e a o autodidacta.
repetir Estudiar detenidamente la leccin o Intentar todos los ejercicios hasta que las tcnicas se dominen perfectamente e

El seudocdigo relativo a las iteraciones del tipo DoWhile(p,f) se muestra a o continuacin; en este caso se presenta un mtodo alternativo al ejemplo anterior. o e
mientras no se dominen las tcnicas hacer e Estudiar detenidamente la leccin o Intentar todos los ejercicios

7.3. Aspectos metodologicos

141

Obsrvese el distinto matiz de cada ejemplo. Este matiz reeja la distincin, e o resaltada anteriormente, entre los bucles DoWhile y DoUntil: para el estudio autodidacta es necesario estudiar al menos una vez la leccin, mientras que si se o atiende en clase3 este paso puede no ser necesario.

7.3.2

Dise o descendente n

La tcnica de programacin de diseo descendente que se comenta en este e o n apartado est basada en el empleo de renamientos sucesivos para la obtencin a o de programas que resuelvan un cierto problema. En cierto modo, la tcnica de renamiento progresivo se puede comparar con e la actitud de un escultor ante un bloque de mrmol con el objetivo de obtener un a desnudo humano: para empezar, usando el cincel y el martillo grandes, proceder a a esbozar sin mucho miramiento una gura humanoide con cabeza, tronco y extremidades; posteriormente, y ya con utiles de precisin, comenzar la labor o a de renamiento y la obtencin de detalles espec o cos. En el diseo descendente, se parte de una especicacin en lenguaje natural n o del problema que se quiere resolver y, sucesivamente, se va depurando poco a poco, perlndose mejor las distintas partes del programa, apareciendo unos a detalles acabados y otros a medio camino. El proceso de renamiento contina u hasta que nalmente se obtiene una versin en la que el nivel de detalle de los o objetos y las acciones descritos puede expresarse de forma comprensible por el computador. A continuacin se muestran algunos ejemplos de renamiento progresivo: o Un ejemplo numrico: tablas de multiplicar e Apliquemos la tcnica de diseo descendente al problema de escribir una e n tabla de multiplicar de tamao n n. Por ejemplo, para n = 10 tendr n amos 1 2 3 . . . 2 4 6 . . . 3 6 9 . . . .. . 10 20 30 . . .

10 20 30 100 Una primera versin (burda) del programa podr ser o a


Principio
3

Leer n

Construir Tabla nxn

fin

. . . y se dispone de un buen profesor. . .

142

Cap tulo 7. Programacion estructurada

donde el programa se plantea como la secuencia de dos acciones: la primera consiste en conocer el tamao de la tabla deseada, Leer n, y la segunda en n construir tal tabla. La primera de las acciones est sucientemente renada a (se puede traducir directamente a un lenguaje de programacin) pero no as la o segunda. La construccin de la tabla se puede realizar fcilmente escribiendo en una o a la los mltiplos de 1, en la la inferior los mltiplos de 2. . . hasta que lleguemos u u a los mltiplos de n. Esta es la idea subyacente al siguiente renamiento de la u funcin que construye la tabla de tamao n n o n
ii+1

i1

in
NO

S I

Escribir Fila i

donde aparece la accin Escribir Fila i, que escribe cada una de las las de la o tabla (no se debe olvidar aadir un salto de l n nea detrs del ultimo nmero de a u la la). Esta funcin aparece especicada con mayor detalle a continuacin o o
jj+1

j1

jn
NO

S I

Escribir i*j

Saltar L nea

en esta funcin todos los bloques aparecen completamente renados, con lo cual o se habr terminado. a El desarrollo descendente de programas no suele hacerse mediante diagramas como hemos hecho en este ejemplo, aunque se ha presentado as para ilustrar de forma grca los conceptos estudiados en este cap a tulo. En la prctica, lo a que se hace es renar progresivamente usando seudocdigo, de modo que al o llegar al ultimo renamiento la traduccin a un lenguaje de programacin sea o o prcticamente inmediata. a La primera aproximacin en seudocdigo al programa que calcula la tabla de o o n n podr ser la siguiente: a

7.3. Aspectos metodologicos


Leer n Construir la tabla n

143

A continuacin se muestra el primer renamiento, en el que se especica o la funcin que construye la tabla como la aplicacin sucesiva de la accin que o o o construye cada una de las las de la tabla:
Leer n {Construir la tabla:} para i1 hasta n hacer Escribir la l nea i-sima e

El paso siguiente consiste en depurar la especicacin de la funcin que conso o truye las las.
Leer n para i1 hasta n hacer {Escribir la lnea i-sima:} e para j1 hasta n hacer Escribir i*j Salto de l nea

Y esta versin admite una traduccin directa a Pascal como la siguiente: o o


Program Tabla (input, output); var n, i, j: integer; begin {Peticin de datos:} o Write(Escriba el valor de n y pulse intro: ); ReadLn(n); {Construccin de la tabla:} o for i:= 1 to n do begin {Escribir la lnea i-sima:} e for j:= 1 to n do Write(i * j:6); {Elemento i-j-simo} e WriteLn {Salto de lnea} end {for i:= 1} end. {Tabla}

Es de resaltar que la idea del renamiento progresivo consiste simplemente en caminar desde el seudocdigo hasta, por ejemplo, el Pascal; por esta razn, o o durante el renamiento se puede escribir directamente en Pascal lo que se traduce trivialmente, por ejemplo, escribir a:= b en lugar de ab. En el siguiente ejemplo utilizaremos esta observacin. o

144

Cap tulo 7. Programacion estructurada

Otro ejemplo numrico: suma parcial de una serie e En este caso pretendemos aplicar las tcnicas de diseo descendente para e n obtener un programa que realice el clculo de a
n i=1

i+1 i!

La primera versin de este programa podr ser la siguiente: o a


Leer n Calcular la suma Escribir la suma

En este fragmento de seudocdigo simplemente se descompone la tarea eno comendada en tres subtareas: la entrada de informacin, su manipulacin y la o o salida de informacin buscada. El siguiente renamiento ha de ser realizado soo bre la manipulacin de la informacin, esto es, habr que especicar cmo se o o a o calcula la suma buscada. La suma se puede calcular mediante una variable acumulador suma y un bucle del tipo DoFor que en la iteracin i-sima calcule el trmino i-simo y lo o e e e aada a la suma parcial suma. Este renamiento aparece reejado en el siguiente n fragmento:
{Calcular la suma:} suma:= 0 para i:= 1 hasta n hacer Hallar trmino i-simo, t e e Aadir t a suma n

Slo queda por renar la accin de calcular el trmino i-simo, ya que, coo o e e nocido ste, aadirlo a suma no plantea mayores problemas. e n Conocido i, para calcular el trmino ti = i + 1 bastar con calcular (iteratie a i! vamente) el denominador y realizar la asignacin correspondiente o
{Hallar trmino i-simo:} e e Hallar denominador , denom = i! term:= (i + 1)/denom

El renamiento del clculo del denominador vuelve a ser un bucle del tipo a DoFor, que usando seudocdigo se puede escribir as o :

7.3. Aspectos metodologicos


{Hallar denominador, denom = i!} denom:= 1; para j:= 1 hasta i hacer denom:= denom * j

145

Agrupando todos los renamientos nalmente tendr amos el siguiente programa en Pascal:
Program Sumatorio (input, output); var denom, n, i, j: integer; suma, term: real; begin {Entrada de datos:} Write(Escriba el valor de n y pulse intro: ); ReadLn(n); {Clculo de la suma:} a suma:= 0; for i:= 1 to n do begin {Clculo del trmino i-simo: (i + 1)/i!} a e e denom:= 1; for j:= 1 to i do denom:= denom * j; term:= (i + 1)/denom; {Acumularlo:} suma:= suma + term end; {for i:= 1} {Salida de resultados:} WriteLn(Suma = ,suma:12:10) end. {Sumatorio}

Mejora de repeticiones innecesarias El ejemplo anterior es correcto, aunque sensiblemente mejorable. En efecto, se observa que los factoriales hallados en cada vuelta, 1!, 2!, . . . , (i 1)!, i!, . . . , n! pueden hallarse ms rpidamente simplemente actualizando el precedente. As a a pues, en vez de
{Hallar denom = i!} denom:= 1; for j:= 1 to i do denom:= denom * j

146 se puede hacer, simplemente,


{Actualizar denom = i! denom:= denom * i

Cap tulo 7. Programacion estructurada

(desde denom=(i-1)!}

con el mismo efecto e indudablemente ms rpido. En este caso se requiere a a establecer el denominador inicialmente a uno antes de entrar en el bucle. En resumen,
suma:= 0; denom:= 1; for i:= 1 to n do begin {Clculo del trmino i-simo: a e e denom:= denom * i; term:= (i + 1)/denom; {Acumular al trmino:} e suma:= suma + term end {for}

(i + 1)/i!}

Este mtodo se basa en la posibilidad de calcular cierta(s) (sub)expresin(es) e o reciclando otras anteriores, economizando as los clculos. Precisamente, el nom a bre de diferenciacin nita procede de la actualizacin de las expresiones neceo o sarias hallando slo la diferencia con respecto a las anteriores.4 o En el ejercicio 5f del cap tulo 18 se comprueba cmo esta segunda versin o o constituye una mejora importante en cuanto al tiempo de ejecucin del programa. o

7.4

Renamiento correcto de programas con instrucciones estructuradas

La idea de este apartado es aplicar la metodolog anterior para resolver, a con garant un problema mediante un programa descomponiendo el problema as, en subproblemas y renando (resolviendo) stos posteriormente. Para que el e renamiento sea correcto habr que denir con precisin el cometido de cada a o subproblema, garantizar su correccin y organizar (estructurar) bien entre s los o (sub)algoritmos respectivos. Dicho de otro modo, para lograr programas correctos mediante el mtodo e de los renamientos progresivos, debe asegurarse la correccin en cada paso del o
El esquema de mejora ejemplicado tiene su origen en la tabulacin de polinomios de forma o eciente por el mtodo de las diferencias (Briggs, s. xvi). e
4

7.4. Refinamiento correcto de programas

147

desarrollo:5 en la denicin rigurosa de los subalgoritmos (encargados de resolver o problemas parciales) y en el correcto ensamblaje (estructurado) de los mismos. Concretaremos estas ideas con un ejemplo detallado. Despus de estudiarlo, e recomendamos releer los dos prrafos anteriores. a

7.4.1

Un ejemplo detallado

Se plantea resolver el siguiente problema: dado el natural N , deseamos averiguar si es un cuadrado perfecto o no; esto es, si existe un natural k tal que k2 = N . Dejando de lado la solucin trivial, o Sqr(Round(SqRt(N))) = N operaremos buscando el nmero natural k (si existe) tal que k 2 = N , o parando u la bsqueda cuando sepamos con seguridad que no existe tal nmero k. u u Las dos soluciones que explicamos a continuacin se basan en buscar el o 2 N . Llamemos m a esa cantidad, m nimo k IN tal que k m = m {k IN tal que k 2 N } n que existe con seguridad para cualquier N IN y es unico. Entonces, se puede asegurar que Todo natural k < m verica que k 2 < N Todo natural k > m verica que k 2 > N Por lo tanto, hay dos posibilidades: Que m2 = N , en cuyo caso N s es un cuadrado perfecto. Que m2 > N , en cuyo caso N no es un cuadrado perfecto. En otros trminos, tenemos en un nivel de renamiento intermedio: e
var n, {Dato} m : integer; {Nmero buscado} u ... ReadLn(n); {Sup. n>=0} Buscar el nmero m descrito u
5

En vez de razonar (vericar), a posteriori, la correccin de un programa ya desarrollado. o

148
{m = mn k if Sqr(m) = n WriteLn(n, else WriteLn(n,

Cap tulo 7. Programacion estructurada


IN tal que k 2 n} then s es cuadrado perfecto) no es cuadrado perfecto)

La garant de que el mensaje emitido es correcto se tiene porque: a La rama then se ejecuta cuando resulta cierta la condicin (m2 = N ), lo o que basta para que N sea un cuadrado perfecto. La rama else se ejecuta cuando la condicin m2 = N es falsa. Como, o adems, a m = m {k IN tal que k 2 N } n es seguro que ningn natural k elevado al cuadrado da N . Por consiguiente, u N no es un cuadrado perfecto. El desarrollo de este procedimiento nos lleva a renar la accin o Buscar el nmero m descrito u de manera tal que, a su trmino, se verique la (post)condicin e o m = mn k IN tal que k2 N La bsqueda de m descrita puede llevarse a cabo de diversas maneras. En u los subapartados siguientes desarrollamos con detalle dos posibilidades. B squeda secuencial u Una primera forma de buscar el m nimo k IN tal que k 2 N consiste en tantear esa condicin sucesivamente para los valores de i = 0, 1, . . . hasta que uno o la verique. Como el tanteo se realiza ascendentemente, el primer i encontrado que verique el test ser, evidentemente, el m a nimo m buscado. El mtodo de bsqueda descrito se puede expresar directamente en Pascal e u con un bucle: i:= 0; while Sqr(i) < N do (7.7) i:= i+1; {i = m} Es importante resaltar una propiedad (invariante) de ese bucle: ningn nau 2 N , de m; esto es, m i. Por otra parte, tural k < i verica la propiedad k

7.4. Refinamiento correcto de programas

149

como los bucles while terminan cuando su condicin de entrada (en nuestro o caso, Sqr(i) < N) es falsa, a la salida del mismo se verica que i m. Esto, junto con el invariante, nos garantiza que, a la salida del mismo el valor de i es el m nimo en esas condiciones, esto es, m. Por consiguiente, bastar con hacer a m:= i para tener en m el valor descrito. B squeda dicotmica u o Partiendo del intervalo {0, . . . , N } en que est m, se trata de reducir ese a intervalo (conservando m dentro) cuantas veces sea preciso hasta lograr que sea unitario, con lo que su unico valor nal ser m. a En otras palabras, siendo
var izda, dcha: integer;

los extremos del intervalo, y estableciendo


izda:= 0; dcha:= N

se logra que izda m dcha, y ahora se trata de hacer


while not izda = dcha do Reducir {izda, . . . , dcha}, manteniendo izda m dcha

A la salida de este bucle ser izda m dcha y izda = dcha, con lo que, a efectivamente, izda = m = dcha. Y entonces bastar con aadir m:= izda. a n Debe sealarse adems que, como el tamao del intervalo disminuye en cada n a n vuelta, la terminacin del bucle es segura. o El siguiente paso es renar la reduccin del intervalo de la forma descrita, o esto es: manteniendo m en su interior y de forma que la reduccin sea efectiva. o Siendo c el valor central entero del intervalo, c= izda + dcha 2

el mtodo6 que escogemos para reducirlo consiste en comparar c2 con N , para e ver si m est a la izquierda de c, a su derecha o es el propio c: a
Y de aqu procede el nombre de bsqueda dicotmica, comprese con el mtodo de biparticin u o a e o para aproximar una ra real de una ecuacin. z o
6

150

Cap tulo 7. Programacion estructurada


var c: integer; {el valor central} ... c:= (a + b) div 2 Segn c2 sea: u = N, hacer izda:= c; dcha:= c < N, hacer izda:= c + 1 > N, hacer dcha:= c

Veamos ahora si esta reduccin es vlida, es decir, si, sea cual sea sea el valor o a de c2 en comparacin con N , las asignaciones consecuentes mantienen m entre o izda y dcha y adems la reduccin es efectiva. a o El primer caso cumple trivialmente esas exigencias, al ser c2 = N , y adems a produce la ultima reduccin del intervalo. o En los otros dos casos, debe tenerse en cuenta que izda m dcha (invariante) y que el valor de c cumple que izda c < dcha, por ser a = b. En el segundo caso adems, al ser c2 < N , es seguro que m c + 1. Por consia guiente, tras la asignacin izda:= c + 1, se puede asegurar que izda m y o que izda dcha: {izda m dcha y izda c < dcha y c2 < N } izda:= c + 1 {izda m dcha}

El tercer caso se deja como ejercicio. Trivialmente se observa que, al ser izda c < dcha, cualquiera de los intervalos producidos por las tres ramas (c, c), (c+1, dcha) y (izda, c) es estrictamente ms pequeo que (izda, dcha), lo que asegura la terminacin del bucle while. a n o

7.4.2

Recapitulacin o

A tenor de lo expuesto en este cap tulo podr parecer que el uso renamiento a progresivo y el uso de aserciones para comprobar la correccin de un programa es o algo excesivo porque, en general, se suelen escribir ms l a neas de seudocdigo y o aserciones que de codicacin propiamente dicha. La ventaja es que se propicia o el diseo correcto de algoritmos complejos. n Por otro lado, es cierto que no todo programa se puede vericar, con lo cual de qu nos sirve todo esto? Nuestra propuesta consiste en adoptar un punto e intermedio entre la formalizacin absoluta que requiere la vericacin rigurosa, o o y la ausencia total de anlisis sobre la correccin, esto es: a o 1. Durante el proceso de aprendizaje, examinar con detalle cada constructor que se aprenda, para habituarse a desarrollar algoritmos con garant as sucientes de que son correctos.

7.5. Conclusion

151

2. En la prctica, se deben examinar aquellas fases del desarrollo que, por a ser novedosas o complicadas, no presenten todas las garant de funcionar as correctamente. En resumen, se trata de dedicar, en cada fase del desarrollo, el grado de atencin que se requiera y con el rigor necesario para convencernos de que el o algoritmo desarrollado es correcto.

7.5

Conclusin o

La programacin estructurada es una disciplina de programacin desarrollada o o sobre los siguientes principios bsicos: a 1. La percepcin de una estructura lgica en el problema. Esta estructura o o debe reejarse en las acciones y datos involucrados en el algoritmo diseado n para la solucin. o 2. La realizacin de esa estructura mediante un proceso de renamiento proo gresivo, abordando en cada momento unicamente un aspecto del problema. 3. El uso de una notacin que asista al renamiento progresivo de la estructura o requerida. Los benecios de la programacin estructurada se orientan hacia la limitacin o o de la complejidad en el diseo, en la validacin y en el mantenimiento de los n o programas. Asimismo, la metodolog de diseo descendente facilita la tarea de prograa n macin en equipo, puesto que en las distintas etapas de renamiento se pueden o emplear distintas personas para que se dediquen al renamiento de distintos bloques del programa. Esta metodolog de trabajo, llevada hasta las ultimas a consecuencias, nos dirige hacia el concepto de programacin con subprogramas, o que se estudiar en los cap a tulos siguientes.

7.6

Ejercicios

1. Construya el diagrama nal resultante para el problema de las tablas de multiplicar estudiado en este cap tulo. 2. Se llaman nmeros triangulares a los obtenidos como suma de los n primeros u nmeros naturales, esto es 1, 1 + 2, 1 + 2 + 3,. . . Use seudocdigo y la tcnica de u o e diseo descendente para desarrollar programas que: n (a) calculen el n-simo nmero triangular, e u

152

Cap tulo 7. Programacion estructurada

Cierto Falso

Figura 7.8. (b) dado un nmero natural decir si es triangular, y si no lo fuera decir entre u qu dos nmeros triangulares se encuentra. e u 3. (a) Los bucles repeat se pueden simular haciendo uso de bucles while, cmo? o (b) Por el contrario, los bucles while no pueden expresarse haciendo uso unica mente de bucles repeat. Puede encontrar una razn simple que justique o esta armacin? o (c) En cambio, while puede simularse combinando repeat con la instruccin o condicional if. Cmo? o (d) Simlese la instruccin if C then I1 else I2 mediante instrucciones for. u o 4. Una estructura de repeticin que aparece en algunos textos tiene la condicin en o o el interior del bucle. Su diagrama aparece en la gura 7.8. (a) Es propio? Justicar la respuesta. (b) Es BJ? Justicar la respuesta. (c) Se puede convertir a BJ? Hgase si es posible considerando que la salida a del bucle tiene la etiqueta Cierto (resp. Falso). 5. Considrese de nuevo el bucle (7.7) de la pgina 148 para la bsqueda secuencial: e a u (a) Por qu se ha escogido la instruccin while para codicar el bucle? e o (b) En las condiciones de entrada de ste, se puede armar que termina para e cualquier valor de N? 6. Halle una aproximacin de o (a) mediante la frmula de Wallis o 2 2 4 4 6 6 8 8 = ... 2 1 3 3 5 5 7 7 9 multiplicando los 50 primeros factores.

7.7. Referencias bibliograficas


(b) mediante la frmula de Leibnitz o 1 1 1 1 = + + ... 4 1 3 5 7 hasta incluir un trmino menor que = 104 en valor absoluto. e (c) mediante la frmula de Vieta o = 2 2 2 2+ 2 2 2+ 2 2+ 2

153

...

sabiendo que, para obtener un error menor que , debemos iterar hasta incluir un factor mayor que 1 2 . Nota: obsrvese que cada trmino se puede hallar rpidamente a partir del anterior e e a siguiendo el mtodo de la diferenciacin nita. e o 7. Halle una aproximacin de sen( ), mediante su desarrollo de Taylor o 6 x x3 x5 + ... 3! 5!

sumando los diez primeros trminos. e (Apl quese la diferenciacin nita para evitar la repeticin innecesaria de clculos.) o o a 8. En el ejemplo de bsqueda dicotmica, razonar por qu en la tercera posibilidad u o e (c2 > N ), despus de la correspondiente instruccin (dcha:= c) se tiene que e o izda m dcha. 9. Siguiendo los pasos de los ejemplos explicados, desarrolle el siguiente ejercicio: dado el entero positivo N , se trata de hallar su ra cuadrada entera, es decir, el z N . Naturalmente, no se trata de escribir la expresin o entero Trunc(SqRt(N))) sino de seguir los pasos de los ejemplos explicados. Por lo tanto, se puede desarrollar con dos tipos de bsqueda: secuencial y dicotmica. u o

7.7

Referencias bibliogrcas a

En ocasiones, la programacin estructurada ha sido considerada como programacin o o sin goto, en alusin directa al art o culo de Dijkstra [Dij68] en el que hizo la primera advertencia al mundo de la computacin sobre el peligro potencial que para la programacin o o supon el uso irreexivo de rdenes de bifurcacin incondicional del tipo goto. a o o Cerca del origen de la programacin estructurada puede ser situada la refereno cia [DDH72], que contiene tres art culos sobre la programacin estructurada, la eso tructuracin de datos y las estructuras jerrquicas de programas. El primero, que es el o a ms interesante para los contenidos de este cap a tulo, explica los diagramas de secuencia, seleccin e iteracin y desarrolla varios ejemplos con renamientos progresivos. o o

154

Cap tulo 7. Programacion estructurada

Un nmero especial de la revista [dat73] se dedic slo a la programacin estrucu o o o turada. En l podemos encontrar art e culos que resaltan distintos aspectos de la programacin estructurada, desde el diseo descendente hasta la programacin sin goto. o n o Como muestra de la importancia suscitada por aquellas fechas citamos textualmente unas palabras de McCracken [McC73]: Este nmero de Datamation es importante! u Lean atentamente cada art culo, porque se describe un movimiento que va a cambiar su futuro. Un texto reciente de programacin estructurada en Pascal es [CGL+ 94], que contiene o un acercamiento gradual a las tcnicas de programacin estructurada, con numerosos e o ejercicios y ejemplos con el compilador Turbo Pascal de Borland. A un nivel ms elevado a que el presente texto y con un gran nfasis en la correccin y vericacin de programas e o o podemos citar [AA78]. Por lo que respecta a la vericacin de programas cabe destacar el cap o tulo 4 de [Ben86] en el que, mediante un sencillo ejemplo, se muestra que la escritura del cdigo nal resulta una tarea fcil despus de una correcta denicin del problema, o a e o diseo del algoritmo y eleccin de las estructuras de datos. n o Se han escrito muchos art culos en los que se reconoce la importancia de la introduccin de los invariantes de bucle desde los primeros contactos con las instrucciones o iterativas, como ejemplo podemos destacar [Arn94, Tam92, Col88]. Para profundizar en los mtodos numricos presentados, biparticin y Newtone e o Raphson, as como disponer de un gran surtido de mtodos iterativos para resolver e problemas matemticos, se recomienda leer [DM84]. a El problema de los nmeros pedrisco se comenta en [Hay84]. En este art u culo se presenta el problema, su historia y experimentos con computador llevados a cabo. En particular, se seala que se han ensayado todos los valores de N hasta 240 (aproximadan mente un billn y cien mil millones) y en todos los casos el resultado ha sido el mismo: o nalmente se cae en el bucle 1, 4, 2, 1. A pesar de todo, an no se ha encontrado una u demostracin general de que ocurre lo mismo para cada N . o La versin dicotmica del problema propuesto acerca de la ra cuadrada entera se o o z puede leer en [Hig93].

Tema III

Subprogramas

Cap tulo 8

Procedimientos y funciones

8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 158 o Subprogramas con parmetros . . . . . . . . . . . . . . 162 a Estructura sintctica de un subprograma . . . . . . . 169 a Funcionamiento de una llamada . . . . . . . . . . . . . 170 Ambito y visibilidad de los identicadores . . . . . . . 174 Otras recomendaciones sobre el uso de parmetros . 183 a Desarrollo correcto de subprogramas . . . . . . . . . . 184 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

Para la construccin de programas de tamao medio o grande es necesario o n disponer de herramientas que permitan organizar el cdigo. Por una parte, las o tcnicas de la programacin estructurada hacen posible relacionar las acciones e o por realizar mediante constructores de secuencia, seleccin e iteracin, tal y o o como se vio en los cap tulos anteriores. Por otra parte, la programacin con o subprogramas permite al programador separar partes de cdigo con un cometido o bien determinado, los subprogramas, que pueden ser invocados desde diferentes puntos del programa principal. As se extiende el juego de instrucciones bsicas a con otras nuevas a la medida del problema que se est resolviendo. Una eleccin a o adecuada de subprogramas, entre otras ventajas, hace que los programas sean ms legibles y que su cdigo sea ms fcilmente reutilizable. De esta forma se a o a a facilita en gran medida el paso de los algoritmos a los programas, especialmente cuando se sigue un mtodo de diseo descendente. e n

158

Cap tulo 8. Procedimientos y funciones

8.1

Introduccin o

La presentacin de los principales contenidos de este cap o tulo se har en base a al ejemplo que se describe a continuacin. Supongamos que queremos escribir o un programa que pida al usuario el valor de un cierto ngulo en grados sexagesia males, calcule su tangente y escriba su valor con dos decimales. En una primera aproximacin podr o amos escribir:
Sean a, t IR Leer el valor del ngulo a (en grados) a Calcular la tangente, t, de a Escribir el valor de t con dos decimales

Este nivel de renamiento1 se puede escribir en Pascal, dejando sin denir la expresin tangente de a (dado en grados) y la accin Escribir un valor dado, o o con dos decimales:
Program CalculoTangente (input, output); {Se halla la tangente de un ngulo, dado en grados} a var a, {ngulo} a t: real; {su tangente} begin Leer el valor del ngulo a (en grados) a t:= tangente de a; Escribir el valor de t, con 2 decimales end. {CalculoTangente}

Desde el punto de vista del seudoprograma principal, tanto la lectura del a ngulo a, como la expresin tangente de a y la accin Escribir el valor de t, o o con dos decimales son abstractas: se ignora su particular modo de operar. Por otra parte, al no estar predenidas, es necesario concretarlas, usando recursos del lenguaje (predenidos o aadidos por el programador), para que puedan ser n ejecutadas. Como se puede ver, resulta util empezar utilizando acciones o expresiones abstractas, aun sin estar denidas todav En principio, basta con saber qu a. e tiene que hacer (o calcular) cada accin (o expresin) abstracta e introducir un o o nombre adecuado para ellas, por ejemplo
LeerGrados(a); t:= TanGrados(a); EscrDosDec(t)
En la presentacin de estos primeros ejemplos seguiremos un proceso de renamiento, pero o sin detallar las especicaciones, para no entorpecer la exposicin de los contenidos del cap o tulo.
1

8.1. Introduccion

159

De esta forma, se puede proceder al diseo general del algoritmo en ese nivel n posponiendo el desarrollo de cada accin abstracta. Cuando, ms tarde, se o a concreten sus detalles, el lenguaje de programacin se habr ampliado (en el o a a mbito de nuestro programa, vase el apartado 8.5) con esta accin o expresin, e o o legitimando entonces su uso. En Pascal, una accin se introduce mediante un procedimiento. Por ejemplo, o la lectura del ngulo se puede hacer as a :
procedure LeerGrados(var angulo: begin Write(ngulo en grados?: ); a ReadLn(angulo); end; {LeerGrados} real);

En Pascal, una expresin abstracta se introduce mediante una funcin. En o o nuestro ejemplo, vamos a crear una funcin IR IR, a la que llamaremos o TanGrados, que recibe un argumento real, lo pasa a radianes y devuelve el valor de la tangente, calculado a partir de las funciones predenidas Sin y Cos:2
function TanGrados(angSexa: real): real; {Dev. la tangente de angSexa, en grados} const Pi = 3.141592; var angRad: real; begin {Conversin de grados en radianes:} o angRad:= angSexa * Pi/180; {Clculo de la tangente:} a TanGrados:= Sin(angRad)/Cos(angRad) end; {TanGrados}

Ahora, la asignacin de t en el programa principal denitivo: o


t:= TanGrados(a)

es vlida. a Finalmente, en nuestro ejemplo utilizaremos otro procedimiento al que llamaremos EscrDosDec, que recibe un valor de tipo real y lo muestra con dos decimales.
En adelante emplearemos Dev. como abreviatura de Devuelve en las especicaciones de las funciones.
2

160

Cap tulo 8. Procedimientos y funciones


procedure EscrDosDec(valor: real); {Efecto: escribe valor, con dos decimales} begin WriteLn(El valor es: , valor:14:2) end; {EscrDosDec}

Una vez denido el procedimiento EscrDosDec, es posible sustituir la accin o abstracta Escribir el valor de t con dos decimales por la siguiente llamada:

EscrDosDec(t)

y, de esta forma, si ensamblamos todos estos trozos obtenemos el programa completo:

Program CalculoTangente (input, output); var a, {ngulo} a t: real; {su tangente} procedure LeerGrados(var angulo: begin Write(ngulo en grados?: ); a ReadLn(angulo); end; {LeerGrados} real);

function TanGrados(angSexa: real): real; {Dev. la tangente de angSexa, en grados} const Pi = 3.141592; var angRad: real; begin {Conversin de grados en radianes:} o angRad:= angSexa * Pi/180; {Clculo de la tangente:} a TanGrados:= Sin(angRad)/Cos(angRad) end; {TanGrados} procedure EscrDosDec(valor: real); {Efecto: escribe valor, con dos decimales} begin WriteLn(El valor es: , valor:14:2) end; {EscrDosDec}

8.1. Introduccion
begin LeerGrados(a); t:= TanGrados(a); EscrDosDec(t) end. {CalculoTangente}

161

Concretando algunas de las ideas introducidas en el ejemplo, se observa lo siguiente: Pascal proporciona mecanismos para ampliar los procedimientos y funciones predenidos (tales como WriteLn y Sin), deniendo otros nuevos (como EscrDosDec y TanGrados) a la medida de las necesidades del programador. Cada procedimiento o funcin es, en s mismo, un pequeo programa,3 o n tanto por su estructura sintctica (con encabezamiento, declaraciones y a cuerpo) como por su cometido (resolver un problema concreto con los datos recibidos y ofrecer los resultados obtenidos). En nuestro ejemplo hemos tenido que incluir como declaraciones propias (locales, vase la seccin 8.5) la constante Pi y la variable angRad. e o Existen dos puntos de consideracin de estos subprogramas: su denicin, o o donde se introducen, y su llamada, donde se utilizan. En su denicin, ambas clases de subprogramas operan sobre datos genrio e cos, sus parmetros, que tomarn valores en cada llamada, esto es, cuando a a se hace uso de los subprogramas para unos datos particulares. En nuestro ejemplo el valor de la variable a pasa a la funcin TanGrados a travs del o e parmetro angSexa, y el valor de t pasa al procedimiento EscrDosDec por a medio del parmetro valor. a Un procedimiento es un subprograma que desempea el papel de una n instruccin, mientras que una funcin es un subprograma que desempea o o n el de una expresin, puesto que calcula un valor, que se reemplaza por la o llamada a la funcin. o Este distinto cometido se reeja en su llamada: los procedimientos se usan como las dems instrucciones, a
WriteLn(...); EscrDosDec(t); a:= a + 1
3

Por eso se conocen como subprogramas, o tambin subrutinas. e

162

Cap tulo 8. Procedimientos y funciones mientras que las funciones representan un valor, por lo que tienen sentido como expresiones:
t:= TanGrados(a); WriteLn (La medida buscada es: , radio * TanGrados(a) - 1); x:= 2 * TanGrados(a)/(y - 1)

Por el contrario, no est permitido ni tiene sentido llamar a una funcin a o como un procedimiento:
WriteLn(...); TanGrados(a); a:= a + 1

ni tampoco llamar a un procedimiento como una funcin: o


x:= 4 * EscrDosDec(t)

Una vez denido un subprograma, queda incorporado al lenguaje para ese programa, siendo posible usarlo en el mismo tantas veces como sea necesario.

8.2

Subprogramas con parmetros a

Los parmetros permiten que el programa y los procedimientos y funciones a puedan comunicarse entre s intercambiando informacin. De esta forma las o instrucciones y expresiones componentes de los subprogramas se aplican sobre los datos enviados en cada llamada ofreciendo una exibilidad superior a los subprogramas sin parmetros. Al mismo tiempo, si la ejecucin de los subprogramas a o produce resultados necesarios en el punto de la llamada, los parmetros pueden a actuar como el medio de transmisin de esos resultados. o

8.2.1

Descripcin de un subprograma con parmetros o a

Veamos en primer lugar un ejemplo sencillo de un procedimiento sin parmea tros:


procedure TrazarLinea; {Efecto: traza una lnea de 10 guiones} var i: integer;

8.2. Subprogramas con parametros


begin for i:= 1 to 10 do Write (-); WriteLn end; {TrazarLinea}

163

La llamada al procedimiento ser a:


TrazarLinea

El procedimiento anterior realiza siempre una accin ja y totalmente detero minada; traza una l nea formada por diez guiones. La unica relacin existente o entre el programa y el procedimiento es la llamada. Si se quisiera trazar una l nea de 15 guiones habr que escribir un nuevo a procedimiento; en cambio, si aadimos un parmetro para determinar la longitud n a en caracteres de la l nea por trazar:
procedure TrazarLineaLong(longitud: integer); {Efecto: traza una lnea de guiones, con la longitud indicada} var i: integer; begin for i:=1 to longitud do Write(-); WriteLn end; {TrazarLineaLong}

Al efectuar la llamada hay que indicar la longitud de la l nea por trazar, por ejemplo:
TrazarLineaLong(15)

que trazar una l a nea formada por quince guiones. Otra posible llamada ser a:
largo:= 10; TrazarLineaLong(largo + 5)

que trazar una l a nea idntica a la anterior. e En resumen, mediante la inclusin de un parmetro, se ha pasado de un o a procedimiento que traza una l nea de longitud ja y determinada a otro que puede trazar una l nea de cualquier longitud aumentando la exibilidad y el grado de abstraccin del procedimiento. o

164

Cap tulo 8. Procedimientos y funciones

En Pascal, es obligatorio indicar el tipo de los parmetros que pasan como a argumentos a los subprogramas. En el caso de una funcin, se debe indicar o adems el tipo del resultado que se devuelve al programa principal o subprograma a que efectu la llamada.4 o Hemos visto que la funcin TanGrados, que es una aplicacin de IR en IR, o o recibe un argumento real y devuelve un resultado tambin real. e Veamos otro ejemplo de una funcin para calcular el factorial de un nmero o u entero positivo:
function Fac(n: integer): integer; {Dev. n!} var i, prodAcum: integer; begin prodAcum:= 1; for i:= 2 to n do prodAcum:= prodAcum * i; Fac:= prodAcum end; {Fac}

Como podemos ver, la funcin Fac tiene un argumento entero y devuelve un o resultado tambin entero. Los sucesivos productos 2 3 ... n se van almacee nando en la variable prodAcum tantas veces como indica el bucle for. Una vez terminado, el valor del factorial presente en prodAcum se asigna al nombre de la funcin reemplazando su llamada. Por ejemplo, la instruccin o o
WriteLn(Fac(4))

escribe el valor 24. No es obligado que el(los) argumento(s) de una funcin sea(n) del mismo tipo o que su resultado. El siguiente ejemplo de funcin que determina si un nmero es o u o no es primo recibe un argumento entero positivo y devuelve un valor booleano: True si el nmero es primo y False en caso contrario. u
function EsPrimo(n: integer): boolean; {Dev. True si n es primo y False en caso contrario} var divisor: integer; conDivisores: boolean;
En Pascal, el resultado de una funcin slo puede ser un objeto simple. Sin embargo, esta o o limitacin se supera fcilmente (vase el apartado 8.6.3). o a e
4

8.2. Subprogramas con parametros


begin divisor:= 2; conDivisores:= False; repeat if n mod divisor = 0 then conDivisores:= True; divisor:= divisor + 1 until conDivisores or (divisor > n - 1); EsPrimo:= not conDivisores end; {EsPrimo}

165

En su funcionamiento se supone inicialmente que el nmero es primo, ya que u an no se ha encontrado divisor alguno del mismo; a continuacin, se avanza u o desde 2, tanteando posibles divisores, hasta dar con uno (en cuyo caso la condicin conDivisores se hace cierta), o llegar al propio n, sin haber hallado o divisor alguno del mismo (en cuyo caso el nmero es primo).5 u

8.2.2

Parmetros formales y reales a

Recordando los dos aspectos de denicin y llamada que encontramos en los o subprogramas, tenemos que distinguir dos tipos de parmetros. a Cuando se dene un subprograma es necesario dar nombres a los parmetros a para poder mencionarlos. A los parmetros utilizados en la denicin de proa o cedimientos y funciones se les denomina parmetros formales. A veces se llaa man tambin cticios, pues se utilizan solamente a efectos de la denicin pero e o no con valores reales. En nuestro ejemplo de referencia, angSexa y valor son parmetros formales de la funcin TanGrados y del procedimiento EscrDosDec a o respectivamente. En cambio, a los argumentos concretos utilizados en la llamada de un subprograma se les llama parmetros reales.6 Por ejemplo, a y t son los parmetros reaa a les de la funcin TanGrados y del procedimiento EscrDosDec, respectivamente, o en las llamadas que se hacen en el ejemplo anterior.

8.2.3

Mecanismos de paso de parmetros a

Antes de entrar en materia conviene que nos jemos en los procedimientos Read y Write que vamos a aplicar a una cierta variable entera a la que llamaremos a.
5 En realidad, no es necesario comprobar todos los divisores desde 2 hasta n 1, sino que bastar con comprobar hasta la ra cuadrada de n, como puede conrmar fcilmente el lector. a z a 6 En ingls, actual parameters, lo que ha dado lugar en ocasiones a la traduccin errnea e o o parmetros actuales en castellano. a

166

Cap tulo 8. Procedimientos y funciones

Supongamos, en primer lugar, que esta variable tiene un valor que le ha sido asignado previamente en el programa, por ejemplo 10, y a continuacin esta o variable es pasada como parmetro al procedimiento Write. Este procedimiento a recibe el valor de a y lo escribe en la pantalla. La accin de Write no modica o el valor de a, que sigue siendo 10. El proceso seguido es el siguiente:
a:= 10; {a = 10} Write(a) {aparece el valor de a en la pantalla} {a = 10}

En cambio, supongamos ahora que utilizamos el procedimiento Read con la misma variable a, y que el usuario escribe por el teclado un valor distinto al que ten a, por ejemplo 20. Como consecuencia de la llamada, el valor de la variable a a es modicado, de 10 a 20. Esquemticamente tenemos que: a
a:= 10; {a = 10} Read(a) {el usuario da el valor 20 por el teclado} {a = 20}

Estas diferencias se deben a que en Pascal existen dos formas de pasar parmetros que se diferencian en la forma en que se sustituyen los parmetros a a formales por los reales al efectuarse la llamada. Estos mecanismos se conocen como: Parmetros por valor : a

En este caso, se calcula el valor de los parmetros reales y despus se a e copia su valor en los formales, por lo tanto los parmetros reales deben ser a expresiones cuyo valor pueda ser calculado. Este mecanismo se llama paso de parmetros por valor y tiene como consecuencia que, si se modican los a parmetros formales en el cuerpo del subprograma, los parmetros reales a a no se ven afectados. Dicho de otra forma, no hay transferencia de informacin desde el subproo grama al programa en el punto de su llamada. Por lo tanto, los parmetros a por valor actan slo como datos de entrada al subprograma. u o

Parmetros por referencia (o por direccin o por variable): a o

En este otro caso, se hacen coincidir en el mismo espacio de memoria los parmetros reales y los formales, luego los parmetros reales han de ser a a

8.2. Subprogramas con parametros

167

variables. Este segundo mecanismo se denomina paso de parmetros por a referencia (tambin por direccin o por variable), y tiene como consecuene o cia que toda modicacin de los parmetros formales se efecta directao a u mente sobre los parmetros reales, y esos cambios permanecen al nalizar a la llamada. Es decir, que se puede producir una transferencia de informacin desde el subprograma al programa, o dicho de otro modo, que los o parmetros por referencia no slo actan como datos de entrada, sino que a o u tambin pueden representar resultados de salida del procedimiento. e Para distinguir los parmetros pasados por valor de los pasados por variaa ble, stos ultimos van precedidos de la palabra reservada var en la dee nicin del subprograma. o Veamos las diferencias entre parmetros por valor y referencia mediante un a ejemplo consistente en un procedimiento que incrementa el valor de una variable en una unidad. En el caso de parmetros por valor, el incremento tiene efectos a unicamente dentro del procedimiento, mientras que en el caso de parmetros por a referencia los efectos se extienden tambin al programa principal. e En el paso de parmetros por valor, a
procedure EscribirSiguiente (v: integer); {Efecto: escribe en la pantalla v + 1} begin v:= v + 1; WriteLn(v) end; {EscribirSiguiente}

la siguiente secuencia de instrucciones produce la salida que se muestra a la derecha: w:= 5; WriteLn(w); EscribirSiguiente(w); WriteLn(w) 5 6 5

En este ejemplo, la variable w que hace de parmetro real tiene inicialmente a el valor 5, como puede verse en la salida. Este valor se copia en el parmetro a formal v y dentro del procedimiento v se incrementa en una unidad. Sin embargo, por tratarse de parmetros por valor, este cambio en v no tiene efecto sobre el a parmetro real w, lo que comprobamos al volver al programa principal y escribir a su valor que sigue siendo 5.

168

Cap tulo 8. Procedimientos y funciones En el paso de parmetros por referencia, a


procedure IncrementarYescribir (var v: integer); begin v:= v + 1; WriteLn(v) end; {IncrementarYescribir}

la siguiente llamada produce esta salida: w:= 5 WriteLn(w); IncrementarYescribir(w); WriteLn(w)

5 6 6

En este segundo caso, al tratarse de parmetros por referencia, el espacio en a memoria de w coincide durante la llamada con el de v; por ello, el incremento de v se efecta tambin sobre w. Al terminar el procedimiento, w tiene el valor 6. u e

8.2.4

Consistencia entre denicin y llamada o

Es imprescindible que la denicin y la llamada a un subprograma encajen: o para ello, la llamada debe efectuarse utilizando el mismo identicador denido para el subprograma, seguido entre parntesis de los parmetros, separados por e a comas. Estos argumentos reales debern coincidir con los parmetros formaa a les en nmero y ser respectivamente del mismo tipo. Como dijimos antes, los u argumentos reales correspondientes a parmetros formales por valor podrn ser a a expresiones cualesquiera (con el requisito, ya mencionado, de tener el mismo tipo):
WriteLn(n + 2)

En cambio, los argumentos correspondientes a parmetros formales por referencia a debern ser necesariamente variables, para que las modicaciones efectuadas en a el subprograma repercutan en el espacio de memoria asociado a las variables argumentos. Como contraejemplo, obsrvese la siguiente llamada imposible: e
ReadLn(n + 2)

En el caso de las funciones, el tipo del resultado devuelto debe, adems, a encajar en la llamada efectuada.

8.3. Estructura sintactica de un subprograma


Programa:
Encabezamiento de programa

169
Funcin:

Procedimiento:
Encabezamiento de procedimiento

Encabezamiento de funcin

Declaraciones

Declaraciones

Declaraciones

Cuerpo de instrucciones

Cuerpo de instrucciones

Cuerpo de instrucciones

Figura 8.1. Diagramas sintcticos generales de programa, procedimiento y funcin. a o

8.3

Estructura sintctica de un subprograma a

Como es norma en Pascal, es necesario denir cualquier componente del programa, en particular los diferentes subprogramas, antes de poder utilizarlos. La estructura de un subprograma es semejante a la del programa principal: tanto los procedimientos como las funciones constan de un encabezamiento, una parte de declaraciones y deniciones y una parte de instrucciones, como se muestra en la gura 8.1. En el encabezamiento del programa y los subprogramas, se da su identicador y la lista de sus parmetros. Recordemos que para un programa los parmetros a a representan los archivos, externos al mismo, con los que intercambia informacin o con el exterior: input como archivo de datos y output para los resultados. En el caso de los subprogramas, debe especicarse el tipo de los parmetros y su a mecanismo de paso, y para las funciones se debe incluir adems el tipo del resula tado. Las guras 8.2 y 8.3 muestran los diagramas sintcticos correspondientes a a los encabezamientos de procedimiento y funcin, respectivamente. o La parte de declaraciones y deniciones de un subprograma (vase la e gura 8.4) es idntica a la de un programa. En ella se pueden declarar y denir e constantes, variables e incluso procedimientos y funciones, propios de cada sub-

170

Cap tulo 8. Procedimientos y funciones

var procedure Identificador ( Identificador ,


;

tipo

Figura 8.2. Diagrama sintctico del encabezamiento de procedimiento. a


var function Identificador ( Identificador ,
;

tipo

tipo

Figura 8.3. Diagrama sintctico del encabezamiento de funcin. a o

programa y a los que slo desde l se tiene acceso (vase el apartado 8.5). o e e Estos objetos y los propios parmetros son elementos locales del subproa grama, se crean al producirse la llamada al subprograma y permanecen solamente mientras se ejecuta sta. De ello tratamos en el siguiente apartado. e En la parte de instrucciones se concreta la accin o expresin que desempea o o n el programa o el subprograma mediante una instruccin compuesta. Estas inso trucciones se ejecutan cuando se llama al subprograma.

8.4

Funcionamiento de una llamada

Veamos cmo se realiza la llamada a un subprograma y cmo se produce o o el paso de parmetros utilizando un ejemplo con un procedimiento para la leca tura de nmeros enteros y con la conocida funcin Fac dentro de un programa u o completo:

Program DemoParametros (input,output); var numero: integer;

8.4. Funcionamiento de una llamada


... Definicin de constantes ... Declaracin de variables ... Definicin de subprogramas ...

171

Figura 8.4. Diagrama sintctico de las declaraciones y deniciones. a procedure LeerNumPos(var n: integer); {Efecto: solicita un entero hasta obtener uno positivo} begin {2A} repeat Write(Escriba un entero positivo: ); ReadLn(n) until n >= 0 {2B} end; {LeerNumPos} function Fac(num: integer): integer; {Dev. num!} var i, prodAcum: integer; begin {4A} prodAcum:= 1; for i:= 2 to num do prodAcum:= prodAcum * i; Fac:= prodAcum {4B} end; {Fac} begin {1} LeerNumPos(numero); {num >= 0}

172

Cap tulo 8. Procedimientos y funciones


{3} WriteLn(El factorial de , numero, es , Fac(numero)) {5} end. {DemoParametros}

Al comienzo del programa slo se dispone de la variable numero que est o a indenida en el punto {1} del programa (y en su correspondiente estado de memoria). El programa llama al procedimiento LeerNumPos y le pasa por referencia el parmetro real numero. Al producirse la llamada, la ejecucin del programa a o principal queda suspendida y se pasan a ejecutar las instrucciones del procedimiento LeerNumPos. Como numero se ha pasado por referencia, en la llamada, numero y n, que es el parmetro formal de LeerNumPos, coinciden en memoria. a Dado que inicialmente numero est indenido tambin lo estar n en el estado a e a {2A}, al principio de LeerNumPos.

Una vez activado LeerNumPos, ste pide al usuario un nmero entero positivo, e u que queda asignado a n. Supongamos que el valor introducido ha sido, por ejemplo, 5. En el punto {2B}, este valor es el que queda asignado a n y a numero al coincidir ambos. Al terminar el procedimiento LeerNumPos, se reanuda la ejecucin del proo grama principal, con lo cual n desaparece de la memoria (estado {3}).

Le llega el turno a la instruccin de escritura, que hace una llamada a Fac o pasndole por valor el contenido de numero. De nuevo, al producirse la llamada, a la ejecucin del programa principal queda suspendida, hasta que Fac termine y o devuelva el resultado. La funcin dispone del parmetro formal num, que recibe el contenido de o a numero, y de dos variables propias i y prodAcum, que al comenzar la funcin o (estado {4A}) estn indenidas. a

Al terminar el bucle for, se ha acumulado en prodAcum el producto 2 * 3 * 4 * 5 sucesivamente, por lo que su valor es 120. Dicho valor, que corresponde al del factorial pedido, es asignado al nombre de la funcin (estado {4B}), quien o lo devuelve al programa principal.
ee r r El nombre de la funcin se utiliza como un almacenamiento temporal del o resultado obtenido para transferirlo al programa principal. Aunque puede ser asignado como una variable, el parecido entre ambas termina aqu El . nombre de la funcin no es una variable y no puede ser utilizado como tal o (es decir sin parmetros) a la derecha de la instruccin de asignacin. a o o

Al terminar la funcin, su valor se devuelve al programa principal, termina o la escritura y nalmente termina el programa (estado {5}).

8.4. Funcionamiento de una llamada

173

Program . . . Declaraciones y deniciones begin ... llamada al proc. P ... end.

procedure P (parmetros); a Declaraciones y deniciones begin ... instrucciones ... end;

Figura 8.5. Llamada a un subprograma.

o Con la entrada de datos 5 por ejemplo, la evolucin de la memoria en los distintos estados ser la siguiente: a
Estado 1: Estado 2A, al principio de la llamada a LeerNumPos: Estado 2B, al nal de la llamada a LeerNumPos: Estado 3, tras la llamada a LeerNumPos en el programa principal: Estado 4A, al comienzo de la llamada a Fac: Estado 4B, al comienzo de la llamada a Fac: Estado 5, al terminar el programa principal: num 5 num 5 i ? i 6 num ? num ? num 5 num 5 prodAcum ? prodAcum 120 num 5 Fac ? Fac 120 n ? n 5

En la gura 8.5 se esquematiza el orden de ejecucin de las distintas instruco ciones.


ee r r En Pascal el funcionamiento de los parmetros es el mismo tanto para proa cedimientos como para funciones. Sin embargo, el cometido de las funciones es calcular un valor, por lo que no tiene sentido que stas utilicen parmetros e a por referencia.

174

Cap tulo 8. Procedimientos y funciones

8.5

Ambito y visibilidad de los identicadores

Como sabemos, en un programa en Pascal hay que declarar los identicadores que nombran los diferentes objetos utilizados en el programa. De esta forma se declaran, entre otros, las constantes, tipos, variables y los propios identicadores de procedimientos y funciones. A su vez, dentro de un procedimiento o funcin se pueden declarar sus proo pios identicadores, de forma similar al programa principal, e incluso pueden declararse otros procedimientos o funciones que contengan asimismo sus propios identicadores, y as sucesivamente, sin ms limitaciones que las propias de la a memoria disponible. En esta seccin vamos a estudiar cmo se denomina a los diferentes identio o cadores segn el lugar en que se hayan declarado y cul es la parte del programa u a en que tienen vigencia.

8.5.1

Tipos de identicadores seg n su mbito u a

Recordemos el programa ejemplo que desarrollamos al principio del cap tulo:


Program CalculoTangente (input, output); var a, {ngulo} a t: real; {su tangente} procedure LeerGrados(var angulo: begin Write(ngulo en grados?: ); a ReadLn(angulo); end; {LeerGrados} real);

function TanGrados(angSexa: real): real; {Dev. la tangente de angSexa, en grados} const Pi = 3.141592; var angRad: real; begin {Conversin de grados en radianes:} o angRad:= angSexa * Pi/180; {Clculo de la tangente:} a TanGrados:= Sin(angRad)/Cos(angRad) end; {TanGrados}

8.5. Ambito y visibilidad de los identificadores


procedure EscrDosDec(valor: real); {Efecto: escribe valor, con dos decimales} begin WriteLn(El valor es: , valor:14:2) end; {EscrDosDec} begin LeerGrados(a); t:= TanGrados(a); EscrDosDec(t) end. {CalculoTangente}

175

Los identicadores declarados o denidos en el programa principal, como a y t, se denominan globales, y su mbito es (son visibles en) todo el programa, a incluso dentro de los subprogramas (excepto si en stos se declara una variable e con el mismo identicador, la cual ocultar a la variable global homnima, como a o se detalla en el apartado 8.5.2). Los identicadores declarados o denidos dentro de subprogramas, como Pi y angRad y el identicador de la funcin TanGrados, y sus propios parmetros o a formales, como angSexa de TanGrados y valor de EscrDosDec, se denominan locales, slo son vlidos dentro de los subprogramas a los que pertenecen y, por o a tanto, no son reconocidos fuera de ellos (es decir, quedan ocultos al resto del programa). Si dentro de un subprograma se dene otro, se dice que los parmetros locales a del subprograma superior se denominan no locales con respecto al subprograma subordinado y son visibles dentro de ambos subprogramas. Los objetos globales se crean al ejecutarse el programa y permanecen denidos hasta que ste termina. En cambio, los objetos locales se crean en el e momento de producirse la llamada al subprograma al que pertenecen y se destruyen al terminar ste. La gestin de los objetos locales suele efectuarse con e o una estructura de tipo pila (vase el cap e tulo 17 y el apartado 3.4 de [PAO94]), donde se introducen los identicadores de los objetos y el espacio necesario para almacenar sus valores cuando los haya, y de donde se extraen una vez que ste e termina. El proceso de reserva y liberacin de memoria para los objetos se o prepara de forma automtica por el compilador del lenguaje. a

8.5.2

Estructura de bloques

De las deniciones anteriores se deduce que el programa principal, los procedimientos y funciones en l declarados, y los que a su vez pudieran declararse e dentro de ellos, constituyen un conjunto de bloques anidados que determinan el a mbito de validez de los identicadores.

176

Cap tulo 8. Procedimientos y funciones

MultiplicaCifras

LeerNumPos

ProductoCifras

QuitarUltimaCifra

Figura 8.6.

Veamos un ejemplo algo ms complejo que el anterior para explicar esta a estructura de bloques. El problema que se plantea consiste en multiplicar las cifras de un nmero positivo. Para ello, se hace la siguiente descomposicin: u o
Obtener un nmero entero positivo numPosit; u Calcular el producto p de las cifras de numPosit; Mostrar p.

A su vez, Calcular el producto p de las cifras de numPosit podr descompoa nerse as :


Repetir Tomar una cifra c de numPosit; Multiplicar por c el producto acumulado de las restantes cifras hasta que no queden ms cifras a

El programa consta de un procedimiento LeerNumPos, que lee un nmero u positivo distinto de cero y de una funcin ProductoCifras que calcula el proo ducto de sus cifras. Para ello, la funcin dispone de un procedimiento loo cal QuitaUltimaCifra que, dado un nmero, elimina su ultima cifra y la deu vuelve junto con el nmero modicado. ProductoCifras llama repetidamente u a QuitaUltimaCifra obteniendo las sucesivas cifras del nmero y calculando su u producto. El nmero se va reduciendo hasta que no quedan ms cifras, con lo que u a nalizan las repeticiones. En la gura 8.6 puede verse la estructura jerrquica a del programa. Para diferenciar los distintos mbitos de visibilidad de los identicadores a hemos rodeado con l neas los distintos bloques del programa en la gura 8.7. El programa utiliza una variable global numPosit, que es pasada como para metro a los dos subprogramas, a LeerNumPos por referencia y a ProductoCifras

8.5. Ambito y visibilidad de los identificadores

177

Program MultiplicaCifras (input, output); var numPosit: integer; procedure LeerNumPos (var n: integer); {Efecto:Solicita un numero hasta obtener uno positivo} begin repeat Write(Escriba un entero mayor que cero: ); ReadLn(n) until n > 0 end; {LeerNumPos} function ProductoCifras (numero: integer): integer; var acumProd, cifrUnidades: integer; procedure QuitarUltimaCifra (var n, ultima: integer); { Efecto: Elimina la ultima cifra de numero y la almacena en ultima } begin ultima:= n mod 10; n:= n div 10 end; begin; {ProductoCifras} acumProd:= 1; repeat QuitarUltimaCifra(numero,cifrUnidades); acumProd:= acumProd * cifrUnidades until numero = 0; ProductoCifras:= acumProd end; {ProductoCifras}

begin LeerNumPos (numPosit); WriteLn (El producto de las cifras de , numPosit, vale , ProductoCifras (numPosit)) end.

Figura 8.7. El programa MultiplicaCifras.

178

Cap tulo 8. Procedimientos y funciones

por valor. El procedimiento LeerNumPos le da su valor inicial, que despus se e pasa a ProductoCifras para su procesamiento. La funcin ProductoCifras tiene dos variables locales acumProd (en la que o se acumula el producto de las cifras del nmero) y cifrUnidades (donde se anou tan los valores de dichas cifras). Estas variables se utilizan solamente dentro de ProductoCifras, no teniendo ninguna utilidad fuera de la funcin, por lo que se o han declarado como locales. Quedan ocultas al programa principal y al procedimiento LeerNumPos, pero son visibles desde el procedimiento QuitaUltimaCifra para el que son no locales. La funcin tiene tambin un parmetro por valor llamado numero, a travs o e a e del cual se recibe el dato inicial, y que acta adems como variable, que se va u a modicando al ir quitndole cifras. a El procedimiento QuitaUltimaCifra se ha denido dentro del procedimiento ProductoCifras, por lo que es local a esta funcin y tiene sentido slo dentro de o o la misma, que es donde se necesita, quedando oculto al resto del programa. No tiene variables locales, pero utiliza los parmetros por referencia n y ultima. En a el primero se recibe el nmero sobre el que operar y devuelve el nmero sin la u u ultima cifra, actuando como dato y como resultado. La cifra de las unidades se devuelve en el parmetro ultima, que representa slo este resultado. Ambos son a o locales por ser parmetros, y se pasan por referencia para enviar los resultados a a ProductoCifras. Hay que observar que el identicador n se ha utilizado como parmetro de a LeerNumPos y de QuitaUltimaCifra, sin provocar ningn conicto. u Cada uno de estos bloques trazados en el programa representa el mbito en el a cual estn denidos los identicadores del bloque. Existe un bloque exterior en a el que son reconocidos todos los identicadores predenidos de Pascal. Dentro de este bloque universal se encuentra el bloque del programa, correspondiente a los identicadores globales. Si dentro del bloque del programa se denen subprogramas, cada uno constituye un bloque local, si bien su nombre es global, lo que permite que sea llamado desde el programa. Sin embargo, si se denen subprogramas dentro de otros subprogramas, los primeros constituyen bloques locales, si bien los identicadores del bloque exterior son no locales al bloque interior, mientras que sus parmetros formales son locales y no tienen vigencia a en el bloque del programa principal. Los identicadores globales y los no locales son reconocidos en la totalidad de su bloque incluso dentro de los bloques interiores. Solamente hay una excepcin: o cuando dentro del bloque local existe un identicador con el mismo nombre. En este caso, el identicador global queda oculto por el local, y toda mencin a ese o identicador en el mbito ms interno corresponde al objeto ms local. a a a

8.5. Ambito y visibilidad de los identificadores

179

Adems, el orden en que se denen los subprogramas es relevante, ya que los a denidos en primer lugar pueden ser usados por los siguientes. Como ejemplo, vamos a modicar los identicadores de nuestro programa MultiplicaCifras de forma que coincidan sus nombres en los diferentes bloques. Llamaremos numero a numPosit del programa principal, a n de LeerNumPos y a n de QuitaUltimaCifra. El programa quedar de la siguiente forma: a Program MultiplicaCifras (input, output); var numero: integer; procedure LeerNumPos(var numero: integer); {Efecto: solicita un numero hasta obtener uno positivo} begin repeat Write(Escriba un entero mayor que cero: ); ReadLn(numero) until numero > 0 end; {LeerNumPos} function ProductoCifras(numero: integer): integer; {Dev. el producto de las cifras de numero} var acumProd, cifrUnidades: integer; procedure QuitaUltimaCifra(var numero, ultima: integer); {Efecto: elimina la ltima cifra de numero y la almacena u en ultima} begin ultima:= numero mod 10; numero:= numero div 10 end; {QuitaUltimaCifra} begin acumProd:= 1; repeat QuitaUltimaCifra(numero, cifrUnidades); acumProd:= acumProd * cifrUnidades until numero = 0; ProductoCifras:= acumProd end; {ProductoCifras}

180

Cap tulo 8. Procedimientos y funciones

begin LeerNumPos(numero); WriteLn (El producto de las cifras de , numero, vale , ProductoCifras(numero)) end. {MultiplicaCifras} Si nos situamos dentro de LeerNumPos, la variable global numero, que en principio est denida en todo el programa, no es accesible, porque es ocultada a por el parmetro formal numero de LeerNumPos que es local. a Por el mismo motivo, si nos situamos en QuitaUltimaCifra, el parmetro a formal numero de ProductoCifras, que en principio estar denido dentro de a QuitaUltimaCifra por ser no local a dicho procedimiento, no es accesible, al ser ocultado por su parmetro formal numero, que es local. a A veces se diferencia entre los bloques en que un identicador podr ser a vlido si no hubiera otros identicadores con el mismo nombre, lo que se conoce a como alcance del identicador, de los bloques en que verdaderamente el identicador es accesible, al existir otros con el mismo nombre, lo que se denomina visibilidad del identicador. En el ejemplo anterior, los subprogramas LeerNumPos, ProductoCifras y QuitaUltimaCifra estn dentro del alcance de la variable global numero y sin a embargo no pertenecen a su visibilidad. En resumen, para saber a qu identicador nos referimos en cada caso y si e su utilizacin es correcta, enunciamos las siguientes reglas de mbito: o a 1. No se puede declarar un identicador ms de una vez en el mismo bloque, a pero s en bloques diferentes aunque uno est anidado en otro. Ambos e identicadores representan dos objetos distintos. 2. Para saber a qu objeto se reere un cierto identicador, hay que buscar e el bloque ms interior que contenga su declaracin. a o 3. Un identicador slo se puede utilizar en el bloque en que se ha declarado o y en los que estn contenidos en ste.7 a e
Es conveniente destacar que, como consecuencia inmediata de esta regla, el identicador de un subprograma es visible en su propio cuerpo de instrucciones. Por consiguiente, en cualesquiera de sus instrucciones pueden estar contenidas llamadas del subprograma a s mismo. Si esto ocurre, el subprograma se llama recursivo. Este tipo de subprogramas se estudia en profundidad en el cap tulo 10.
7

8.5. Ambito y visibilidad de los identificadores

181

8.5.3

Criterios de localidad

Los diferentes mbitos de validez de los identicadores, correctamente utia lizados, permiten alcanzar una gran independencia entre el programa principal y sus subprogramas, y entre stos y los subprogramas en ellos contenidos. De e esta forma se puede modicar un subprograma sin tener que cambiar los dems, a facilitando tanto el diseo del programa como posteriormente su depuracin y n o mantenimiento. Adems, facilitan la utilizacin de subprogramas ya creados (bia o bliotecas de subprogramas) dentro de nuevos programas, eliminando las posibles interferencias entre los objetos del programa y los de los subprogramas. Para lograr estos efectos es necesario comprender primero con claridad cul a es el mbito de los identicadores y seguir en lo posible unos sencillos criterios a de localidad. Los identicadores locales se deben utilizar para nombrar objetos utilizados dentro de un subprograma, incluyendo sus parmetros formales. Para conseguir a el mximo grado de independencia es recomendable que se cumplan las siguientes a condiciones: Principio de mxima localidad a

Todos los objetos particulares de un subprograma, necesarios para que desempee su cometido, deben ser locales al mismo. n

Principio de autonom de los subprogramas a

La comunicacin con el exterior debe realizarse exclusivamente mediante o parmetros, evitndose dentro de los subprogramas toda referencia a oba a jetos globales.

Si se cumplen ambas condiciones, en el punto de la llamada el subprograma se compara con una caja negra de paredes opacas cuyo contenido no puede verse desde fuera del mismo. Obsrvese que ambos principios estn relacionados, pues una mayor localidad e a implica una mayor ocultacin de la informacin al quedar ms objetos invisibles o o a al resto del programa. De este modo, la independencia del subprograma con respecto al programa que lo invoca es mxima. a

8.5.4

Efectos laterales

Hemos visto distintos mecanismos por los cuales un procedimiento o funcin o pueden devolver o enviar resultados al programa principal (o a otro procedimiento o funcin). En el caso de las funciones existe un mecanismo espec o co de transmisin a travs del propio nombre de la funcin, aunque limitado a tipos o e o

182

Cap tulo 8. Procedimientos y funciones

simples. Tanto para los procedimientos como para las funciones, dichos valores pueden enviarse mediante parmetros por referencia. a Una tercera v consiste en utilizar las variables globales (o las no locales), a porque dichas variables son reconocidas en cualquier lugar del bloque. En consecuencia, si dentro de un procedimiento o funcin se hace referencia a una variable o global (o no local), asignndole un nuevo valor, dicha asignacin es correcta, al a o menos desde el punto de vista sintctico. a Sin embargo, esta ultima posibilidad merma la autonom de los subprogra a mas, y es perjudicial porque puede introducir cambios en variables globales y errores dif ciles de detectar. Asimismo, resta independencia a los subprogramas, reduciendo la posibilidad de reutilizarlos en otros programas.
ee r r Si se evita sistemticamente el uso de los objetos globales en los subprograa mas, los cambios que efecta un subprograma se identican inspeccionando u la lista de parmetros por referencia. Por ello, se recomienda adquirir esta a costumbre desde el principio. Si por el contrario se suelen escribir subprogramas que emplean objetos globales, para conocer los efectos de un subprograma se tendr que repasar a cuidadosamente la totalidad del procedimiento o funcin. Por ello, esta o prctica es desaconsejable y debe evitarse siempre. a

Como norma general, debe evitarse toda alusin a las variables globales deno tro de los subprogramas. No obstante, se incluirn como parmetros cuando sea a a preciso. Es importante que la comunicacin se realice exclusivamente a travs o e de los parmetros para garantizar la independencia de los subprogramas. a A los cambios en variables globales producidos por subprogramas se les denomina efectos laterales o secundarios. Veamos un ejemplo de una funcin cuya o ejecucin modica una variable global de la que depende el propio resultado de o la funcin. o
Program ConDefectos (output); var estado: boolean; function Fea (n: integer): integer; begin if estado then Fea:= n else Fea:= 2 * n + 1; estado:= not estado end; {Fea}

8.6. Otras recomendaciones sobre el uso de parametros


begin estado:= True; WriteLn(Fea(1), , Fea(1)); WriteLn(Fea(2), , Fea(2)); WriteLn(Fea(Fea(5))) end. {ConDefectos}

183

La salida obtenida al ejecutar el programa es la siguiente: 1 3 2 5 11 Como puede apreciarse, sucesivas llamadas con los mismos parmetros dea vuelven resultados diferentes al estar ligados al valor de variables externas. Una buena costumbre (posible en Turbo Pascal) es denir las variables despus e de los subprogramas. As se evita el peligro de producir efectos laterales.

8.6
8.6.1

Otras recomendaciones sobre el uso de parmetros a


Parmetros por valor y por referencia a

Se recomienda emplear parmetros por valor siempre que sea posible (asegua rando que los argumentos no se alteran) y reservar los parmetros por referencia a para aquellos casos en que sea necesario por utilizarse como parmetros de salida. a Cuando se trabaja sobre datos estructurados grandes, como pueden ser vectores o matrices (vase el cap e tulo 12), puede estar justicado pasar dichas estructuras por referencia, aunque solamente se utilicen como parmetros de entrada, a porque de esta forma no hay que duplicar el espacio en la memoria para copiar la estructura local, sino que ambas comparten la misma posicin de memoria. o Tambin se ahorra el tiempo necesario para copiar de la estructura global a la e local. Algunos compiladores modernos disponen de mecanismos de optimizacin o que detectan los parmetros por valor no modicados en los subprogramas, evia tando el gasto innecesario de tiempo y memoria invertido en efectuar su copia. Ello evita al programador alterar el mecanismo de paso, mantenindolo por valor e (lo que reeja el comportamiento del programa, que deja intacto al argumento) y a la vez se lleva a cabo ecientemente, usando el mecanismo de referencia.

8.6.2

Parmetros por referencia y funciones a

En Pascal, tanto los procedimientos como las funciones pueden utilizar para metros por valor y por referencia. Sin embargo la utilizacin de los parmetros o a

184

Cap tulo 8. Procedimientos y funciones

por referencia no es apropiada en las funciones, porque su cometido natural consiste slo en hallar el valor que representan. o

8.6.3

Funciones con resultados m ltiples u

Ya se ha dicho que las funciones tienen en Pascal la limitacin de devolver o unicamente valores pertenecientes a tipos simples. Si queremos que un subpro grama devuelva valores mltiples, se recomienda utilizar procedimientos. Por u ejemplo, si quisiramos descomponer una cantidad de dinero en monedas de 100, e de 25, duros y pesetas:
procedure Descomponer(cantDinero: integer; var mon100, mon25, mon5, mon1: integer);

Igualmente, en aquellos casos en que, adems del valor de la funcin, interese a o hallar algn valor adicional (por ejemplo, un cdigo que indique si la funcin ha u o o podido calcularse correctamente o si se ha producido algn error), se debe usar u un procedimiento en su lugar ms en consonancia con ese cometido. a

8.7

Desarrollo correcto de subprogramas

De la misma forma que se ha expuesto para otros mecanismos del lenguaje, en esta seccin estudiamos los elementos necesarios para lograr desarrollar subo programas correctos. Hay dos aspectos de inters, la llamada es la que efecta e u un cierto encargo, y la denicin la que debe cumplimentarlo. Por eso, estos o aspectos son complementarios. El necesario acuerdo entre denicin y llamada o se garantiza por medio de la especicacin, ms o menos formalmente. o a En lo que respecta a la denicin, para asegurar la correccin de un subo o programa lo consideraremos como lo que es: un pequeo programa. As la n , tarea esencial en cuanto al estudio de su correccin consistir en considerar sus o a instrucciones componentes y garantizar que cumple con su cometido, que es el descrito en la especicacin. As por ejemplo: o ,
function Fac(n: integer): integer; {Dev. n!} var i, prodAcum: integer; begin prodAcum:= 1; {Inv.: i n y prodAcum = i!} for i:= 2 to n do prodAcum:= prodAcum * i;

8.7. Desarrollo correcto de subprogramas


{ prodAcum = n!} Fac:= prodAcum {F ac = n!} end; {Fac} ee r r

185

En el caso de que en el cdigo de nuestro subprograma aparezcan llamao das a otros subprogramas, la vericacin depender, naturalmente, de la o a correccin de stos. Nuestra tarea en este caso consistir en comprobar que o e a esas llamadas son correctas de la forma que se detallar a continuacin. En a o el caso particular de que las llamadas sean al mismo subprograma (subprogramas recursivos) habr que recurrir a tcnicas de vericacin espec a e o cas que sern explicadas en el cap a tulo 10.

Adems, para cada subprograma especicaremos su interfaz de una forma a semi-formal. Para ello explicitaremos al principio de cada subprograma una precondicin que describa lo que precisa el subprograma para una ejecucin coo o rrecta y una postcondicin que indique los efectos que producir. As en nuestro o a , ejemplo:8
function Fac(n: integer): integer; {PreC.: 0 n 7 y n MaxInt} {Devuelve n!} var i, prodAcum: integer; begin prodAcum:= 1; {Inv.: i n y prodAcum = i! } for i:= 2 to n do prodAcum:= prodAcum * i; {prodAcum = n!} Fac:= prodAcum {F ac = n!} end; {Fac}

Ms precisamente consideraremos que la precondicin de un subprograma es a o una descripcin informal de los requisitos que se deben cumplir para su correcto o funcionamiento. La postcondicin es una descripcin ms o menos formal del o o a resultado o del comportamiento del subprograma. La l nea que proponemos es, pues, incluir las precondiciones y postcondiciones como parte de la documentacin del subprograma. Visto desde un punto ms o a formal, consideraremos que la especicacin del subprograma est formada por o a
8

n debe ser menor que 8 para que n! MaxInt, ya que 8! = 40320 > MaxInt.

186

Cap tulo 8. Procedimientos y funciones

el encabezamiento (con el nombre del subprograma y su correspondiente lista de parmetros), y las precondiciones y postcondiciones. a Por otra parte, es necesario vericar tambin la correccin de las llamadas a e o subprogramas. Para ello nos basaremos en que estas llamadas son slo instruccioo nes (en el caso de los procedimientos) o expresiones (en el caso de las funciones). Por lo tanto, la vericacin de la llamada se har estudiando la precondicin y o a o la postcondicin de la instruccin en la que aparece (la misma llamada en el caso o o de los procedimientos). Estas precondiciones y postcondiciones se extraern, a a su vez, de las precondiciones y postcondiciones del subprograma llamado. As , si comprobamos que las expresiones que se pasan como parmetros cumplen la a precondicin, podemos deducir que los parmetros que devuelven los resultados o a vericarn lo especicado en la postcondicin del subprograma. Las propiedaa o des heredadas por los parmetros de salida constituirn la postcondicin de la a a o llamada. Por ejemplo:
ReadLn(a); {a = a0 } f:= Fac(a); {f = a0 !} WriteLn(El factorial de ,a:4, es ,f:6)

Con este tratamiento de la correccin de subprogramas se contina en la o u l nea adoptada por los autores: buscar un compromiso entre un estudio riguroso de los programas y una visin prctica de la programacin. o a o

8.8

Ejercicios

1. Escriba una lista de los identicadores que hay en el programa MultiplicaCifras, indicando el tipo de objetos de que se trata, su mbito y, si son parmetros, su a a modo de paso. 2. Dena el subprograma EscribirFecha que, aplicado a los datos D, 18, 9 y 60, d lo siguiente: e Domingo, 18 de septiembre de 1.960 3. Dena la funcin mannana que halle el d de la semana, siguiente a uno dado, o a (a) Representando los d de la semana como los enteros {1, ..., 7}. as

(b) Representando los d de la semana como los caracteres {L, M, X, as J, V, S, D}. 4. Dena un subprograma que intercambie los valores de dos variables enteras. 5. Dena un subprograma que averige si un carcter es o no: u a

8.8. Ejercicios
(a) una letra minscula u (b) una letra mayscula u (c) una letra, haciendo uso de las funciones anteriores

187

6. Escriba funciones para calcular las expresiones de los apartados (a)-(g) del ejercicio 10 del cap tulo 3. 7. Dena el subprograma RepetirCaracter que escriba un carcter dado tantas a veces como se indique. (a) Con l, escriba un programa que dibuje las guras del ejercicio 1 del cap e tulo 5. (b) Escriba un programa que dibuje la siguiente gura, consistente en n las, donde la la j es la secuencia de 2j grupos formados cada uno por 2nj1 blancos y el mismo nmero de estrellas: u
******************************** **************** **************** ******** ******** ******** ******** **** **** **** **** **** **** **** **** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

8. Escriba un procedimiento PasaPasa que manipule dos nmeros enteros supriu miendo la ultima cifra del primero y aadindola al nal del segundo. n e Incluya ese procedimiento en un programa que invierta un nmero num (partiendo u del propio num y de otro, con valor inicial cero), (12345, 0) (1234, 5) (123, 54) (12, 543) (1, 5432) (0, 54321) a base de repetir la operacin PasaPasa cuantas veces sea preciso. o 9. Desarrolle un subprograma, procedure QuitarDivisor(var ddo: integer; dsor: integer);

que divida al dividendo (ddo) por el divisor (dsor) cuantas veces sea posible, dando la l nea de la descomposicin correspondiente de la manera usual: o dividendo | divisor Usar el procedimiento descrito en un programa que realice la descomposicin de o un nmero en sus factores primos. u 10. Escriba un subprograma que halle el mximo comn divisor de dos nmeros entea u u ros. Para ello, se pueden usar los mtodos de Nicmaco o de las diferencias (dese o crito en la gura 7.3) y el de Euclides, a base de cambiar el mayor por el resto de la divisin entera (vase el ejercicio 2 del apartado 1.6 de [PAO94]). o e (a) Qu requisitos deben exigirse a los datos para poder garantizar que los e subprogramas denidos pararn? a

188

Cap tulo 8. Procedimientos y funciones


(b) Prubelos para distintos pares de enteros y compare la eciencia de los mise mos.

11. Escriba funciones para hallar las siguientes cantidades: (a) Las cifras que tiene un entero. (b) La cifra k-sima de un entero, siendo la de las unidades la 0-sima. e e (c) La suma de las cifras de un entero.9 12. Desarrolle un programa que busque el primer nmero perfecto10 a partir de un u cierto entero dado por el usuario haciendo uso de la funcin lgica EsPerfecto, o o que a su vez se apoya en la funcin SumCifras denida en el ejercicio anterior. o 13. Desarrolle un programa que escriba todos los primos del 1 al 1000 haciendo uso de la funcin lgica EsPrimo. Esta funcin se denir como en el apartado 8.2.1. o o o a 14. Dena la funcin SerieArmonica : Z R denida as o : SerieArmonica(n) = 1 + 1 1 1 + + ... + 2 3 n

A esta cantidad se le llama ra digital. z Un nmero es perfecto si la suma de sus divisores (excluido l mismo) es igual al propio u e nmero. u
10

Cap tulo 9

Aspectos metodolgicos de la o programacin con subprogramas o

9.1 9.2 9.3 9.4 9.5 9.6 9.7

Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . o Un ejemplo de referencia . . . . . . . . . . . . . . . . . Metodolog de la programacin con subprogramas a o Estructura jerrquica de los subprogramas . . . . . a Ventajas de la programacin con subprogramas . . o Un ejemplo detallado: representacin de funciones o Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

189 190 192 199 201 203 207

En este cap tulo se exponen los aspectos metodolgicos necesarios para que o el programador aplique de una forma adecuada las tcnicas de la programacin e o con subprogramas. Puesto que stas no deben ser empleadas de forma aislae da, tambin se explica cmo combinarlas con otras tcnicas presentadas con e o e anterioridad, como la programacin estructurada o el renamiento correcto de o programas. En la parte nal del cap tulo se destacan las ventajas aportadas por la correcta utilizacin de la programacin con subprogramas. o o

9.1

Introduccin o

Los primeros computadores dispon de una memoria limitada, lo que oblian gaba a dividir un programa extenso en partes ms pequeas llamadas mdulos a n o

190

Cap tulo 9. Programacion con subprogramas

que constituyeran unidades lgicas del programa. Una parte era cargada en meo moria y ejecutada, almacenndose los resultados o soluciones parciales obtenidos. a A continuacin, otra parte era cargada y ejecutada, accediendo a los resultados o parciales, y as sucesivamente hasta alcanzar la solucin nal. Esta forma de o operar, conocida como segmentacin o memoria virtual segmentada (vase el o e apartado 4.2.6 de [PAO94]), representa una primera aplicacin del concepto de o modularidad. A partir de los aos setenta, se habla de un nuevo concepto de modularidad n que no deriva de las limitaciones de memoria, sino de la creciente extensin de o los programas, lo que diculta su desarrollo, depuracin y mantenimiento. En o efecto, las necesidades crecientes de los usuarios generan programas cada vez ms extensos y por lo tanto ms dif a a ciles de comprender. Por ello, es aconsejable dividirlos en partes para resolverlos en vez de intentar hacerlo en su totalidad, de una forma monol tica. En este sentido se pronuncian diversos estudios emp ricos realizados sobre la capacidad humana para resolver problemas. Este nuevo concepto de programacin con subprogramas, que es el vigente en o nuestros d complementa las tcnicas de programacin estructurada y est reas, e o a lacionado estrechamente con las tcnicas de diseo descendente y de renamiene n tos sucesivos. En resumen, la idea esencial de la programacin con subprogramas o se puede expresar as : Una forma de resolver algor tmicamente un problema complejo consiste en descomponerlo en problemas ms sencillos, bien especicados a e independientes entre s disear por separado los subalgoritmos co, n rrespondientes a estos subproblemas y enlazarlos correctamente mediante llamadas a los mismos. La programacin con subprogramas presenta dos aspectos inseparables: la o descomposicin de un programa en partes, formadas por acciones y datos. En o este cap tulo vamos a estudiar el primero de esos aspectos: la descomposicin de o las acciones involucradas en un programa en otras ms sencillas, que en Pascal se a realiza mediante los subprogramas y su estructura de bloques. El aspecto de los datos se abordar despus de estudiar la denicin de tipos por el programador, a e o completndose entonces el concepto de programacin con subprogramas y su a o aplicacin a la construccin de tipos abstractos de datos (vase el cap o o e tulo 19).

9.2

Un ejemplo de referencia

Supongamos que tratamos de hacer un programa para sumar dos fracciones representadas cada una por su numerador y su denominador, ambos enteros. Veamos una posible descomposicin: o

9.2. Un ejemplo de referencia


Sean n1 y n2 las fracciones que deseamos sumar d1 d2 Hallar la fraccin n suma o d n , obtenindose n que es el resultado. Simplicar e d d

191

A su vez, Hallar la fraccin n suma consiste en: o d


Calcular d = d1*d2 Calcular n = n1*d2+d1*n2

y la accin Simplicar n , obtenindose n se puede renar como sigue: o e d d


Calcular mcd = mximo comn divisor de n y d a u Calcular n = n div mcd Calcular d = d div mcd

Ahora solamente faltar desarrollar Calcular mcd = mximo comn divisor a a u de n y d. Este es un clculo muy frecuente y que aparece resuelto fcilmente a a de distintas formas en la mayor de los manuales de programacin (vase el a o e ejercicio 2 del primer cap tulo de [PAO94], y tambin el ejercicio 10 del cap e tulo anterior):
Sean n, d, r IN mientras d = 0 hacer r n mod d nd dr El mximo comn divisor es n a u

Si decidimos escribir un programa en Pascal (al que podr amos llamar por ejemplo SumaDeFracciones), tenemos que tomar algunas decisiones. En primer lugar hay que observar que el algoritmo consta de dos partes claramente diferenciadas: en la primera se efecta la suma y en la segunda se simplica la fraccin. u o Este reparto de tareas se puede concretar en forma de dos procedimientos, que podr amos llamar SumarFracciones y SimplificarFraccion. El primero recibir las dos fracciones dadas (parmetros por valor) y devola a ver la fraccin suma (parmetros por referencia). El segundo actuar sobre la a o a a fraccin suma simplicndola (parmetros por referencia). o a a A su vez, el procedimiento SimplificarFraccion, y solamente l, ha de e disponer del mximo comn divisor del numerador y denominador de la fraccin, a u o por lo que es preciso denir una funcin MCD local a SimplificarFraccion. o

192

Cap tulo 9. Programacion con subprogramas

SumaDeFracciones SumarFracciones SimplificarFraccion M.C.D.

Figura 9.1. Estructura de bloques de SumaDeFracciones.

Por otra parte, SumarFracciones y SimplificarFraccion constituyen bloques locales e independientes entre s El programa principal no tiene acceso al . interior de dichos bloques, sino que ha de llamarlos mediante sus nombres. En resumen, la estructura de bloques del programa ser la de la gura 9.1. a

9.3

Metodolog de la programacin con a o subprogramas

La programacin con subprogramas consiste en un conjunto de tcnicas que o e permiten y facilitan la descomposicin de un algoritmo en partes ms simples eno a lazadas entre s para su ejecucin mediante llamadas realizadas por el programa o principal o por otros subprogramas. En el ejemplo de referencia, la accin correspondiente al programa principal o SumaDeFracciones se descompone en dos ms simples: SumarFracciones y a SimplificarFraccion. El programa principal estar formado por una llamada a a cada una de estas acciones, obtenindose la solucin buscada. e o Un subprograma est formado por una agrupacin de acciones y datos, de a o las cuales una parte (a la que llamamos interfaz ) es visible fuera del mismo y permite su comunicacin con el exterior, y la otra queda oculta al resto del o programa. La interfaz est constituida por el identicador del subprograma y el a tipo de sus parmetros. Esta parte tiene que ser conocida all donde se efecta la a u llamada. En cambio, el contenido de los subprogramas es privado y permanece oculto. As en el ejemplo, el programa principal no puede acceder a la funcin , o MCD porque es un objeto local del procedimiento SimplificarFraccion. Cada subprograma debe desempear una accin espec n o ca e independiente de los dems de forma que sea posible aislar un subprograma determinado y a concentrarnos en las acciones que desempea sin preocuparnos por las posibles n interferencias con los restantes. Las acciones expresadas en los subprogramas

9.3. Metodolog de la programacion con subprogramas a

193

SumarFracciones y SimplificarFraccion son totalmente independientes entre s de manera que ambas pueden usarse y vericarse por separado. Se fa, cilita as la legibilidad del programa y la posibilidad de modicar alguna de sus partes sin preocuparnos por los efectos o la repercusin de stas sobre otros o e subprogramas. Supongamos que en vez de sumar hubiera que multiplicar fracciones; bastar entonces con sustituir SumarFraccion por un nuevo procedimiento a MultiplicarFraccion, quedando el resto del programa inalterado. Tambin se facilita el mantenimiento del programa, puesto que las modicae ciones necesarias afectarn solamente a algunos subprogramas. a En la programacin con subprogramas debe atenderse a los siguientes aspeco tos: El cometido de cada subprograma, que se reeja en la interfaz y en la especicacin. o El desarrollo del subprograma en s que es un aspecto privado, oculto al , exterior. Los objetos que surjan en este desarrollo son particulares del subprograma, por lo que deben ocultarse al exterior. Esta ocultacin de la informacin o o consiste en que los objetos y acciones particulares de los subprogramas sean inaccesibles desde el exterior. Cada subprograma pasa a constituir una caja negra en la que se introducen unos datos y de la que se extraen unos resultados pero sin poder ver lo que pasa dentro. Como consecuencia, toda la comunicacin con los subprogramas se debe o realizar unicamente a travs de los parmetros, alcanzndose entonces la e a a independencia de los subprogramas entre s La independencia es deseable . al facilitar el desarrollo del programa, su comprensin y vericacin, su o o mantenimiento y reutilizacin posterior. o En el desarrollo de subprogramas es corriente que surja la necesidad de crear nuevos subprogramas . . , dando lugar a una estructura jerrquica a derivada de la aplicacin de las tcnicas de diseo descendente. o e n

9.3.1

Dise o descendente con subprogramas n

La divisin de un algoritmo en subprogramas requiere un proceso de abstraco cin por el que se usan, como si existieran, subalgoritmos sin concretar todav o a. Debe entonces establecerse la interfaz y el cometido (especicacin) de ese subo programa, que constituye su enunciado y que ser util para su posterior cona crecin y vericacin segn las ideas dadas en el apartado 8.7. o o u

194

Cap tulo 9. Programacion con subprogramas

SumaDeFracciones

SumarFracciones

SimplificarFraccion

M.C.D.

Figura 9.2. Estructura jerrquica del programa. a

En fases sucesivas los subprogramas se van renando alcanzndose un nivel a inferior de abstraccin. Cada una de estas fases puede determinar una nueva o divisin en partes apareciendo nuevos subprogramas subordinados a los del nivel o superior. Al descender en la estructura de subprogramas disminuye el nivel de abstraccin y, al alcanzar el nivel inferior, el algoritmo queda totalmente concretado. o Durante las etapas iniciales del proceso de diseo descendente por renamienn tos sucesivos ciertas acciones quedan sin formalizar, determinadas solamente por su nombre, por los objetos sobre los que actan y por su cometido, expresado u ms o menos formalmente. En la programacin con subprogramas se nombran a o estas acciones, se establecen los parmetros necesarios para su correcto funcioa namiento y se desarrollan con detalle en los siguientes niveles de renamiento. Por este motivo, las tcnicas de la programacin con subprogramas son muy e o adecuadas para aplicar la metodolog de diseo descendente. a n Esta descomposicin de un problema en partes suele representarse grcao a mente mediante una estructura jerrquica de tipo arborescente, como la de la a gura 9.2, que muestra las relaciones y dependencias entre los subprogramas. En general, los subprogramas pertenecientes a los niveles superiores ejercen el control del ujo del programa, al efectuar llamadas a los de niveles inferiores, mientras que stos realizan las acciones y calculan las expresiones. En otras palae bras, se puede entender que los niveles superiores expresan la losof general del a algoritmo, mientras que los inferiores o subordinados se ocupan de los detalles.

9.3.2

Programa principal y subprogramas

Al desarrollar el programa principal, lo fundamental es determinar correctamente cada una de las acciones y expresiones que lo componen y cules de a

9.3. Metodolog de la programacion con subprogramas a

195

ellas se convertirn en subprogramas. A tal n se denirn con precisin las a a o especicaciones de los subprogramas (o sea, su cometido) y las condiciones que deben cumplir sus parmetros, pero no se entrar a detallar las acciones que los a a integran. En consecuencia, el programa principal expresa una solucin del problema o con un elevado nivel de abstraccin. En l se relacionan los nombres de las o e distintas acciones y expresiones abstractas simples en que se descompone el algoritmo, enlazndolas entre s mediante instrucciones estructuradas. Desde l se a e activan dichas acciones y expresiones, y a l retorna el control de la ejecucin e o del programa una vez que el subprograma llamado naliza. La descomposicin de un problema en partes ms sencillas para constituir o a el programa principal se puede hacer atendiendo a las distintas acciones necesarias para obtener la solucin del problema (descomposicin por acciones) o o o bien considerando cul es la estructura de los datos, y una vez establecida, pasar a a considerar las acciones que se aplicarn a dichos datos (descomposicin por a o datos). En nuestro ejemplo de referencia se ha realizado una descomposicin o por acciones: SumarFracciones, SimplificarFraccion y MCD porque no se ha utilizado una estructura de datos para representar las fracciones, y por ser ms a natural. En el cap tulo 19 estudiaremos la descomposicin por datos. o Cundo debe considerarse la creacin de un nuevo subprograma? Si dua o rante el desarrollo del programa principal es necesario empezar a profundizar en detalles sobre datos o instrucciones es porque en ese punto se necesita un subprograma. Por consiguiente, se dar nombre al nuevo subprograma, se denir a a su cometido y se incluir dentro del programa principal. a El programa principal depende directamente del problema por resolver, por lo tanto ser diferente para cada problema, y no es reutilizable, aunque s adaptable. a La jerarqu de la estructura del programa es, entre otros aspectos, una a jerarqu de control, por lo que los efectos de un subprograma determinado deben a afectar a sus subprogramas subordinados y en ningn caso a un subprograma u superior. Deber repasarse la estructura, subordinando aquellos subprogramas a cuyo control sea ejercido por subprogramas inferiores.

9.3.3

Documentacin de los subprogramas o

Se ha dicho que, cuando surge la necesidad de un subprograma, debe denirse con precisin su cometido, incluyendo la informacin necesaria como doo o cumentacin del subprograma. Para ello, deben tenerse en cuenta las siguientes o posibilidades: El identicador es el primer descriptor de su cometido: suelen emplearse verbos en innitivo para los procedimientos (LeerDatos, por ejemplo) y

196

Cap tulo 9. Programacion con subprogramas sustantivos para las funciones (como LetraMayuscula), salvo las booleanas, que se indican con predicados (por ejemplo EsDiaLaborable). Tambin en el encabezamiento, los parmetros deben nombrarse adecuae a damente, y su tipo y modo ya ofrecen una informacin sobre los datos y o resultados. Adems del tipo, frecuentemente los datos deben acogerse a ciertos rea quisitos (precondicin del subprograma), lo que se indicar en forma de o a comentario:
function Division (numerador, denominador: {PreC.: denominador <> 0} integer): integer;

Cuando el identicador del subprograma deje lugar a dudas sobre su cometido, se indicar con otro comentario. En el caso de las funciones, indicando a el valor que calculan:
function Division (numerador, denominador: integer): integer; {Dev. el cociente de la divisin entera entre numerador y o denominador}

ya sea informal o formalmente. Y en el caso de los procedimientos, se indicar qu efecto tienen y qu parmetros se modican cuando sea necesario: a e e a
procedure Dividir (num, den: integer; var coc, resto: integer); {Efecto: coc:= cociente entero de la divisin num/den o resto:= resto de la divisin entera num/den} o

9.3.4

Tama o de los subprogramas n

En general, el tamao depende de lo complicado que sea el problema, siendo n aconsejable descomponer un problema de complejidad considerable en subproblemas. Si los subprogramas obtenidos en una primera descomposicin son exceo sivamente complejos, pueden descomponerse a su vez en nuevos subprogramas auxiliares que son llamados por los subprogramas de los que proceden. Sin embargo, esta divisin no puede proseguir indenidamente, puesto que tambin o e aumenta el esfuerzo necesario para enlazarlas. Se debe parar la descomposicin o cuando el problema por resolver no presente especial dicultad o afronte una tarea de dif descomposicin en partes.1 cil o
Aunque es dif hablar de tamao f cil n sico, rara vez se requieren subprogramas que supere una pgina de extensin (en Pascal), si bien ste es un valor relativo que depende adems de la a o e a expresividad del lenguaje adoptado.
1

9.3. Metodolog de la programacion con subprogramas a

197

Ambito de la modificacin Lneas de cdigo afectadas


Figura 9.3.

Si la descomposicin del problema es correcta, cada subprograma se tiene que o corresponder con una cierta accin abstracta funcionalmente independiente de o las dems que puede ser desarrollada y probada por separado. Para conseguirlo a se debe analizar la estructura del programa, disminuyendo la dependencia mediante la integracin de aquellos subprogramas que utilicen espacios o estructuras o comunes de datos y fraccionando aqullos que agrupen tareas diferentes. e El tamao de los subprogramas es uno de los aspectos que ms inuyen en n a el esfuerzo requerido por las operaciones de mantenimiento de un programa. Si un programa est formado por subprogramas de tamao reducido los efectos a n de una modicacin afectarn a menos l o a neas de cdigo, aunque probablemente o aumente el nmero de subprogramas a los que stas pertenecen, como se ve en u e la gura 9.3.

9.3.5

Renamiento con subprogramas y con instrucciones estructuradas

Aplicando todo esto a nuestro ejemplo, y una vez que los distintos niveles han quedado renados, pasamos a desarrollar las acciones y expresiones abstractas que componen los subprogramas utilizando las instrucciones estructuradas, como en el clculo del mximo comn divisor segn Euclides: a a u u
function MCD(n, d: integer): integer; {PreC.: n = 0 y d = 0} {Dev. el m.c.d. de n y d}

198

Cap tulo 9. Programacion con subprogramas


var r: integer; begin while d <> 0 do begin r:= n mod d; n:= d; d:= r end; {while} MCD:= n end; {MCD}

Las tcnicas de programacin estructurada descomponen las acciones come o plejas mediante instrucciones estructuradas que controlan acciones ms sencillas a o realizan llamadas a subprogramas. En este caso, los subprogramas realizan acciones abstractas denidas mediante sus especicaciones. Recordemos, por ejemplo, el esquema de un programa controlado por men: u

repetir Mostrar men u Leer opcion en caso de que opcion sea 0: Salir 1: Entrada de datos por teclado 2: Lectura de datos de archivo 3: Listado de datos ... n: Ejecutar la opcin n-sima o e hasta opcion = 0

La instruccin estructurada Repetir. . . hasta est controlando la ejecucin de o a o las acciones Mostrar men y Leer opcin, y la instruccin de seleccion mltiple u o o u En caso de que. . . sea controla las acciones Entrada de datos por teclado, Lectura de datos de archivo, etc. correspondientes a las sucesivas opciones del programa cuyo desarrollo est todav por hacer. Estas acciones posiblemente pertenecern a a a a subprogramas con uno o ms niveles inferiores cuando sean renadas. a En consecuencia, el diseo por renamientos sucesivos genera una estructura n jerrquica de tipo arborescente en la que las llamadas a los subprogramas se cona trolan mediante instrucciones estructuradas (secuencia, seleccin y repeticin) o o y, a su vez, los distintos subprogramas se desarrollan internamente mediante instrucciones estructuradas.

9.4. Estructura jerarquica de los subprogramas

199

A B C D Secuencialidad: (B, C) (D, E) Anidamiento: A(B) A(C(D)) A(C(E))

Figura 9.4. Subordinacin de bloques. o

9.4

Estructura jerrquica de los subprogramas a

En este apartado se ofrece una visin ms terica y menos tcnica de los o a o e conceptos explicados en el apartado 8.5 del cap tulo anterior. Al objeto de poder expresar la estructura arborescente que reeja la jerarqu a entre subprogramas y las caracter sticas deseables de ocultacin de la informacin o o e independencia funcional, ciertos lenguajes de programacin (como Pascal) utio lizan una estructura de bloques que permite dividir el programa en partes con sus propias instrucciones y datos. La disposicin de los bloques se puede hacer o en forma secuencial (sin que esta secuencia tenga nada que ver con el orden de ejecucin de los bloques, que vendr dado por la disposicin de las llamadas reso a o pectivas), para los bloques situados en un mismo nivel, o en forma anidada, para representar la subordinacin de los bloques con distintos niveles de anidamiento, o como puede verse en la gura 9.4. Los lenguajes de programacin con estructura de bloques facilitan el cumplio miento de las condiciones necesarias para alcanzar un elevado nivel de ocultacin o de la informacin: o Cada bloque subordinado puede contar con sus propios objetos, llamados objetos locales, a los que los subprogramas superiores no tienen acceso. La activacin de un subprograma subordinado por la llamada de otro suo perior o de su mismo nivel es la unica forma posible para ejecutar sus instrucciones. La comunicacin entre un bloque y su subordinado puede y debe efectuarse o solamente mediante los parmetros. a

200

Cap tulo 9. Programacion con subprogramas

. . . F

. . . G

... H F

H . . .

(Subrutina)

G ...

Figura 9.5.

Los objetos propios del programa principal se llaman globales y los objetos de un bloque que tiene otro anidado son no locales con respecto a este ultimo. Desde los subprogramas subordinados de un determinado nivel se puede acceder a los objetos globales y no locales, permitiendo la utilizacin de espacios comunes o de datos, en cuyo caso disminuir la deseable independencia funcional de los a subprogramas. En general debe evitarse este tipo de acceso, aunque en ciertos casos pueda estar justicado. Supongamos, por ejemplo, que dos o ms subprogramas situados en un mismo a nivel tengan un mismo subprograma subordinado, como se muestra en la gura 9.5. En este caso, el subprograma subordinado no puede estar anidado dentro de uno de los subprogramas superiores, pues no podr ser llamado por el otro. a Tiene que estar al mismo nivel que los subprogramas que lo llaman. Algunos autores denominan subrutinas a este tipo de subprogramas con grado de entrada mayor que uno para diferenciarlos de los subprogramas. El uso de subrutinas puede justicar la vulneracin del principio de mxima localidad (vase el aparo a e tado 8.5.3). Los parmetros son objetos locales de los subprogramas a travs de los cules a e a se comunican con sus subprogramas superiores. Cuando el subprograma superior efecta una llamada a su subordinado, adems de su nombre debe incluir aquellos u a objetos cuyos valores van a ser utilizados por el subprograma subordinado. Este proceso se conoce como paso de parmetros y puede hacerse bsicamente de dos a a formas: En la primera, el subprograma recibe unicamente el valor de los objetos, por lo que no puede modicarlos. En la segunda, el subprograma recibe la direccin de los objetos, por lo o

9.5. Ventajas de la programacion con subprogramas que puede modicarlos.2

201

La primera forma es la que presenta una mayor independencia, por lo que debe utilizarse siempre que sea posible. La segunda tiene una dependencia mayor, pues el subprograma subordinado y el superior comparten el mismo espacio de datos, pero permite que el subprograma subordinado env resultados al sue perior, por lo que su uso estar justicado en dichos casos. a Cuando es necesario pasar una estructura de datos extensa desde un subprograma a otro, el paso por valor exige ms tiempo y ms espacio de almacenaa a miento que el paso por direccin, y por motivos de eciencia, se suele hacer una o excepcin a esta regla. o

9.5

Ventajas de la programacin con subprogramas o

En este apartado se van a comentar las ventajas de la programacin con subo programas, que han hecho esta metodolog imprescindible para abordar cuala quier problema no trivial. Programas extensos Las tcnicas de la programacin con subprogramas facilitan la construccin e o o de programas extensos y complejos al permitir su divisin en otros ms sencillos, o a formados por menos instrucciones y objetos, hacindolos abarcables y comprene sibles para el intelecto humano. El desarrollo del programa principal de un problema extenso no es una tarea fcil, por lo que requiere programadores con gran experiencia y capacitacin. a o Sin embargo, la creacin de los restantes subprogramas es ms sencilla, lo que o a permite la intervencin de programadores noveles. En este sentido, la prograo macin con subprogramas favorece el trabajo en grupo y permite la creacin o o de las grandes aplicaciones tan frecuentes hoy en d lo que ser una misin a, a o imposible para individuos aislados. Cdigo reutilizable o La estructura del programa principal representa la l nea lgica del algoritmo, o por lo que es diferente en cada caso. No sucede lo mismo con los restantes subprogramas, que pueden ser reutilizados en otros algoritmos distintos de aqul e en que fue diseado siempre que se requieran las mismas acciones simples. n
Estas dos formas de paso de parmetros se corresponden con el paso de parmetros por valor a a y por referencia que hemos estudiado en Pascal. (Vase el apartado 8.2.3.) e
2

202

Cap tulo 9. Programacion con subprogramas

En consecuencia, el cdigo generado aplicando los principios de la prograo macin con subprogramas es reutilizable, por lo que puede ser incorporado en o otros programas, lo que signica un importante ahorro de tiempo y trabajo. De hecho, es frecuente la creacin de bibliotecas compuestas por subprogramas espeo cializados para ciertas aplicaciones, como clculo numrico, estad a e stica, grcos, a etc. Dichas bibliotecas estn disponibles en ciertas instituciones de forma graa tuita o comercial; de ellas, se toman aquellos subprogramas que se precisen y se introducen dentro del programa. Las tcnicas de programacin con subprograe o mas facilitan la utilizacin de las bibliotecas y garantizan que no se produzcan o incompatibilidades entre los subprogramas debido, esencialmente, a su independencia. Cuando se dispone de los subprogramas ms elementales, procedentes de a bibliotecas o de otros programas creados con anterioridad, y se integran para realizar acciones ms complejas, y stas se integran a su vez para efectuar otras a e ms complejas, y as sucesivamente, hasta obtener la solucin de un problema, a o se dice que se ha seguido una metodolog de diseo ascendente (bottom-up). a n Depuracin y vericacin o o Un subprograma puede comprobarse por separado, mediante un programa de prueba que efecte la llamada al subprograma, le pase unos datos de prueba u y muestre los resultados. Una vez que se hayan comprobado separadamente los subprogramas correspondientes a una seccin del programa, pueden comprobarse o conjuntamente, y por ultimo probar el programa en su totalidad. La compro bacin de un programa dividido en subprogramas es ms fcil de realizar y por o a a su propia estructura ms exhaustiva que la de un programa monol a tico. Tambin puede utilizarse la llamada estrategia incremental de pruebas, cone sistente en codicar en primer lugar los subprogramas de los niveles superiores, utilizando subprogramas subordinados provisionales (que realicen su tarea lo ms simplicadamente posible). De esta forma se dispone de una versin prea o via del sistema funcionando continuamente durante todo el proceso de pruebas, facilitando as la intervencin del usuario en stas. o e Igualmente, el proceso de vericacin formal ser tambin ms llevadero soo a e a bre un programa dividido en partes que sobre la totalidad. Como se explic en o el apartado 8.7, la vericacin de un programa con subprogramas consistir en o a vericar cada uno de stos, as como su correcto ensamblaje (mediante llamae das). Ninguna de estas tareas ser complicada, y se simplicar notablemente a a la comprobacin de la correccin con respecto a la de un programa de una sola o o pieza. Por consiguiente, un programa construido mediante subprogramas tendr a menos errores y stos sern ms fciles de detectar y subsanar. e a a a

9.6. Un ejemplo detallado: representacion de funciones Mantenimiento

203

Por otra parte, la programacin con subprogramas sirve de gran ayuda en o el mantenimiento y modicacin de los programas, ya que si se ha respetado o la independencia funcional entre subprogramas, introducir cambios o subsanar errores tendr unos efectos nulos o m a nimos sobre el resto del programa.

9.6

Un ejemplo detallado: representacin de o funciones

Se trata de representar funciones reales de una variable real en la pantalla del computador de forma aproximada. La funcin representada es ja para el proo grama; en nuestro ejemplo, se ha tomado f (x) = sen(x), aunque puede cambiarse fcilmente aprovechando las ventajas de la programacin con subprogramas. Los a o datos solicitados por el programa determinan el fragmento del plano XY que se desea representar: [xmnima , xmxima ] [ymnima , ymxima ] a a En nuestro ejemplo representaremos el fragmento [0.5, 6.5] [0.9, 0.9] que es bastante ilustrativo acerca del comportamiento de la funcin seno. o Por otra parte, como el tamao de la pantalla es jo, la representacin se n o efecta sobre una cuadr u cula de tamao jo, formada por nmX nmY puntos, n u u que estar representado por sendas constantes del programa: a
const NumX=15; NumY=50;

Por comodidad, el eje de abscisas ser vertical y avanzar descendentemente, a a y el de ordenadas ser horizontal y avanzar hacia la derecha de la pantalla, a a como se ve en la gura 9.6. Como podemos ver se ha trazado una cabecera con los l mites de la representacin de las ordenadas (en la gura -0.90 y 0.90), el nombre de la funcin o o representada (y = sen (x) en el ejemplo) y una l nea horizontal de separacin. o Debajo, para cada l nea, se ha escrito el valor de la abscisa (0.50, 0.90, . . . ) correspondiente, una l nea vertical para representar un fragmento de eje y un asterisco para representar la posicin de la funcin. Si la funcin se sale fuera o o o de la zona de representacin, se ha escrito un s o mbolo < >, segn caiga por la o u izquierda o por la derecha, respectivamente. As pues, el programa consta de cuatro pasos:

204

Cap tulo 9. Programacion con subprogramas

-0.90 y = sen (x) 0.90 ------+--------------------------------------------------> 0.50 | * 0.90 | * 1.30 | > 1.70 | > 2.10 | * 2.50 | * 2.90 | * 3.30 | * 3.70 | * 4.10 | * 4.50 | < 4.90 | < 5.30 | * 5.70 | * 6.10 | * 6.50 | * | x V
Figura 9.6.

9.6. Un ejemplo detallado: representacion de funciones


Pedir los datos xmnima , xmxima , ymnima , ymxima a a Trazar la cabecera de la grca a Trazar las l neas sucesivas Trazar el pie de la grca a

205

La lectura de los datos es trivial:


procedure PedirDatos(var xMin, xMax, yMin, yMax: real); {Efecto: lee el fragmento del plano que se desea ver} begin Write(xMnimo, xMximo: ); a ReadLn(xMin, xMax); Write(yMnimo, yMximo:); a ReadLn(yMin, yMax) end; {PedirDatos}

La cabecera de la representacin grca debe reejar el intervalo de las ordeo a nadas elegido y escribir un eje del tamao numY : n
procedure TrazarCabecera(yMin, yMax: real); {Efecto: Traza la cabecera centrada dependiendo del tama~o de la n pantalla} begin WriteLn(yMin:9:2, {a la izquierda} y = sen (x): NumY div 2-1, {en el centro} yMax:(NumY div 2-1):2); {a la derecha} Write(------+); for i:= 1 to NumY do Write(-); WriteLn(>) end; {TrazarCabecera}

siendo NumX, NumY las constantes (globales) descritas al principio. (Los parmea tros de formato tienen por misin centrar el nombre de la funcin de manera que o o no haya que redenir este procedimiento si cambia el tamao de la pantalla.) n El trazado de cada l nea consiste en lo siguiente:
Hallar la abscisa xi Hallar la posicin (en la pantalla) de la ordenada f (xi ) o Escribir la l nea (comprobando si cae fuera de la zona)

lo que se detalla a continuacin. La abscisa xi se halla fcilmente: o a xi = xmn + i xmx xmn a , NumX i {0, . . . , NumX}

206

Cap tulo 9. Programacion con subprogramas

Para cada ordenada yi = f (xi ), su posicin (que ser un entero de {0, . . . NumY} o a cuando yi [ym, ymx ]: a [ymn , ymx ] {0, . . . , NumY} a Ello se consigue sencillamente as : posYi = Round NumY yi ymn ymx ymn a

Un valor de posYi negativo o nulo indica que la funcin se sale por la izquierda o del fragmento del plano representado, mientras que un valor mayor que NumY signica que se sale por la derecha, con lo que la l nea i-sima se traza como e 3 sigue:
procedure TrazarLinea(i: integer; xMin, xMax, yMin, yMax: real); {Efecto: se imprime la lnea i-sima} e var xi: real; {el valor de abscisa} posYi: integer; {el valor redondeado de la funcin en xi} o begin xi:= xMin + i * (xMax - xMin)/NumX; posYi:= Round(NumY * ((Sin(xi)-yMin)/(yMax-yMin))); Write(xi:5:2, | ); if posYi <= 0 then WriteLn(<) else if posYi > NumY then WriteLn(>:NumY) else {dentro de la zona} WriteLn(*:posYi) end; {TrazarLinea}

Finalmente, el pie de la grca se dibuja as a :


procedure TrazarPie; begin WriteLn( |); WriteLn( x V) end; {TrazarPie}

En resumen, el programa consta de lo siguiente:


Dadas las especiales caracter sticas grcas de este ejemplo, se indican mediante el s a mbolo los espacios en blanco en las instrucciones de escritura.
3

9.7. Ejercicios
Program ReprGrafica (input, output); const NumX = 15; NumY = 50; var xMinimo, xMaximo, yMinimo, yMaximo: i: integer; procedure procedure procedure procedure PedirDatos(...); TrazarCabecera(...); TrazarLinea(...); TrazarPie; {... {... {... {...

207

real;

descrito descrito descrito descrito

antes antes antes antes

... ... ... ...

} } } }

begin PedirDatos(xMinimo, xMaximo, yMinimo, yMaximo); TrazarCabecera(yMinimo, yMaximo); for i:= 0 to NumX do TrazarLinea (i, xMinimo, xMaximo, yMinimo, yMaximo); TrazarPie end. {ReprGrafica}

9.7

Ejercicios

1. Escriba un programa en Pascal para el ejemplo de referencia del apartado 9.2. 2. Utilice la independencia de subprogramas en el programa anterior para sustituir el clculo del mximo comn divisor mediante el mtodo de Euclides por otro a a u e que utilice las siguientes propiedades debidas a Nicmaco de Gersasa, tambin o e llamado mtodo de las diferencias: e si a>b, entonces m.c.d.(a, b) = m.c.d.(ab, b) si a<b, entonces m.c.d.(a, b) = m.c.d.(a, ba) si a=b, entonces m.c.d.(a, b) = m.c.d.(b, a) = a = b Por ejemplo, el clculo del m.c.d. de 126 y 56 seguir la siguiente evolucin: a a o (126, 56) ; (70, 56) ; (14, 56) ; (14, 42) ; (14, 28) ; (14, 14) 3. Escriba un programa que pida dos fracciones, las simplique y las sume, hallando para ello el m nimo comn mltiplo de los denominadores y simplicando nueu u vamente el resultado. Organ los subprogramas de acuerdo con el siguiente ce diagrama de la gura 9.7.4
Obsrvese que, tanto la funcin MCM como el procedimiento para Simplificar, se apoyan en e o la funcin MCD. Puesto que M CM (a, b) M CD(a, b) = a b, se tiene que o M CM (a, b) = ab M CD(a, b)
4

(Util cese el subprograma denido en el ejercicio 2 para el clculo del MCD.) a

208

Cap tulo 9. Programacion con subprogramas SumaDeFracciones


    c

LeerFrac

  C 

SumarFracc
c MCM rr r j r

SimplFrac

MCD
Figura 9.7. 4. Desarrolle una funcin Producto o Producto(a, b) = a * (a+1) * ... * (b-1) * b y, basndose en ella, escriba funciones para hallar las cantidades n! y ( n ). a k Incluya esta ultima funcin en un programa que tabule los coecientes binomiales o ( n ) de la siguiente forma: k 1 1 1 1 1 4 3 6 ... 2 3 4 1 1 1 1

hasta la l nea numLnea, dato extra del input. do 5. Dena distintas versiones de la funcin arcsen segn las siguientes descripciones: o u
x (a) arcsen(x) = arctg 1x2

(b) arcsen(x) = x +

1 x3 2 3

13 x5 24 5

135 x7 246 7

+ ...

(c) Como arcsen(a) es un cero de la funcin f (x) = sen(x) a, llegar a ste o e

por biparticin, es decir, siguiendo el teorema de Bolzano (vase el o e apartado 6.5.1) por el mtodo de la tangente (vase el apartado 6.5.2) e e

6. Cada ao, el 10% de la gente que vive en la ciudad emigra al campo huyendo de n los ruidos y la contaminacin. Cada ao, el 20% de la poblacin rural se traslada o n o a la ciudad huyendo de esa vida montona. o Desarrolle un subprograma EmigracionAnual que modique las poblaciones rural y urbana con arreglo a lo explicado.

9.7. Ejercicios

209

Desarrolle un programa que muestre la evolucin de las migraciones, paro tiendo de unas poblaciones rural y urbana iniciales de cinco y cuatro millones de habitantes respectivamente, hasta que se estabilicen esas migraciones, esto es, cuando un ao la poblacin rural (por ejemplo) no sufra variacin n o o alguna. 7. Realice una descomposicin en subprogramas y escriba el correspondiente proo grama para el Juego de Nicmaco para dos jugadores, en el que se parte de un o par de nmeros positivos, por ejemplo (124, 7), y se van restando alternativamente u por cada jugador mltiplos del nmero ms pequeo al nmero ms grande. As u u a n u a , del par inicial se puede pasar al (103, 7), restando 21 (=7*3) a 124, o incluso al (5, 7) al restar 119 (7*17). A continuacin se restar 5 de 7 obtenindose (5, 2) o a e y as sucesivamente hasta que un jugador consiga hacer cero uno de los nmeros, u ganando la partida.

Cap tulo 10

Introduccin a la recursin o o

10.1 Un ejemplo de referencia . . . . . . . . . . . . . . . . . . 212 10.2 Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . 213 a 10.3 Otros ejemplos recursivos . . . . . . . . . . . . . . . . . 216 10.4 Correccin de subprogramas recursivos o . . . . . . . . 219

10.5 Recursin mutua . . . . . . . . . . . . . . . . . . . . . . . 222 o 10.6 Recursin e iteracin . . . . . . . . . . . . . . . . . . . . 226 o o 10.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 10.8 Referencias bibliogrcas . . . . . . . . . . . . . . . . . . 228 a

En este cap tulo se estudia una tcnica de programacin que tiene su origen e o en ciertos clculos matemticos y que consiste en describir los clculos o las a a a acciones de una manera autoalusiva, esto es, resolver problemas describindolos e en trminos de ejemplares ms sencillos de s mismos. e a Esta tcnica puede entenderse como un caso particular de la programacin e o con subprogramas en la que se planteaba la resolucin de un problema en trminos o e de otros subproblemas ms sencillos. El caso que nos ocupa en este cap a tulo es aquel en el que al menos uno de los subproblemas es una instancia del problema original.

212

Cap tulo 10. Introduccion a la recursion

10.1

Un ejemplo de referencia

Consideremos el clculo del factorial de un entero positivo n que se dene de a la siguiente forma: n! = n (n 1) (n 2) ... 1 Como, a su vez, (n 1)! = (n 1) (n 2)... 1 tenemos que n! se puede denir en trminos de (n 1)!, para n > 0, as e : n! = n (n 1)! siendo por denicin 0! = 1, lo que permite terminar correctamente los clculos. o a Por ejemplo, al calcular el factorial de 3: 3! = 3 2! = 3 2 1! = 3 2 1 0! = 3 2 1 1 = 6 Por lo tanto, si n es distinto de cero tendremos que calcular el factorial de n 1, y si es cero el factorial es directamente 1: n! =
1

si n = 0 si n 1

n (n 1)!

Observamos en este ejemplo que en la denicin de factorial interviene el o propio factorial. Este tipo de deniciones en las que interviene lo denido se llaman recursivas. La denicin anterior se escribe en Pascal directamente como sigue:1 o
function Fac(num: integer): integer; {PreC.: num 0} {Dev. num!} begin if num = 0 then Fac:= 1 else Fac:= num * Fac(num - 1) end; {Fac}
En estos primeros ejemplos obviaremos algunos detalles de correccin que sern explicados o a ms adelante. a
1

10.2. Conceptos basicos

213

La posibilidad de que la funcin Fac se llame a s misma existe, porque en o Pascal el identicador Fac es vlido dentro del bloque de la propia funcin (vase a o e el apartado 8.5). Al ejecutarlo sobre el argumento 4, se produce la cadena de llamadas sucesivas a Fac(4), Fac(3), Fac (2), Fac(1) y a Fac(0), as : Fac(4) ; 4 * Fac(3) ; 4 * (3 * Fac (2)) ; 4 * (3 * (2 * Fac(1))) ; 4 * (3 * (2 * (1 * Fac(0)))) ; ... y, como Fac(0) = 1, este valor es devuelto a la llamada anterior Fac(1) multiplicndose 1 * Fac(0), que a su vez es devuelto a Fac(2), donde se multiplica a 2 * Fac(1) y as sucesivamente, deshacindose todas las llamadas anteriores en e orden inverso: 2 ... ; 4 * (3 * (2 * (1 * 1))) ; 4 * (3 * (2 * 1)) ; 4 * (3 * 2) ; 4 * 6 ; 24

10.2

Conceptos bsicos a

En resumen, los subprogramas recursivos se caracterizan por la posibilidad de invocarse a s mismos. Debe existir al menos un valor del parmetro sobre el que se hace la recursin, a o llamado caso base, que no provoca un nuevo clculo recursivo, con lo que naliza a y puede obtenerse la solucin; en el ejemplo del factorial, es el cero. Si este valor o no existe, el clculo no termina. Los restantes se llaman casos recurrentes, y son a aqullos para los que s se produce un nuevo clculo recursivo; en el ejemplo, se e a trata de los valores positivos 1, 2, 3. . . En las sucesivas llamadas recursivas los argumentos deben aproximarse a los casos base, n n 1 ... 1 0
2 La mayor de los entornos de desarrollo (como Turbo Pascal) integran un mdulo depurador a o que permite observar los valores adoptados por los diferentes parmetros y variables que interviea nen en un programa durante su ejecucin (vase el apartado C.2.6). Esto es particularmente util o e para la comprensin y el desarrollo de subprogramas recursivos. o

214

Cap tulo 10. Introduccion a la recursion

Fac(4) . . . else Fac:=4*Fac(3) Fac 24 Fac(3) . . . else Fac:=3*Fac(2) Fac 6

Fac(2) . . . else Fac:=2*Fac(1) Fac 2

Fac(1) . . . else Fac:=1*Fac(0) Fac 1 Fac(0) . . . then Fac:= 1 Fac 1

Figura 10.1. Esquema de llamadas de Fac.

para que el proceso concluya al alcanzarse stos. De lo contrario, se produce la e llamada recursin innita. Por ejemplo, si se aplicase la denicin de factorial o o a un nmero negativo, u 3 4 5 . . . los clculos sucesivos nos alejan cada vez ms del valor cero, por lo que nunca a a dejan de generarse llamadas. El proceso de ejecucin de un subprograma recursivo consiste en una cadena o de generacin de llamadas (suspendindose los restantes clculos) y reanudacin o e a o de los mismos al trmino de la ejecucin de las llamadas, tal como se recoge en e o la gura 10.1. Para comprender mejor el funcionamiento de un subprograma recursivo, recordemos el proceso de llamada a un subprograma cualquiera: Se reserva el espacio en memoria necesario para almacenar los parmetros a y los dems objetos locales del subprograma. a Se reciben los parmetros y se cede la ejecucin de instrucciones al suba o programa, que comienza a ejecutarse. Al terminar ste, se libera el espacio reservado, los identicadores locales e dejan de tener vigencia y pasa a ejecutarse la instruccin siguiente a la de o llamada.

10.2. Conceptos basicos


Programa begin

215

... ...
llamada a subprograma

Subprograma begin

... ...
llamada a subprograma

Subprograma begin

end.

... ... ...


end.

end.

Figura 10.2. Esquema de llamadas de subprogramas.

En el caso de un subprograma recursivo, cada llamada genera un nuevo ejemplar del subprograma con sus correspondientes objetos locales. Podemos imaginar cada ejemplar como una copia del subprograma en ejecucin. En este o proceso (resumido en la gura 10.2) destacamos los siguientes detalles:

El subprograma comienza a ejecutarse normalmente y, al llegar a la llamada, se reserva espacio para una nueva copia de sus objetos locales y parmetros. Estos datos particulares de cada ejemplar generado se agrua pan en la llamada tabla de activacin del subprograma. o El nuevo ejemplar del subprograma pasa a ejecutarse sobre su tabla de activacin, que se amontona sobre las de las llamadas recursivas anteriores o formando la llamada pila recursiva (vase el apartado 17.2.3). e Este proceso termina cuando un ejemplar no genera ms llamadas recursia vas por consistir sus argumentos en casos bsicos. a Entonces, se libera el espacio reservado para la tabla de activacin de ese o ejemplar, reanudndose las instrucciones del subprograma anterior sobre a la tabla penltima. u Este proceso de retorno naliza con la llamada inicial.

216

Cap tulo 10. Introduccion a la recursion

10.3
10.3.1

Otros ejemplos recursivos


La sucesin de Fibonacci o

Un clculo con denicin recursiva es el de la sucesin de nmeros de Fia o o u 3 1, 1, 2, 3, 5, 8, 13, 21, 34, . . . (vase el ejercicio 6). Si llamamos bonacci: e bn al trmino ensimo de la secuencia de Fibonacci, la secuencia viene descrita e e recurrentemente as : b0 = 1 b1 = 1 bn = bn2 + bn1 , si n 2 La correspondiente funcin en Pascal es una transcripcin trivial de esta o o denicin: o
function Fib(num: integer): integer; {PreC.: num 0} {Dev. f ibnum } begin if (num = 0) or (num = 1) then Fib:= 1 else Fib:= Fib(num - 1) + Fib(num - 2) end; {Fib}

Los casos base son los nmeros 0 y 1, y los recurrentes los naturales siguientes: u 2, 3, . . .

10.3.2

Torres de Hanoi

En la exposicin mundial de Par de 1883 el matemtico francs E. Lucas o s a e 4 que tiene una solucin recursiva present un juego llamado Torres de Hanoi, o o relativamente sencilla y que suele exponerse como ejemplo de la potencia de la recursin para resolver ciertos problemas cuya solucin es ms compleja en forma o o a iterativa.
Descubierta por Leonardo da Pisa (1180-1250) y publicada en su Liber Abaci en 1202. Segn reza la leyenda, en la ciudad de Hanoi, a orillas del r Rojo, descansa una bandeja de u o cobre con tres agujas verticales de diamante. Al terminar la creacin, Dios ensart en la primera o o de ellas sesenta y cuatro discos de oro puro de tamaos decrecientes. Esta es la torre de Brahma. n Desde entonces, los monjes empean su sabidur en trasladar la torre hasta la tercera aguja, n a moviendo los discos de uno en uno y con la condicin de que ninguno de ellos se apoye en otro de o menor tamao. La leyenda arma que el trmino de esta tarea coincidir con el n del mundo, n e a aunque no parece que, por el momento, estn cerca de lograrlo. e
4 3

10.3. Otros ejemplos recursivos

217

Figura 10.3. Las torres de Hanoi.

El juego estaba formado por una base con tres agujas verticales, y en una de ellas se encontraban engarzados unos discos de tamao creciente formando una n torre, segn se muestra en la gura 10.3. El problema por resolver consiste en u trasladar todos los discos de una aguja a otra, movindolos de uno en uno, pero e con la condicin de que un disco nunca descanse sobre otro menor. En distintas o fases del traslado se debern usar las agujas como almacn temporal de discos. a e Llamaremos A, B y C a cada una de las agujas sin importar el orden siempre que se mantengan los nombres. Consideremos inicialmente dos discos en A que queremos pasar a B utilizando C como auxiliar. Las operaciones por realizar son sencillas:
Mover un disco de A a C Pasar dos discos de A a B = Mover un disco de A a B Mover un disco de C a B

Ahora supongamos que tenemos tres discos en A y queremos pasarlos a B. Haciendo algunos tanteos descubrimos que hay que pasar los dos discos superiores de A a C, mover el ultimo disco de A a B y por ultimo pasar los dos discos de C a B. Ya conocemos cmo pasar dos discos de A a B usando C como auxiliar, o para pasarlos de A a C usaremos B como varilla auxiliar y para pasarlos de C a B usaremos A como auxiliar:

218

Cap tulo 10. Introduccion a la recursion

En general, Pasar n discos de A a B (siendo n 1), consiste en efectuar las siguientes operaciones,
Pasar n1 discos de A a C Pasar n discos de A a B = Mover 1 disco de A a B Pasar n1 discos de C a B

Mover 1 Pasar dos de A a C = Mover 1 Mover 1 Pasar 3 discos de A a B = Mover un disco de A a B Mover 1 Pasar dos de C a B = Mover 1 Mover 1

disco de A a B disco de A a C disco de B a C

disco de C a A disco de C a B disco de A a B

siendo 1 el caso base, que consiste en mover simplemente un disco sin generar llamada recursiva. Ahora apreciamos claramente la naturaleza recursiva del proceso, pues para pasar n discos es preciso pasar n-1 discos (dos veces), para n-1 habr que pasar n-2 (tambin dos veces) y as sucesivamente. a e Podemos escribir un procedimiento para desplazar n discos directamente:
procedure PasarDiscos(n: integer; inicial, final, auxiliar: char); {PreC.: n 0} {Efecto: se pasan n discos de la aguja inicial a la final} begin if n > 0 then begin PasarDiscos (n - 1,inicial, auxiliar, final); WriteLn(mover el disco , n:3, desde , inicial, a , final); PasarDiscos (n - 1,auxiliar, final, inicial) end {if} end; {PasarDiscos}

Como ejemplo de funcionamiento, la llamada PasarDiscos(4, A, B, C) produce la siguiente salida:5 Cuntos discos: 4 a mover disco 1 desde mover disco 2 desde mover disco 1 desde mover disco 3 desde mover disco 1 desde mover disco 2 desde mover disco 1 desde
5

A A C A B B A

a a a a a a a

C B B C A C C

mover mover mover mover mover mover mover mover

disco disco disco disco disco disco disco disco

4 1 2 1 3 1 2 1

desde desde desde desde desde desde desde desde

A C C B C A A C

a a a a a a a a

B B A A B C B B

Como puede apreciarse los nmeros de los discos indican su tamao. u n

10.4. Correccion de subprogramas recursivos

219

10.3.3

Funcin de Ackermann o

Otro interesante ejemplo recursivo es la funcin de Ackermann que se dene o recurrentemente as : Ack(0, n) = n + 1 Ack(m, 0) = Ack(m 1, 1), si m > 0 Ack(m, n) = Ack(m 1, Ack(m, n 1)) si m, n > 0 La funcin correspondiente en Pascal se escribe as o :
function Ack(m, n: integer): integer; {PreC.: m, n 0} {Dev. Ack(m, n)} begin if m = 0 then Ack:= n + 1 else if n = 0 then Ack:= Ack(m - 1, 1) else Ack:= Ack(m - 1, Ack(m, n - 1)) end; {Ack}

10.4

Correccin de subprogramas recursivos o

En este apartado presentaremos los conceptos y tcnicas necesarias para la e vericacin (o derivacin) de subprogramas recursivos. o o En este sentido, la pauta viene dada por la consideracin de que un subo programa recursivo no es ms que un caso particular de subprograma en el que a aparecen llamadas a s mismo. Esta peculiaridad hace que tengamos que recurrir a alguna herramienta matemtica, de aplicacin no demasiado complicada en la a o mayor de los casos, que encontraremos en este libro. a El proceso de anlisis de la correccin de subprogramas recursivos puede ser a o dividido, a nuestro entender, en dos partes: una primera, en la que consideraremos los pasos de la vericacin comunes con los subprogramas no recursivos, y o una segunda con los pasos en los que se aplican tcnicas espec e cas de vericacin o de la recursin. o De acuerdo con esta divisin, incluiremos en primer lugar, y tal como se ha o hecho hasta ahora, las precondiciones y postcondiciones de cada subprograma que, junto con el encabezamiento, formarn su especicacin (semi-formal). Rea o cordemos que las precondiciones y postcondiciones actan como generalizaciones u de las precondiciones y postcondiciones, respectivamente, de las instrucciones simples, explicitando los requisitos y los efectos del subprograma.

220

Cap tulo 10. Introduccion a la recursion

Asimismo, la vericacin de las llamadas a subprogramas recursivos se har o a igual que en el resto de los subprogramas, estableciendo las precondiciones y postcondiciones de stas en base a las precondiciones y postcondiciones de los e subprogramas llamados. Por otra parte estudiaremos la correccin de la denicin del subprograma. o o En esta tarea lo natural es plantearse el proceso de vericacin (o correccin, o o segn el caso) habitual, es decir, especicar las precondiciones y postcondiciones u de cada una de las instrucciones implicadas, y en base a ellas y a su adecuado encadenamiento demostrar la correccin. Pero en el caso de un subprograma o recursivo nos encontramos que, para al menos una de las instrucciones (aqulla e en la que aparece la llamada recursiva), no se tiene demostrada la correccin (de o hecho es esa correccin la que intentamos demostrar). o Para salir de este ciclo recurrimos a tcnicas inductivas de demostracin. e o

10.4.1

Principios de induccin o

Informalmente, podemos decir que estos principios permiten armar una propiedad para todo elemento de un conjunto (pre)ordenado, si se dan ciertas condiciones. As si suponiendo el cumplimiento de la propiedad para los elementos , del conjunto menores que uno dado podemos demostrar la propiedad para el elemento en cuestin, armaremos que todo elemento del conjunto verica la o propiedad. La formalizacin ms simple y conocida del Principio de Induccin se hace o a o sobre el conjunto de los nmeros naturales y es la siguiente: u Si tenemos que Hiptesis de induccin: 0 cumple la propiedad P o o Paso inductivo: Para todo x > 0, si x 1 cumple la propiedad P , entonces x cumple la propiedad P Entonces Para todo y IN, y cumple la propiedad P . La relacin entre induccin y recursin queda clara: la hiptesis de induccin o o o o o se corresponde con el caso base, y el paso inductivo con el caso recurrente. Por ejemplo, este principio se puede aplicar para demostrar que el nmero u N de elementos del conjunto P(E), donde E representa un conjunto nito de n elementos y P(E) es el conjunto de las partes de E, es 2n . Como caso base tenemos que para n = 0, es decir, para E = , se tiene que P(E) = , y por tanto N = 1 = 20 .

10.4. Correccion de subprogramas recursivos

221

Supongamos ahora que para n 1, es decir, para E = {x1 , x2 , . . . , xn1 } se cumple que N = 2n1 y veamos si se puede demostrar que para n tambin se e tiene que N = 2n . Distribuyamos las partes de E = {x1 , x2 , . . . , xn } en dos clases: una con las que no contienen al elemento xn , y otra con las que s lo contienen. La hiptesis o de induccin expresa que la primera est constituida por 2n1 subconjuntos, o a mientras que los subconjuntos de la segunda son los que resultan de la unin de o {xn } con cada uno de los subconjuntos de la primera clase. Por tanto, el nmero u total de subconjuntos es N = 2n1 + 2n1 = 2n . En consecuencia, aplicando el principio de induccin se puede armar que o n elementos. para todo n IN se cumple que P(E) tiene 2

Aunque esta formalizacin del Principio de Induccin es suciente para un o o gran nmero de casos, en otros se puede requerir otra en la que tomamos como u hiptesis la vericacin de la propiedad por todos los elementos menores que x: o o Es decir, Si para cualquier x IN se tiene que, si todo y < x tiene la propiedad P , entonces x tambin tiene la propiedad P e entonces todo z IN tiene la propiedad P Disponemos ya de la herramienta necesaria para retomar la vericacin de o nuestro subprograma recursivo. Recordemos que nos encontrbamos con el proa blema de comprobar la correccin de una llamada al propio subprograma que o estamos vericando, lo cual nos hace entrar, aparentemente, en un ciclo sin n. La clave para salir de este ciclo es darnos cuenta de que, si la recursin est bien o a denida, la llamada que intentamos vericar tiene como parmetro de llamada a un valor menor (o, en otras palabras, ms cercano al caso base de la recursin). a o Por ejemplo, en el caso de la funcin factorial, la estructura de seleccin que o o controla la recursin es: o
if num = 0 then Fac:= 1 else Fac:= num * Fac(num - 1)

En este punto es donde entra en juego el Principio de Induccin, ya que, o basndonos en l, si a e 1. el subprograma es correcto en el caso base (en nuestro caso es obvio que Fac(0) = 1 = 0!), y

222

Cap tulo 10. Introduccion a la recursion

2. demostramos que la construccin del paso recursivo es correcta, suponiendo o que lo es la llamada al subprograma para valores menores del parmetro a sobre el que se hace la recursin. En este caso tenemos asegurada la coo rreccin de nuestro subprograma para cualquier valor del parmetro de o a entrada. En el ejemplo, basta con demostrar que, si suponemos que Fac(num 1) = (num 1)! entonces Fac(num) = num Fac(num 1) = num (num 1)! = num!

En resumen, para demostrar la correccin de un subprograma recursivo heo mos de comprobar: La correccin del caso base. o La correccin de los casos recurrentes. Para ello, se supone la de las llao madas subsidiarias, como ocurre en el paso inductivo con la hiptesis de o induccin. o Que las llamadas recursivas se hacen de manera que los parmetros se a acercan al caso base; por ejemplo, en el clculo del factorial, en las sucesivas a llamadas los parmetros son n, n 1, . . ., que desembocan en el caso base a 0, siempre que n > 0, lo cual se exige en la condicin previa de la funcin. o o

10.5

Recursin mutua o

Cuando un subprograma llama a otro y ste a su vez al primero, se produce e lo que se denomina recursin mutua o cruzada, que consiste en que un subproo grama provoque una llamada a s mismo, indirectamente, a travs de otro u otros e subprogramas. En estos casos, se presenta un problema para denir los subprogramas, porque uno de ellos tendr que ser denido antes del otro, y la llamada que haga a al segundo se hace a un identicador desconocido, contraviniendo la norma de Pascal por la que un identicador tiene que ser declarado antes de usarlo. No obstante, el mismo lenguaje nos da la solucin mediante el uso de la pao labra reservada forward. Con su uso, el identicador del subprograma denido

10.5. Recursion mutua

223

en segundo lugar es predeclarado, escribiendo su encabezamiento seguido por forward, y por lo tanto es reconocido en el subprograma denido en primer lugar. Al denir el segundo subprograma no es necesario repetir sus parmetros. a El esquema de implementacin en Pascal de dos procedimientos mutuamente o recursivos es el siguiente:
procedure Segundo(parmetros); forward; a procedure Primero(parmetros); a ... begin {Primero} ... Segundo (...) ... end; {Primero} procedure Segundo(parmetros); a ... begin {Segundo} ... Primero (...) ... end; {Segundo}

A continuacin se muestra un ejemplo en el que se aplica el concepto de o recursin mutua. Se trata de un programa que comprueba el correcto equilibrado o de parntesis y corchetes en expresiones introducidas por el usuario.6 As si se e , da como entrada la expresin [3 (2 + 1) 5] + 7, el programa debe dar un o mensaje que indique que los parntesis y los corchetes estn equilibrados, y, en e a cambio, si la entrada proporcionada por el usuario es la expresin (a + [b 5) c], o debe dar al menos un mensaje de error en el equilibrado. Con este objetivo, un primer paso en el diseo del programa Equilibrado n puede ser:
repetir Leer carcter c a en caso de que c sea (: Cerrar parntesis e ): Tratar parntesis de cierre e [: Cerrar corchete ]: Tratar corchete de cierre hasta n de la entrada
Se considera que la expresin viene dada en una sola l o nea del input, para simplicar la codicacin. o
6

224

Cap tulo 10. Introduccion a la recursion

En el caso en que se encuentre en el input un parntesis o un corchete e abierto, hay que seguir leyendo caracteres hasta encontrar (si es que existe) el correspondiente s mbolo de cierre. En caso de encontrarlo se dar un mensaje a de xito, y en caso contrario, dependiendo del s e mbolo encontrado, se tomar la a accin correspondiente: o si es un s mbolo de cierre equivocado, se dar un mensaje de error. a si es un parntesis o corchete abierto, se har una llamada recursiva. e a si es el n del input, tambin se dar un mensaje indicndolo. e a a Teniendo en cuenta estas indicaciones, el siguiente nivel de renamiento de Cerrar parntesis puede ser: e
Repetir Leer carcter c a en caso de que c sea (: Cerrar parntesis e ): Dar mensaje de xito e [: Cerrar corchete ]: Dar mensaje de error hasta c = ) o n de la entrada Si n de la entrada entonces Dar mensaje de error

Y simplemente cambiando los corchetes por parntesis y viceversa, puede el e lector obtener el siguiente nivel de Cerrar corchete. Ya en este nivel de diseo se puede observar que las tareas Cerrar parntesis n e y Cerrar corchete son mutuamente recursivas, y como tales deben ser tratadas en la codicacin en Pascal que se da a continuacin: o o
Program Equilibrado (input, output); {Estudia el equilibrado de parntesis y corchetes en secuencias de e caracteres} var c: char; procedure CierraCorchete; forward; procedure CierraPar; {PreC.: se ha ledo un carcter ( y no EoLn} a {Efecto: se ha recorrido la entrada hasta encontrar un carcter ) a o el fin de la entrada, dando los mensajes adecuados si se ha ledo un smbolo inapropiado}

10.5. Recursion mutua


var c: char; begin repeat Read(c); case c of (: CierraPar; {Llamada recursiva para tratar una pareja de parntesis e anidados} ): WriteLn(cuadra el parntesis); e [: CierraCorchete; {Llamada recursiva para tratar una pareja de corchetes anidados} ]: WriteLn(error: cierra parntesis con corchete) e end {case} until (c = )) or EoLn; if EoLn and (c <> )) then {Se llega al fin de la entrada sin encontrar el cierre de parntesis} e WriteLn(error: se queda un parntesis abierto) e end; {CierraPar}

225

procedure CierraCorchete; {PreC.: se ha ledo un carcter [ y no EoLn} a {Efecto: se ha recorrido la entrada hasta encontrar un caracter ] o el fin de la entrada, dando los mensajes adecuados si se ha ledo un smbolo inapropiado} var c: char; begin repeat Read(c); case c of (: CierraPar; {Llamada recursiva para tratar una pareja de parntesis e anidados} ): WriteLn(error: cierra corchete con parntesis); e [: CierraCorchete; {Llamada recursiva para tratar una pareja de corchetes anidados} ]: WriteLn(cuadra el corchete) end {case} until (c = ]) or EoLn; if EoLn and (c <> ]) then {Se llega al fin de la entrada sin encontrar el cierre de corchete} WriteLn(error: se queda un corchete abierto)

226
end; {CierraCorchete}

Cap tulo 10. Introduccion a la recursion

begin {Equilibrado} repeat Read(c); case c of (: if not EoLn then CierraPar {Se intenta equilibrar el parntesis} e else {La entrada acaba en (} WriteLn(error: se queda un parntesis abierto); e ): WriteLn(error: parntesis cerrado incorrectamente); e [: if not EoLn then CierraCorchete {Se intenta equilibrar el corchete} else {La entrada acaba en { } ]: WriteLn(error: se queda un corchete abierto) end {case} until EoLn end. {Equilibrado}

10.6

Recursin e iteracin o o

Si un subprograma se llama a s mismo se repite su ejecucin un cierto nmero o u de veces. Por este motivo, la recursin es una forma especial de iteracin y, de o o hecho, cualquier proceso recursivo puede expresarse de forma iterativa, con ms a o menos esfuerzo, y viceversa. Un ejemplo de ello es el clculo del factorial a (vanse los apartados 8.2.1 y 10.1). e Sabiendo que un determinado problema puede resolverse de las dos maneras, cundo se debe usar una u otra? Como norma general, debe adoptarse siempre a (al menos en un primer momento) la solucin que resulte ms natural, conceno a trando los esfuerzos en la correccin del algoritmo desarrollado. Por ejemplo, los o problemas que vienen descritos en forma recurrente se prestan ms fcilmente a a a una solucin recursiva. Un ejemplo es el problema de las torres de Hanoi, cuya o versin iterativa es bastante ms complicada que la recursiva. o a Por otra parte el mecanismo de la recursin produce, adems de la iteracin, o a o la creacin automtica de nuevos parmetros y objetos locales en cada llamada o a a (apilndose stos). Por consiguiente, se tiene un gasto adicional de memoria (el a e de la pila recursiva, para almacenar las sucesivas tablas de activacin), adems o a del tiempo necesario para realizar esas gestiones. Todo esto puede hacer que ciertos programas recursivos sean menos ecientes que sus equivalentes iterativos.

10.7. Ejercicios

227

Por ello, cuando sean posibles soluciones de ambos tipos,7 es preferible la iterativa a la recursiva, por resultar ms econmica su ejecucin en tiempo y memoria. a o o

10.7

Ejercicios

1. Escriba una funcin recursiva para calcular el trmino n-simo de la secuencia de o e e Lucas: 1, 3, 4, 7, 11, 18, 29, 47, ... 2. Dado el programa Program Invertir (input, output); {Se lee una lnea del input y se escribe invertida} procedure InvertirRec; var c: char; begin Read(c); if c <> . then begin InvertirRec; Write(c) end end; {InvertirRec} begin {Invertir} WriteLn(Teclee una cadena de caracteres ("." para finalizar)); InvertirRec end. {Invertir} Analice su comportamiento y estudie qu resultado dar para la secuencia de e a entrada aeiou., describiendo la evolucin de la pila recursiva (vase el aparo e tado 10.2). Obsrvese que el uso de esta pila recursiva nos permite recuperar los e caracteres en orden inverso al de su lectura. 3. Escriba una funcin recursiva para calcular el mximo comn divisor de dos o a u nmeros enteros dados aplicando las propiedades recurrentes del ejercicio 2 del u cap tulo 9. 4. Escriba una versin recursiva del clculo del mximo comn divisor de dos nmeros o a a u u enteros por el mtodo de Euclides. e 5. Dena subprogramas recursivos para los siguientes clculos: a (a) (1 +
7

1 2

1 + . . . + n ) = (1 +

1 2

+ ... +

1 n1 )

1 n

Como es, por ejemplo, el caso de la funcin factorial. o

228

Cap tulo 10. Introduccion a la recursion


(b) La potencia de un real elevado a un entero positivo: x0 xn xn = = = 1 n (x x) 2 , x (xn1 ), si n > 0 y es par si n > 0 y es impar

(c) La cifra i-sima de un entero n; es decir, e la ultima, si i = 0 la cifra (i-1)-sima de n div 10, en otro caso. e (n) = (n) = 1 0 n ( n ) = ( n1 ) = ( n1 ) k1 k k 6. Sabiendo que, para inf, sup Z, tales que inf sup, se tiene
sup

(d) El coeciente binomial, denido recurrentemente:

ai =
i=inf

ai ,

med i=inf

ai +

sup i=med+1

ai

si inf = sup si inf < sup,


sup 1 i=inf i2 ,

(siendo med = (inf + sup) div 2) dena una funcin recursiva para o 100 1 inclyala en un programa que halle i=1 i2 , u 7. Use el hecho de que
b m b

f (x)dx =
a a

f (x)dx +
m

f (x)dx

o (siendo m = a+b ) para desarrollar una funcin recursiva que halle aproximada2 mente la integral denida de la funcin sen(x) a base de dividir el intervalo [a, b] o en dos hasta que sea lo bastante pequeo (| b a |< ), en cuyo caso aceptamos n b que a f (x)dx (b a) f (m). 8. Sabiendo que 0 es par, es decir, EsPar(0) ; true EsImpar(0) ; false y que la paridad de cualquier otro entero positivo es la opuesta que la del entero anterior, desarrolle las funciones lgicas, mutuamente recursivas, EsPar y EsImpar, o que se complementen a la hora de averiguar la paridad de un entero positivo.

10.8

Referencias bibliogrcas a

En [Sal93] y [CCM+ 93] se ofrecen buenos enfoques de la programacin con subproo gramas. El primero de ellos introduce los subprogramas antes incluso que las instrucciones estructuradas. El segundo ofrece una concrecin de los conceptos de programacin o o modular explicados en los lenguajes C y Modula-2.

10.8. Referencias bibliograficas

229

El libro de Alagic y Arbib [AA78] es una referencia obligada entre los libros orientados hacia la vericacin con un enfoque formal. o Algunos de los conceptos contenidos en el tema provienen de la ingenier del softa ware. Para ampliar estos conceptos recomendamos un texto sobre esta disciplina, como es [Pre93]. En [PJ88] se describen con detalle las tcnicas para obtener diseos de jee n rarqu de subprogramas de la mayor calidad apoyndose en los criterios de indepenas a dencia funcional, caja negra, tamao de los mdulos y muchos otros. n o La recursin es un concepto dif de explicar y comprender, que se presenta freo cil cuentemente relacionndolo con la iteracin, a partir de ejemplos que admiten versiones a o iterativas y recursivas similares. En [Wie88] y [For82] se ofrece este enfoque. En el libro [RN88] puede leerse la historia completa sobre las torres de Hanoi y los grandes logros del famoso matemtico francs E. Lucas, entre otros muchos temas a e matemticos, que se presentan con una pequea introduccin histrica. Existe un proa n o o blema similar al de las Torres de Hanoi, llamado de los anillos chinos, cuya solucin est o a desarrollada en [ES85] y en [Dew85b]

Tema IV

Tipos de datos denidos por el programador

Cap tulo 11

Tipos de datos simples y compuestos

11.1 11.2 11.3 11.4

Tipos ordinales denidos Denicin de tipos . . . . o Conjuntos . . . . . . . . . Ejercicios . . . . . . . . . .

por el programador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

234 240 244 250

Ya hemos visto que los programas se describen en trminos de acciones y e datos. En cuanto a las acciones, se ha mostrado que admiten un tratamiento estructurado, pudindose combinar mediante unos pocos esquemas: la secuene cia, la seleccin y la repeticin. De igual forma se pueden estructurar los datos. o o Hasta ahora slo hemos trabajado con los tipos de datos que estn predenidos o a en Pascal (integer, real, char y boolean), pero en muchas situaciones se manejan unidades de informacin que necesitan algo ms que un dato predenido, o a por ejemplo: Un color del arco iris, (rojo, naranja, amarillo, verde, azul, ail, violeta) n cuya representacin mediante un carcter o un entero ser forzosamente o a a articiosa. El valor de un d del mes, que en realidad no es un entero cualquiera, sino a uno del intervalo [1,31], por lo que ser impreciso usar el tipo integer. a El conjunto de letras necesarias para formar una cierta palabra.

234

Cap tulo 11. Tipos de datos simples y compuestos Un vector del espacio IRn , un crucigrama o un mazo de la baraja espaola. n Una cha de un alumno donde se recoja su nombre, direccin, edad, o telfono, calicacin, D.N.I. y cualquier otro tipo de informacin necesaria. e o o Una carta.

Para poder tratar con datos como los descritos y otros muchos, Pascal permite introducir tipos de datos denidos por el programador. As podemos clasicar los datos en dos grandes grupos: , 1. Los tipos de datos simples que son aqullos cuyos valores representan un e dato atmico. Dentro de estos tipos de datos se encuentran los tipos predeo nidos integer, real, boolean y char junto con los nuevos tipos enumerado y subrango, los cuales permiten recoger datos como, por ejemplo, los colores del arco iris y los d del mes, respectivamente. as 2. Los tipos de datos compuestos que son aqullos cuyos valores pueden ene globar a varios datos simultneamente. Los tipos de datos compuestos son: a el tipo conjunto (que permite expresar el caso del conjunto de letras de una palabra), el tipo array1 (que recoge los ejemplos de vectores, crucigramas o una baraja), el tipo registro (con cuyos valores se pueden representar chas de alumnos), y el tipo archivo (en uno de cuyos valores se puede almacenar una carta). Una vez vista la necesidad de ampliar la gama de tipos de datos disponibles, vamos a estudiar cada uno de los tipos de datos denidos por el programador (tanto los simples como los compuestos) mencionados anteriormente. Como en todo tipo de datos, tendremos que precisar su dominio (los valores que pertenecen a l) y las operaciones sobre ste. e e En este cap tulo se van a estudiar los tipos de datos ms sencillos (enumerado, a subrango y conjunto) y, junto con ellos, algunos conceptos generales, vlidos para a todos los tipos de datos denidos por el programador.

11.1

Tipos ordinales denidos por el programador

Los tipos de datos simples que el programador puede denir son los tipos enumerado y subrango. Estos, junto con los tipos de datos predenidos, son la base para construir los tipos de datos compuestos.
No existe una traduccin clara al castellano del trmino array, por lo que seguiremos usando o e este nombre en lo que sigue.
1

11.1. Tipos ordinales definidos por el programador


Identificador

235

Figura 11.1.

Veamos un ejemplo en el que sealaremos la utilidad y necesidad de ampliar n los tipos de datos predenidos con estos dos nuevos tipos de datos: supongamos que deseamos hacer un horario de estudio para todos los d de la semana as durante todo un ao. En este caso ser util denir tipos de datos que contengan n a los meses (al que llamaremos tipo tMeses), los d de cada mes (tipo tDiasMes) as y los d de la semana (tipo tDiasSemana) para poder hacer planes del tipo: as Mircoles 1 de Junio: estudiar los temas 12 y 13 de Matemticas e a Nuestro inters es denir los tipos tDiasSemana y tMeses enumerando uno a e uno todos sus posibles valores y, limitar el rango del tipo integer a los nmeros u enteros comprendidos entre 1 y 31 para denir el tipo tDiasMes. Veamos que Pascal permite crear estos tipos de una forma muy cmoda. o

11.1.1

Tipos enumerados

Un problema puede precisar de un determinado tipo de dato cuyos valores no estn denidos en Pascal (como ocurre en el ejemplo anterior con los d de la a as semana). Podr amos optar por numerar los valores haciendo corresponder a cada valor un nmero entero que lo represente (por ejemplo, identicar los d de la u as semana con los nmeros del 1 al 7), pero esta solucin no es muy comprensible u o y, adems, podr fcilmente conducirnos a errores dif a a a ciles de encontrar por la posibilidad de mezclarlos con los enteros de verdad y la posibilidad de aplicar operaciones sin sentido. Para solucionar este problema, Pascal nos proporciona la posibilidad de denir los datos de tipo enumerado. Es importante sealar que n una vez que denamos un tipo de datos que contenga los d de la semana, este as tipo va a ser completamente distinto del tipo predenido integer. El diagrama sintctico para la descripcin de un tipo enumerado aparece en la gura 11.1. a o Por ejemplo, para denir un tipo compuesto por los d de la semana incluias mos:
type tDiasSemana = (lun, mar, mie ,jue, vie, sab, dom);

que se situar antes de la declaracin de variables. a o

236
ee r r

Cap tulo 11. Tipos de datos simples y compuestos


Obsrvese que los valores de un tipo enumerado son identicadores, y no e cadenas de caracteres (es decir, no son literales de Pascal, como los descritos en el apartado 3.6).

Como se puede observar, basta con enumerar los valores del tipo uno a uno, separndolos por comas y encerrndolos entre parntesis. A partir de este moa a e mento Pascal reconoce el identicador tDiasSemana como un nuevo nombre de tipo de datos del cual se pueden declarar variables:
var ayer, hoy, mannana:

tDiasSemana;

Como se podr esperar, el dominio de un tipo enumerado est formado por a a los valores incluidos en su descripcin. o Para asignar valores de tipo enumerado a las variables, se usa el operador de asignacin habitual: o
ayer:= dom; hoy:= lun;

Operaciones de los tipos enumerados En todo tipo enumerado se tiene un orden establecido por la descripcin del o tipo y, por lo tanto, los tipos enumerados son tipos ordinales al igual que los tipos predenidos integer, char y boolean (vase el apartado 3.6). De este modo, e los operadores relacionales son aplicables a los tipos enumerados, y el resultado de su evaluacin es el esperado: o lun < mie ; True jue = sab ; False (mar > lun) = (lun < mar) ; True Adems de los operadores relacionales, son tambin aplicables las funciones ya a e conocidas Ord, Pred y Succ: Succ(jue) ; vie Succ(lun) = Pred(mie) ; True Pred(lun) ; error (ya que no existe el anterior del primer valor)

11.1. Tipos ordinales definidos por el programador Succ(dom) ; error (ya que no existe el siguiente al ultimo valor) Ord(jue) ; 3

237

Como en todo tipo ordinal, salvo en el caso de los enteros, el nmero de orden u comienza siempre por cero. Observaciones sobre los tipos enumerados Dado que los tipos enumerados son ordinales, se pueden utilizar como ndices en instrucciones for:
var d: tDiasSemana; for d:= lun to dom do

Pueden pasarse como parmetros en procedimientos y funciones. Incluso, a por tratarse de un tipo simple, puede ser el resultado de una funcin: o
function DiaMannana(hoy: tDasSemana): tDiasSemana; {Dev. el da de la semana que sigue a hoy} begin if hoy = dom then DiaMannana:= lun else DiaMannana:= Succ(hoy) end; {DiaMannana}

Los valores de un tipo enumerado no se pueden escribir directamente. Para resolver este problema hay que emplear un procedimiento con una instruccin case que escriba, segn el valor de la variable del tipo enumeo u rado, la cadena de caracteres correspondiente al identicador del valor. El cdigo de dicho procedimiento podr ser: o a
procedure EscribirDiaSemana(dia: tDiasSemana); {Efecto: escribe en el output el nombre de dia} begin case dia of lun: WriteLn(Lunes); mar: WriteLn(Martes);

238

Cap tulo 11. Tipos de datos simples y compuestos

constante

..

constante

Figura 11.2. dom: WriteLn(Domingo) end {case} end; {EscribirDiaSemana}

De igual forma, tampoco se puede leer directamente un valor de un tipo enumerado, por lo que, en caso necesario, habr que desarrollar una funcin a o anloga al procedimiento EscribirDiaSemana que lea un valor de un tipo a enumerado devolvindolo como resultado de la funcin. e o No se pueden repetir valores en distintas descripciones de tipos enumerados ya que, en tal caso, su tipo ser ambiguo. As el siguiente fragmento es a , errneo: o
type tAmigos = (pepe, juan, pedro, miguel); tEnemigos = (antonio, pedro, enrique);

En este caso, la ambigedad se reeja en que no se puede saber si el valor u de Succ(pedro) es miguel o enrique ni si Ord(pedro) es 1 2. o

11.1.2

Tipo subrango

El tipo de datos subrango se utiliza cuando se quiere trabajar con un intervalo de un dominio ordinal ya existente, bien de un tipo predenido, o bien de un tipo creado con anterioridad. A este dominio ordinal lo denominamos tipo base, ya que va a ser el tipo sobre el que se dene el tipo subrango. La descripcin de un tipo subrango se hace segn el diagrama sintctico de o u a la gura 11.2 Los valores de las constantes tienen que ser de un mismo tipo ordinal y son los que van a delimitar el intervalo con el que se trabajar. Tienen que estar a en orden creciente, esto es, Ord(constante1) Ord(constante2), para que el intervalo tenga sentido. Como ejemplo, consideremos las siguientes descripciones:

11.1. Tipos ordinales definidos por el programador


type tNaturales = 1..MaxInt; tDiasMes = 1..31; tContador = 1..20;

239

El uso de tipos subrango es muy aconsejable debido a que: El compilador puede comprobar que el valor almacenado en una variable de un tipo subrango se encuentra en el intervalo de denicin, produciendo o el correspondiente error en caso contrario (vase el apartado C.3.3). e Proporcionan una mayor claridad, ya que el compilador verica la consistencia de los tipos usados, lo que obliga al programador a una disciplina de trabajo clara y natural. Como es lgico, el dominio de un tipo subrango estar formado por la parte o a del dominio de su tipo base que indique el intervalo considerado en la descripcin. o Operaciones del tipo subrango Un tipo subrango hereda todas las funciones y operaciones de su tipo base, por lo tanto se permiten realizar asignaciones, comparaciones, pasar como para metros en procedimientos y funciones e incluso ser el resultado de una funcin. o As aunque el procedimiento EscribirDiaSemana ten como parametro una , a variable de tipo tDiasSemana, la herencia recibida por el tipo subrango permite realizar la siguiente instruccin: o
type tDiasSemana = (lun, mar, mie, jue, vie, sab, dom); tLaborables = lun..vie; var d: tLaborables; ... EscribirDiaSemana(d)

Observaciones sobre el tipo subrango El tipo base puede ser cualquier tipo ordinal, es decir, char, integer, boolean o un tipo enumerado denido anteriormente, como ocurre en el tipo tLaborables y en el siguiente ejemplo:

240

Cap tulo 11. Tipos de datos simples y compuestos


type tMeses = (ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic); tVerano = jul..sep;

Los valores de un tipo subrango conservan el orden de su tipo base. Turbo Pascal verica las salidas de rangos siempre que se le indique que lo haga (vase el apartado C.3.3). e

11.2

Denicin de tipos o

Como hemos visto, Pascal permite al programador utilizar tipos de datos propios y para ello es necesario que ste dena los tipos que quiere crear. La e denicin de un tipo consiste, bsicamente, en dar nombre al nuevo tipo y espeo a cicar cules sern sus valores, esto es, nombrar el tipo y denir su dominio. Una a a vez que se haya denido un tipo, ya podremos declarar variables no slo de los o tipos predenidos, sino tambin de este nuevo tipo denido por el programador. e Naturalmente, la denicin de tipos debe situarse antes de la declaracin de o o variables. Aunque en la mayor de los casos se puede obviar la denicin de tipos, a o sta es muy recomendable para desarrollar programas ms legibles, claros y no e a redundantes. Este hecho ser explicado ms adelante. a a Las deniciones de tipo se sitan entre las deniciones de constantes y de u variables, como se muestra en la gura 11.3. El diagrama sintctico para la a denicin de tipos es el de la gura 11.4, donde la palabra reservada type indica o el comienzo de la denicin de tipos, identificador2 es el nombre que deseamos o ponerle y Tipo es la descripcin del tipo que vamos a nombrar. o Por ejemplo, en el caso de los tipos simples la descripcin del tipo puede ser: o 1. Una enumeracin de sus valores (tipos enumerados): o
type tColores = (rojo, azul, amarillo, negro, blanco);

pudiendo entonces hacer declaraciones de variables como:


En este texto se seguir el convenio de anteponer la letra t en los identicadores de los tipos a de datos.
2

11.2. Definicion de tipos

241

Encabezamiento
constantes

tipos variables subprogramas

Figura 11.3.

type

Identificador

tipo

Figura 11.4.

242
var color:

Cap tulo 11. Tipos de datos simples y compuestos

tColores;

2. Un intervalo de un tipo ordinal existente, sea predenido o enumerado (es decir, cualquier tipo subrango):
type tColores = (rojo, azul, amarillo, negro, blanco); tNatural = 0..MaxInt; tPrimarios = rojo..amarillo;

pudiendo entonces declarar variables como:


var color: tPrimarios; contador: tNatural;

Como veremos ms adelante, para poder denir tipos compuestos, se usarn a a palabras reservadas (tales como set, array, record o le) para los tipos de datos conjunto, array, registro y archivo respectivamente.

11.2.1

Observaciones sobre la denicin de tipos o

Adems de las caracter a sticas sealadas anteriormente para las deniciones n de tipos, podemos hacer las siguientes observaciones: 1. Hasta ahora, hemos estado utilizando variables de los tipos predenidos o bien de tipos denidos anteriormente con una instruccin type. Sin embaro go, existe otra forma de declarar variables de tipo enumerado o subrango sin necesidad de denir el tipo previamente. Para ello se incluye directamente la descripcin del tipo enumerado o subrango en la zona de declaracin de o o variables. Por ejemplo:
var dia : (lun, mar, mie, jue, vie, sab, dom); diasMes : 1..31;

Esta forma de denicin de tipo recibe el nombre de tipos annimos y las o o variables declaradas as reciben el nombre de variables de tipo annimo. o En cualquier caso, su utilizacin no es recomendable, sobre todo si se van o a utilizar varias variables de un mismo tipo annimo en distintos subproo gramas del mismo programa, ya que habr que denir el tipo cada vez que a necesitemos alguna variable local de estos tipos.

11.2. Definicion de tipos 2. Se pueden renombrar los tipos predenidos en una denicin de tipos: o
type tEntero = integer;

243

El renombramiento, aunque no es recomendable, puede ser util para evitar memorizar algn tipo predenido; as en el ejemplo anterior, podremos hau , cer declaraciones de variables de tipo tEntero y no necesitaremos recordar que en Pascal ser integer: a
var n, i, j :

tEntero;

3. Pascal permite redenir los tipos predenidos:


type boolean = (falso, verdadero);

De todas formas, no es recomendable redenir un tipo predenido, ya que el cdigo resultante puede ser confuso y dif de entender o modicar por o cil otra persona distinta de la que lo ha escrito. 4. No se pueden redenir palabras reservadas como valores de un tipo enumerado ni como nombres de tipo:
type notas = (do, re, mi, fa, sol, la, si); while = (nada, poco, bastante, mucho);

En el ejemplo anterior, la primera denicin de tipo enumerado es incoo rrecta porque uno de sus valores (do) es una palabra reservada; tampoco es posible hacer la segunda denicin porque el nombre (while) utilizado o para el tipo es una palabra reservada. 5. Es muy importante, como se ha comentado anteriormente, la eleccin del o identicador de tipo a la hora de denir un tipo subrango o enumerado de forma que permita identicar claramente lo que queremos denir. 6. No se puede poner la denicin de un tipo (annimo) en el encabezamiento o o de un subprograma, por lo que el siguiente encabezamiento ser incorrecto: a
function MannanaMes(d: 1..31) : 1..31;

244

Cap tulo 11. Tipos de datos simples y compuestos


tipo ordinal

set

of

Figura 11.5.

Es preciso denir previamente el tipo y hacer referencia a su identicador en el encabezamiento, como se muestra a continuacin: o
type tDiaMes = 1..31; function MannanaMes(d: tDiaMes): tDiaMes;

11.3

Conjuntos

El primer tipo de datos compuesto que vamos a estudiar en Pascal es el tipo conjunto, que intenta representar el concepto de los conjuntos utilizados en Matemticas. As podemos denir un conjunto como una coleccin de objetos a , o de un tipo ordinal. En efecto, los elementos de un conjunto no ocupan en l e una posicin determinada;3 simplemente, se puede decir que pertenecen o no al o mismo. El tipo de los elementos que integran el conjunto se llama tipo base, que en Pascal debe ser ordinal (vase el apartado 3.6). La denicin de un tipo de datos e o conjunto se hace segn el diagrama sintctico de la gura 11.5. u a
ee r r No hay que confundir los elementos de un conjunto C con el dominio del tipo set of C, que ser P(C), es decir el conjunto formado por todos los a posibles subconjuntos de C.

El cardinal mximo de un conjunto en Pascal depende del compilador que a estemos utilizando. Por ejemplo, en Turbo Pascal se admiten conjuntos de hasta 256 elementos (con ordinales comprendidos entre 0 y 255), por lo que no podremos denir:
type tConjunto1 = set of 1..2000; tConjunto2 = set of integer;
Nosotros vamos a considerar los conjuntos como datos compuestos (por ninguno o ms elea mentos) aunque sin estructura, por carecer de organizacin entre sus elementos. Sin embargo, o algunos autores clasican los conjuntos como un dato estructurado.
3

11.3. Conjuntos

245

En nuestros programas utilizaremos conjuntos con una cardinalidad menor. Por ejemplo:
type tConjuntoCar = set of char;

Una vez denido, podemos declarar variables de dicho tipo:


var vocales, letras, numeros, simbolos, vacio: tConjuntoCar;

Para asignar valores a un conjunto se utiliza la instruccin usual de asigo nacin, representndose los elementos del conjunto entre corchetes. Dentro o a del conjunto podemos expresar los valores uno a uno o indicando un intervalo abreviadamente (con una notacin similar a la utilizada para denir un tipo o subrango):
vocales:= [A,E,I,O,U]; letras:= [A..Z]; simbolos:= [#..&,?]; vacio:= []

11.3.1

Operaciones sobre el tipo conjunto

Las operaciones que se pueden realizar con los conjuntos son las que normalmente se utilizan con los conjuntos matemticos, es decir: unin, interseccin, a o o diferencia, igualdad, desigualdad, inclusin y pertenencia. Pasamos ahora a ver o la descripcin y efecto de cada una de ellas: o 1. Unin de conjuntos (): se expresa con el signo +, y su resultado es el o conjunto formado por todos los elementos que pertenecen al menos a uno de los conjuntos dados: [A..C] + [B..D,G] + [A,E,I,O,U] ; [A,B,C,D,E,G,I,O,U] 2. Interseccin de conjuntos (): se expresa con el signo *, y su resultado o es el conjunto formado por los elementos comunes a todos los conjuntos dados: [A..C] * [B..F] ; [B,C] 3. Diferencia de conjuntos (\): se expresa con el signo -, y su resultado es el conjunto formado por los elementos que pertenecen al primer conjunto y no pertenecen al segundo: [A..C] - [B..F] ; [A]

246

Cap tulo 11. Tipos de datos simples y compuestos

4. Igualdad de conjuntos (=): se utiliza el s mbolo = y representa la relacin o de igualdad de conjuntos (iguales elementos): [A..D] = [A,B,C,D] ; True 5. Desigualdad de conjuntos (=): se utiliza el s mbolo <> que representa la relacin de desigualdad de conjuntos: o [A..D] <> [A,B,C,D] ; False 6. Inclusin de conjuntos (, ): se utilizan dos s o mbolos: El s mbolo <= que representa relacin de inclusin contenido en: o o [A..D] <= [A,C,D,E] ; False El s mbolo >= que representa la relacin de inclusin contiene a: o o [A..Z] >= [A,H,C] ; True 7. Pertenencia (): se utiliza para saber si un elemento pertenece a un conjunto. Para ello se utiliza la palabra reservada in: A in [A..D] * [A,D,F] ; True Las operaciones unin, interseccin, diferencia y los operadores relacionales o o y el de pertenencia se pueden combinar en una misma sentencia, en cuyo caso se mantienen las reglas de prioridad descritas en el apartado 3.5. Estas prioridades se pueden cambiar mediante el uso de parntesis: e [A..D] + [A..C] * [B..H] ; [A..D] mientras que ([A..D] + [A..C]) * [B..H] ; [B..D] Un ejemplo t pico de aplicacin de conjuntos es simplicar las condiciones de o un repeat o un if :
type tDiasSemana = (lun, mar, mie, jue, vie, sab, dom); procedure LeerDiaSemana(var dia: tDiasSemana); {Efecto: dia es el da de la semana cuya inicial se ha leido en el input} var inicial : char; begin repeat Write(Escriba la inicial del da de la semana: ); ReadLn(inicial) until inicial in [L,l,M,m,X,x,J,j,V,v, S,s,D,d];

11.3. Conjuntos
case inicial of L,l: dia:= lun; M,m: dia:= mar; X,x: dia:= mie; J,j: dia:= jue; V,v: dia:= vie; S,s: dia:= sab; D,d: dia:= dom end {case} end; {LeerDiaSemana}

247

Piense el lector cmo se complicar el cdigo de este procedimiento en caso o a o de no usar conjuntos.

11.3.2

Observaciones sobre el tipo conjunto

Adems de lo anteriormente dicho sobre el tipo de datos conjunto, cabe a considerar las siguientes observaciones: 1. Podemos denir un conjunto de forma annima, en el sentido explicado o anteriormente:
var vocales, letras, numeros, simbolos, vacio:

set of char;

2. Al igual que para el tipo de datos enumerado, los conjuntos no se pueden leer o escribir directamente, por lo que se tendrn que desarrollar procedia mientos para tal n como los que se muestran a continuacin: o
type tLetrasMayusculas = A..Z; tConjuntoLetras = set of tLetrasMayusculas; procedure EscribirConjunto(letras: tConjuntoLetras); {Efecto: se muestran en la pantalla los elementos del conjunto letras} var car: char; begin for car:= A toZ do if car in letras then Write(car, ) end; {EscribirConjunto}

248

Cap tulo 11. Tipos de datos simples y compuestos


procedure LeerConjunto(var conj: tConjuntoLetras); {Efecto: conj contiene las letras ledas del input} var car: char; begin conj:= []; WriteLn(Escribe las letras que forman el conjunto: ); while not EoLn do begin Read(car); if car in [A..Z] then conj:= conj + [car] end; {while} ReadLn end; {LeerConjunto}

El primer procedimiento muestra en la pantalla todos los elementos de un conjunto formado por letras maysculas. Para ello recorre los valores del u tipo base y comprueba la pertenencia de cada uno de ellos al conjunto, escribindolo en su caso. e El segundo procedimiento lee del input los elementos de un conjunto de letras maysculas. Para ello recorre los caracteres de una l u nea (hasta la marca ), incluyendo en el conjunto los del tipo base (A..Z) e ignorando los dems. a

11.3.3

Un ejemplo de aplicacin o

Es conveniente concretar todas estas ideas mediante un ejemplo. En este caso vamos a utilizar el tipo de datos conjunto para implementar un programa que escriba los nmeros primos menores que 256 (esta cota viene dada por la u limitacin del cardinal de un conjunto en Pascal) usando el conocido mtodo o e 4 La idea de esta implementacin es disponer de un de la criba de Eratstenes. o o conjunto inicial con todos los enteros positivos menores que 256 e ir eliminando del conjunto aquellos nmeros que se vaya sabiendo que no son primos (por ser u mltiplos de otros menores). De acuerdo con esta descripcin, una primera etapa u o de diseo del programa podr ser: n a
Calcular los nmeros primos menores que 256 u Escribir los nmeros primos menores que 256 u
4 Eratstenes, conocido astrnomo y gegrafo griego (siglo iii a. C.), es reconocido por haber o o o ideado este mtodo para elaborar una tabla de nmeros primos. El nombre de criba se debe a e u que el algoritmo va eliminando los nmeros que son mltiplos de otros menores, siendo los primos u u aquellos que quedan tras realizar esta criba.

11.3. Conjuntos

249

Y en un segundo renamiento de Calcular los nmeros primos menores que u 5 256 se obtendr a:
Generar el conjunto inicial primos para cada elemento elem mayor que 1 y menor o igual que 16 Eliminar del conjunto todos sus mltiplos u

Y en un nivel de renamiento inferior, Eliminar del conjunto todos sus mltiplos u se puede desarrollar as :
Dar valor inicial (igual a 2) al coeciente k repetir Eliminar elem * k del conjunto primos Incrementar k en una unidad hasta que elem * k sea mayor o igual que 256

Desde este nivel de renamiento es fcil pasar a una implementacin en Pasa o cal, como puede ser la siguiente:

Program Criba (output); {Halla y escribe los primeros nmeros primos} u const N = 255; type tConjuntoPositivos = set of 1..N; var primos: tConjuntoPositivos; elem, k: integer; procedure EscribirConjuntoPositivos(c: tConjuntoPositivos); {Efecto: se muestran en la pantalla los elementos del conjunto c} var i: integer; begin for i:= 1 to N do if i in c then WriteLn(i : 3, es primo) end; {EscribirConjuntoPositivos}

Como se coment en el apartado 8.2.1, basta con eliminar los mltiplos de los nmeros natuo u u rales menores o iguales que la ra cuadrada de la cota (en nuestro ejemplo, los menores o iguales z que 256 = 16).

250

Cap tulo 11. Tipos de datos simples y compuestos

begin {Criba} primos:= [1..N]; {Se genera el conjunto inicial} elem:= 2; while elem < SqRt(N) do begin {Inv.: Para todo i primos tal que 1 i < elem, se tiene que i es primo} if elem in primos then begin k:= 2; repeat primos:= primos - [elem * k]; {Se eliminan los nmeros no primos del conjunto} u k:= k + 1 until elem * k > N end; {if} elem:= elem + 1 end; {while} WriteLn(Los primos menores que , N, son); EscribirConjuntoPositivos(primos) end. {Criba}

11.4

Ejercicios

1. Se desea averiguar qu letras intervienen en un texto (sin distinguir entre letras e maysculas y minsculas ni importar su frecuencia), y cules se han omitido. u u a (a) Dena un tipo apropiado para controlar las letras que van apareciendo. (b) Escriba un subprograma que lea el input, formado por varias l neas, ofreciendo como resultado el conjunto de las letras encontradas. (c) Escriba un subprograma de escritura de conjuntos de letras. (d) Escriba un subprograma que averige el cardinal de un conjunto de letras. u (e) Integre los apartados anteriores en un programa que averige cules y cuntas u a a son las letras distintas encontradas y las omitidas en el input. 2. Dados los conjuntos conjN um {1, . . . , 25} y conjLet { A , . . . , Z }, escriba un subprograma que escriba en la pantalla el producto cartesiano conjN um conjLet. 3. Un modo de calcular el mximo comn divisor de dos nmeros m y n, enteros a u u estrictamente positivos no superiores a 50, consiste en hallar los conjuntos de sus divisores, divisDeM y divisDeN, luego su interseccin, divisComunes, y despus o e el mayor elemento de este conjunto. Desarrolle un programa que realice estos pasos dando una salida detallada del proceso seguido. 4. Los meses del ao se conocen por su nombre, aunque a veces tambin se abrevian n e por su nmero. Desarrolle los siguientes apartados para ambas representaciones. u

11.4. Ejercicios

251

(a) Dena un tipo para representar los meses del ao, as como subprogramas n apropiados para su lectura, mediante sus letras iniciales, y escritura, ofreciendo siempre su nombre (o su nmero). u (b) Dena la funcin ProximoMes, y emplela en un programa que, a partir de o e un mes dado, ofrece los siguientes n meses. (c) Dena la funcin MesAnterior, con un argumento esteMes, que repasa la o lista de los meses posibles hasta dar con uno, mesAnt, de manera que se obtenga ProximoMes(mesAnt) ; esteMes. Ignore la existencia de la funcin o estndar Pred. a (d) Integre los apartados anteriores en un programa que escriba una tabla con los meses del ao, y para cada uno, su mes anterior y siguiente. n 5. Escriba un programa que lea los caracteres del input hasta su nal, desprecie los que no son letras maysculas y, con stas, forme el conjunto de las que no u e aparecen, el de las que aparecen una vez y el de las que aparecen ms de una. a 6. Partes de un conjunto (a) Dado un conjunto C, desarrolle un programa que escriba en la pantalla todos los posibles subconjuntos de C, esto es, el conjunto de sus partes, P(C).

(b) Escriba un programa para mostrar que todo conjunto C tiene 2n subconjuntos, siendo n = card(C). Para ello, modique el programa anterior de manera que genere las partes de C y las cuente en vez de escribirlas en la pantalla. 7. Combinaciones de un conjunto (a) Dado un conjunto C, de cardinal m, desarrolle un programa que escriba en la pantalla todas las posibles combinaciones que pueden formarse con n elementos de C, siendo n m.

(b) Escriba un programa que cuente el nmero de combinaciones descritas en el u apartado anterior.

Cap tulo 12

Arrays

12.1 Descripcin del tipo de datos array . . . . . . . . . . . 253 o 12.2 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 12.3 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 12.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

En el cap tulo anterior, vimos la diferencia entre tipos de datos simples y compuestos. Dentro de los datos compuestos, se estudiaron los ms sencillos a (los conjuntos), pero, adems de stos, existen otros datos compuestos que se a e construyen dotando de una estructura a colecciones de datos pertenecientes a tipos ms bsicos, y por esto se llaman tipos estructurados. a a En este cap tulo se presenta el tipo de datos estructurado array, que ser util a para trabajar con estructuras de datos como, por ejemplo, vectores, matrices, una sopa de letras rectangular, una tabla de multiplicar, o cualquier otro objeto que necesite una o varias dimensiones para almacenar su contenido.

12.1

Descripcin del tipo de datos array o

Los arrays son tipos de datos estructurados ampliamente utilizados, porque permiten manejar colecciones de objetos de un mismo tipo con acceso en tiempo constante, y tambin porque han demostrado constituir una herramienta e de enorme utilidad.

254

Cap tulo 12. Arrays

Consideremos los siguientes ejemplos en los que veremos la necesidad y la utilidad de usar el tipo de datos array: Imaginemos que queremos calcular el producto escalar, uv, de dos vectores u y v de IR3 mediante la conocida frmula: u v = u1 v1 + u2 v2 + u3 v3 . o Con los datos simples que conocemos hasta ahora, tendr amos que denir una variable para cada una de las componentes de los vectores, es decir, algo parecido a:
var u1, u2, u3, v1, v2, v3:

real;

y a la hora de calcular el producto escalar tendr amos que realizar la operacin: o


prodEscalar:= u1 * v1 + u2 * v2 + u3 * v3;

Para este caso ser ms natural disponer de una variable estructurada a a que agrupe en un solo objeto las componentes de cada vector. El tipo array de Pascal permite resolver este problema. Imaginemos que una constructora acaba de nalizar un grupo de 12 bloques de pisos (numerados del 1 al 12), cada uno de los cuales tiene 7 plantas (numeradas del 1 al 7) y en cada planta hay 3 viviendas (A, B y C). Supongamos que el encargado de ventas quiere llevar un control lo ms a sencillo posible sobre qu viviendas se han vendido y cules no. Para e a ello, podr amos utilizar 12 7 3 = 252 variables de tipo boolean de la forma: bloqueiPlantajLetraX asignndole un valor True para indicar a que la vivienda del bloque i, planta j, letra X est vendida o bien False a para indicar que no lo est. a En este caso, ser mucho ms cmodo utilizar algn tipo de datos estructua a o u rado para almacenar esta informacin de forma ms compacta y manejable o a (por medio de instrucciones estructuradas). La estructura ms adecuada a ser la de una matriz tridimensional: la primera dimensin indicar el a o a nmero del bloque, la segunda dimensin la planta, y la tercera la letra de u o la vivienda. As para indicar que en el bloque 3, el piso 5oA est vendido, , a asignaremos un valor True el elemento que ocupa la posicin [3,5,A] de o este dato estructurado; mientras que si en el bloque 5, el 1oC sigue estando disponible, asignaremos un valor False a la posicin [5,1,C]. o

12.1. Descripcion del tipo de datos array

255

El tipo array ofrece la posibilidad de referirnos a las componentes de un modo genrico, por su posicin, lo que hace ms cmodo y comprensible el desarrollo e o a o de programas. El tipo estructurado array captura la idea de los vectores y matrices del a lgebra (como podr ser IR3 , M25 (Z an Z)), aunque sus elementos componentes no tienen que ser nmeros: pueden ser de cualquier tipo. A semejanza de estas u estructuras matemticas, los arrays se pueden manipular fcilmente, debido a a a su organizacin regular (es fcil verlos como hileras, tablas, estructuras cbicas, o a u etc.): 1 2 3 4 5 m= v = 0.24 3.14 3.56 6 7 8 9 0 a c= c e b d f

El diagrama sintctico de la denicin de un array es: a o

array

Tipondice

of

TipoBase

Por lo tanto, su denicin en Pascal es de la siguiente forma: o


array [TipoIndice1, TipoIndice2, ..., TipoIndiceL] of TipoBase

o equivalentemente:
array [TipoIndice1] of array [TipoIndice2] of ... ... array [TipoIndiceL] of TipoBase

Los tipos TipoIndice1, TipoIndice2 ... TipoIndiceL tienen que ser de un tipo simple ordinal, es decir integer, char, boolean, enumerado o subrango. L es el nmero de dimensiones del array. Por ejemplo, se pueden realizar las u siguientes deniciones de tipos y declaraciones de variables:
type tPlanetas = (mercurio, venus, tierra, marte, jupiter, saturno, urano, neptuno, pluton); tVector = array[1..3] of real; tMatriz = array[1..2, 1..5] of integer;

256

Cap tulo 12. Arrays


tCubo = array[1..2, 1..2, 1..3] of char; tDistancia = array[tPlanetas, tPlanetas] of real; tUrbanizacion = array[1..12, 1..7,A..C] of boolean; var u, v: tVector; m: tMatriz; c: tCubo; d: tDistancia; costaMar: tUrbanizacion;

El dominio de un array es el producto cartesiano de los dominios de los tipos de los ndices. Como se ha visto en la denicin genrica anterior, los arrays multidimeno e sionales se pueden denir de varias formas (lo vemos para el caso de dos dimensiones): 1. Como un vector de vectores:
type tMatriz = array[1..8] of array[A..E] of real;

2. Como un vector de un tipo denido anteriormente que ser otro array: a


type tVector = array[A..E] of real; tMatriz = array[1..8] of tVector;

3. Introduciendo los ndices dentro de los corchetes separados por comas:


type tMatriz = array[1..8, A..E] of real;

Esta ultima es la forma ms recomendable de denir un array multidimen a sional, ya que evita posibles errores en el orden de los ndices. Si se quiere denir una variable de tipo tMatriz se realiza de la forma usual y es igual para cualquiera de las tres deniciones anteriores, esto es:
var m : tMatriz;

con lo que estaremos declarando una variable que ser una matriz de tamao a n 8 5. Otra posibilidad es la siguiente:
var m : array[1..8, A..E] of real;

12.1. Descripcion del tipo de datos array

257

12.1.1

Operaciones del tipo array y acceso a sus componentes

Las operaciones permitidas con los componentes de un array son las mismas que las permitidas a su tipo base. Acceso y asignacin a componentes de un array o Se accede a sus elementos mediante el uso de tantos ndices como dimensiones tenga el array, siguiendo el siguiente esquema:
idArray [expres1, expres2, ..., expresL]

o, equivalentemente:
idArray [expres1][expres2]...[expresL]

donde expres1, expres2, ..., expresL son expresiones del tipo de los L ndices de idArray, respectivamente. Por ser expresiones, pueden ser literales de los tipos de los ndices:
v[3] m[2,3] m[2][4] c[1,2,1] c[1][2][1] d[venus, tierra] costaMar[12,3,B]

o resultados de alguna operacin; as por ejemplo, si i = 2, las siguientes expreo , siones son equivalentes a las anteriores:
v[i+1] m[i,2*i-1] m[i][2*i-1] c[i-1,i,1] c[i-1][i][1] d[Succ(mercurio), Pred(marte)] costaMar[6 * i,i + 1,B]

Para dar valores a las componentes de un array se usa la instruccin de o asignacin: o


v[2]:= 3.14 m[i,2 * i - 1]:= 8 c[1][2][1]:= b d[mercurio, pluton]:= 3.47E38 costaMar[12,3,B]:= True

Al igual que ocurre con las variables de tipo simple, una referencia a una componente de un array puede representar la posicin de memoria donde se o almacena su valor (como en los ejemplos anteriores), o su valor, si se utiliza dentro de una expresin (como ocurre en la instruccin Write(v[2])). o o

258 Asignacin y operaciones de arrays completos o

Cap tulo 12. Arrays

Adems de la asignacin a las componentes de un array, se pueden realizar a o asignaciones directas de arrays siempre que sean del mismo tipo. Por ejemplo, dadas las deniciones
type tDiasSemana = (lun, mar, mie, jue, vie, sab, dom); tIndice1 = -11..7; tIndice2 = A..Z; tIndice3 = lun..vie; tMatrizReal = array[Indice1, Indice2, Indice3] of real; var i: tIndice1; j: tIndice2; k: tIndice3; m1, m2: tMatrizReal;

la asignacin: o
m2:= m1

es equivalente a la instruccin: o
for i:= -11 to 7 do for j:= A to Z do for k:= lun to vie do m2[i,j,k]:= m1[i,j,k]

Tambin se pueden realizar asignaciones por las o columnas siempre que se e hayan denido las las o columnas como tipos con nombre. Por ejemplo,
type tVector = array[1..3] of real; tMatriz = array[1..5] of tVector; var v: tVector; m: tMatriz; ... m[4]:= v ...

En Pascal no es posible comparar arrays completos aunque sean del mismo tipo. Para realizar esta operacin es necesario comparar elemento a elemento, o comprobando la igualdad entre ellos. Por ejemplo, suponiendo que m1 y m2 son matrices de tipo tMatriz, la comprobacin de su igualdad se puede hacer as o :

12.1. Descripcion del tipo de datos array


var i, j: integer; ... iguales := True; i:= 0; while iguales and (i <= 3) do begin j:= 0; while iguales and (j <= 5) do begin iguales := m1[i,j] = m2[i,j]; j := Succ(j) end; i := Succ(i) end {PostC.: iguales indica si m1 = m2 o no}

259

Por otra parte, en cuanto a las operaciones de entrada y salida, solamente se pueden leer y escribir arrays completos cuando se trata de arrays de caracteres. En este caso, y por lo que respecta a la lectura, la cadena le debe coincidir da en longitud con el nmero de componentes del vector. u En los restantes tipos de componentes de arrays, se deben leer y escribir los valores de las componentes una a una siempre que sean de tipo simple. En el siguiente ejemplo se denen procedimientos para la lectura y escritura de un array de una dimensin: o
const Tamanno = 5; type tRango = 1..Tamanno; tVector = array[tRango] of real; procedure LeerVector(var vec: tVector); var i: tRango; begin for i:= 1 to Tamanno do begin Write(Introduzca v(,i,)= ); ReadLn(vec[i]) end end; {LeerVector} procedure EscribirVector(vec: vector); var i: tRango; begin for i:= 1 to Tamanno do WriteLn(v(,i,)= ,vec[i]); WriteLn end; {EscribirVector}

260

Cap tulo 12. Arrays

12.1.2

Caracter sticas generales de un array

Adems de las operaciones permitidas con un array y la forma de acceder a a sus elementos, hay que destacar las siguientes caracter sticas comunes al tipo array, independientemente de su tamao o de su dimensin: n o Los arrays son estructuras homogneas, en el sentido de que sus elementos e componentes son todos del mismo tipo. El tamao del array queda jado en la denicin y no puede cambiar n o durante la ejecucin del programa, al igual que su dimensin. As el o o , tamao de cualquier variable del tipo n
type vector = array[1..3] of real;

ser de 3 elementos, mientras que su dimensin ser 1. Como estos valores a o a no podrn variar, si necesitsemos utilizar un vector de IR4 tendr a a amos que denir otro tipo array con tamao 4 y dimensin 1. n o Los datos de tipo array se pueden pasar como parmetro en procedimientos a y funciones, pero el tipo que devuelve una funcin no puede ser un array o (ya que un array es un tipo de datos compuesto). Para solucionar este problema, se debe utilizar un procedimiento con un parmetro por variable a de tipo array en vez de una funcin. Este parmetro adicional va a ser el o a resultado que pretend amos devolver con la funcin (se incluyen ejemplos o de esto en los apartados siguientes). Cuando los arrays son grandes y se pasan como parmetro por valor a los a subprogramas, se duplica el espacio en memoria necesario para su almacenamiento, puesto que hay que guardar el array original y la copia local (vase el apartado 8.2.3). Adems, el proceso de copia requiere tambin e a e un cierto tiempo, tanto mayor cuanto ms grande es el array. Por estos a motivos, cuando el array no se modica en el cuerpo del subprograma, es aconsejable pasarlo como parmetro por referencia, pues de esta forma a no hay duplicacin de espacio ni tiempo ocupado en la copia (vase el o e apartado 8.6.1). En cualquier caso, el programador debe cuidar extremadamente las eventuales modicaciones de esa estructura en el subprograma, ya que repercutir en el parmetro real. an a Usualmente, los arrays de una dimensin se suelen denominar vectores, mieno tras que los de ms de una dimensin reciben el nombre genrico de matrices. a o e En los siguientes apartados se presentan algunas de sus particularidades.

12.2. Vectores

261

12.2

Vectores

En trminos generales, un vector es una secuencia, de longitud ja, formada e por elementos del mismo tipo. Teniendo en cuenta que un vector es un array de dimensin 1, su denicin es sencilla. Por ejemplo: o o
type tNumeros = 1..10; tDiasSemana = (lun,mar,mie,jue,vie,sab,dom); tVectorDeR10 = array[tNumeros] of real; tFrase = array[1..30] of char; tVectorMuyGrande = array[integer] of real;

Hay que observar que el ultimo vector llamado vectorMuyGrande slo se podr o a denir si dispusiramos de mucha memoria. 1 e Con las deniciones anteriores se pueden realizar operaciones como:
var v: tVectorDeR10; refran: tFrase; ... v[4]:= 3.141516; v[2 * 4 - 1]:= 2.7172 * v[4]; refran:= Al que madruga, Dios le ayuda. ...

Ejemplo Veamos ahora un ejemplo de manejo de vectores en Pascal. Para indicar cuntos viajeros van en cada uno de los 15 vagones de un tren (con una capacidad a mxima de 40 personas por vagn), en lugar de utilizar 15 variables enteras (una a o para cada vagn), se puede y se debe utilizar un vector de la siguiente forma: o
const CapacidadMax = 40; type tCapacidad = 0..CapacidadMax; tVagones = array[1..15] of tCapacidad; var vagon : tVagones;
Por otra parte, se debe sealar que Turbo Pascal facilita un tratamiento ms cmodo y directo n a o de las cadenas de caracteres por medio de los llamados strings (vase el apartado B.5). e
1

262

Cap tulo 12. Arrays

As haremos referencia al nmero de pasajeros del vagn i-simo mediante , u o e vagon[i], mientras que el total de viajeros en el tren ser a
15

vagon[i]
i=1

que en Pascal se calcula como sigue:


total:= 0; for i:= 1 to 15 do total:= total + vagon[i]

Como todo array, un vector se puede pasar como parmetro en procedimiena tos y funciones:
const Tamanno = 5; type tVector = array[1..Tamanno] of real; function Norma(vec:
T amanno i=1

tVector) : vec2 i

real;

} {Dev. var i: 1..Tamanno; sumaCuad: real; begin sumaCuad:= 0.0; for i:= 1 to Tamanno do sumaCuad:= sumaCuad + Sqr(vec[i]); Norma:= SqRt(sumaCuad) end; {Norma}

La funcin anterior devuelve la norma de un vector de IRTamanno . Supongamos o ahora que queremos desarrollar una funcin tal que, dado un vector, devuelva o el vector unitario de su misma direccin y sentido. En este caso no podremos o utilizar una funcin, ya que un vector no puede ser el resultado de una funcin. o o Por esta razn tendremos que utilizar un procedimiento cuyo cdigo podr ser o o a el siguiente:
procedure HallarUnitario(v: tVector; var uni: tVector); {PostC.: para todo i, si 1 i Tamanno, entonces uni[i] = var norm: real; i: 1..Tamanno;
vi Norma(v) }

12.3. Matrices

263

function Norma(vec: {Dev. ... end; {Norma}


T amanno i=1

tVector): vec2 } i

real;

begin norm:= Norma(v); for i:= 1 to Tamanno do uni[i]:= v[i]/norm end; {HallarUnitario}

12.3

Matrices

Como ya se dijo, los arrays multidimensionales reciben el nombre genrico e de matrices. En este apartado se presentan algunos ejemplos de utilizacin de o matrices. Evidentemente, la forma de denir los tipos de datos para las matrices es la misma de todos los arrays, as como el modo de declarar y manipular variables de estos tipos. As por ejemplo: ,
type tMeses = (ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic); tDiasMes = 1..31; tHorasDia = 0..23; tFrase = array[1..30] of char; tMCuadrada = array[1..5, 1..5] of real; tFiestas95 = array[tDiasMes, tMeses] of boolean; tAgenda95 = array[tDiasMes, tMeses, tHorasDia] of tFrase; var m: tMCuadrada; festivos: tFiestas95; agenda: tAgenda95; begin m[1,2]:= SqRt(2); m[2,3-2]:= 2.43 * m[1,2]; festivos[25,dic]:= True; festivos[28,dic]:= False; agenda[18,mar,17]:= Boda de Jose Luis y Mavi. ... end.

264

Cap tulo 12. Arrays

En el siguiente ejemplo, que calcula el producto de dos matrices reales, se utilizan las operaciones permitidas a los arrays, haciendo uso del hecho de que las matrices se pueden pasar como parmetros en funciones y procedimientos. En a un primer nivel de diseo se tiene: n
Leer matrices a y b Multiplicar matrices a y b, hallando la matriz producto prod Mostrar la matriz prod

Dada la simplicidad y familiaridad de los subprogramas a implementar, no se considera necesario un nivel inferior de diseo, razn por la cual se presenta n o directamente la codicacin en Pascal: o

Program MultiplicacionDeMatrices (input,output); {El programa lee dos matrices cuadradas de dimensin N y de o componentes reales y las multiplica, mostrando la matriz producto en la pantalla} const N = 10; type tMatriz = array[1..N, 1..N] of real; var a, b, prod: tMatriz; procedure LeerMatriz(var mat : tMatriz); {Efecto: Este procedimiento lee del input una matriz cuadrada mat MN ( IR), componente a componente} var fil, col: 1..N; begin for fil:= 1 to N do for col:= 1 to N do begin Write(Introduzca la componente , fil, ,, col, de la matriz: ); ReadLn(mat[fil,col]) end end; {LeerMatriz} procedure MultiplicarMat(m1, m2: tMatriz; var resul: tMatriz); {Efecto: resul:= m1 * m2} var i, j, k: 1..N; {i recorre las filas de m1 y de resul, j recorre las columnas de m1 y las filas de m2 y k recorre las columnas de m2 y las de resul}

12.3. Matrices
begin for i:= 1 to N do for k:= 1 to N do begin resul[i,k]:= 0; for j:= 1 to N do resul[i,k]:= resul[i,k] + m1[i,j] * m2[j,k] end {for k} end; {MultiplicarMat} procedure EscribirMatProd(m: tMatriz); {Efecto: escribe en la pantalla los elementos de la matriz m} var i, j: 1..N; begin for i:= 1 to N do for j:= 1 to N do {Escribe mij } WriteLn(m(, i, ,, j, ) = , m[i,j]); end; {EscribirMatProd} begin WriteLn(Lectura de la matriz A); LeerMatriz(a); WriteLn(Lectura de la matriz B); LeerMatriz(b); MultiplicarMat(a,b,prod); EscribirMatProd(prod) end. {MultiplicacionDeMatrices}

265

Un ejemplo completo El siguiente ejemplo utiliza todos los tipos de datos denidos por el programador vistos hasta ahora, es decir, los tipos enumerado, subrango y array. Se trata de construir un almanaque del siglo xx, asignando en una matriz con tres ndices correspondientes a los d del mes (1 al 31), meses (ene, feb, as etc.), y aos desde el 1901 al 2000, los d de la semana. n as El punto de partida es el d 31 de diciembre de 1900 que fue lunes. A a partir de esta fecha se van recorriendo consecutivamente todos los d del siglo as asignndoles el d de la semana correspondiente, teniendo especial cuidado en a a determinar cuntos d tiene cada mes, para lo cual se ha de comprobar si el ao a as n es bisiesto. En un primer nivel de renamiento podr amos escribir el siguiente seudocdigo: o

266
El d 31 de diciembre de 1900 fue lunes a Para todos los aos del siglo hacer n Para todos los meses del ao hacer n Asignar el d de la semana a cada d del mes a a

Cap tulo 12. Arrays

donde, la accin Asignar el d de la semana a cada d del mes puede ser o a a renada en un nivel inferior de la siguiente forma:

Calcular cuntos d tiene el mes a as Para todos los d del mes hacer as asignar al d actual el maana de ayer a n asignar a ayer el d actual a

Los bucles correspondientes a los aos y los meses no presentan dicultad, n sin embargo, los d del mes dependen del propio mes, y en el caso de febrero, as de que el ao sea o no bisiesto. Por eso hay que calcular CuantosDias tiene el n mes y para ello es necesario conocer el mes y el ao. n Durante el clculo de CuantosDas se har una llamada a EsBisiesto, una a a funcin que determina el nmero de d de febrero.2 Se usa tambin una funcin o u as e o Mannana para el clculo correcto del d siguiente, de forma que al domingo le a a siga el lunes. El programa completo es el siguiente:
Program AlmanaqueSigloXX (input,output); {Calcula el da de la semana correspondiente a cada da del siglo XX} type tMeses = (ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic); tDiasSemana = (lun, mat, mie, jue, vie, sab, dom); tAnnos = 1901..2000; tDiasMes = 1..31; tCalendarioSigloXX = array[tDasMes, tMeses, tAnnos] of tDasSemana; var almanaque: tCalendarioSigloXX; mes: tMeses; ayer: tDiasSemana; contAnnos: tAnnos; dias, contaDias: tDiasMes;
En el desarrollo de esta funcin se tendr en cuenta que los aos mltiplos de 100 no son o a n u bisiestos, salvo que sean mltiplos de 400. u
2

12.3. Matrices

267

function Mannana(hoy: tDiasSemana): tDiasSemana; {Dev. el da de la semana que sigue a hoy} begin if hoy = dom then Mannana:= lun {se evita Succ(dom) ; error} else Mannana:= Succ(hoy) end; {Mannana} function EsBisiesto(anno: tAnnos): boolean; {Efecto: Averigua si un a~o es bisiesto o no} n begin EsBisiesto:= ((anno mod 4 = 0) and (anno mod 100 <> 0)) or (anno mod 400 = 0) end; {EsBisiesto} function CuantosDias(unmes: tMeses, anno: tAnnos): tDiasMes; {Dev. el numero de das del mes unmes del a~o anno} n begin case unmes of abr,jun,sep,nov : CuantosDias:= 30; feb : if EsBisiesto(anno) then CuantosDias:= 29 else CuantosDias:= 28; ene,mar,may,jul,ago,oct,dic : CuantosDias:= 31 end {case} end; {CuantosDias} begin {AlmanaqueSigloXX} {Crea un almanaque del siglo XX} ayer:= lun; {El dia 31 de diciembre de 1900 fue lunes} for contAnnos:= 1901 to 2000 do for mes:= ene to dic do begin das:= CuantosDias (mes, contAnnos); {nmero de das del mes actual} u for contaDias:= 1 to dias do begin almanaque[contaDias, mes, contAnnos]:= Mannana(ayer); ayer:= almanaque[contaDias,mes,contAnnos] end {for contaDias} end {for mes} end {for contAnnos} end. {AlmanaqueSigloXX}

268

Cap tulo 12. Arrays

Para saber qu d de la semana fue el 12 de enero de 1925, se mirar la e a a posicin almanaque[12,ene,1925] (cuyo valor es lun). o

12.4

Ejercicios

1. Complete adecuadamente los interrogantes de la denicin que sigue, o const n = ?; {un entero positivo} var i, j: 1..n; a : array[1..n, 1..n] of ? e indique cul es el efecto de la siguiente instruccin: a o for i:= 1 to n do for j:= 1 to n do a[i, j]:= i=j 2. Considrese que los N primeros trminos de una sucesin estn registrados en un e e o a array de N componentes reales. Dena el subprograma Sumar, que transforma los elementos del array en los de la correspondiente serie:
n

an

ai
i=1

3. Escriba un programa que realice la suma de dos nmeros positivos muy grandes u (de 50 cifras, por ejemplo). 4. Para vectores de IR3 , dena subprogramas para calcular (a) el mdulo: IR3 IR o

(b) un vector unitario con su misma direccin o (d) el producto escalar: IR3 IR3 IR (c) la suma: IR3 IR3 IR3

(e) el producto vectorial: IR3 IR3 IR3

(f) el producto mixto: IR3 IR3 IR3 IR

Usando en lo posible los subprogramas anteriores, dena otros para averiguar lo siguiente: (a) dados dos vectores de IR3 , ver si uno de ellos es combinacin lineal del otro. o (b) dados tres vectores de IR3 , ver si uno de ellos es combinacin lineal de los o otros dos.

12.4. Ejercicios

269

5. Escriba un subprograma que mezcle dos vectores (de longitudes m y n respectivamente), ordenados ascendentemente, produciendo un vector (de longitud m + n), tambin ordenado ascendentemente. e 6. Escriba un subprograma que averige si dos vectores de N enteros son iguales. u (La comparacin deber detenerse en cuanto se detecte alguna diferencia.) o a 7. Dados dos vectores de caracteres del mismo tipo, escriba un subprograma que averige si el primero de ellos precede al segundo en orden alfabtico. u e 8. Escriba un subprograma que desplace todas las componentes de un vector de N enteros un lugar a la derecha, teniendo en cuenta que la ultima componente se ha de desplazar al primer lugar. General cese el subprograma anterior para desplazar las componentes k lugares. 9. Escriba un subprograma que lea una secuencia de caracteres del teclado registrndola a en un vector de caracteres. Cuando el nmero de caracteres escritos sea inferior u a la longitud del vector, se deber rellenar con espacios en blanco por la derecha; a y cuando haya ms caracteres de los que caben, se eliminarn los ultimos. a a 10. Si representamos la ecuacin de una recta en el plano como un vector de tres o componentes, Ax + By + C = 0, escriba subprogramas para determinar: (a) La ecuacin de la recta que pasa por dos puntos dados. o (b) La ecuacin de la recta que pasa por un punto dado y tiene una cierta o pendiente. (c) El ngulo que forman dos rectas dadas. a (d) La distancia de un punto dado a una recta dada. 11. Dena una funcin que averige el mximo elemento de una matriz de M N o u a enteros. 12. Se tiene un sistema de ecuaciones lineales representado mediante una matriz de 3 4, donde las tres primeras columnas contienen los coecientes del sistema (con determinante distinto de cero) y la cuarta los trminos independientes. Escriba e un subprograma para calcular la solucin del sistema por la regla de Cramer. o 13. Se tiene ahora un sistema de N ecuaciones con N incgnitas, supuestamente o compatible determinado. Escriba un subprograma para calcular la solucin del o sistema por el mtodo de Gauss. e 14. Dena el tipo de una matriz cuadrada de dimensin N de elementos reales, y o escriba un subprograma Trasponer que intercambie los elementos de posiciones (i, j) y (j, i) entre s i, j {1, . . . N }. , 15. Dena un procedimiento para descomponer una matriz cuadrada M en otras dos, A y B, con sus mismas dimensiones, de manera que M =S+A y tales que S es simtrica y A antisimtrica. Ello se consigue forzando que e e Si,j = Sj,i = Mi,j + Mj,i , i, j {1, . . . N } 2

270
y Ai,j = Aj,i =

Cap tulo 12. Arrays

Mi,j Mj,i , i, j {1, . . . N } 2

16. Dena subprogramas para determinar si una matriz es simtrica y si es triangular e inferior, parando cuando se detecte que no lo es. 17. (a) Escriba un programa que lea los caracteres del input y efecte una esu tad stica de las letras que aparecen, contando la frecuencia de cada una de ellas sin tener en cuenta si es mayscula o minscula. u u (b) Modique el programa del apartado (a) de manera que calcule cuntas veces a aparece cada par de letras contiguas en el texto. (c) Modique el programa del apartado anterior para que se muestre tambin e cuntas veces aparece cada tr de letras contiguas en el texto. a o 18. Dada una matriz real de 33, escriba un programa que calcule: (a) Su determinante. (b) Su matriz adjunta. (c) Su matriz inversa. 19. (a) Escriba un subprograma para hallar el producto de dos matrices de m n y de n l.

(b) Escriba un subprograma que calcule la potencia de una matriz cuadrada de orden n. (Obsrvese que este problema admite un algoritmo iterativo y otro e recursivo, como ocurre con la potencia de nmeros.) u

20. Un modo de averiguar el mximo elemento de un vector V de tamao n es el a n siguiente: si el vector consta de un solo elemento, se es el mximo; de lo contrario, e a se consideran los fragmentos V1,...,pm y Vpm+1,...,n y se averigua el mximo en cada a una de sus mitades (mediante este mismo procedimiento), resultando que el mayor de tales nmeros es el de V . u Desarrolle un subprograma que halle el mximo elemento de un vector en un a fragmento dado mediante el procedimiento descrito. 21. Se tiene una frase en un array de caracteres. Desarrolle subprogramas para averiguar los siguientes resultados: (a) El nmero de palabras que contiene. u (b) La longitud de la palabra ms larga. a (c) De todas las palabras, la que aparece antes en el diccionario. (Se entiende por palabra la secuencia de letras seguidas, delimitadas por un carcter que no es una letra o por los l a mites del array.)

Cap tulo 13

Registros

13.1 Descripcin del tipo de datos registro o

. . . . . . . . . 271

13.2 Arrays de registros y registros de arrays . . . . . . . . 279 13.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282

En el cap tulo anterior se ha estudiado cmo se trabaja en Pascal con coleco ciones de datos del mismo tipo. Pero en los problemas relacionados con la vida real es necesario agrupar con frecuencia datos de distintos tipos: por ejemplo, el documento nacional de identidad contiene, entre otros, un entero (el nmero) u y cadenas de caracteres (el nombre, los apellidos, etc.); una cha de art culo en un almacn debe contener datos numricos (cdigo, nmero de unidades en e e o u stock, precio, etc.), booleanos (para indicar si se le aplica o no descuento) y cadenas de caracteres (la descripcin del art o culo, el nombre del proveedor, etc.). En este cap tulo presentamos un nuevo tipo de datos, los registros, que nos van a permitir almacenar y procesar este tipo de informacin. o

13.1

Descripcin del tipo de datos registro o

Los registros1 son otro tipo de datos estructurados muy utilizados en Pascal. Su principal utilidad reside en que pueden almacenar datos de distintos tipos, a diferencia de los dems datos estructurados. Un registro estar formado por a a
1

En ingls record. e

272
Identificador
, ;

Cap tulo 13. Registros


Tipo

record

end

Figura 13.1.

varios datos (simples o estructurados) a los que llamaremos campos del registro y que tendrn asociado un identicador al que llamaremos nombre de campo. a La denicin de un registro se hace segn el diagrama sintctico de la o u a gura 13.1 y, por tanto, la denicin de un registro genrico en Pascal es: o e
type tNombReg = record idenCampo1: idTipo1; idenCampo2: idTipo2; ... idenCampoN: idTipoN end; {tNombReg}

Por ejemplo, supongamos que un encargado de obra quiere tener registrados varios datos de sus trabajadores, tales como: nombre, direccin, edad y nmero o u de D.N.I. Con los tipos de datos que conocemos resultar bastante dif a cil, ya que tendr amos que indicar que las variables direccion, edad y dni estn rea lacionadas con el nombre de un trabajador en concreto. Para solucionarlo, se utiliza el tipo de datos estructurado registro, de la siguiente forma:
type tEdades = 16..65; tDigitos = 0..9; tFicha = record nombre: array[1..30] of char; direccion: array[1..50] of char; edad: tEdades; dni: array[1..8] of tDigitos end; {tFicha}

Como se puede observar, en este ejemplo se utilizan datos estructurados en la denicin de otro dato estructurado (dentro de la estructura del dato registro o

13.1. Descripcion del tipo de datos registro

273

se utilizar un vector de caracteres para almacenar el nombre y la direccion de a un empleado). Es conveniente destacar que el tipo de datos registro, al igual que el tipo array, es un tipo estructurado de tamao jo; sin embargo se diferencian de ellos n principalmente en que los componentes de un array son todos del mismo tipo, mientras que los componentes de un registro pueden ser de tipos distintos.

13.1.1

Manejo de registros: acceso a componentes y operaciones

El dominio de un registro estar formado por el producto cartesiano de los a dominios de sus campos componentes. Para poder trabajar con el tipo de datos registro es necesario saber cmo o acceder a sus campos, cmo asignarles valores y que tipo de operaciones podemos o realizar con ellos: Para acceder a los campos de los registros se utilizan construcciones de la forma nomVarRegistro.nomCampo, es decir, el nombre de una variable de tipo registro seguido de un punto y el nombre del campo al que se quiere acceder. Por ejemplo, si la variable f es de tipo tFicha, para acceder a sus campos nombre, direccion, edad y dni se utilizarn, respectivamente, a las construcciones:
f.nombre f.direccion f.edad f.dni

En este punto se debe sealar que, a diferencia de los arrays, en los cuales n el acceso se realiza por medio de ndices (tantos como dimensiones tenga el array, que pueden ser el resultado de una expresin y por tanto calculables), o en los registros se accede por medio de los identicadores de sus campos, que deben darse expl citamente. Los tipos de los campos pueden ser tipos predenidos o denidos por el programador mediante una denicin de tipo previa. Incluso un campo de o un registro puede ser de tipo registro. As por ejemplo, si queremos alma, cenar para cada alumno, su nombre, fecha de nacimiento y nota, podr amos denir tipos y variables de la siguiente forma:

274

Cap tulo 13. Registros


type tMeses = (ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic); tCalificaciones = (NP, Sus, Apr, Notab, Sob, MH); tNombre = array[1..50] of char; tFecha = record dia: 1..31; mes: tMeses; anno: 1900..2000 end; {tFecha} tFicha = record nombre: tNombre; fechaNac: tFecha; nota: tCalificaciones end; {tFicha} var alumno: tFicha;

La asignacin de valores a los campos se har dependiendo del tipo de o a cada uno de ellos. As en el ejemplo anterior, para iniciar los datos de la , variable Alumno tendr amos que utilizar las siguientes asignaciones:
alumno.nombre:= Mario Aguilera alumno.fechaNac.dia:= 3; alumno.fechaNac.mes:= feb; alumno.fechaNac.anno:= 1973; alumno.nota:= Notab ;

Las operaciones de lectura y escritura de registros han de hacerse campo por campo, empleando procedimientos o funciones especiales si el tipo del campo as lo requiere. Por ejemplo:
procedure EscribirFicha(unAlumno: tFicha); {Efecto: Escribe en la pantalla el contenido del registro unAlumno} begin WriteLn(Nombre: , unAlumno.nombre); Write(Fecha de nacimiento: ,unAlumno.fechaNac.dia); EscribirMes(unAlumno.fechaNac.mes); WriteLn(unAlumno.fechaNac.anno); Write(Nota: ); EscribirNota(unAlumno.nota) end; {EscribirFicha}

Obsrvese que los campos fecha.mes y nota son de tipo enumerado y e necesitarn dos procedimientos especiales (EscribirMes y EscribirNota, a respectivamente) para poder escribir sus valores por pantalla.

13.1. Descripcion del tipo de datos registro


Instruccin

275

with

vbleRegistro

do

Figura 13.2.

Al igual que todos los tipos de datos compuestos, un registro no puede ser el resultado de una funcin. Para solucionar este problema actuaremos o como de costumbre, transformando la funcin en un procedimiento con un o parmetro por variable adicional de tipo registro que albergue el resultado a de la funcin. As por ejemplo: o ,
procedure LeerFicha(var unAlumno: tFicha); {Efecto: lee del input el contenido del registro unAlumno} begin Write(Nombre del alumno:); LeerNombre(unAlumno.nombre); Write(Da de nacimiento: ); ReadLn(unAlumno.fechaNac.dia); Write(Mes de nacimiento: ); LeerMes(unAlumno.fechaNac.mes); Write(A~o de nacimiento: ); n ReadLn(unAlumno.fechaNac.anno); Write(Calificacin: ); o LeerNota(unAlumno.nota) end; {LeerFicha}

Como puede observarse, es incmodo estar constantemente repitiendo el o identicador unAlumno. Para evitar esta repeticin Pascal dispone de la o instruccin with que se emplea siguiendo la estructura descrita en el diao grama sintctico de la gura 13.2. a Por ejemplo, para el procedimiento LeerFicha se podr utilizar dos an instrucciones with anidadas de la siguiente forma:
procedure LeerFicha(var unAlumno: tFicha); {Efecto: lee del input el contenido del registro unAlumno} begin with unAlumno do begin WriteLn(Introduce el nombre del alumno:); LeerNombre(nombre); with fechaNac do begin

276
Write(Da de nacimiento: ReadLn(dia); Write(Mes de nacimiento: LeerMes(mes); Write(A~o de nacimiento: n ReadLn(anno) end; {with fechaNac} Write(Calificacin: ); o LeerNota(nota) end {with unAlumno} end; {LeerFicha} ); ); );

Cap tulo 13. Registros

Al igual que los arrays, si dos variables r1 y r2 son del mismo tipo registro, se pueden realizar asignaciones de registros completos (con la instruccin o r1:= r2) evitando as tener que ir copiando uno a uno todos los campos del registro.

13.1.2

Registros con variantes

En ciertos casos es conveniente poder variar el tipo y nombre de algunos de los campos existentes en un registro en funcin del contenido de uno de o ellos. Supongamos, por ejemplo, que en el registro tFicha denido anteriormente queremos incluir informacin adicional dependiendo de la nacionalidad. Si es o espaola, aadiremos un campo con el D.N.I., y si no lo es, aadiremos un n n n campo para el pa de origen y otro para el nmero del pasaporte. s u Con este objetivo se pueden denir en Pascal los registros con variantes, que constan de dos partes: la primera, llamada parte ja, est formada por aquellos a campos del registro que forman parte de todos los ejemplares; la segunda parte, llamada parte variable, est formada por aquellos campos que slo forman parte a o de algunos ejemplares. En la parte ja, debe existir un campo selector mediante el cual se determina la parte variable que se utilizar. Este campo selector debe ser unico, es decir, a slo se permite un campo selector. o El diagrama sintctico de la denicin de un registro con variantes es el de a o la gura 13.3. Para resolver el problema del ejemplo anterior se puede emplear un registro con variantes de la siguiente forma:

13.1. Descripcion del tipo de datos registro

277

Lista de campos

Parte variante

record

end

Lista de campos:

Identificador ,

Tipo

; Parte variante: case

Identificador

Tipo

of ;

...

, ... Constante : ( Lista de Campos

Figura 13.3.

278
type... tNombre = array[1..50] of char; tDNI = array[1..8] of 0..9; tPais = array[1..20] of char; tPasaporte = array[1..15] of 0..9; tFicha = record nombre: tNombre; fechaNac: tFecha; nota: tCalificaciones; case espannol : boolean of True : (dni : tDNI; False : (pais : tPais; pasaporte : tPasaporte end; {tFicha}

Cap tulo 13. Registros

Con la denicin anterior, el procedimiento LeerFicha queda como sigue: o


procedure LeerFicha(var unAlumno: tFicha); {Efecto: lee del input el contenido del registro unAlumno} var c: char; begin with unAlumno do begin Write(Introduce el nombre del alumno:); ReadLn(nombre); with fechaNac do begin Write(Da de nacimiento: ); ReadLn(dia); Write(Mes de nacimiento: ); LeerMes(mes); Write(A~o de nacimiento: ); n ReadLn(anno) end; {with fechaNac} Write(Calificacion: ); LeerNota(nota); repeat Write(Es ,nombre, espa~ol? (S/N)); n ReadLn(c) until c in [s,S,n,N]; case c of s,S : begin {el alumno es espa~ol} n espannol:= True; Write(DNI: );

13.2. Arrays de registros y registros de arrays


Campos comunes

279

...

Campos variantes

Figura 13.4. LeerDNI(dni) end; n,N : begin {el alumno es extranjero} espannol:= False; Write(pas: ); LeerPais(pais); Write(pasaporte: ); LeerPasaporte(pasaporte) end end {case} end {with unAlumno} end; {LeerFicha}

La utilizacin de registros con campos variantes relaja en parte la condicin o o de tipos fuertes de Pascal al permitir que una variable de esta clase almacene valores de diferentes tipos. Sin embargo, debe tenerse cuidado con el uso de la parte variante, ya que los compiladores no suelen comprobar que esa parte se utiliza correctamente. Al implementar los registros con variantes el compilador hace reserva de memoria para la variante ms grande, aunque no se aproveche en el caso de las a variantes ms pequeas, como se muestra en la gura 13.4. a n

13.2

Arrays de registros y registros de arrays

Dado que los registros permiten almacenar datos de diferentes tipos correspondientes a una persona u objeto y que los arrays agrupan datos de un mismo

280

Cap tulo 13. Registros

tipo, es frecuente combinarlos formando arrays de registros que permitan almacenar y gestionar la informacin relativa a un grupo de personas u objetos. o Por ejemplo, una vez denido el tipo tFicha del apartado 13.1.1 donde almacenamos los datos de un alumno, podemos denir el tipo siguiente:
type tVectorFichas = array[1..40] of tFicha;

y declarar las variables:


var alumno: tFicha; clase: tVectorFichas;

De esta forma, en el vector clase podemos almacenar los datos de los alumnos de una clase. Para acceder al ao de nacimiento del alumno nmero 3, n u tendr amos que utilizar la siguiente expresin: o clase[3].fecha.anno mientras que para acceder a su nota usar amos: clase[3].nota Los arrays tienen un tamao jo, sin embargo, hay casos en los que el nmero n u de datos no se conoce a priori, pero para los que puede suponerse un mximo. a En este caso, se puede denir un registro que contenga a un vector del tamao n mximo y una variable adicional para llevar la cuenta de la parte utilizada. a Por otra parte, hay casos en los que puede resultar util denir registros de arrays. Supongamos, por ejemplo, que queremos calcular el valor medio de una variable estad stica real de una muestra cuyo tamao no supera los cien n individuos. Para ello se podr hacer las siguientes deniciones y declaraciones: an
const MaxCompo = 100; type tVector = array [1..MaxCompo] of real; tRegistro = record vector: tVector; ocupado: 0..MaxCompo endtRegistro; var indice: 1..MaxCompo; reg: tRegistro; valor, suma, media: real; fin: boolean;

13.2. Arrays de registros y registros de arrays

281

reg.ocupado=0

0<reg.ocupado<MaxCompo

reg.ocupado=MaxCompo

ocupado lleno vaco

Figura 13.5.

Para introducir los datos hay que comprobar si el vector est lleno y, en a caso contrario, se pide el dato y se incrementa el valor de reg.ocupado, que almacena el ndice del ultimo elemento introducido. Si reg.ocupado vale cero, el vector est vac si 0 < reg.ocupado < MaxCompo tiene reg.ocupado daa o, tos y quedan MaxCompo reg.ocupado espacios libres, y por ultimo, cuando reg.ocupado = MaxCompo, el vector est lleno. Todas estas situaciones se recoa gen en la gura 13.5. Para introducir los valores en el vector podr amos escribir un fragmento de programa como el siguiente:
reg.ocupado:= 0; fin:= False; while (reg.ocupado < MaxCompo) and not(fin) do begin Write(Introduzca el valor del individuo , reg.ocupado + 1) Write( o un valor negativo para terminar ); ReadLn(valor); if valor >= 0 then begin reg.ocupado:= reg.ocupado + 1; reg.vector[reg.ocupado]:= valor end else fin:= True end {while}

Para calcular la media se recorre la parte ocupada del vector acumulando sus valores, como se muestra a continuacin: o
for indice:= 1 to reg.ocupado do suma:= suma + reg.vector[indice]; media:= suma/reg.ocupado

282

Cap tulo 13. Registros

13.3

Ejercicios

1. Dena un tipo de datos para manejar fechas, incluyendo la informacin usual para o un d cualquiera del calendario: el nmero de d dentro del mes, el d de la a u a a semana, el mes y el ao, y con l, los siguientes subprogramas: n e (a) Lectura y escritura. (b) Avance (que pasa de un d al siguiente), con ayuda de una funcin que a o indica el nmero de d de un mes de un cierto ao. u as n (c) Distancia entre fechas, usando la funcin avance. o 2. Considrese una representacin de los nmeros reales mediante su signo, positivo e o u o negativo, su parte entera, formada por N d gitos (por ejemplo, 25) de cero a nueve y por su parte decimal, formada por NDEC cifras (por ejemplo, 5). (a) Dena en Pascal este tipo de datos. (b) Dena procedimientos apropiados para su lectura y escritura. (c) Dena un subprograma para sumar reales, controlando el posible desbordamiento. (d) Dena un subprograma para comparar reales. 3. (a) Dena un tipo de datos registro que permita almacenar un punto del plano real y un tipo vector formado por tres registros del tipo anterior.2 (b) Escriba un subprograma que determine si los tres puntos almacenados en una variable del tipo vector forman un tringulo. a (c) Escriba un subprograma tal que, si tres puntos forman un tringulo, calcule a su rea aplicando la frmula de Hern (vase el ejercicio 6 del cap a o o e tulo 4). 4. (a) Dena un tipo registro que permita almacenar un ngulo dado en forma de a grados (sexagesimales), minutos y segundos. (b) Escriba dos subprogramas, el primero para convertir un ngulo dado en a radianes en una variable del tipo registro anterior, y el segundo para realizar la conversin inversa. En el primero se tendr en cuenta que el resultado o a debe tener el nmero de grados inferior a 360o , el de minutos inferior a 60 u y el de segundos inferior a 60. 5. La posicin de un objeto lanzado con velocidad inicial v y con ngulo con o a respecto a la horizontal, transcurrido un cierto tiempo t, puede expresarse (despreciando el rozamiento) con las siguientes ecuaciones: x = vt cos 1 y = vt sen gt2 2

donde g es la aceleracin de la gravedad (9.8m/seg 2 ). o (a) Dena un registro que almacene la velocidad inicial y el ngulo . a
Obsrvese que sta es una denicin alternativa a la de arrays, tal vez ms apropiada por ser e e o a las componentes del mismo tipo.
2

13.3. Ejercicios

283

(b) Dena un registro que almacene las coordenadas x, y y el tiempo t transcurrido desde el lanzamiento. (c) Escriba un subprograma que utilice un registro de cada uno de los tipos anteriores y calcule la posicin del objeto a partir de v, y t. o (d) Escriba un subprograma que calcule la altura mxima aproximada alcana zada por el objeto utilizando intervalos de tiempo pequeos, por ejemplo n dcimas o centsimas de segundo. e e (e) Escriba un subprograma que calcule la distancia mxima aproximada alcana zada por el objeto, con la misma tcnica del apartado anterior. Indicacin: e o la distancia mxima se alcanza cuando la altura se hace cero. a (f) Redena los registros de los apartados anteriores para utilizar ngulos en a forma de grados (sexagesimales), minutos y segundos, realizando las conversiones con los subprogramas del apartado 4b. 6. Un comercio de alimentacin almacena los siguientes datos de sus productos: o producto (nombre del producto), marca (nombre del fabricante), tamao (un n nmero que indica el peso, volumen, etc. de un determinado envase del producto), u precio (del tamao correspondiente) y unidades (cantidad existente en inventario). n (a) Dena un tipo de datos registro que permita almacenar dichos campos. (b) Dena un vector sucientemente grande de dichos registros. (c) Dena un registro que incluya el vector y un campo adicional para indicar la parte ocupada del vector de acuerdo con la tcnica expuesta en el e apartado 13.2. (d) Escriba los subprogramas necesarios para realizar las siguientes operaciones: Altas: Consiste en introducir nuevos productos con todos sus datos. Normalmente se efectuarn varias altas consecutivas, deteniendo el proa ceso cuando se introduzca un producto cuyo nombre comience por una clave especial (un asterisco, por ejemplo). Bajas: Se introduce el nombre del producto a eliminar, se muestran sus datos y se pide al usuario conrmacin de la baja. Para eliminar o el producto se desplazan desde el producto inmediato siguiente una posicin hacia delante los sucesivos productos, y se reduce en una unidad o la variable que indica la posicin ocupada. Indicacin: se deben tratar o o de forma especial los casos en que no hay productos, cuando slo hay o uno y cuando se elimina el ultimo. Modicaciones: Se introduce el nombre del producto, se muestra y se piden todos sus datos. Consultas: Pueden ser de dos tipos: por producto, pidiendo el nombre y mostrando todos sus datos, y total, mostrando el listado de todos los productos con sus datos. Se puede aadir el coste total por producto n y el coste total del inventario. Se puede mostrar por pantalla y por impresora.

284

Cap tulo 13. Registros


(e) Escriba un programa completo comandado por un men con las operaciones u del apartado anterior.

7. Construya un tipo registro para manejar una hora del d dada en la forma de a, horas (entre 0 y 23), minutos y segundos (entre 0 y 59). Utilizando este tipo, escriba subprogramas para: (a) Leer correctamente un instante dado. (b) Mostrar correctamente una hora del d a. (c) Dado un tiempo del d pasarlo a segundos. a, (d) Dados dos tiempos del d calcular su diferencia, en horas, minutos y sea, gundos. (e) Dado un tiempo del d calcular el instante correspondiente a un segundo a, despus. e (f) Dado un tiempo del d mostrar un reloj digital en la pantalla durante a, un nmero de segundos predeterminado. (El retardo se puede ajustar con u bucles vac os.) 8. La posicin de un punto sobre la supercie de la tierra se expresa en funcin de o o su longitud y latitud. La primera mide el ngulo que forma el meridiano que pasa a por el punto con el meridiano que pasa por el observatorio de Greenwich, y toma valores angulares comprendidos entre 0 y 180o , considerndose longitud Este (E) a cuando el ngulo se mide hacia el Este y longitud Oeste (W) en caso contrario. a La segunda mide el ngulo que forma la l a nea que pasa por el punto y por el centro de la tierra con el plano que contiene al ecuador, y toma valores angulares comprendidos entre 0 y 90o , considerndose latitud Norte (N) cuando el punto a est situado al Norte del ecuador y latitud Sur (S) en caso contrario. En ambos a casos los valores angulares se miden en grados, minutos y segundos. Dena un tipo registro que permita almacenar los datos anteriores. Escriba subprogramas para leer y escribir correctamente variables del tipo anterior, en grados, minutos y segundos. 9. Escriba dos tipos registro, uno para almacenar las coordenadas cartesianas de un punto del plano y otro para almacenarlo en coordenadas polares con el ngulo a en radianes. Escriba subprogramas para pasar de unas coordenadas a otras. Escriba un subprograma que calcule la distancia entre dos puntos en coordenadas cartesianas, polares y ambas. 10. Dada una lista de puntos del plano, en un vector no completo (vase el apare tado 13.2) que se supone que denen los vrtices de un pol e gono, determine el a rea del mismo mediante la frmula siguiente: o A= 1 ((X2 Y1 X1 Y2 ) + (X3 Y2 X2 Y3 ) + (X4 Y3 X3 Y4 ) + . . . + (X1 YN XN Y1 )) 2

Cap tulo 14

Archivos

14.1 Descripcin del tipo de datos archivo . . . . . . . . . . 285 o 14.2 Manejo de archivos en Pascal . . . . . . . . . . . . . . . 286 14.3 Archivos de texto . . . . . . . . . . . . . . . . . . . . . . 294 14.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

Como se dijo en [PAO94] (vanse los apartados 4.2.2 y 6.1.1) los archivos se e pueden entender como un conjunto de datos que se almacenan en un dispositivo de almacenamiento, por ejemplo, una unidad de disco. Para expresar este concepto, Pascal dispone del tipo de datos estructurado le (archivo en ingls), que se explica en los apartados siguientes. e

14.1

Descripcin del tipo de datos archivo o

Un archivo en Pascal se estructura como una secuencia homognea de datos, e de tamao no jado de antemano, la cual se puede representar como una la de n celdas en las que se almacenan los datos componentes del archivo. Una marca especial llamada n de archivo seala el n de la secuencia. La estructura del n archivo se muestra grcamente en la gura 14.1. a De hecho, hemos trabajado con archivos en Pascal desde el principio de este texto, ya que, como sabemos, los programas que producen alguna salida por pantalla necesitan el archivo estndar output, que se incluye en el encabezamiento a

286

Cap tulo 14. Archivos

comp

comp

comp

comp

...

comp

Fin de archivo

Figura 14.1.

de la siguiente forma:1
Program NombrePrograma (output);

Si el programa requiere, adems, que se introduzca algn valor por teclado, a u necesitaremos tambin el archivo estndar input que debe entonces declararse e a en el encabezamiento del programa:
Program NombrePrograma (input, output);

En este apartado se detalla cmo utilizar otros archivos que sirvan para la o lectura y escritura de datos. Estos nuevos tipos de archivos de entrada y salida se asociarn a archivos almacenados en unidades de disco. Pascal permite acceder a a ellos para guardar datos que posteriormente podrn ser le a dos por el mismo o por otro programa. Imaginemos que se ejecuta el programa AlmanaqueSigloXX que aparece como ejemplo en el apartado 12.3. Una vez que hemos ejecutado dicho programa ser a de gran utilidad almacenar los resultados obtenidos en un archivo y, as poder , utilizarlos posteriormente sin tener que generar nuevamente el almanaque, con el consiguiente ahorro de tiempo. En los siguientes apartados se presentan los archivos de Pascal en general, y se detalla el caso particular de los archivos de texto, por su frecuente utilizacin. o Los archivos tienen como limitacin el que sus elementos no pueden ser archio vos. Por lo tanto, no es posible la declaracin o
tArchivo = le of le of ...;

14.2

Manejo de archivos en Pascal

Un archivo es un tipo de datos estructurado que permitir almacenar en una a unidad de disco informacin homognea, es decir, datos de un mismo tipo, ya sea o e
Es conveniente incluir siempre en el encabezamiento del programa el archivo de salida output aunque no se vaya a realizar ninguna salida por pantalla, ya que sto permitir al programa e a escribir en pantalla los mensajes de error que pudieran originarse.
1

14.2. Manejo de archivos en Pascal


tipoBase

287

file

of

Figura 14.2.

bsico o estructurado, por lo que las componentes del archivo van a ser valores a de este tipo. Como es natural, antes de trabajar con el tipo de datos archivo es necesario conocer su denicin. El diagrama sintctico de la denicin de archivos es el o a o de la gura 14.2. Por ejemplo, si queremos trabajar con un archivo cuyos elementos sean arrays de caracteres, utilizaremos la siguiente denicin: o
type tTarjeta = array[1..50] of char; tArchivo = le of tTarjeta; var unaTarjeta: tTarjeta; archivoTarjetas: tArchivo;

Si en un programa Pascal se va a utilizar un archivo externo, es necesario incluir el identicador del archivo (por ejemplo nombreArchivo) en el encabezamiento. Por tanto, un programa que maneje el archivo nombreArchivo, debe ser declarado como sigue:
Program TratamientoDeArchivo (input, output, nombreArchivo);

Con esta declaracin el programa ya reconocer al archivo con el que vamos o a a trabajar y estar preparado para poder realizar operaciones de acceso a dicho a archivo.2 As en el ejemplo anterior debemos realizar la siguiente declaracin , o de programa:
Program TratamientoDeTarjetas (input, output, archivoTarjetas);
Adems, es necesario asociar el archivo lgico (declarado en el programa) con un archivo a o f sico en el disco (vase el apartado B.9). Para realizar esta operacin en Turbo Pascal se e o utiliza la instruccin Assign (nombreArchivoLogico, NombreArchivoFisico). Por ejemplo, la o instruccin o Assign (archivoTarjetas, C:\TARJETAS\DATOS.TXT) indica que los datos de archivoTarjetas se almacenarn en el archivo de disco DATOS.TXT dentro a del subdirectorio TARJETAS de la unidad C:.
2

288

Cap tulo 14. Archivos

comp1

comp

comp 3

comp

...

comp

Fin de archivo

nombreArchivo^ comp

Figura 14.3.

Para acceder a las componentes de un archivo se utiliza el llamado cursor del archivo. Este cursor se puede entender como una ventana por la cual vemos una componente del archivo (aqulla a la que apunta), como se muestra en la e gura 14.3. La notacin utilizada en Pascal para referenciar el cursor de un archivo es:3 o
nombreArchivo^

Como se cit al comienzo de este cap o tulo, todo archivo tiene asociada una marca de n de archivo. En Pascal se dispone de una funcin booleana llao mada EoF.4 Para utilizarla tendremos que indicarle el nombre del archivo. As , EoF(nombreArchivo) devuelve el valor False si no se ha alcanzado el nal del archivo o True en caso contrario (en cuyo caso el contenido del cursor nombreArchivo^ es indeterminado). Hemos de destacar en este momento que, como se puede comprobar, el manejo de archivos en Pascal no es muy eciente debido a que slo se dispone de o archivos secuenciales.

14.2.1

Operaciones con archivos

Las operaciones ms importantes que se pueden realizar con los archivos son a la escritura y lectura de sus componentes. Estas operaciones se van a llevar a cabo de forma muy similar a la lectura y escritura usual (con las instrucciones Read y Write), salvo que se redireccionarn al archivo adecuado. a Un archivo se crea o se ampl escribiendo en l. Cada vez que se realice a e una operacin de escritura, se aadir una nueva componente al nal del archivo o n a
3 En Turbo Pascal no se puede utilizar directamente el cursor nombreArchivo^ ni las operaciones Put y Get que se presentan ms adelante; posiblemente sta es la razn por la que esta a e o notacin ha ca en desuso. (Vase el apartado B.9 para ver las operaciones equivalentes.) o do e 4 Del ingls End Of File (n de archivo). e

14.2. Manejo de archivos en Pascal

289

Rewrite(nombreArchivo) Fin de archivo

nombreArchivo^

???

EoF(nombreArchivo) = true

Figura 14.4.

secuencial. El cursor del archivo avanzar una posicin cada vez que se escriba a o o se lea en el archivo. La creacin de un archivo se hace mediante la siguiente instruccin: o o
Rewrite(nombreArchivo)

que sita el cursor al principio del archivo nombreArchivo y, adems, destruye u a cualquier posible informacin existente en l, como se muestra en la gura 14.4. o e Una vez ejecutada la instruccin anterior, el archivo est preparado para recibir o a operaciones de escritura como la siguiente:
Put(nombreArchivo)

que aade una componente ms al archivo en la posicin que indique el cursor n a o y avanza ste un lugar en el archivo nombreArchivo, como puede verse en la e representacin grca de la gura 14.5. Despus de ejecutar dicha instruccin, o a e o nombreArchivo^ queda indenido. As teniendo en cuenta la declaracin hecha anteriormente y suponiendo , o que la variable unaTarjeta posee la informacin que queremos almacenar en el o archivo archivoTarjetas, realizaremos las siguientes instrucciones para aadir n una nueva componente al archivo:
archivoTarjetas^:= unaTarjeta; Put(archivoTarjetas)

Estas dos instrucciones son equivalentes a la instruccin: o

290

Cap tulo 14. Archivos

Fin de archivo nombreArchivo^ = componente

nombreArchivo^

componente

Put(nombreArchivo)

componente

Fin de archivo

nombreArchivo^

???

En la ejecucin de Put(nombreArchivo) se hace hueco para una componente y se copia su valor desde nombreArchivo^

EoF(nombreArchivo) = true

Figura 14.5.

14.2. Manejo de archivos en Pascal

291

Reset(nombreArchivo)

comp1

comp 2

comp

...

comp n

Fin de archivo

nombreArchivo^

comp1

EoF(nombreArchivo) = false

Figura 14.6. Write(archivoTarjetas, unaTarjeta);

Dado que Pascal trabaja con archivos secuenciales, el cursor est siempre a situado al nal del archivo cuando se va a realizar una operacin de escritura. o Las sucesivas componentes se van aadiendo por el nal del archivo, desplazando n la marca de n de archivo. Una vez que hemos creado un archivo, es importante poder leer sus componentes. Dado que el acceso se hace de forma secuencial, hay que situar, nuevamente, el cursor al principio del archivo. Para ello, se ejecutar la instruccin: a o
Reset(nombreArchivo)

Con esta instruccin tambin se coloca el cursor en la primera componente o e del archivo. Si el archivo no est vac su primera componente est disponible a o, a en la variable nombreArchivo^, como puede comprobarse en la gura 14.6.
e e Obsrvese que las funciones Rewrite y Reset son muy parecidas, ya que r r e ambas sitan el cursor al principio del archivo. La diferencia existente entre u ambas es que la primera prepara el archivo exclusivamente para escritura (destruyendo la informacin existente), mientras que la segunda lo prepara o exclusivamente para lectura.

Una vez que el cursor apunte a la primera componente, se puede mover el cursor a la siguiente posicin y copiar la informacin de la siguiente componente o o de nombreArchivo utilizando la instruccin o

292

Cap tulo 14. Archivos

Get(nombreArchivo)

comp

comp

comp

comp

...

compn

Fin de archivo

nombreArchivo^

comp 2

...

Fin de archivo

nombreArchivo^

???

EoF(nombreArchivo) = false

Figura 14.7. Get(nombreArchivo)

Su efecto se muestra en la gura 14.7. Siguiendo con el ejemplo anterior, si deseamos leer el contenido de la componente del archivo apuntada por el cursor realizaremos las siguientes instrucciones:
unaTarjeta:= archivoTarjetas^; Get(archivoTarjetas)

o, equivalentemente,
Read(archivoTarjetas, unaTarjeta)

Es muy importante tener en cuenta que antes de leer de un archivo se ha de comprobar, mediante la funcin EoF, que quedan componentes por leer. Esta o

14.2. Manejo de archivos en Pascal

293

funcin es imprescindible para realizar la lectura de archivos en los que descoo nocemos a priori el nmero de componentes o para detectar archivos vac u os. En el siguiente ejemplo se presenta el esquema de un programa que lee un archivo completo controlando el nal de archivo con EoF:
Program LeerArchivo (input, output, archivoTarjetas); type tTarjeta = array[1..50] of char; tArchivo = le of tTarjeta; var archivoTarjetas: tArchivo; una Tarjeta: tTarjeta begin {LeerArchivo} ... Reset(archivoTarjetas); while not EoF(archivoTarjetas) do begin Read(archivoTarjetas, unaTarjeta); Procesar unaTarjeta end; {while} end. {LeerArchivo}

El principal inconveniente que presentan los archivos en Pascal es que no se pueden alternar las operaciones de lectura y escritura en un archivo. Por tanto, si deseamos escribir y leer un archivo, en primer lugar se tendr que escribir en a l, y posteriormente situar el cursor al principio del archivo para leerlo. e Para realizar una copia de un archivo no se puede utilizar la asignacin, a o diferencia de los dems tipos de datos. Para poder copiar un archivo en otro se a debe desarrollar un procedimiento cuyo cdigo podr ser: o a
type tTarjeta = array[1..50] of char; {por ejemplo} tArchivoTarjetas = le of tTarjeta; ... procedure CopiarArchivo(var archiEnt, archiSal: tArchivoTarjetas); {Efecto: archiSal:= archiEnt} var unaTarjeta: tTarjeta; begin Reset(archiEnt); Rewrite(archiSal); while not EoF(archiEnt) do begin Read(archiEnt, unaTarjeta); Write(archiSal, unaTarjeta) end {while} end; {CopiarArchivo}

294

Cap tulo 14. Archivos

14.3

Archivos de texto

Son muy frecuentes los programas en los que es necesario manejar textos, entendidos como secuencias de caracteres de una longitud usualmente grande, como, por ejemplo, una carta, un formulario, un informe o el cdigo de un o programa en un lenguaje de programacin cualquiera. Estos textos se almacenan o en archivos de caracteres que reciben el nombre de archivos de texto. Los archivos de texto en Pascal se denen utilizando el tipo predenido text. Este tipo de datos es un archivo con tipo base char al que se aade una marca n de n de l nea. La generacin y tratamiento del n de l o nea se realiza con los procedimientos WriteLn(archivoDeTexto) y ReadLn(archivoDeTexto) y la funcin EoLn(archivoDeTexto), que no se pueden utilizar con el tipo le. o El tipo text es un tipo estndar predenido en Pascal, como integer o char, a y por lo tanto, se pueden declarar variables de este tipo de la siguiente forma:
var archivoDeTexto: text;

En particular, los archivos input y output son de texto y representan la entrada y salida estndar, que en vez de emplear un disco utilizan, normalmente, a el teclado y la pantalla como origen y destino de las secuencias de caracteres. Siempre que se utiliza una instruccin Read o Write sin indicar el archivo, se o asume que dichas operaciones se realizan sobre los archivos input y output, respectivamente. Ambos archivos, como es sabido, deben ser incluidos en el encabezamiento de todo programa sin redeclararlos dentro del programa, ya que se consideran declarados impl citamente como:
var input, output : text;

Adems de esta declaracin impl a o cita se asumen, para los archivos input y output, las instrucciones Reset(input) y ReWrite(output) efectuadas al empezar un programa. Debido a que los archivos de texto son muy utilizados, Pascal proporciona, adems de las instrucciones comunes a todos los archivos con tipo genrico, funa e ciones espec cas de gran utilidad para los archivos de texto. Si archivoDeTexto es una variable de tipo text, segn lo que vimos en el apartado anterior, slo u o podr amos realizar instrucciones de la forma Read(archivoDeTexto, c) o bien Write(archivoDeTexto, c) siempre que c fuese de tipo char. Sin embargo, para los archivos input y output se permite que c pueda ser tambin de tipo e integer, real o incluso boolean5 o un array de caracteres para el caso de
5

Slo para archivos de salida. o

14.3. Archivos de texto

295

Write.6 Pascal permite este hecho no slo a los archivos input y output, sino a o todos los archivos de tipo text. Pascal tambin permite que las instrucciones Read y Write tengan varios e parmetros cuando se usan archivos de texto. As la instruccin a , o
Read(archivoDeTexto, v1, v2, ..., vN)

es equivalente a:
Read(archivoDeTexto, v1); Read(archivoDeTexto, v2); ... Read(archivoDeTexto, vN)

y la instruccin o
Write(archivoDeTexto, e1, e2, ..., eM)

es equivalente a:
Write(archivoDeTexto, e1); Write(archivoDeTexto, e2); ... Write(archivoDeTexto, eM)

donde archivoDeTexto es un archivo de tipo text, los parmetros v1, v2,..., a vN pueden ser de tipo integer, real o char y los parmetros e1,e2,...,eM a pueden ser de tipo integer, real, char, boolean o un array de caracteres. Las operaciones de lectura y escritura en archivos de texto son similares a los de input o output (vanse los apartados 4.3.2 y 4.3.3, donde se detalla su e funcionamiento). Los archivos de texto pueden estructurarse por l neas. Para manejar este tipo de organizacin existen la funcin EoLn(archivoDeTexto) para detectar el o o n de l nea y las instrucciones de lectura y escritura ReadLn(archivoDeTexto) y WriteLn(archivoDeTexto). Su funcionamiento se describe a continuacin: o La funcin booleana EoLn7 devuelve el valor True si se ha alcanzado la o marca de n de l nea o False en otro caso. Cuando EoLn(ArchivoDeTexto) ; True el valor de la variable apuntado por el cursor (archivoDeTexto^)
6 7

En estos casos se ejecutarn automticamente subprogramas de conversin. a a o Del ingls End Of LiNe (n de l e nea.)

296
ArchivoDeTexto

Cap tulo 14. Archivos

Fin de lnea

EoLn(ArchivoDeTexto) = false

Fin de lnea

EoLn(ArchivoDeTexto) = true

Figura 14.8.

es un caracter especial de n de l nea.8 En la gura 14.8 puede verse una representacin grca. o a La instruccin ReadLn(archivoDeTexto, v) lee el siguiente elemento del o archivo, lo almacena en v y salta todos los caracteres hasta llegar al carcter a especial de n de l nea, es decir, la instruccin es equivalente a: o
Read(archivoDeTexto, v); while not EoLn(archivoDeTexto) do Get(archivoDeTexto); {se avanza el cursor hasta encontrar el fin de lnea} Get(archivoDeTexto) {se salta el fin de lnea}

con lo que con la siguiente llamada a Read se leer el primer carcter a a de la siguiente l nea. El parmetro v es opcional. Si se omite, el efecto a de la instruccin Read(archivoDeTexto) ser el mismo salvo que no se o a almacena la componente que est actualmente apuntada por el cursor. e De la misma forma que Read, la instruccin ReadLn puede utilizarse con el o formato:
8 En realidad este carcter especial depende del compilador que se utilice. As por ejemplo, en a , Pascal estndar se devuelve un espacio en blanco, mientras que Turbo Pascal devuelve el carcter a a de alimentacin de l o nea (Line Feed, nmero 10 del juego de caracteres ASCII) seguido de un u retorno de carro (Carriage Return, nmero 13 del juego de caracteres ASCII). u

14.3. Archivos de texto


ReadLn(archivoDeTexto, v1, v2, ..., vN)

297

que es equivalente a:
Read(archivoDeTexto, v1); Read(archivoDeTexto, v2); ... ReadLn(archivoDeTexto, vN)

La instruccin WriteLn(archivoDeTexto, expresin) se usa para escrio o bir el valor de expresin en el archivoDeTexto y poner la marca de n de o l nea. Asimismo, podemos omitir la expresin, producindose entonces o e simplemente un salto de l nea. Existe tambin la posibilidad de utilizar la instruccin: e o
WriteLn(archivoDeTexto, e1, e2, ..., eM)

que es equivalente a:
Write(archivoDeTexto, e1); Write(archivoDeTexto, e2); ... WriteLn(archivoDeTexto, eM)

Como una generalizacin de lo dicho en este tema sobre el uso de las funciones o EoLn y EoF, se observa que la estructura general de los programas que procesan archivos de texto constan frecuentemente de dos bucles anidados: uno controlado por EoF y otro por EoLn, como se muestra a continuacin, en un programa o genrico de procesamiento de archivos de texto. e
Program LecturaArchivoTexto (input, output, archivoDeTexto); Denicin de tipos y declaracin de variables o o begin {Proceso de archivo de texto} ... Reset(archivoDeTexto); while not EoF(archivoDeTexto) do begin while not EoLn(archivoDeTexto) do begin Read(archivoDeTexto, dato);

298
Procesar dato end; {while not EoLn} ReadLn(archivoDeTexto) end {while not EoF} ... end. {LecturaArchivoTexto}

Cap tulo 14. Archivos

En cualquiera de los casos considerados anteriormente, si se omite el archivo archivoDeTexto se supondr por defecto el archivo input para el caso de Read o a el archivo output para el caso de Write, producindose, en tal caso, una lectura e por teclado o una escritura por pantalla respectivamente. Finalmente se debe destacar que los archivos, al igual que todo tipo de datos compuesto, se pueden pasar como parmetros en funciones y procedimientos, a pero no pueden ser el resultado de una funcin. No obstante, siempre se deben o pasar como parmetros por variable. a

14.4

Ejercicios

1. Basndose en el ejercicio 2 del cap a tulo 10 desarrolle un programa para las siguientes tareas: (a) Invertir una serie de l neas, manteniendo su orden. (b) Copiar una serie de l neas en orden inverso. e 2. Escriba un programa que tabule los coecientes binomiales n (vase el ejercik cio 10 del cap tulo 6), confeccionando el archivo BINOM.TXT de la siguiente forma: 1 1 1 1 2 1 3 1 4 1 5 ...

1 3 1 6 4 1 10 10 5 1
n k

Escriba una funcin que, en vez de hallar el coeciente o creada.

, lo consulte en la tabla

3. Se desea tabular los valores de la funcin de distribucin normal o o f (t) = 1 1 + 2 2


t 0

ex

/2

dx

para los valores de t entre 0 00 y 1 99, aumentando a pasos de una centsima e (vase el ejercicio 11 del cap e tulo 6).

14.4. Ejercicios

299

Desarrolle un programa que construya un archivo de texto NORMAL.TXT con veinte las de diez valores, as : f (0.00) f (0.10) ... f (1.90) f (0.01) f (0.11) ... f (1.91) ... ... ... ... f (0.09) f (0.19) ... f (1.99)

Escriba una funcin que extraiga de la tabla NORMAL.TXT creada el valor correso pondiente en vez de calcularlo. 4. Se ha efectuado un examen de tipo test, con 20 preguntas, a los alumnos de un grupo. En el archivo EXAMENES.TXT se hallan las respuestas, con arreglo al siguiente formato: en los primeros 25 caracteres se consigna el nombre, a continuacin sigue un espacio en blanco y, en los 20 siguientes, letras de la A a la E, o correspondientes a las respuestas. En la primera l nea del archivo SOLUCION.TXT se hallan las soluciones correctas consignadas en las 20 primeras casillas. Se considera que una respuesta vlida suma cuatro puntos, una incorrecta resta un a punto y un carcter distinto a los posibles anula esa pregunta, no puntuando posia tiva ni negativamente. La nota se halla usando la expresin Round(puntuacin/8). o o Escriba un programa que confeccione otro archivo RESULT.TXT con la lista de aprobados junto con la puntuacin obtenida. o 5. (a) Partiendo del conjunto de los primos entre 2 y 256 creado mediante el algoritmo de la criba de Eratstenes (vase el apartado 11.3.3), escriba un o e programa que los guarde en un archivo de disco, PRIMOS.TXT. (b) Dena una funcin que compruebe si un nmero menor que 256 es primo, o u simplemente consultando la tabla PRIMOS.TXT. (Al estar ordenada ascendentemente, con frecuencia ser innecesario llegar al nal de la misma.) a (c) Dena una funcin que compruebe si un nmero n entre cien y 2562 es primo, o u tanteando como posibles divisores los nmeros de la tabla PRIMOS.TXT entre u n que sea necesario. 2y (d) Integre los apartados anteriores que convengan en un programa que genere los primos menores que 2562 . 6. Escriba un programa que convierta en archivo de texto, de nombre dado, las l neas introducidas desde el input, de forma similar al funcionamiento de la orden copy ... con: del DOS. Escriba igualmente un programa que muestre un archivo de texto, de nombre dado, por pantalla, tal como hace la orden type del DOS. 7. Escriba un subprograma que reciba dos archivos de texto y los mezcle, carcter a a carcter, en un tercer archivo de texto. Si alguno de los archivos origen terminara a antes que el otro, el subprograma aadir al archivo destino lo que quede del otro n a archivo origen. 8. Aada al programa del ejercicio 6 del cap n tulo anterior una opcin que permita o almacenar y recuperar los datos de los productos en un archivo de texto o con tipo.

Cap tulo 15

Algoritmos de b squeda y u ordenacin o

15.1 Algoritmos de b squeda en arrays . . . . . . . . . . . . 301 u 15.2 Ordenacin de arrays . . . . . . . . . . . . . . . . . . . . 306 o 15.3 Algoritmos de b squeda en archivos secuenciales . . 320 u 15.4 Mezcla y ordenacin de archivos secuenciales . . . . . 322 o 15.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 15.6 Referencias bibliogrcas . . . . . . . . . . . . . . . . . . 330 a

Una vez vistos los distintos tipos de datos que el programador puede denir, se presentan en este cap tulo dos de las aplicaciones ms frecuentes y utiles de a los tipos de datos denidos por el programador: la bsqueda y la ordenacin. u o En particular, estas aplicaciones afectan directamente a los dos tipos de datos estudiados hasta ahora que permiten el almacenamiento de datos: los arrays, para datos no persistentes en el tiempo, y los archivos, para datos que deben ser recordados de una ejecucin a otra de un determinado programa. o

15.1

Algoritmos de b squeda en arrays u

Es evidente que, si tenemos datos almacenados, es interesante disponer de algn mecanismo que permita saber si un cierto dato est entre ellos, y, en caso u a armativo, localizar la posicin en que se encuentra para poder trabajar con o

302

Cap tulo 15. Algoritmos de busqueda y ordenacion

l. Los mecanismos que realizan esta funcin son conocidos como algoritmos de e o bsqueda. u El problema que se plantea a la hora de realizar una bsqueda (concretamente u en un array) puede ser enunciado de la siguiente forma: Supongamos que tenemos un vector v con n elementos (los ndices son los 1. . . n) y pretendemos construir una funcin Busqueda que encuentre un o ndice i de tal forma que v[i] = elem, siendo elem el elemento que se busca. Si no existe tal ndice, la funcin debe devolver un cero, indicando as que el elemento o elem buscado no est en el vector v. En resumen, a Busqueda : Vn (tElem) tElem {0, 1, . . . , n} de forma que
i {1, . . . , n} 0

si existe i tal que elem = vi en otro caso

Busqueda(v, elem) =

En todo el cap tulo se denen los elementos del vector con el tipo tElem:
const N = 100; {tama~o del vector} n type tIntervalo = 0..N; tVector = array [1..N] of tElem;

Para simplicar, digamos por ahora que tElem es un tipo ordinal, siendo por tanto comparables sus valores. Los algoritmos ms usuales que se pueden desarrollar para tal n son los a algoritmos de bsqueda secuencial y bsqueda binaria.1 u u

15.1.1

B squeda secuencial u

La bsqueda secuencial consiste en comparar secuencialmente el elemento u deseado con los valores contenidos en las posiciones 1, . . . , n hasta que, o bien encontremos el ndice i buscado, o lleguemos al nal del vector sin encontrarlo, concluyendo que el elemento buscado no est en el vector. a La bsqueda secuencial es un algoritmo vlido para un vector cualquiera u a sin necesidad de que est ordenado. Tambin se puede aplicar con muy pocas e e
1

Conocida tambin con los nombres de bsqueda dicotmica o por biparticin. e u o o

15.1. Algoritmos de busqueda en arrays

303

variaciones a otras estructuras secuenciales, como, por ejemplo, a los archivos (vase el apartado 15.3). e El primer nivel en el diseo de la funcin BusquedaSec puede ser: n o
ind:= 0; Buscar elem en v; Devolver el resultado de la funcin o

Renando Buscar elem en v, se tiene:


repetir ind:= ind + 1 hasta que v[ind] = elem o ind = n

Por ultimo, renando Devolver el resultado de la funcin se obtiene: o


si v[ind] = elem entonces BusquedaSec:= ind si no BusquedaSec:= 0

Una posible implementacin de la funcin BusquedaSec siguiendo el esquema o o de este algoritmo secuencial es:
function BusquedaSec(v: tVector; elem: tElem): tIntervalo; {Dev. 0 (si elem no est en v) i (si v[i] = elem)} a o var i: tIntervalo; begin i:= 0; {se inicia el contador} repeat {Inv.: j, 0 j i v[j] = elem} i:= i + 1 until (v[i] = elem) or (i = N); {v[i] = elem if v[i] = elem then {se ha encontrado el elemento elem} BusquedaSec:= i else BusquedaSec:= 0 end; {BusquedaSec}

304

Cap tulo 15. Algoritmos de busqueda y ordenacion

15.1.2

B squeda secuencial ordenada u

El algoritmo de bsqueda secuencial puede ser optimizado si el vector v est u a ordenado (supongamos que de forma creciente). En este caso, la bsqueda seu cuencial desarrollada anteriormente es ineciente, ya que, si el elemento buscado elem no se encuentra en el vector, se tendr que recorrer todo el vector, cuando a se sabe que si se llega a una componente con valor mayor que elem, ya no se encontrar el valor buscado. a Una primera solucin a este nuevo problema ser modicar la condicin de o a o salida del bucle repeat cambiando v[i]=elem por v[i]>=elem, debido a que el vector se encuentra ordenado de forma creciente:
function BusquedaSecOrd(v: tVector; elem: tElem): tIntervalo; {PreC.: v est ordenado crecientemente} a {Dev. 0 (si elem no est en v) i (si v[i] = elem)} a o var i: tIntervalo; begin i:= 0; repeat {Inv.: j, 0 j i, v[j] = elem} i:= i + 1 until (v[i]>=elem) or (i=N); {v[i]=elem if v[i] = elem then {se ha encontrado el elemento elem} BusquedaSecOrd:= i else BusquedaSecOrd:= 0 end; {BusquedaSecOrd}

Esta solucin tambin se puede aplicar a archivos secuenciales ordenados o e (vase el apartado 15.3). e

15.1.3

B squeda binaria u

El hecho de que el vector est ordenado se puede aprovechar para conseguir e una mayor eciencia en la bsqueda planteando el siguiente algoritmo: comparar u elem con el elemento central; si elem es ese elemento ya hemos terminado, en otro caso buscamos en la mitad del vector que nos interese (segn sea elem menor u o mayor que el elemento mitad, buscaremos en la primera o segunda mitad del vector, respectivamente). Posteriormente, si no se ha encontrado el elemento repetiremos este proceso comparando elem con el elemento central del subvector seleccionado, y as sucesivamente hasta que o bien encontremos el valor elem o bien podamos concluir que elem no est (porque el subvector de bsqueda est a u a

15.1. Algoritmos de busqueda en arrays

305

vac o). Este algoritmo de bsqueda recibe el nombre de bsqueda binaria, ya u u que va dividiendo el vector en dos subvectores de igual tamao. n Vamos ahora a realizar una implementacin de una funcin siguiendo el alo o goritmo de bsqueda binaria realizando un diseo descendente del problema, del u n cual el primer renamiento puede ser:
Asignar valores iniciales extInf, extSup, encontrado; Buscar elem en v[extInf..extSup]; Devolver el resultado de la funcin o

Renando Asignar valores iniciales se tiene:


extInf:= 1; extSup:= N; {se supone que N es el tama~o del array inicial} n encontrado:= False;

En un nivel ms renado de Buscar elem en v[extInf..extSup] se tiene: a


mientras el vector no sea vac y o no se ha encontrado el valor c hacer calcular el valor de posMed; si v[posMed] = elem entonces actualizar el valor de encontrado si no actualizar los valores extInf o extSup segn donde est elem u e

Renando devolver el resultado de la funcin obtenemos: o


si se ha encontrado el valor entonces BusquedaBinaria:= posMed; si no BusquedaBinaria:= 0;

Con todo esto, una posible implementacin ser o a:


function BusquedaBinaria(v: tVector; elem: tElem): tIntervalo; {PreC.: v est ordenado crecientemente} a {Dev. 0 (si elem no est en v) i (si v[i] = elem)} a o var extInf, extSup, {extremos del intervalo} posMed: tIntervalo; {posicin central del intervalo} o encontrado: boolean;

306

Cap tulo 15. Algoritmos de busqueda y ordenacion


begin extInf:= 1; extSup:= N; encontrado:= False; while (not encontrado) and (extSup >= extInf) do begin {Inv.: si elem est en v, v[extInf] elem v[extSup]} a posMed:= (extSup + extInf) div 2; if elem = v[posMed] then encontrado:= True else if elem > v[posMed] then {se actualizan los extremos del intervalo} extInf:= posMed + 1 else extSup:= posMed - 1 end; {while} if encontrado then BusquedaBinaria:= palMed else BusquedaBinaria:= 0 end; {BusquedaBinaria}

La necesidad de acceder de forma directa a las componentes intermedias no permite la aplicacin de este tipo de soluciones a archivos secuenciales. o

15.2

Ordenacin de arrays o

En muchas situaciones se necesita tener ordenados, segn algn criterio, los u u datos con los que se trabaja para facilitar su tratamiento. As por ejemplo, en , un vector donde se tengan almacenados los alumnos de cierta asignatura junto con la calicacin obtenida, ser interesante ordenar los alumnos por orden o a alfabtico, o bien, ordenar el vector segn la calicacin obtenida para poder e u o sacar una lista de Aprobados y Suspensos. En este apartado se presentan los algoritmos de ordenacin ms usuales, de o a los muchos existentes. Su objetivo comn es resolver el problema de ordenacin u o que se enuncia a continuacin: o Sea v un vector con n componentes de un mismo tipo, tElem. Denimos la funcin ordenacin como: o o ordenacin : Vn (tElem) Vn (tElem) o de tal forma que ordenacin(v) = v donde v=(v1 ,..., vn ) es una pero mutacin de v=(v1 ,..., vn ) tal que v1 v2 . . . vn donde es la o relacin de orden elegida para clasicar los elementos de v. o

15.2. Ordenacion de arrays


ee r r En todos los algoritmos que se van a estudiar a continuacin, se supondr o a que la ordenacin se llevar a cabo en forma creciente. Es obvio que las o a modicaciones que se tendr que realizar para ordenar un vector con otra an relacin de orden son inmediatas y se dejan para el lector. o

307

15.2.1

Seleccin directa o

Este algoritmo resuelve el problema de la ordenacin recorriendo el vector o y seleccionando en cada recorrido el menor elemento para situarlo en su lugar correspondiente. El esquema bsico del algoritmo de seleccin directa es: a o 1. Se sita en v1 el menor valor entre v1 , . . . , vn . Para ello se intercambian u los valores de v1 y vm siendo vm = m {vk }. n
k=1,...,n

Por ejemplo, para el vector (4, 5, 7, 1, 9, 8, 2), este primer paso produce el intercambio representado en la siguiente gura:

4 _

1 _

2. Se sita en v2 el menor valor entre v2 , . . . , vn . Para ello se intercambian u los valores de v2 y vm siendo vm = m {vk }. n
k=2,...,n

En el ejemplo, se produce el intercambio:

5 _

8 2 _

... (j-1). Se sita en vj1 el menor valor entre vj1 , . . . , vn . Para ello se intercambian u los valores de vj1 y vm siendo vm = m {vk }. n
k=j1,...,n

... (n-1). Se sita en vn1 el menor valor entre vn1 y vn . Para ello se intercambian u los valores de vn1 y vn si es necesario.

308

Cap tulo 15. Algoritmos de busqueda y ordenacion La situacin nal en el ejemplo es representada en la siguiente gura: o
1 2 4 5 7 _ 8_ 9

El primer nivel de diseo del algoritmo es: n


para cada i entre 1 y n - 1 hacer Colocar en vi el menor entre vi , . . . , vn ; Devolver v, ya ordenado

donde Colocar en vi el menor entre vi , . . . , vn es:


valMenor:= v[i]; posMenor:= i; para cada j entre i + 1 y n hacer si v[j] < valMenor entonces valMenor:= v[j]; posMenor:= j n {si} n {para} Intercambiar v[i] con v[PosMenor];

Por lo tanto, una posible implementacin de ordenacin de un vector, sio o guiendo el algoritmo de seleccin directa, podr ser la siguiente: o a
procedure SeleccionDirecta(var v: tVector); {Efecto: se ordena v ascendentemente} var i, j, posMenor: tIntervalo; valMenor, aux: integer; begin for i:= 1 to N-1 do begin {Inv.: j, 1 j i 1, v[j] v[j+1] y adems todos los v[i]...v[N] son mayores que v[i-1]} a valMenor:= v[i]; {se dan valores iniciales} posMenor:= i; for j:= i + 1 to n do if v[j] < valMenor then begin {se actualiza el nuevo valor menor y la posicin donde se encuentra} o valMenor:= v[j]; posMenor:= j end {if}

15.2. Ordenacion de arrays


if posMenor <> i then begin {Si el menor no es v[i], se intercambian los valores} aux:= v[i]; v[i]:= v[posMenor]; v[posMenor]:= aux end {if} end; {for i} end; {SeleccionDirecta}

309

15.2.2

Insercin directa o

Este algoritmo recorre el vector v insertando el elemento vi en su lugar correcto entre los ya ordenados v1 , . . . , vi1 . El esquema general de este algoritmo es: 1. Se considera v1 como primer elemento. 2. Se inserta v2 en su posicin correspondiente en relacin a v1 y v2 . o o 3. Se inserta v3 en su posicin correspondiente en relacin a v1 , . . . , v3 . o o ... i. Se inserta vi en su posicin correspondiente en relacin a v1 , . . . , vi . o o ... n. Se inserta vn en su posicin correspondiente en relacin a v1 , . . . , vn . o o En el diagrama de la gura 15.1 se muestra un ejemplo de aplicacin de este o algoritmo. Atendiendo a esta descripcin, el diseo descendente de este algoritmo tiene o n como primer nivel el siguiente:
para cada i entre 2 y N hacer Situar v[i] en su posicin ordenada respecto a v[1],. . . , v[i-1] o

Donde Situar v[i] en su posicin ordenada respecto a v[1],. . . , v[i-1] o puede renarse de la siguiente forma:
Localizar la posicin j entre 1 e i-1 correspondiente a v[i]; o Desplazar una posicin las componentes v[j+1],. . . , v[i-1]; o v[j]:= v[i]

Por lo tanto, una implementacin de este algoritmo ser: o a

310

Cap tulo 15. Algoritmos de busqueda y ordenacion

4 2 2 2 2 2 1

2 4 3 3 3 3 2

3 3 4 4 4 4 3

9 9 9 9 5 5 4

5 5 5 5 9 8 5

8 8 8 8 8 9 8

1 1 1 1 1 1 9

Figura 15.1. procedure InsercionDirecta(var v: tVector); {Efecto: se ordena v ascendentemente} var i, j: tIntervalo; aux: tElem; begin for i:= 2 to N do begin {Inv.: j, 1 j < i, v[j] v[j+1]} aux:= v[i]; {se dan los valores iniciales} j:= i - 1; while (j >= 1) and (v[j] > aux) do begin v[j+1]:= v[j]; {Desplazamiento de los valores mayores que v[i]} j:= j-1 end; {while} v[j+1]:= aux end {for} end; {InsercionDirecta}

15.2.3

Intercambio directo

El algoritmo por intercambio directo recorre el vector buscando el menor elemento desde la ultima posicin hasta la actual y lo sita en dicha posicin. o u o Para ello, se intercambian valores vecinos siempre que estn en orden decreciente. e As se baja, mediante sucesivos intercambios, el valor menor hasta la posicin o deseada. Ms concretamente, el algoritmo consiste en: a 1. Situar el elemento menor en la primera posicin. Para ello se compara o el ultimo elemento con el penltimo, intercambiando sus valores si estn u a

15.2. Ordenacion de arrays

311

4 4 4 4 4 4
El menor est colocado

9 9 9 9 9 1 4

7 7 7 7 1 9 9

1 1 1 1 7 7 7

8 8 2 2 2 2 2

7 2 8 8 8 8 8

2 7 7 7 7 7 7

1 _

Figura 15.2.

en orden decreciente. A continuacin se comparan el penltimo elemento o u con el anterior, intercambindose si es necesario, y as sucesivamente hasta a llegar a la primera posicin. En este momento se puede asegurar que el o elemento menor se encuentra en la primera posicin. o As para el vector (4, 9, 7, 1, 8, 7, 2), se realiza el proceso representado en , la gura 15.2. 2. Situar el segundo menor elemento en la segunda posicin. Para ello se o procede como antes nalizando al llegar a la segunda posicin, con lo que o se sita el elemento buscado en dicha posicin. u o En el ejemplo, se obtiene en este paso el vector (1, 2, 4, 9, 7, 7, 8). . . . Se repite el proceso para las posiciones intermedias. (n-1). Se comparan los dos ultimos valores, intercambindose si estn en orden a a decreciente, obteniendose as el vector ordenado. En el caso del ejemplo se obtiene el vector (1, 2, 4, 7, 7, 8, 9). El seudocdigo correspondiente al primer nivel de diseo de este algoritmo o n es:
para i entre 1 y n-1 hacer Desplazar el menor valor desde vn hasta vi , intercambiando pares vecinos, si es necesario Devolver v, ya ordenado

Por lo tanto, una implementacin del algoritmo puede ser: o

312

Cap tulo 15. Algoritmos de busqueda y ordenacion


procedure OrdenacionPorIntercambio(var v: tVector); {Efecto: se ordena v ascendentemente} var i, j: tIntervalo; aux: tElem; begin for i:= 1 to N-1 do {Inv.: j, 1 j < i, v[j] v[k], k tal que j k < N} for j:= N downto i + 1 do {Se busca el menor desde atrs y se sita en vi } a u if v[j-1] > v[j] then begin {intercambio} aux:= v[j]; v[j]:= v[j-1]; v[j-1]:= aux end {if} end; {OrdenacionPorIntercambio}

15.2.4

Ordenacin rpida (Quick Sort) o a

El algoritmo de ordenacin rpida 2 debido a Hoare, consiste en dividir el o a vector que se desea ordenar en dos bloques. En el primer bloque se sitan u todos los elementos del vector que son menores que un cierto valor de v que se toma como referencia (valor pivote), mientras que en el segundo bloque se colocan el resto de los elementos, es decir, los que son mayores que el valor pivote. Posteriormente se ordenarn (siguiendo el mismo proceso) cada uno a de los bloques, unindolos una vez ordenados, para formar la solucin. En la e o gura 15.3 se muestran grcamente las dos fases de ordenacin. a o Evidentemente, la condicin de parada del algoritmo se da cuando el bloo que que se desea ordenar est formado por un unico elemento, en cuyo caso, e obviamente, el bloque ya se encuentra ordenado. Tambin se puede optar por detener el algoritmo cuando el nmero de elee u mentos del bloque sea sucientemente pequeo (generalmente con un nmero n u aproximado de 15 elementos), y ordenar ste siguiendo alguno de los algoritmos e vistos anteriormente (el de insercin directa, por ejemplo). o
ee r r El nmero elegido de 15 elementos es orientativo. Se deber elegir dicha u a cantidad mediante pruebas de ensayo para localizar el valor ptimo. o

Aunque cualquier algoritmo de ordenacin visto anteriormente sea ms o a lento (como veremos en el cap tulo de complejidad algor tmica) que el Quick Sort, este ultimo pierde gran cantidad de tiempo en clasicar los elementos en
2

Quick Sort en ingls. e

15.2. Ordenacion de arrays

313

sort izq der

Separar

Pivote

Ordenar izq < l < p

v[l] < p

v[n] > p
k < n < der sort

sort

Figura 15.3.

los dos bloques, por lo que, cuando el nmero de elementos es pequeo, no es u n rentable utilizar Quick Sort. En este apartado vamos a desarrollar el algoritmo Quick Sort con la primera condicin de parada, dejando al lector el desarrollo de la segunda versin. o o Este algoritmo sigue el esquema conocido con el nombre de divide y vencers a (vase el apartado 20.2) que, bsicamente, consiste en subdividir el problema e a en dos iguales pero de menor tamao para posteriormente combinar las dos n soluciones parciales obtenidas para producir la solucin global. o El seudocdigo correspondiente al primer nivel en el diseo descendente del o n algoritmo Quick Sort es:
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos bloques A y B con todos los elementos de A menores que los de B n {si} Ordenar A y B usando Quick Sort Devolver v ya ordenado como concatenacin o de las ordenaciones de A y de B

314

Cap tulo 15. Algoritmos de busqueda y ordenacion

izq

der

v[l] < p
izq < l < i

v[m] = p
< m <

v[n] > p
k < n < der

Figura 15.4.

Donde Dividir v en dos bloques A y B se puede renar en:


Elegir un elemento p (pivote) de v para cada elemento del vector hacer si elemento < p entonces Colocar elemento en A, el subvector con los elementos de v menores que p en otro caso Colocar elemento en B, el subvector con los elementos de v mayores que p ee r r

De cara a la implementacin, y por razones de simplicidad y ahorro de o memoria, es preferible situar los subvectores sobre el propio vector original v en lugar de generar dos nuevos arrays.

Con todo lo anterior, una implementacin de este algoritmo podr ser: o a


procedure QuickSort(var v: tVector); {Efecto: se ordena v ascendentemente} procedure SortDesdeHasta(var v: tVector; izq,der: tIntervalo); {Efecto: v[izq..der] est ordenado ascendentemente} a var i,j: tIntervalo; p,aux: tElem; begin i:= izq; j:= der; {se divide el vector v[izq..der] en dos trozos eligiendo como pivote p el elemento medio del array} p:= v[(izq + der) div 2]; {si i >= d el subvector ya est ordenado} a {Inv.: s, izq s < i, v[s] < p y t tal que j < t der v[s] > p}

15.2. Ordenacion de arrays


while i < j do begin {se reorganizan los dos subvectores} while v[i] < p do i:= i + 1; while p < v[j] do j:= j - 1; if i <= j then begin {intercambio de elementos} aux:= v[i]; v[i]:= v[j]; v[j]:= aux; {ajuste de posiciones} i:= i + 1; j:= j - 1 end {if} end; {while} if izq < j then SortDesdeHasta(v,izq,j); if i < der then SortDesdeHasta(v,i,der) end; {SortDesdeHasta} begin {QuickSort} SortDesdeHasta(v,1, n) end; {QuickSort}

315

Para facilitar la comprensin del mtodo, se ha realizado un ejemplo de su o e funcionamiento provocando algunas llamadas sobre el vector inicial v= [0, 3, 86, 20, 27] y mostrando el vector tras cada llamada: v v v v = = = = [0, [0, [0, [0, 3, 3, 3, 3, 86, 27, 27, 27, 20, 20, 20, 20, 27] 86] 86] 86]

SortDesdeHasta(v,1,5) SortDesdeHasta(v,1,4) SortDesdeHasta(v,1,4)

A propsito de este algoritmo, es necesaria una observacin sobre su ecieno o cia. El mejor rendimiento se obtiene cuando el pivote elegido da lugar a dos subvectores de igual tamao, dividindose as el vector en dos mitades. Este n e algoritmo tambin proporciona un coste m e nimo cuando los elementos del vector se distribuyen aleatoriamente (equiprobablemente). Sin embargo, puede ocurrir que los elementos estn dispuestos de forma que, e cada vez que se elige el pivote, todos los elementos queden a su izquierda o todos a su derecha. Entonces, uno de los dos subvectores es vac y el otro carga con todo o el trabajo. En este caso, el algoritmo da un psimo rendimiento, comparable a e

316

Cap tulo 15. Algoritmos de busqueda y ordenacion

sort izq centro der

v sort izq Divisin sort centro centro + 1 ... ...

der

Mezcla ordenada

...

Figura 15.5.

los algoritmos de seleccin, insercin e intercambio. Un ejemplo de esta situacin o o o se tiene cuando el vector por ordenar tiene los elementos inicialmente dispuestos en orden descendente y el pivote elegido es, precisamente, el primer (o el ultimo) elemento.

15.2.5

Ordenacin por mezcla (Merge Sort) o

Al igual que Quick Sort, el algoritmo de ordenacin Merge Sort va a utilizar o la tcnica divide y vencers. En este caso la idea clave del algoritmo consiste en e a dividir el vector v en dos subvectores A y B (de igual tamao, si es posible), sin n tener en cuenta ningn otro criterio de divisin. u o Posteriormente se mezclarn ordenadamente las soluciones obtenidas al ordea nar A y B (aplicando nuevamente, a cada uno de los subvectores, el algoritmo Merge Sort). En la gura 15.5 se muestra un esquema de este algoritmo. Para Merge Sort tambin se pueden considerar las condiciones de parada e descritas en el apartado anterior para el algoritmo Quick Sort, desarrollndose a a continuacin el diseo descendente y la implementacin para el primer caso o n o de parada (cuando se llega a subvectores de longitud 1). Se deja como ejercicio para el lector el desarrollo de la implementacin de Merge Sort con la segunda o condicin de parada, es decir, la combinacin de Merge Sort con otro algoritmo o o de ordenacin cuando se llega a subvectores de una longitud dada. o

15.2. Ordenacion de arrays El algoritmo Merge Sort puede esbozarse como sigue:
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos subvectores A y B n {si} Ordenar A y B usando Merge Sort Mezclar las ordenaciones de A y B para generar el vector ordenado.

317

En este caso, el paso Dividir v en dos subvectores A y B consistir en: a


Asignar a A el subvector [v1 , . . . , vn div 2 ] Asignar a B el subvector [vn div 2+1 , . . . , vn ]

mientras que Mezclar las ordenaciones de A y B consistir en desarrollar un a procedimiento (que llamaremos Merge) encargado de ir entremezclando adecuadamente las componentes ya ordenadas de A y de B para obtener el resultado buscado. De acuerdo con este diseo se llega a la implementacin que se muestra a n o continuacin. Al igual que en la implementacin de Quick Sort, se sitan los o o u subvectores sobre el propio vector original v en lugar de generar dos nuevos arrays.
procedure MergeSort(var vector: tVector); {Efecto: se ordena vector ascendentemente} procedure MergeSortDesdeHasta(var v: vector; izq, der: {Efecto: se ordena v[izq..der] ascendentemente} var centro : tIntervalo;

integer);

procedure Merge(vec: tVector; iz, ce, de: tIntervalo; var w: tVector); {Efecto: w := mezcla ordenada de los subvectores v[iz..ce] y v[ce+1..de]} var i,j,k: 1..N; begin {Merge} i:= iz; j:= ce + 1; k:= iz; {k recorre w, vector que almacena la ordenacin total} o while (i <= ce) and (j <= de) do begin {Inv.: m, iz m < k, w[m] w[m+1]} if vec[i] < vec[j] then begin

318

Cap tulo 15. Algoritmos de busqueda y ordenacion


w[k]:= vec[i]; i:= i + 1 end {Then} else begin w[k]:= vec[j]; j:= j + 1 end; {Else} k:= k + 1; end; {While} for k:= j to de do w[k]:= vec[k] for k:= i to ce do w[k+de-ce]:= vec[k] end; {Merge} begin {MergeSortDesdeHasta} centro:= (izq + der) div 2; if izq < centro then MergeSortDesdeHasta(v, izq, centro); if centro < der then MergeSortDesdeHasta(v, centro+1, der); Merge(v, izq, centro, der, v) end; {MergeSortDesdeHasta} begin {MergeSort} MergeSortDesdeHasta(vector, 1, N) end; {MergeSort}

Para facilitar la comprensin del mtodo se ha realizado un ejemplo, provoo e cando algunas llamadas sobre el vector inicial v= [8, 5, 7, 3] y mostrando el vector tras cada llamada: v v v v = = = = [8, [5, [5, [3, 5, 8, 8, 5, 7, 7, 3, 7, 3] 3] 7] 8]

MergeSortDesdeHasta(v,1,2) MergeSortDesdeHasta(v,3,4) Merge(v,1,2,4,v)

15.2.6

Vectores paralelos

Supngase que se desea ordenar un vector vectFich de la forma o array[1..N] of tFicha siendo el tipo tFicha de un gran tamao. n

15.2. Ordenacion de arrays


1 2 3 4 5 rsula Enrique Orlando Adrin Isabel ... ... ... ... ... 1 Adrin Enrique Isabel Orlando rsula ... ... ... ... ...

319

Ordenacin

2 3 4 5

Figura 15.6.

Una posibilidad interesante consiste en crear otro vector vectPosic de la forma array[1..N] of [1..N] cuyas componentes reeren una cha completa, mediante su posicin, siendo o inicialmente vectPosic[i] = i i {1, . . . N }

El inters de esta representacin indirecta reside en aplicar este mtodo a e o e cualquiera de los algoritmos de ordenacin estudiados, con la ventaja de que la o ordenacin se puede efectuar intercambiando las posiciones en vez de los registros o (extensos) completos. Por ejemplo, supongamos que el tipo de datos tFicha es un registro con datos personales, entre los que se incluye el nombre. La gura 15.6 muestra el vector vectFich antes y despus de la ordenacin. e o Si no se emplea el mtodo de los vectores paralelos, el algoritmo de ordenacin e o empleado deber incluir instrucciones como las siguientes: a
if vectFich[i].nombre > vectFich[j].nombre then begin {Intercambiar vectFich[i], vectFich[j]} elemAux:= vectFich[i]; vectFich[i]:= vectFich[j]; vectFich[j]:= elemAux end

Como se puede comprobar, en las instrucciones de intercambio es necesario mantener tres copias de elementos del tipo tElemento. Sin embargo, si se utiliza un vector paralelo (como se muestra en la gura 15.7), slo se producen o cambios en el vector de posiciones. El fragmento de cdigo correspondiente a los o intercambios quedar como sigue: a

320

Cap tulo 15. Algoritmos de busqueda y ordenacion

Pos 1 2 3 4 5 1 2 3 4 5

v 1 rsula 2 Enrique ... ... Ordenacin de los ndices 1 2 3 4 5

Pos 4 2 5 3 1 1

v rsula . . .

2 Enrique . . . 3 4 5 Orlando . . . Adrin . . . Isabel ...

3 Orlando . . . 4 Adrin 5 Isabel ... ...

Figura 15.7. if vectFich[vectPosic[i]].nombre > vectFich[vectPosic[j]].nombre then begin {Intercambiar vectPosic[i], vectPosic[j] [1..N]} posAux:= vectPosic[i]; vectPosic[i]:= vectPosic[j]; vectPosic[j]:= posAux end

As se manipulan unicamente tres copias de posiciones, que son de tipo inte ger, con el consiguiente ahorro de tiempo y espacio, ya que es ms eciente a intercambiar dos ndices que dos elementos (siempre y cuando stos sean grane des). Adems, el mtodo de los vectores paralelos tiene la ventaja aadida de que a e n permite mantener varios ndices, que en el ejemplo permitir realizar ordenaan ciones por nombre, DNI, etc. Finalmente, tambin conviene indicar que la idea de manejar las posiciones en e lugar de los elementos mismos se explota ampliamente en programacin dinmica o a (vanse los cap e tulos 16 y 17).

15.3

Algoritmos de b squeda en archivos u secuenciales

La variedad de algoritmos de bsqueda en archivos se ve muy restringida u en Pascal por la limitacin a archivos de acceso secuencial. Este hecho obliga o a que la localizacin del elemento buscado se haga examinando las sucesivas o

15.3. Algoritmos de busqueda en archivos secuenciales

321

componentes del archivo. Por tanto, la estrategia de bsqueda ser la misma u a que se emplea en los algoritmos de bsqueda secuencial en arrays (vanse los u e apartados 15.1.1 y 15.1.2). En este breve apartado nos limitaremos a mostrar la adaptacin directa de o estos algoritmos de bsqueda para archivos arbitrarios y para archivos ordenados. u

15.3.1

B squeda en archivos arbitrarios u

Supongamos que se dispone de un archivo denido de la siguiente forma:


type tElem = record clave: tClave; resto de la informacin o end; tArchivoElems = le of tElem;

Con esto, el procedimiento de bsqueda queda como sigue (por la simpliciu dad de la implementacin se han obviado los pasos previos de diseo y algunos o n aspectos de la correccin): o
procedure Buscar(var f: tArchivoElems; c: tClave; var elem: var encontrado: boolean); {PostC: si c est en f entonces encontrado = True y elem es a el elemento buscado; en otro caso encontrado = False} begin encontrado:= False; while not EoF(f) and not encontrado do begin Read(f, elem); if c = elem.clave then encontrado:= True end {while} end; {Buscar} tElem;

15.3.2

B squeda en archivos ordenados u

Al igual que ocurre con la bsqueda secuencial en arrays, el algoritmo puede u mejorarse si las componentes del archivo estn ordenadas por sus claves. En a este caso, se puede evitar recorrer intilmente la parte nal del archivo si hemos u detectado una clave mayor que la buscada, ya que entonces tendremos la certeza de que el elemento no est en el archivo. La modicacin en el cdigo es muy a o o sencilla, puesto que basta con variar la condicin de salida del bucle while para o que contemple la ordenacin del archivo. El nuevo procedimiento es el siguiente: o

322

Cap tulo 15. Algoritmos de busqueda y ordenacion


procedure BuscarOrd(var f: tArchivoElems; c: clave; var elem: tElem; var encontrado: boolean); {PostC.: si c est en f entonces encontrado = True y elem es a el elemento buscado; en otro caso encontrado = False} var ultimaClave: clave; begin encontrado:= False; ultimaClave:= cota superior de las claves; while not EoF(f) and ultimaClave > c do begin Read(f, elem); if c = elem.clave then encontrado:= True; ultimaClave:= elem.clave end {while} end; {BuscarOrd}

15.4

Mezcla y ordenacin de archivos secuenciales o

Una de las operaciones fundamentales en el proceso de archivos es el de su ordenacin, de forma que las componentes contiguas cumplan una cierta relacin o o de orden. Cuando los archivos tengan un tamao pequeo pueden leerse y almacenarn n se en un array, pudindose aplicar las tcnicas de ordenacin estudiadas para e e o arrays. Estas tcnicas son tambin aplicables a archivos de acceso directo (vase e e e el apartado B.9). Por ultimo, cuando el unico acceso permitido a los archivos es el secuencial, como en los archivos de Pascal, es necesario recurrir a algoritmos espec cos para dicha ordenacin. o El mtodo ms frecuente de ordenacin de archivos secuenciales es en realidad e a o una variante no recursiva del algoritmo Merge Sort estudiado para arrays en el apartado anterior. Consiste en dividir el archivo origen en dos archivos auxiliares y despus mezclarlos ordenadamente sobre el propio archivo origen, obteniendo e de esta forma, al menos, pares de valores ordenados. Si ahora se vuelve a dividir el archivo origen y se mezclan otra vez, se obtienen, al menos, cudruplas de a valores ordenados, y as sucesivamente hasta que todo el archivo est ordenado. e A este proceso que usa dos archivos auxiliares se le denomina de mezcla simple. Si se usan ms archivos auxiliares, se llama de mezcla mltiple, con el a u que se puede mejorar el tiempo de ejecucin. o

15.4. Mezcla y ordenacion de archivos secuenciales

323

sort

Particin por tramos ... aux1 Mezcla ordenada

arch

... aux2

arch

Figura 15.8.

Por lo tanto se tienen dos acciones diferentes: por una parte hay que dividir el archivo original en otros dos, y por otra hay que mezclar ordenadamente los dos archivos auxiliares sobre el archivo original, como se puede ver en la gura 15.8. Vamos a concentrarnos primero en esta segunda accin de mezcla ordenada, o ya que tiene entidad propia dentro del proceso de archivos: en el caso de disponer de dos archivos ordenados, situacin que se da con relativa frecuencia, la mezcla o ordenada garantiza que el archivo resultante tambin est ordenado. e e Para realizar la mezcla se ha de acceder a las componentes de los dos archivos, utilizando el operador , que aplicaremos a los archivos aux1 y aux2. Si la primera componente de aux1 es menor que la de aux2, se escribe en el archivo arch y se accede al siguiente valor de aux1. En caso contrario, se escribe en arch la primera componente de aux2 avanzando en este archivo. En el caso de que se llegara, por ejemplo, al nal del archivo aux1, hay que copiar las restantes componentes de aux2 en arch. Si por el contrario, terminara aux2, habremos de copiar las restantes componentes de aux1 en arch. Dados los valores de aux1 y aux2, la secuencia de lecturas y escrituras ser a la que se muestra el al gura 15.9.

324

Cap tulo 15. Algoritmos de busqueda y ordenacion


... ...

aux1

aux2

arch

...

Figura 15.9.

Veamos el diseo descendente de Mezcla: n


mientras no se acabe si aux1^ < aux2^ arch^:= aux1^ Avanzar aux1 Poner en arch n {si} si aux2^ < aux1^ arch^:= aux2^ Avanzar aux2 Poner en arch n {si} n {mientras} mientras no se acabe arch^:= aux1^ Avanzar aux1 Poner en arch n {mientras} mientras no se acabe arch^:= aux2^ Avanzar aux2 Poner en arch n {mientras} aux1 y no se acabe aux2 hacer entonces

entonces

aux1 hacer

aux2 hacer

A continuacin se ha escrito el procedimiento Mezcla en Pascal: o


procedure Mezcla(var aux1, aux2, arch: archivo); {Efecto: arch := mezcla ordenada de aux1 y aux2} begin Reset(aux1); Reset(aux2); ReWrite(arch);

15.4. Mezcla y ordenacion de archivos secuenciales


while not EoF(aux1) and not EoF(aux2) do if aux1^ < aux2^ then begin arch^:= aux1^; Put(arch); Get(aux1) end {if} else begin arch^:= aux2^; Put(arch); Get(aux2) end; {else} while not EoF(aux1) do begin {Se copia en arch el resto de aux1, si es necesario} arch^:= aux1^; Put(arch); Get(aux1) end; {while} while not EoF(aux2) do begin {Se copia en arch el resto de aux2, en caso necesario} arch^:= aux2^; Put(arch); Get(aux2) end; {while} Close(arch); Close(aux1); Close(aux2) end; {Mezcla}

325

Como puede apreciarse el procedimiento Mezcla que se propone utiliza el cursor de archivo, lo que no est permitido en Turbo Pascal (vase el apartado B.9). a e Para poder utilizar dicho procedimiento en Turbo Pascal hay que modicarlo, utilizando en su lugar el procedimiento Mezcla que se detalla seguidamente. El nuevo procedimiento Mezcla dispone, a su vez, de dos procedimientos anidados: el primero, llamado LeerElemDetectandoFin, comprueba si se ha alcanzado el nal del archivo, y en caso contrario lee una componente del archivo. El segundo, PasarElemDetectando, escribe una componente dada en el archivo de destino, y utiliza el procedimiento LeerElemDetectandoFin para obtener una nueva componente. A continuacin se muestra el procedimiento Mezcla o modicado:
procedure Mezcla(var aux1, aux2, arch: tArchivo); {Efecto: arch := mezcla ordenada de aux1 y aux2} var c1,c2: tComponente; finArch1, finArch2: boolean;

326

Cap tulo 15. Algoritmos de busqueda y ordenacion

procedure LeerElemDetectandoFin(var arch: tArchivo; var comp: tComponente; var finArch: boolean); begin finArch:= EoF(arch); if not finArch then Read(arch, comp) end; {LeerElemDetectandoFin} procedure PasarElemDetectandoFin(var archOrigen, archDestino: tArchivo; var comp: tComponente; var finArchOrigen: boolean); begin Write(archDestino, comp); LeerElemDetectandoFin(archOrigen, comp, finArchOrigen) end; {PasarElemDetectandoFin} begin {Mezcla} Reset(aux1); Reset(aux2); Rewrite(arch); LeerElemDetectandoFin(aux1, c1, finArch1); LeerElemDetectandoFin(aux2, c2, finArch2); while not finArch1 and not finArch2 do if c1 < c2 then PasarElemDetectandoFin (aux1, arch, c1, finArch1) else PasarElemDetectandoFin (aux2, arch, c2, finArch2); while not finArch1 do PasarElemDetectandoFin (aux1, arch, c1, finArch1); while not finArch2 do PasarElemDetectandoFin (aux2, arch, c2, finArch2); Close(arch); Close(aux1); Close(aux2) end; {Mezcla}

Abordamos ahora el desarrollo de la otra accin a realizar en este algoritmo, o que es la divisin de arch en los dos archivos auxiliares aux1 y aux2. Para o optimizar esta tarea conviene tener en cuenta los posibles tramos ordenados que existan ya en arch para no desordenarlos. As mientras los sucesivos valores , que se van leyendo de arch estn ordenados, los vamos escribiendo, por ejemplo, e en aux1. En el momento en que una componente de arch est desordenada e pasamos a escribir en aux2 donde seguiremos escribiendo los sucesivos valores de arch que formen otro tramo ordenado. Al aparecer un nuevo valor fuera de orden cambiamos de nuevo a aux2.

15.4. Mezcla y ordenacion de archivos secuenciales


...

327

arch

aux1

...

aux2

...

Figura 15.10.

Dados los valores de arch, un ejemplo de divisin ser la que aparece en la o a gura 15.10. Este mtodo de divisin hace que los archivos tengan el mismo nmero de e o u tramos o a lo sumo que dieran en uno, por lo que su mezcla se denomina mezcla equilibrada. Adems, se detecta inmediatamente si arch est ordenado, ya que a a existir un unico tramo que se escribir en aux1, quedando aux2 vac a a o. Para saber cundo cambiar de archivo auxiliar hay que comprobar si el a valorAnterior le es mayor que el valorActual, lo que obliga a realizar una do lectura inicial, sin comparar, para tener un valor asignado a valorAnterior, y a almacenar en una variable booleana cambio el destino actual. Al escribir al menos una componente en aux2, ste ya no est vac lo que sealizaremos con e a o, n una variable booleana esVacio2. Veamos cmo podr ser un primer esbozo de Division: o a
si no se ha acabado arch entonces Leer valorActual de arch Escribir valorActual en aux1 valorAnterior:= valorActual n {si} mientras no se acabe arch hacer Leer valorActual de arch si valorAnterior > valorActual entonces Cambiar cambio si cambio entonces Escribir valorActual en aux1 si no Escribir valorActual en aux2 esVacio2:= False n {si no} valorAnterior:= valorActual n {mientras}

328

Cap tulo 15. Algoritmos de busqueda y ordenacion

A continuacin se ha implementado el procedimiento Division en Pascal: o


procedure Division(var arch, aux1, aux2: tArchivo; var esVacio2: boolean); {Efecto: arch se divide en dos archivos aux1 y aux2, copiando alternativamente tramos ordenados maximales} var valorActual, valorAnterior: tComponente; cambio: boolean; {conmuta la escritura en aux1 y aux2} begin Reset(arch); ReWrite (aux1); ReWrite (aux2); cambio:= True; esVacio2:= True; if not EoF(arch) then begin Read(arch, valorActual); Write(aux1, valorActual); valorAnterior:= valorActual end; while not EoF(arch) do begin {se buscan y copian los tramos ordenados} Read(arch, valorActual); if valorAnterior > valorActual then cambio:= not cambio; if cambio then Write(aux1, valorActual) else begin Write(aux2, valorActual); esVacio2:= False end; valorAnterior:= valorActual end; {while} Close(arch); Close(aux1); Close(aux2) end; {Division}

15.5. Ejercicios

329

15.5

Ejercicios

1. Se desea examinar el funcionamiento del mtodo de bsqueda por biparticin del e u o siguiente modo: Nmero buscado: 27 u 1 4 5 12 25 27 31 42 43 56 73 76 78 80 99 [ < ] [ > ] [ = ] El 27 est en la 6^a posicin a o Modique el programa dado en el apartado 15.1.3 para que d su salida de esta e forma. 2. Para examinar igualmente la evolucin de los mtodos de ordenacin por intero e o cambio, tambin se pueden insertar los siguientes subprogramas: e (a) Uno de escritura que muestre el contenido de un vector (que, supuestamente, cabe en una l nea) despus de cada intercambio que se produzca. e (b) Otro que site las marcas > y < debajo de las componentes que se u intercambian para facilitar su seguimiento visual. Desarrolle los subprogramas descritos e incorprelos en un programa que muestre o de este modo los intercambios que lleve a cabo. 3. Ordenacin por el mtodo de la burbuja o e Un mtodo de ordenacin consiste en examinar todos los pares de elementos cone o tiguos (intercambindolos si es preciso), efectuando este recorrido hasta que, en a una pasada, no sea preciso ningn intercambio. Desarrolle un procedimiento para u ordenar un vector siguiendo este algoritmo. 4. Desarrolle un procedimiento para ordenar una matriz de m n considerando que el ultimo elemento de cada la y el primero de la siguiente son contiguos. 5. Dados dos archivos secuenciales ordenados ascendentemente, escriba un programa que los intercale, produciendo otro con los componentes de ambos ordenados ascendentemente. 6. Ordenacin por separacin o o Sea un archivo de texto compuesto por una l nea de enteros separados por espacios. (a) Escriba un subprograma que construya otro archivo a partir del original aplicando la siguiente operacin a cada l o nea: Si la l nea est vac se ignora. a a, Si la l nea tiene un solo elemento, se copia. De lo contrario, se extrae su primer elemento y se construyen tres l neas en el archivo nuevo: la primera formada por los elementos menores que el primero, en la segunda el primero, y en la tercera los mayores que el primero.

330

Cap tulo 15. Algoritmos de busqueda y ordenacion


(b) Escriba un programa que repita el proceso anterior hasta obtener un archivo formado tan slo por l o neas unitarias, copindolas entonces en una sola l a nea. (c) Establezca las semejanzas que encuentre entre este algoritmo y uno de ordenacin de vectores. o

7. Ordenacin por mezcla o Sea un archivo de texto compuesto por una l nea de enteros separados por espacios. (a) Escriba un subprograma que construye otro archivo, a partir del original, de la siguiente forma: Primero se separan los enteros, situando uno en cada l nea. Luego se intercalan las l neas de dos en dos (vase el ejercicio 5). Este e paso se repetir hasta llegar a un archivo con una sola l a nea. (b) Establezca las semejanzas que encuentre entre este algoritmo y uno de ordenacin de vectores. o 8. Complete el programa del ejercicio 6 con las siguientes opciones: (a) Ordenar Inventario: que ordene el vector donde se almacenan los productos, utilizando alguno de los mtodos de ordenacin presentados en el cap e o tulo, de las siguientes formas: De forma ascendente por su nombre, y en caso de que el nombre coincida, por su marca, y en caso de que sta tambin coincidiera, de forma e e descendente por su tamao. n De forma descendente por el valor obtenido al multiplicar su precio por el nmero de unidades inventariadas. u (b) Ordenar Archivo: que ordene un archivo dado, donde se almacenan los productos, utilizando el mtodo de Divisin y Mezcla, en las formas sealadas e o n en el apartado primero

15.6

Referencias bibliogrcas a

Con relacin al contenido de esta parte del libro, en [Aho88] encontramos un tratao miento a un nivel elevado del tipo compuesto conjunto y de los algoritmos de bsqueda u y ordenacin; en particular, se recomienda la lectura del cap o tulo 8 donde se abordan los principales esquemas de clasicacin interna, y el cap o tulo 11 donde se recogen los de clasicacin externa. o Otro texto interesante es [Coll87], con una clara orientacin a Pascal. Los cap o tulos de mayor inters son el 4 dedicado a los conjuntos, el 5 dedicado a la estructura vectorial, e el 12 sobre ordenacin de vectores y el 14 que trata sobre archivos. El texto presenta o un enfoque moderno y utiliza especicaciones formales. En esta bibliograf no podemos dejar de citar el texto clsico [Wir86], y en particua a lar sus dos primeros temas sobre estructuras fundamentales de datos y ordenacin de o vectores y archivos. Utiliza tcnicas de renamientos progresivos, pero no las especie caciones formales.

15.6. Referencias bibliograficas

331

Otro de los textos ms utilizados sobre estos temas es [Dale89], que se caracteriza a por la gran cantidad de ejemplos resueltos. Los cap tulos 9 y del 11 al 15 son los que tratan sobre tipos de datos. Un texto ms reciente es [Sal93], que en su parte cuatro reeja las estructuras de a datos. Hace una presentacin progresiva de los contenidos con ejercicios de complejidad o creciente. Faltan por concretar algunos mtodos de ordenacin de vectores y de archivos e o secuenciales. El mtodo de los vectores paralelos (apartado 15.2.6) no es nuevo; por ejemplo, se e estudia en [DL89], aunque con el nombre de punteros de ordenacin. o

Tema V

Memoria dinmica a

Tema V

Memoria dinmica a

Cap tulo 16

Punteros

16.1 Introduccin al uso de punteros . . . . . . . . . . . . . 336 o 16.2 Aplicaciones no recursivas de los punteros . . . . . . . 344 16.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348

Las estructuras de datos estudiadas hasta ahora se almacenan estticamente a en la memoria f sica del computador. Cuando se ejecuta un subprograma, se destina memoria para cada variable global del programa y tal espacio de memoria permanecer reservado durante toda su ejecucin, se usen o no tales variables; a o por ello, en este caso hablamos de asignacin esttica de memoria. o a Esta rigidez presenta un primer inconveniente obvio: las estructuras de datos estticas no pueden crecer o menguar durante la ejecucin de un programa. a o Obsrvese sin embargo que ello no implica que la cantidad de memoria usada e por un programa durante su funcionamiento sea constante, ya que depende, por ejemplo, de los subprogramas llamados. Y, ms an, en el caso de subprogramas a u recursivos se podr llegar fcilmente a desbordar la memoria del computador. a a Por otra parte, la representacin de ciertas construcciones (como las listas) o usando las estructuras conocidas (concretamente los arrays) tiene que hacerse situando elementos consecutivos en componentes contiguas, de manera que las operaciones de insercin de un elemento nuevo o desaparicin de uno ya exiso o tente requieren el desplazamiento de todos los posteriores para cubrir el vac o producido, o para abrir espacio para el nuevo. Estos dos aspectos, tamao y disposicin r n o gidos, se superan con las llamadas estructuras de datos dinmicas. La denicin y manipulacin de estos objetos a o o

336

Cap tulo 16. Punteros

se efecta en Pascal mediante un mecanismo nuevo (el puntero), que permite al u programador referirse directamente a la memoria. Adems estas estructuras de datos son ms exibles en cuanto a su forma: a a a rboles de tamaos no acotados y con ramicaciones desiguales, redes (como las n ferroviarias por ejemplo), etc. No obstante, esta nueva herramienta no est exenta de peligros en manos a de un programador novel, que deber tratar con mayor cuidado las estructuras a creadas, atender especialmente al agotamiento del espacio disponible, etc. En este cap tulo se estudia el concepto de puntero o apuntador (en ingls e pointer ) as como sus operaciones asociadas. Este es pues un cap tulo tcnico, e que muy bien podr dar como primera impresin que las aportaciones de este a o nuevo mecanismo son sobre todo dicultades y peligros. Naturalmente, no es as una vez identicados los peligros y superados los detalles tcnicos (cap : e tulo presente), el siguiente cap tulo lograr sin duda convencer de que las mltiples a u ventajas superan con creces los inconvenientes mencionados.

16.1

Introduccin al uso de punteros o

Un puntero es una variable que sirve para sealar la posicin de la memoria n o en que se encuentra otro dato almacenando como valor la direccin de ese dato. o Para evitar confusin entre la variable puntero y la variable a la que apunta o (o variable referida) conviene imaginar grcamente este mecanismo. En la a siguiente gura se muestra la variable puntero ap, almacenada en la direccin o 012345, y la celda de memoria que contiene la variable a la que apunta.
Direccin 012345

ap

136520 Direccin 136520 dato

Puesto que no es el contenido real de la variable puntero lo que nos interesa, sino el de la celda cuya direccin contiene, es ms comn usar el siguiente o a u diagrama

16.1. Introduccion al uso de punteros

337

tipo

Figura 16.1. Denicin de un tipo puntero. o

ap

dato

que explica por s mismo el porqu de llamar puntero a la variable ap. e Este ultimo diagrama muestra que un puntero tiene dos componentes: la direccin de memoria a la que apunta (contenido del puntero) y el elemento o referido (contenido de la celda de memoria cuya direccin est almacenada en el o a puntero).
ee r r Al usar punteros conviene tener muy claro que la variable puntero y la variable a la que apunta son dos variables distintas y, por lo tanto, sus valores son tambin distintos. A lo largo del cap e tulo se tendr ocasin de a o diferenciar claramente entre ambas.

16.1.1

Denicin y declaracin de punteros o o

Antes de poder usar punteros en un programa es necesario declararlos: en primer lugar habr que denir el tipo del dato apuntado y, posteriormente, dea clarar la(s) variable(s) de tipo puntero que se usar(n). a Por consiguiente, una variable puntero slo puede sealar a objetos de un o n mismo tipo, establecido en la declaracin. As por ejemplo, un puntero podr o a sealar a caracteres, otro a enteros y otro a vectores pero, una vez que se declara n un puntero, slo podremos usarlo para sealar variables del tipo para el cual ha o n sido denido. Esta exigencia permite al compilador mantener la consistencia del sistema de tipos, as como conocer la cantidad de memoria que debe reservar o liberar para el dato apuntado. El tipo puntero es un tipo de datos simple. El diagrama sintctico de su a denicin aparece en la gura 16.1, en la que hay que resaltar que el circunejo o (^) indica que se est declarando un puntero a variables del tipo tipo. a

338

Cap tulo 16. Punteros

Naturalmente, para usar punteros en un programa, no basta con denir el tipo puntero: es necesario declarar alguna variable con este tipo. En este fragmento de programa se dene el tipo tApuntChar como un puntero a variables de tipo char y, posteriormente, se declara la variable apCar de tipo puntero a caracteres.
type tApuntChar = ^char; var apCar: tApuntChar

Una variable de tipo puntero ocupa una cantidad de memoria ja, independientemente del tipo del dato sealado, ya que su valor es la direccin en que n o reside ste. Por otra parte, si bien el tipo de la variable apCar es tApuntChar, el e dato sealado por apCar se denota mediante apCar^, cuyo tipo es por lo tanto n char. Como ocurre con las otras variables, el valor de un puntero estar en principio a indenido. Pero, adems, los objetos sealados no tienen existencia inicial, esto a n es, ni siquiera existe un espacio en la memoria destinado a su almacenamiento. Por ello, no es correcto efectuar instrucciones como WriteLn(apCar1^) hasta que se haya creado el dato apuntado. Estas operaciones se estudian en el siguiente apartado.

16.1.2

Generacin y destruccin de variables dinmicas o o a

En la introduccin del tema se destac la utilidad de la memoria dinmica, de o o a variables que se generan cuando se necesitan y se destruyen cuando han cumplido su cometido. La creacin y destruccin de variables dinmicas se realiza por o o a medio de los procedimientos predenidos New y Dispose, respectivamente. As pues, la instruccin o
New(apCar)

tiene un efecto doble: 1. Reserva la memoria para un dato del tipo apropiado (en este caso del tipo char). 2. Coloca la direccin de esta nueva variable en el puntero. o

16.1. Introduccion al uso de punteros que, grcamente, se puede expresar as a :


apCar

339

????
ee r r

Obsrvese que la operacin de generacin New ha generado el dato apuntado e o o por apCar, pero ste contiene por el momento una informacin desconocida. e o

Para destruir una variable dinmica se usa el procedimiento estndar Dispose. a a La instruccin o
Dispose(apCar)

realiza las dos siguientes acciones 1. Libera la memoria asociada a la variable referida apCar^ (dejndola disa ponible para otros nes). 2. Deja indenido el valor del puntero. Grcamente, esos efectos llevan a la siguiente situacin: a o
apCar ????

En resumen, una variable dinmica slo se crear cuando sea necesario (lo a o a que ocasiona la correspondiente ocupacin de memoria) y, previsiblemente, se o destruir una vez haya cumplido con su cometido (con la consiguiente liberacin a o de la misma).

16.1.3

Operaciones bsicas con datos apuntados a

Recurdese que el dato referido por el puntero apCar se denota apCar^, que e es de tipo char. Por consiguiente, son vlidas las instrucciones de asignacin, a o lectura y escritura y dems operaciones legales para los caracteres: a

340
apCar apNum1

Cap tulo 16. Punteros


apNum2

Figura 16.2. type tApCaracter = ^char; var apCar: tApCaracter; ... New(apCar); ReadLn(apCar^); {supongamos que se da la letra B} apCar^:= Pred(apCar^); WriteLn(apCar^);

escribindose en el output la letra A. e Igualmente, suponiendo que se ha declarado


type tApNumero = ^integer; var apNum1, apNum2: tApNumero;

el siguiente fragmento de programa es vlido: a


New(apNum1); New(apNum2); apNum1^:= 2; apNum2^:= 4; apNum2^:= apNum1^ + apNum2^; apNum1^:= apNum2^ div 2;

y ambos fragmentos de instrucciones llevan a la situacin de la gura 16.2, que o ser referida varias veces en este apartado. a En general, las operaciones vlidas con un dato apuntado dependen de su a tipo. Por ejemplo, el fragmento de programa siguiente

16.1. Introduccion al uso de punteros

341

apNum1

apNum2

apVect

45

30

20 [1]

40 [2]

...

10240 [10]

Figura 16.3. type tVector10 = array[1..10] of real; tApNumero = ^integer; tApVector10 = ^tVector10; var apNum1, apNum2: ApNumero; apVect: tApVector10; i: integer; ... New(apNum1); New(apNum2); New(apVect); apNum1^:= 45; apNum2^:= 30; apVect^[1]:= 2; for i:= 2 to 10 do apVect^[i]:= apVect^[i-1] * 2;

deja el estado de la memoria como se indica en la gura 16.3. Por su parte, las operaciones siguientes, por ejemplo, no ser legales: an
ReadLn(apVect^); {Lectura de un array de un solo golpe} apNum1^:= apNum2^ + apVect^[1]; {Tipos incompatibles}

16.1.4

Operaciones bsicas con punteros a

Slo las operaciones de comparacin (con la igualdad) y asignacin estn o o o a permitidas entre punteros. En la situacin de la gura 16.2, la comparacin o o apNum1 = apNum2 resulta ser falsa. Ms an, tras ejecutar las instrucciones a u

342
apCar apNum1

Cap tulo 16. Punteros


apNum2

Figura 16.4.

apNum1^:= 6; apNum2^:= 6;

la comparacin: o
apNum1 = apNum2

seguir siendo False, ya que las direcciones apuntadas no coinciden, a pesar de a ser iguales los datos contenidos en dichas direcciones. Tambin es posible la asignacin e o
apNum1:= apNum2

cuyo resultado puede verse en la gura 16.4 en la que se observa que ambos punteros sealan a la misma direccin, resultando ahora iguales al compararlos: n o
apNum1 = apNum2

produce un resultado True y, como consecuencia, apNum1^ y apNum2^ tienen el mismo valor, 6. Adems, esta coincidencia en la memoria hace que los cambios a efectuados sobre apNum1^ o sobre apNum2^ sean indistintos:
ApNum1^:= 666; WriteLn(ApNum2^);

{666}

16.1. Introduccion al uso de punteros


ee r r Obsrvese que el espacio de memoria reservado inicialmente por el puntero e apNum1 sigue situado en la memoria. Lo adecuado en este caso habr sido a liberar ese espacio con Dispose antes de efectuar esa asignacin. o

343

En ambos casos, asignacin y comparacin, es necesario conservar la consiso o tencia de los tipos utilizados. Por ejemplo, en la situacin representada en la o gura 16.2, la siguiente asignacin es errnea o o
apCar:= apNum1

porque, aunque ambas variables sean punteros, en su declaracin queda claro o que no apuntan a variables del mismo tipo.
ee r r Obsrvese que las instrucciones apNum1:= apNum2 y apNum1^:= apNum2^ e tienen distinto efecto. A continuacin se muestra grcamente el resultado o a de cada una de las instrucciones anteriores, partiendo de la situacin de la o gura 16.2,

Supuesto que apNum1^=3 y apNum2^=6 sean True apNum1 apNum2 apNum1 apNum2

3 apNum1 := apNum2

apNum1^ := apNum2^

donde se observa que, al asignar los punteros entre s se comparte la variable , referida; en nuestro caso la celda de memoria que conten a apNum1^ se a pierde, sin posibilidad de recuperacin (a menos que otro puntero sealara o n a esta celda). Por otra parte, al asignar las variables referidas se tiene el mismo valor en dos celdas distintas y los punteros permanecen sin variar.

16.1.5

El valor nil

Un modo alternativo para dar valor a un puntero es, simplemente, diciendo que no apunta a ningn dato. Esto se puede conseguir utilizando la constante u predenida nil.

344

Cap tulo 16. Punteros

o bien

Figura 16.5. puntero iniciado en nil.

Por ejemplo, la siguiente asignacin dene el puntero apCar: o


apCar:= nil

Grcamente, el hecho de que una variable puntero no apunte a nada se rea presenta cruzando su celda de memoria con una diagonal, o bien mediante el s mbolo (prestado de la Electricidad) de conexin a tierra, como se indica en la o gura 16.5. En cuanto al tipo del valor nil es de resaltar que esta constante es comn a u cualquier tipo de puntero (sirve para todos), pues slo indica que el puntero est o a anulado. En el cap tulo siguiente se pondr de maniesto la utilidad de esta operacin. a o Por el momento, digamos solamente que es posible saber si un puntero est a anulado, mediante la comparacin con nil, por ejemplo mediante apNum1 = nil. o
ee r r Obsrvese que no es necesario iniciar los punteros apuntando a nil. En el e ejemplo anterior se inician usando New, esto es, creando la variable referida.

16.2

Aplicaciones no recursivas de los punteros

Se ha comentado en la introduccin que las aplicaciones ms importantes o a de los punteros, estn relacionadas con las estructuras de datos recursivas, que a estudiaremos en el cap tulo siguiente. El apartado que pone cierre a ste presenta e algunas situaciones con estructuras no recursivas en las que los punteros resultan utiles. El hecho de que los punteros sean objetos de tipo simple es interesante y permite, entre otras cosas: 1. La asignacin de objetos no simples en un solo paso. o 2. La denicin de funciones cuyo resultado no es simple. o

16.2. Aplicaciones no recursivas de los punteros

345

16.2.1

Asignacin de objetos no simples o

Esta aplicacin adquiere mayor relevancia cuando se consideran registros de o gran tamao en los que el problema es el elevado coste al ser necesaria la copia n de todas sus componentes. Por ejemplo, en la situacin o
type tFicha = record nombre: ... direccion: ... ... end; {tFicha} var pers1, pers2: tFicha;

la asignacin pers1:= pers2 es altamente costosa si el tipo tFicha tiene grandes o dimensiones. Una forma de economizar ese gasto consiste en usar slo su posicin, haciendo o o uso de punteros:
var p1, p2: ^tFicha;

en vez de las variables pers1 y pers2. Entonces, la instruccin o


p1:= p2

es casi instantnea, por consistir tan slo en la copia de una direccin. a o o El articio anterior resulta util, por ejemplo, cuando hace falta intercambiar variables de gran tamao. Si se dene el tipo n
type tApFicha = ^tFicha;

el procedimiento siguiente efecta el intercambio rpidamente, con independenu a cia del tamao del tipo de datos fichas: n
procedure Intercambiar(var p1, p2: tApFicha); var aux: tApFicha; begin aux:= p1; p1:= p2; p2:= aux end; {Intercambiar}

346

Cap tulo 16. Punteros

pDestino
an dist*Sin(ang) st di angulo a ci

(x,y)

dist*Cos(ang)

Figura 16.6.

La ordenacin de vectores de elementos grandes es un problema en el que la o aplicacin mostrada demuestra su utilidad. Por ejemplo, la denicin o o
type tListaAlumnos = array [1..100] of tFicha;

se puede sustituir por un array de punteros,


type tListaAlumnos = array [1..100] of tApFicha;

de este modo la ordenacin se realizar de una manera mucho ms rpida, debido o a a a esencialmente al tiempo que se ahorra al no tener que copiar literalmente todas las chas que se cambian de posicin. o

16.2.2

Funciones de resultado no simple

Esta aplicacin tiene la misma base que la anterior, cambiar el objeto por o el puntero al mismo. A continuacin vemos un sencillo ejemplo en el que se o aprovecha esta caracter stica. Supongamos, por ejemplo, que hemos de denir un programa que, dado un punto del plano, un ngulo y una distancia, calcule un nuevo punto, alcanzado a al recorrer la distancia segn el rumbo dado, segn la gura 16.6. u u Una primera aproximacin al diseo del programa podr ser la siguiente: o n a
Lectura de datos: punto base, distancia y ngulo a Clculo del punto nal a Escritura de resultados

Naturalmente, en primer lugar se deben denir los tipos, constantes y variables que se usarn en el programa: se denir un tipo tPunto como un registro a a

16.2. Aplicaciones no recursivas de los punteros

347

de dos componentes de tipo real y un tipo dinmico tApPunto que sealar al a n a tipo punto, adems se necesitarn dos variables distancia y angulo para leer a a los valores de la distancia y el ngulo del salto, una de tipo punto para el origen a del salto y otra de tipo tApPunto para el punto de destino.
type tPunto = record x, y: real end; {tPunto} tApPunto = ^tPunto; var angulo, distancia: real; origen: tPunto; pDestino: tApPunto;

El clculo del punto nal se puede realizar mediante la denicin (y posterior a o aplicacin) de una funcin. Esta tendr que devolver un punto, formado por o o a dos coordenadas; dado que esto no es posible, se devolver un puntero al tipo a tPunto (de ah la necesidad del tipo tApPunto). Dentro de la funcin utiliza o remos el puntero pPun para generar la variable apuntada y realizar los clculos, a asignndolos nalmente a Destino para devolver el resultado de la funcin. La a o denicin de la funcin es absolutamente directa: o o
function Destino(orig: tPunto; ang, dist: real): tApPunto; var pPun: tApPunto; begin New(pPun); pPun^.x:= orig.x + dist * Cos(ang); pPun^.y:= orig.y + dist * Sin(ang); Destino:= pPun end; {Destino}

Finalmente, si se aaden los procedimientos de lectura de datos y escritura n de resultados tenemos el siguiente programa:
Program Salto (input, output); type tPunto = record x, y: real end {tPunto}; tApPunto = ^tPunto;

348
var angulo, distancia: real; origen: tPunto; pDestino: tApPunto;

Cap tulo 16. Punteros

function Destino(orig: tPunto; ang, dist: real): tApPunto; var pPun: tApPunto; begin New(pPun); pPun^.x:= orig.x + dist * Cos(ang); pPun^.y:= orig.y + dist * Sin(ang) Destino:= pPun end; {Destino} begin Write(Introduzca la x y la y del punto origen: ); ReadLn(origen.x, origen.y); Write(Introduzca el rumbo y la distancia: ); ReadLn(angulo, distancia); pDestino:= Destino(origen, angulo, distancia); WriteLn(El punto de destino es:); WriteLn(X = , pDestino^.x:20:10, Y = , pDestino^.y:20:10) end. {Salto}

La idea ms importante de este programa estriba en que la funcin devuelve a o un valor de tipo puntero, que es un tipo simple, y por lo tanto correcto, como resultado de una funcin. Sin embargo, la variable referenciada por el puntero o es estructurada, y almacena las dos coordenadas del punto de destino. Mediante esta estratagema, conseguimos obtener un resultado estructurado de una funcin. o

16.3

Ejercicios

1. Implementar algn algoritmo de ordenacin de registros (por ejemplo, chas u o de alumnos), mediante un array de punteros, tal como se propone en el apartado 16.2.1. 2. Complete los tipos de datos apropiados para que sea correcta la siguiente instruccin: o for i:= 1 to n do begin New(p); p^:= i end {for}

16.3. Ejercicios
(a) Cul es el efecto de ejecutarla? a

349

(b) Cul es el efecto de aadir las siguientes instrucciones tras la anterior? a n WriteLn(p); WriteLn(p^); Dispose(p) (c) De qu forma se pueden recuperar los valores 1...N 1 generados en el e bucle for? 3. Escriba una funcin que reciba una matriz cuadrada de 3 3 y calcule (si es o posible) su inversa, devolviendo un puntero a dicha matriz.

Cap tulo 17

Estructuras de datos recursivas

17.1 17.2 17.3 17.4 17.5 17.6 17.7

Estructuras recursivas lineales: Pilas . . . . . . . . . . . . . . . . . Colas . . . . . . . . . . . . . . . . Arboles binarios . . . . . . . . . Otras estructuras dinmicas de a Ejercicios . . . . . . . . . . . . . . Referencias bibliogrcas . . . . a

las listas . . . . . . . . . . . . . . . . . . datos . . . . . . . . . . . . . .

enlazadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

351 362 370 376 387 389 391

En este cap tulo se introducen, a un nivel elemental, las estructuras de datos recursivas como la aplicacin ms importante de la memoria dinmica. o a a Se presentan las listas, el tipo de datos recursivo ms sencillo y que tiene a mltiples aplicaciones, y los casos particulares de las pilas y las colas, como u listas con un acceso controlado. Tambin se introducen los rboles binarios e a como ejemplo de estructura de datos no lineal, y se termina apuntando hacia otras estructuras cuyo estudio queda fuera de nuestras pretensiones.

17.1

Estructuras recursivas lineales: las listas enlazadas

Las representaciones para listas que introducimos en este cap tulo nos permitirn insertar y borrar elementos ms fcil y ecientemente que en una implea a a mentacin esttica (para listas acotadas) usando el tipo de datos array. o a

352

Cap tulo 17. Estructuras de datos recursivas

lista

Figura 17.1. Representacin de una lista enlazada dinmica. o a

A medida que desarrollemos las operaciones de insercin y borrado, centrareo mos nuestra atencin en las acciones necesarias para mantener diferentes clases o particulares de listas: las pilas y las colas. Por lo comn, son las distintas opeu raciones requeridas las que determinarn la representacin ms apropiada para a o a una lista.

17.1.1

Una denicin del tipo lista o

Una lista es una coleccin lineal de elementos que se llaman nodos. El trmino o e coleccin lineal debe entenderse de la siguiente manera: tenemos un primer y o un ultimo nodo, de tal manera que a cada nodo, salvo el ultimo, le corresponde un unico sucesor, y a cada nodo, salvo el primero, le corresponde un unico predecesor. Se trata, pues, de una estructura de datos cuyos elementos estn a situados secuencialmente. Una denicin del tipo listas se puede realizar usando punteros y registros. o Para ello consideramos que cada nodo de la lista es un registro con dos componentes: la primera almacena el contenido del nodo de la lista y, la segunda, un puntero que seala al siguiente elemento de la lista, si ste existe, o con el valor n e nil en caso de ser el ultimo. Esta construccin de las listas recibe el nombre de o lista enlazada dinmica. En la gura 17.1 se muestra grcamente esta idea. a a Esencialmente, una lista ser representada como un puntero que seala al a n principio (o cabeza) de la lista. La denicin del tipo tLista de elementos de o tipo tElem se presenta a continuacin, junto con la declaracin de una variable o o del tipo lista:
type tElem = char; {o lo que corresponda} tLista = ^tNodo; tNodo = record contenido: tElem; siguiente: tLista end; {tNodo}

17.1. Estructuras recursivas lineales: las listas enlazadas


lista 1) lista 2) lista 3) nuevoDato nuevoDato ?? ???? ??

353

Figura 17.2. Insercin de un elemento en una lista vac o a. var lista : tLista;

Sobre el cdigo anterior debe sealarse que se dene tLista como un puntero o n a un tipo no denido todav a. Esto est permitido en Pascal (supuesto que a tal tipo es denido posteriormente, pero en el mismo grupo de deniciones) precisamente para poder construir estructuras recursivas.

17.1.2

Insercin de elementos o

Supngase que nuestra lista est iniciada con el valor nil. Para introducir o a un elemento nuevoDato en ella, habr que completar la siguiente secuencia de a pasos, que se muestra grcamente en la gura 17.2: a 1. En primer lugar, habr que generar una variable del tipo tNodo, que ha de a contener el nuevo eslabn: esto se hace mediante la sentencia New(lista). o 2. Posteriormente, se asigna nuevoDato al campo contenido del nodo recin e generado. La forma de esta asignacin depender del tipo de datos de la o a variable nuevoDato. 3. Y por ultimo, hay que anular (con el valor nil) el campo siguiente del nodo para indicar que es el ultimo de la lista. Para insertar un nuevoDato al principio de una lista no vac lista, se ha a, de proceder como se indica (vase la gura 17.3): e 1. Una variable auxiliar listaAux se usa para apuntar al principio de la lista con el n de no perderla. Esto es:

354

Cap tulo 17. Estructuras de datos recursivas

1)

lista

2) lista ???? A A listaAux ??

listaAux 3) 4) lista nuevoDato ?? lista

A listaAux

nuevoDato

listaAux

Figura 17.3. Adicin de un elemento al principio de una lista. o listaAux:= lista

2. Despus se asigna memoria a una variable del tipo tNodo (que ha de cone tener el nuevo elemento): esto se hace mediante la sentencia New(lista). 3. Posteriormente, se asigna nuevoDato al campo contenido del nuevo nodo:
lista^.contenido:= nuevoDato

4. Y, por ultimo, se asigna la listaAux al campo siguiente del nuevo nodo para indicar los dems elementos de la lista, es decir: a
lista^.siguiente:= listaAux

A continuacin se van a denir algunas de las operaciones relativas a liso tas. Para empezar, se desarrolla un procedimiento para aadir un elemento al n principio de una lista, atendiendo al diseo descrito por los pasos anteriores: n

17.1. Estructuras recursivas lineales: las listas enlazadas


procedure AnnadirPrimero(var lista: tLista; nuevoDato: tElem); {Efecto: se a~ade nuevoDato al comienzo de lista} n var listaAux: tLista; begin listaAux:= lista; New(lista); lista^.contenido:= nuevoDato; lista^.siguiente:= listaAux end; {AnnadirPrimero}

355

Obsrvese que este procedimiento sirve tanto para cuando lista es vac como e a para cuando no lo es.

17.1.3

Eliminacin de elementos o

A continuacin se presenta el procedimiento Eliminar que sirve para eliminar o el primer elemento de una lista no vac La idea es bastante simple: slo hay a. o que actualizar el puntero para que seale al siguiente elemento de la lista (si n existe).
procedure EliminarPrimero(var lista: tLista); {PreC.: la lista no est vaca} a {Efecto: el primer nodo ha sido eliminado} var listaAux: tLista; begin listaAux:= lista; lista:= lista^.siguiente; Dispose(listaAux) end; {EliminarPrimero}

17.1.4

Algunas funciones recursivas

La longitud de una lista La naturaleza recursiva de la denicin dada del tipo lista permite denir o fcilmente funciones que trabajen usando un proceso recursivo. Por ejemplo, la a funcin Longitud de una lista se puede denir recursivamente (es evidente que o el caso base es Longitud(nil) = 0) siguiendo la l nea del siguiente ejemplo: Longitud ([a,b,c]) ; 1 + Longitud ([b,c]) ; 1 + (1 + Longitud([c])) ; 1 + (1 + (1 + Longitud(nil))) ; 1 + (1 + (1 + 0)) ; 3

356

Cap tulo 17. Estructuras de datos recursivas

De aqu se deduce que, si lista no est vac y llamamos Resto(lista) a la a a, lista sin su primer elemento, tenemos:
Longitud (lista) = 1 + Longitud (Resto(lista))

Por lo tanto, en resumen,


0 Longitud(lista) = 1 + Longitud (Resto(lista)) en otro caso si lista = nil

Finalmente, Resto(lista) se representa en Pascal como lista^.siguiente. Por lo tanto, la denicin recursiva en Pascal de la funcin Longitud es la o o siguiente:
type natural = 0..MaxInt; ... function Longitud(lista: tLista): natural; {Dev. el nmero de nodos de lista} u begin if lista = nil then Longitud:= 0 else Longitud:= 1 + Longitud(lista^.siguiente) end; {Longitud}

El uso de la recursividad subyacente en la denicin del tipo de datos lista o hace que la denicin de esta funcin sea muy simple y clara, aunque tambin o o e es posible y sencilla una versin iterativa, que se deja como ejercicio. o Evaluacin de polinomios: La regla de Horner o Supongamos que se plantea escribir un programa para evaluar en un punto dado x un polinomio con coecientes reales P (x) = a0 xn + a1 xn1 + a2 xn2 + + an1 x + an , Conocido x, un posible modo de evaluar el polinomio es el siguiente:
Conocido x Dar valor inicial 0 a valor para cada i entre 0 y N Aadir aNi * xi a valor n Devolver valor

17.1. Estructuras recursivas lineales: las listas enlazadas

357

La regla de Horner consiste en disponer el polinomio de forma que su evaluacin se pueda hacer simplemente mediante la repeticin de operaciones aritmo o e ticas elementales (y no tener que ir calculando los valores de las potencias sucesivas de x). No es dif observar que cil P (x) = ((a0 x + a1 )x + a2 )x + a3 x + an1 x + an

y que, con esta expresin, el valor del polinomio se puede calcular de acuerdo o con el siguiente diseo: n
Dar valor inicial a0 a valor para cada i entre 1 y N hacer valor:= ai + x * valor Devolver valor

En este apartado volvemos a considerar la representacin de polinomios en o un computador una vez que se han introducido algunas herramientas de programacin dinmica. o a Hay dos formas de representar un polinomio conocida la lista de sus coecientes: de ms signicativo a menos, o viceversa. A saber: a

P1 (x) = a0 xn + a1 xn1 + a2 xn2 + + an1 x + an = ((a0 x + a1 )x + a2 )x + a3 x + an1 x + an P2 (x) = b0 + b1 x + b2 x2 + + bn1 xn1 + bn xn = b0 + x b1 + x b2 + x(b3 + + x(bn1 + xbn ) ) En el supuesto de que tengamos los coecientes ordenados de grado menor a mayor (si lo estn del otro modo la modicacin del programa es m a o nima) segn su grado y almacenados en una lista (lCoef), es posible dar una versin u o recursiva de la funcin anterior. o La recursividad de la regla de Horner se aprecia mejor si consideramos la siguiente tabla, en la que se renombra el resultado de cada uno de los parntesis e de la expresin de Horner: o c 0 = a0 c1 = a1 + xc0

358

Cap tulo 17. Estructuras de datos recursivas c2 = a2 + xc1 c3 = a3 + xc2 . . . = . . . cn1 = an1 + xcn2 cn = an + xcn1

Claramente se observa que bn = P (x) y, en consecuencia, ya podemos escribir su expresin recursiva. o


Horner(x,lCoef) = lCoef^.contenido + x * Horner(x,lCoef^.siguiente)

El caso base se da cuando la lista de coecientes se queda vac (nil). Un sencillo a ejemplo nos muestra que, en este caso, la funcin debe valer cero. o Supongamos que queremos evaluar un polinomio constante P (x) = K, entonces su lista de coecientes es [K], usando la expresin recursiva anterior tendr o amos que
Horner(x,nil) = 0 Horner(x,[K]) = K + x * Horner(x,nil)

Una vez conocidos el caso base y la expresin recursiva deniremos una o funcin Horner2 con dos variables: el punto donde evaluar y una lista de reao les que representan los coecientes del polinomio ordenados de mayor a menor grado.
function Horner2(x: real; lCoef: tLista): real; {Dev. P(x), siendo P el polinomio de coeficientes lCoef} begin if lCoef = nil then Horner2:= 0 else Horner2:= lCoef^.contenido + x * Horner2(x,lCoef^.siguiente) end; {Horner2}

17.1.5

Otras operaciones sobre listas

A continuacin se enumeran algunas de las operaciones que ms frecuenteo a mente se utilizan al trabajar con listas: 1. Determinar el nmero de elementos de una lista. u

17.1. Estructuras recursivas lineales: las listas enlazadas 2. Leer o modicar el k-simo elemento de la lista. e 3. Insertar o eliminar un elemento en la k-sima posicin. e o 4. Insertar o eliminar un elemento en una lista ordenada. 5. Combinar dos o ms listas en una unica lista. a 6. Dividir una lista en dos o ms listas. a 7. Ordenar los elementos de una lista de acuerdo con un criterio dado.

359

8. Insertar o eliminar un elemento en la k-sima posicin de una lista de e o acuerdo con un criterio dado. 9. Buscar si aparece un valor dado en algn lugar de una lista. u Como ejemplo de implementacin de algunas de estas operaciones, a contio nuacin se presentan procedimientos que eliminan o insertan un nuevo elemento o en una lista. El procedimiento EliminarK En primer lugar, se presenta un procedimiento que elimina el k-simo elee mento de una lista (que se supone de longitud mayor que k). En el caso en que k = 1, podemos utilizar el procedimiento EliminarPrimero desarrollado anteriormente, por lo que en este apartado nos restringimos al caso k > 1. El primer esbozo de este procedimiento es muy sencillo:
Localizar el nodo (k-1)-simo de lista e Asociar el puntero siguiente del nodo (k-1)-simo al nodo (k+1)-simo e e

El proceso se describe grcamente en la gura 17.4. a Emplearemos un mtodo iterativo para alcanzar el (k 1)-simo nodo de la e e lista, de modo que se ir avanzando nodo a nodo desde la cabeza (el primer nodo a de la lista) hasta alcanzar el (k 1)-simo usando un puntero auxiliar apAux. e

Para alcanzar el nodo (k1)-simo, se empezar en el primer nodo, mediante e a la instruccin o


listaAux:= lista;

y luego se avanzar iterativamente al segundo, tercero, . . . , (k 1)-simo ejecua e tando

360

Cap tulo 17. Estructuras de datos recursivas

lista

...

Nodo k-1 x

Nodo k y

Nodo k+1 z

...

...

Nodo k-1 x

Nodo k y

Nodo k+1 z

...

lista

Figura 17.4. Eliminacin del k-simo nodo de una lista. o e for i:= 2 to k - 1 do apAux:= apAux^.siguiente

Una vez hecho esto, slo hace falta saltarse el nodo k-simo, liberando despus o e e la memoria que ocupa. Con esto, el cdigo en Pascal de este procedimiento podr o a ser:
procedure EliminarK(k: integer; var lista: tLista); {PreC.: 1 < k longitud de la lista} {Efecto: se elimina el elemento k-simo de lista} e var apAux, nodoSupr: tLista; i: integer; begin apAux:= lista; {El bucle avanza apAux hasta el nodo k-1} for i:= 2 to k-1 do apAux:= apAux^.siguiente; {Actualizar punteros y liberar memoria} nodoSupr:= apAux^.siguiente; apAux^.siguiente:= nodoSupr^.siguiente; Dispose(nodoSupr) end; {EliminarK}

17.1. Estructuras recursivas lineales: las listas enlazadas

361

lista

...

Nodo k x

Nodo k+1 z

...

...

Nodo k x

Nodo k+1 z

...

y lista

Figura 17.5. Insercin de un nodo tras el k-simo nodo de una lista. o e

Obsrvese que el procedimiento anterior funciona tambin cuando el nodo e e eliminado es el ultimo nodo de la lista. El procedimiento InsertarK Otro procedimiento importante es el de insercin de un elemento tras cierta o posicin de la lista. En particular, a continuacin se implementar la insercin o o a o de un nuevo nodo justo despus del k-simo nodo de una lista (de longitud mayor e e que k). Puesto que la insercin de un nuevo nodo al comienzo de una lista ya se o ha presentado, nos restringiremos al caso k 1. De nuevo el primer nivel de diseo es fcil: n a
Localizar el nodo k-simo de lista e Crear un nuevoNodo y asignarle el contenido nuevoDato Asociar el puntero siguiente del nodo k-simo a nuevoNodo e Asociar el puntero siguiente de nuevoNodo al nodo (k+1)-simo e

En la gura 17.5 se pueden observar las reasignaciones de punteros que hay que realizar. Tambin en este caso es necesario usar un puntero auxiliar para e localizar el nodo tras el cual se ha de insertar el nuevo dato y se volver a hacer a uso del bucle for. La creacin del nuevo nodo y la reasignacin de punteros es bastante directa, o o una vez que apAux seala al nodo k-simo. El cdigo correspondiente en Pascal n e o podr ser: a

362

Cap tulo 17. Estructuras de datos recursivas


New(nuevoNodo); nuevoNodo^.contenido:= nuevoDato; nuevoNodo^.siguiente:= apAux^.siguiente; apAux^.siguiente:= nuevoNodo

Uniendo el bucle, para alcanzar el k-simo nodo, con las asignaciones anteriores e se obtiene la implementacin completa del procedimiento InsertarK: o
procedure InsertarK(k: integer; nuevoDato: tElem; var lista: tLista); {PreC.: k 1 y lista tiene al menos k nodos} {Efecto: nuevoDato se inserta tras el k-simo nodo de lista} e var nuevoNodo, apAux: tLista; i: integer; begin apAux:= lista; {El bucle avanza apAux hasta el nodo k} for i:= 1 to k-1 do apAux:= apAux^.siguiente; {Actualizacin de punteros} o New(nuevoNodo); nuevoNodo^.contenido:= nuevoDato; nuevoNodo^.siguiente:= apAux^.siguiente; apAux^.siguiente:= nuevoNodo end; {InsertarK}

Son muy frecuentes las situaciones en las que se accede a una lista slo a o travs de su primer o ultimo elemento. Por esta razn tienen particular inters e o e las implementaciones de listas en las que el acceso est restringido de esta forma, a siendo las ms importantes las pilas y las colas. A continuacin se denen estos a o tipos de listas junto con determinadas operaciones asociadas: se dar su reprea sentacin y las operaciones de modicacin y acceso bsicas, y se presentarn o o a a ejemplos donde se vea el uso de las mismas.

17.2

Pilas

Una pila es un tipo de lista en el que todas las inserciones y eliminaciones de elementos se realizan por el mismo extremo de la lista. El nombre de pila procede de la similitud en el manejo de esta estructura de datos y la de una pila de objetos. Estas estructuras tambin son llamadas e 1 acrnimo que reeja la caracter listas LIFO, o stica ms importante de las pilas. a
1

Del ingls Last-In-First-Out, es decir, el ultimo en entrar es el primero en salir. e

17.2. Pilas
pila

363

Figura 17.6. Representacin grca del concepto de pila. o a

Es fcil encontrar ejemplos de pilas en la vida real: hay una pila en el apaa rato dispensador de platos de un autoservicio, o en el cargador de una pistola automtica, o bien en el trco en una calle sin salida. En todos estos casos, el a a ultimo tem que se aade a la pila es el primero en salir. Grcamente, podemos n a observar esto en la gura 17.6. Dentro del contexto informtico, una aplicacin importante de las pilas se a o encuentra en la implementacin de la recursin, que estudiaremos con cierto o o detalle ms adelante. a

17.2.1

Denicin de una pila como lista enlazada o

Puesto que una pila es una lista en la cual slo se insertan o eliminan elemeno tos por uno de sus extremos, podr amos usar la declaracin dada de las listas y o usarlas restringiendo su acceso del modo descrito anteriormente.
type tElem = char; {o lo que corresponda} tPila = ^tNodoPila; tNodoPila = record contenido: tElem; siguiente: tPila end; {tNodoPila}

17.2.2

Operaciones bsicas sobre las pilas a

Entre las operaciones bsicas del tipo pila se encuentran la creacin de una a o pila, la consulta del contenido del primer elemento de la pila, la insercin de o

364

Cap tulo 17. Estructuras de datos recursivas

un nuevo elemento sobre la pila (que se suele llamar push), y la eliminacin del o 2 elemento superior de la pila (tambin llamada pop). e

Creacin de una pila vac o a El primer procedimiento que consideramos es el de crear una pila vac La a. idea es bien simple: slo hay que asignar el valor nil a su puntero. o
procedure CrearPila(var pila: tPila); {PostC.: pila es una pila vaca} begin pila:= nil end; {CrearPila}

Averiguar si una pila est vac a a Se trata sencillamente de la siguiente funcin o


function EsPilaVacia(pila: tPila): boolean; begin EsPilaVacia:= pila = nil end; {EsPilaVacia}

Consulta de la cima de una pila Una funcin especialmente importante es la que permite consultar el conteo nido del primer nodo de la pila. En esta implementacin slo hay que leer el o o campo contenido del primer nodo de la pila. La funcin tiene como argumento o una variable de tipo tPila y como rango el tipo de sus elementos tElem. Est a implementada a continuacin bajo el nombre de Cima. o
function Cima(pila: tPila): tElem; {PreC.: pila no est vaca} a {Dev. el contenido del primer nodo de pila} begin Cima:= pila^.contenido end; {Cima}
Los nombres de estas funciones proceden de los verbos usados en ingls para colocar o coger e objetos apilados.
2

17.2. Pilas A adir un nuevo elemento en la cima de la pila n

365

Este procedimiento toma como parmetros una pila y la variable que se va a a apilar; la accin que realiza es la de insertar el objeto como un nuevo nodo al o principio de la pila. La denicin de este procedimiento es directa a partir del o mtodo de insercin de un nodo al principio de una lista visto en el apartado e o anterior.
procedure Apilar(nuevoDato: tElem; var pila: tPila); {Efecto: nuevoDato se a~ade sobre pila} n var pilaAux: tPila; begin pilaAux:= pila; New(pila); pila^.contenido:= nuevoDato; pila^.siguiente:= pilaAux end; {Apilar}

Eliminar la cima de la pila Para quitar un elemento de la pila slo debemos actualizar el puntero. La o denicin del procedimiento no necesita mayor explicacin, ya que es idntica a o o e la del procedimiento Eliminar presentado en el apartado 17.1.3.
procedure SuprimirDePila(var pila: tPila); {PreC.: pila no est vaca} a {Efecto: se suprime el dato de la cima de la pila} var pilaAux: tPila; begin pilaAux:= pila; pila:= pila^.siguiente; Dispose(pilaAux) end; {SuprimirDePila}

17.2.3

Aplicaciones

Pilas y recursin o Una aplicacin importante de las pilas surge al tratar la recursin (vase o o e el cap tulo 10). En efecto, en cada llamada recursiva se aade una tabla de n activacin en una pila (denominada pila recursiva). Dicha tabla incorpora los o argumentos y objetos locales con su valor en el momento de producirse la llamada.

366

Cap tulo 17. Estructuras de datos recursivas

Dicho de otro modo, la recursin se puede transformar en un par de bucles o que se obtienen apilando las llamadas recursivas (primer bucle) para despus ir e evalundolas una a una (con el segundo bucle). a Un ejemplo servir para aclarar estas ideas; supongamos que tenemos una a funcin denida recursivamente, como, por ejemplo, la funcin factorial (vase o o e el apartado 10.1):
function Fac(num: integer): integer; {PreC.: num 0} {Dev. num!} begin if num = 0 then Fac:= 1 else Fac:= num * Fac(num - 1) end; {Fac}

Consideremos cmo se ejecuta la funcin Fac aplicada a un entero, por ejemo o plo el 3:

Fac(3) = 3 Fac(2) = 3 2 Fac(1)

= 3 2 1 Fac(0) = 3 2 1 1 = 3!

El primer bucle de los comentados ms arriba consistir en ir apilando los a a argumentos sucesivos de Fac hasta llegar al caso base, en este ejemplo tenemos 3, 2, 1; el segundo bucle es el encargado de completar las llamadas recursivas usando la parte recursiva de Fac, esto es, se parte del caso base Fac(0) = 1 y se van multiplicando los distintos valores apilados. Atendiendo a esta descripcin, o un primer nivel de diseo para la versin iterativa del factorial podr ser el n o a siguiente:
para cada n entre num y 1 Apilar n en pilaRec Dar valor inicial 1 a fac mientras que pilaRec no est vac hacer e a fac:= Cima(pilaRec) * fac SuprimirDePila(pilaRec) Devolver fac

La implementacin iterativa en Pascal de la funcin factorial se ofrece a o o continuacin: o

17.2. Pilas
Caso base 0 1 2 . . . n-2 n-1 n Primer bucle Fac := 1 (= q(x)) Fac := 1 * 1 Fac := 2 * 1 * 1 = 2! . . . Fac := (n - 2) * Fac = (n - 2)! Fac := (n - 1) * Fac = (n - 1)! Fac := n * Fac = n! Segundo bucle

367

Figura 17.7. Expresin en dos bucles de la funcin factorial. o o function FacIter (num: integer): integer; {PreC.: num 0} {Dev. num!} var pilaRec: tPila; {de enteros} n, fac: integer; begin n:= num; CrearPila(pilaRec); {Primer bucle: acumulacin de las llamadas} o for n:= num downto 1 do Apilar (n, pilaRec); {Segundo bucle: resolucin de las llamadas} o fac:= 1; {Caso base} while pilaRec <> nil do begin fac:= Cima(pilaRec) * fac; SuprimirDePila(pilaRec) end; {while} FacIter:= fac end; {FacIter}

En la gura 17.7 puede verse grcamente el signicado de los dos bucles que a aparecen en el programa anterior. La descomposicin anterior de la funcin factorial no es ms que un ejemo o a plo del caso general de transformacin de recursin en iteracin que se expone o o o seguidamente. Supongamos que se tiene una funcin denida recursivamente, de la siguiente o manera:

368

Cap tulo 17. Estructuras de datos recursivas


function F(x: tdato): tResultado; begin if P(x) then F:= Q(x) else F:= E(x, F(T(x))) end; {F}

donde P(x) es una expresin booleana para determinar el caso base (en el factoo rial es x = 0, Q(x) es el valor de la funcin en el caso base, T es una funcin que o o transforma el argumento x en el de la siguiente llamada (en el factorial es T(x) = x - 1), y, nalmente, E(x, y) es la expresin que combina el argumento x o con el resultado y devuelto por la llamada recursiva subsiguiente (en el factorial se tiene E(x, y) = x * y). En resumen, cualquier funcin de la forma de la funcin F puede ser descrita o o mediante un par de bucles de forma que: 1. El primero almacena los parmetros de las sucesivas llamadas recursivas al a aplicar la funcin T hasta llegar al caso base. o 2. El segundo deshace la recursin aplicando la expresin E(x, F(T(x)) reo o petidamente desde el caso base hasta el argumento inicial. La descripcin general de estos dos bucles se muestra a continuacin: o o
function F(x: tDato): tResultado; var pilaRec: tPila; acum: tDato; begin CrearPila(pilaRec); {Primer bucle} while not P(x) do begin Apilar(x, pilaRec); x:= T(x) end; {while} acum:= Q(x); {Aplicacin de F al caso base} o {Segundo bucle} while pilaRec <> nil do begin acum:= E(Cima(pilaRec), acum); SuprimirDePila(pilaRec) end; {while} F:= acum end; {F}

17.2. Pilas

369

El proceso descrito anteriormente para una funcin recursiva puede llevarse o a cabo tambin para procedimientos recursivos. En el apartado de ejercicios se e propone su generalizacin a un procedimiento recursivo. o Evaluacin postja de expresiones o Quin no ha perdido algn punto por olvidarse un parntesis realizando e u e un examen? Este hecho, sin duda, le habr llevado a la siguiente pregunta: a No ser posible eliminar estos molestos signos del mbito de las expresiones a a aritmticas? La respuesta es armativa: existe una notacin para escribir expree o siones aritmticas sin usar parntesis, esta notacin recibe el nombre de notacin e e o o postja o notacin polaca inversa. o El adjetivo postja o inversa se debe a que, en contraposicin a la notacin o o habitual (o inja) los operadores se ponen detrs de (y no entre) los argumentos. a Por ejemplo, en lugar de escribir a + b se ha de escribir a b +, y en vez de a (b + c) se escribir a b c + . a Para entender una expresin postja hay que leerla de derecha a izquierda; la o expresin anterior, por ejemplo, es un producto, si seguimos leyendo encontramos o un +, lo cual indica que uno de los factores del producto es una suma. Siguiendo con la lectura vemos que tras el signo + aparecen c y b, luego el primer factor es b + c; nalmente hallamos a, de donde la expresin inja del miembro de la o izquierda es a (b + c). Aunque es probable que el lector no se encuentre a gusto con esta notacin y o comience a aorar el uso de parntesis, la ventaja de la notacin postja reside n e o en que es fcilmente implementable en un computador,3 al no hacer uso de a parntesis ni de reglas de precedencia (esto es, convenios tales como que a b + c e representa (a b) + c y no a (b + c)). La gura 17.8 muestra grcamente el proceso de evaluacin de la expresin a o o 2 4 3 +. Ms detalladamente, haciendo uso de una pila, un computador a interpretar esa expresin de la siguiente forma: a o 1. Al leer los dos primeros s mbolos, 2 y 4, el computador an no sabe qu u e ha de hacer con ellos, as que los pone en una pila. 2. Al leer el siguiente s mbolo, , saca dos elementos de la pila, los multiplica, y pone en ella su valor, 8. 3. El siguiente s mbolo, 3, se coloca en la pila, pues no tenemos ningn opeu rador que aplicar.
De hecho, los compiladores de lenguajes de alto nivel traducen las expresiones aritmticas a e notacin postja para realizar las operaciones. Hay lenguajes que usan siempre notacin postja, o o como, por ejemplo, el lenguaje de descripcin de pgina PostScript. o a
3

370

Cap tulo 17. Estructuras de datos recursivas

*
Entrada

4 2
Pendientes

3
Entrada

8
Pendientes

+
Entrada

3 8
Pendientes

EoLn
Entrada

11
Pendientes

EoLn
Entrada Pendientes

11
Resultado

Figura 17.8. Evaluacin postja de una expresin. o o

4. Despus se lee el s e mbolo +, con lo que se toman dos elementos de la pila, 3 y 8, se calcula su suma, 11, y se pone en la pila. 5. Para terminar, ya no hay ningn s u mbolo que leer y slo queda el resultado o de la operacin. o Una vez visto el ejemplo anterior, la evaluacin postja de expresiones arito mticas es muy sencilla de codicar: slo se necesita una pila para almacenar e o los operandos y las operaciones bsicas de las pilas. La codicacin en Pascal se a o deja como ejercicio.

17.3

Colas

Una cola es una lista en la que todas las inserciones se realizan por un extremo y todas las eliminaciones se realizan por el otro extremo de la lista. El ejemplo ms importante de esta estructura de datos, del cual recibe el a nombre, es el de una cola de personas ante una ventanilla. En esta situacin, o el primero en llegar es el primero en ser servido; por esto, las colas tambin se e llaman listas FIFO.4 Un ejemplo ms interesante, dentro de un contexto informtico, resulta al a a considerar la gestin de trabajos de una impresora conectada en red. Todos los o archivos por imprimir se van guardando en una cola y se irn imprimiendo segn a u el orden de llegada (ser ciertamente una ruindad implementar esta lista como a una pila).
4

Del ingls First-In-First-Out , es decir, el primero en entrar es el primero en salir. e

17.3. Colas

371

17.3.1
es:

Denicin del tipo cola o

Las colas, como listas que son, podr denirse de la manera habitual, esto an

type tElem = char; {o lo que corresponda} tCola = ^tNodoCola; tNodoCola = record contenido: tElem; siguiente: tCola end; {tNodoCola}

Sin embargo, algunas operaciones, como poner un elemento en una cola, no resultan ecientes, debido a que debe recorrerse la lista en su totalidad para llegar desde su primer elemento hasta el ultimo. Por ello, suele usarse otra denicin, o considerando una cola como un par de punteros:
type tElem = char; {o lo que corresponda} tApNodo = ^tNodoCola; tNodoCola = record contenido: tElem; siguiente: tApNodo end; {tNodoCola} tCola = record principio: tApNodo; final: tApNodo end; {tCola}

Con esta denicin de colas, cualquier operacin que altere la posicin de los o o o nodos extremos de la lista deber actualizar los punteros principio y final; sin a embargo, esto resulta ventajoso en comparacin con la obligatoriedad de recorrer o toda la cola para aadir un nuevo elemento. n

17.3.2

Operaciones bsicas a

Veamos cmo implementar las operaciones bsicas con colas siguiendo la o a denicin anterior. o Creacin de una cola vac o a Para crear una cola se necesita iniciar a nil los campos principio y final del registro. Nada ms fcil: a a

372

Cap tulo 17. Estructuras de datos recursivas


procedure CrearCola(var cola: tCola); begin cola.principio:= nil; cola.final:= nil end; {CrearCola}

Por eciencia en la implementacin de los siguientes procedimientos resultar o a conveniente considerar una lista vac a aquella cuyo puntero final tiene el valor a nil; esto permitir que para comprobar si una cola es o no vac slo se necesite a a o evaluar la expresin lgica cola.final = nil. Es trivial escribir el subprograma o o que efecta esta comprobacin. u o Consulta del primer elemento Esta operacin es idntica a Cima de una pila, puesto que nos interesa el o e contenido del primer nodo de la cola. Para vericar el nivel de comprensin o sobre el tema, es un buen ejercicio intentar escribirla (sin mirar el anlogo para a pilas, como es natural). A adir un elemento n En las colas, los elementos nuevos se sitan al nal y por esta razn es u o necesario actualizar el puntero final (y tambin principio si la cola est vac e a a). Un simple grco servir para convencerse cmo. El diseo de este procedimiento a a o n es el siguiente:
si cola no est vac entonces a a Crear y dar valores al nuevoNodo Actualizar los punteros y el final de cola en otro caso Crear y dar valores al nuevoNodo Actualizar principio y final de cola

La codicacin en Pascal de este diseo podr ser la siguiente: o n a


procedure PonerEnCola(dato: tElem; var cola: tCola); {Efecto: dato se a~ade al final de cola} n var nuevoNodo: tApNodo; begin New(nuevoNodo); nuevoNodo^.contenido:= dato; nuevoNodo^.siguiente:= nil;

17.3. Colas
if cola.final <> nil then begin {Si la cola no est vaca se actualizan los punteros} a cola.final^.siguiente:= nuevoNodo; cola.final:= nuevoNodo end {then} else begin {Actualizacin de punteros:} o cola.principio:= nuevoNodo; cola.final:= nuevoNodo end {else} end; {PonerEnCola}

373

Suprimir el primer elemento Para denir SacarDeCola tenemos que considerar dos casos distintos: 1. Que la cola sea unitaria, pues al sacar su unico elemento se queda vac a, con lo que hay que actualizar tanto su principio como su final. 2. Que la cola tenga longitud mayor o igual a dos, en cuyo caso slo hay que o actualizar el campo principio. A continuacin se muestra la implementacin del procedimiento: o o
procedure SacarDeCola(var cola: tCola); {PreC.: cola es no vaca} {Efecto.: se extrae el primer elemento de cola} var apuntaAux : tApNodo; begin if cola.principio = cola.final then begin {La cola es unitaria} apuntaAux:= cola.principio; Dispose(apuntaAux); cola.principio:= nil; cola.final:= nil end {then} else begin {Si Longitud(cola) >= 2:} apuntaAux:= cola.principio; cola.principio:= apuntaAux^.siguiente; Dispose(apuntaAux) end {else} end; {SacarDeCola}

374

Cap tulo 17. Estructuras de datos recursivas

17.3.3

Aplicacin: gestin de la caja de un supermercado o o

Con la ayuda del tipo cola se presenta a continuacin un programa de simuo lacin del ujo de clientes en una caja de supermercado. o Una cantidad prejada de clientes se va incorporando a la cola en instantes discretos con una cierta probabilidad tambin jada de antemano;5 cada cliente e lleva un mximo de art a culos, de forma que cada art culo se contabiliza en una unidad de tiempo. Cuando a un cliente le han sido contabilizados todos los art culos, es eliminado de la cola. En cada instante se muestra el estado de la cola. Un primer esbozo en seudocdigo de este programa ser el siguiente: o a
Leer valores iniciales Iniciar contadores repetir Mostrar la cola Procesar cliente en caja Procesar cliente en cola hasta que se acaben los clientes y los art culos

La lectura de los valores iniciales consiste en pedir al usuario del programa el nmero de clientes, numClientes, la probabilidad de que aparezca un cliente por u unidad de tiempo, probLlegada, y el nmero mximo de art u a culos por cliente, maxArti. Tras denir los valores iniciales de los contadores de tiempo t, y de clientes puestos en cola, contClientes, slo hay que renar las acciones del bucle o del diseo anterior. As en un nivel de diseo ms detallado Procesar cliente en n , n a caja se descompondr en a
si el cliente ya no tiene art culos entonces Retirarlo de la cola en otro caso Reducir el nmero de art u culos del cliente

Mientras que el renamiento de Procesar cliente en cola ser a


si quedan clientes y random < probabilidad prejada entonces Aadir un cliente e incrementar el contador de clientes n

A partir del renamiento anterior ya podemos pasar a la implementacin en o Pascal de este programa.
Para incluir factores de aleatoriedad se hace uso de Randomize y Random de Turbo Pascal (vase el apartado A.2.1). e
5

17.3. Colas

375

El unico tipo que se necesita denir para este programa es el tipo tCola. Las variables que almacenarn los datos del programa son el nmero de personas a u que llegarn a la caja, numClientes, el nmero mximo de art a u a culos que cada persona puede llevar, maxArti, y la probabilidad de que una persona se una a la cola, probLlegada. Adems se usar la variable t para medir los distintos a a instantes de tiempo, la variable contClientes para contar el nmero de personas u que se han puesto en la cola y la variable caja, que es de tipo tCola. El encabezamiento del programa es el siguiente:
Program SimuladorDeColas (input, output); type tElem = integer; tApNodo = ^tNodo; tNodo = record contenido: tElem; siguiente: tApNodo end; {tNodo} tCola = record principio, final: tApNodo end; {tCola} var t, contClientes, numClientes, maxArti: integer; probLlegada: real; caja: tCola;

Se usarn los siguientes procedimientos estndar del tipo tCola: CrearCola, a a PonerEnCola, SacarDeCola, y adems MostrarCola que, como su nombre india ca, muestra todos los elementos de la cola. Su implementacin se presenta a o continuacin: o
procedure MostrarCola(cola: tCola); {Efecto: muestra en pantalla todos los elementos de cola} var apuntaAux: tApNodo; begin if cola.final= nil then WriteLn(La caja est desocupada) a else begin apuntaAux:= cola.principio; repeat WriteLn(apuntaAux^.contenido); apuntaAux:= apuntaAux^.siguiente until apuntaAux= nil end {else} end; {MostrarCola}

376

Cap tulo 17. Estructuras de datos recursivas

En el cuerpo del programa, tal como se especica en el diseo, tras la lectura n de datos, se realiza la siguiente simulacin en cada instante t: se procesa un o art culo en la caja (se anota su precio), cuando se acaban todos los art culos de un determinado cliente ste se elimina de la cola (paga y se va); por otra parte e se comprueba si ha llegado alguien (si su nmero aleatorio es menor o igual que u probLlegada), si ha llegado se le pone en la cola y se actualiza el contador de personas. Finalmente, el cuerpo del programa es el siguiente:
begin Randomize; CrearCola(caja); t:= 0; contClientes:= 0; WriteLn(Simulador de Colas); WriteLn(Introduzca nmero de personas); u ReadLn(numClientes); WriteLn(Introduzca la probabilidad de llegada); ReadLn(probLlegada); WriteLn(Introduzca mximo de artculos); a ReadLn(maxArti); repeat Writeln(Tiempo t =,t); MostrarCola(caja); t:= t + 1; if caja.principio^.contenido= 0 then SacarDeCola(caja) else with caja.principio^ do contenido:= contenido - 1; if (Random <= probLlegada) and (contClientes < numClientes) then begin PonerEnCola (Random(maxArti) + 1, caja); contClientes:= contClientes + 1 end; {if} until (contClientes = numClientes) and (caja.principio= nil) end. {SimuladorDeColas}

17.4

Arboles binarios

Es posible representar estructuras de datos ms complejas que las listas a haciendo uso de los punteros. Las listas se han denido como registros que contienen datos y un puntero al siguiente nodo de la lista; una generalizacin o del concepto de lista es el rbol, donde se permite que cada registro del tipo de a dato dinmico tenga ms de un enlace. La naturaleza lineal de las listas hace a a

17.4. Arboles binarios

377

datos

...

...

Figura 17.9.

H
Figura 17.10.

posible, y fcil, denir algunas operaciones de modo iterativo; esto no ocurre con a los rboles, que son manejados de forma natural haciendo uso de la recursin. a o Los rboles son estructuras de datos recursivas ms generales que una lista a a y son apropiados para aplicaciones que involucran algn tipo de jerarqu (tales u a como los miembros de una familia o los trabajadores de una organizacin), o o de ramicacin (como los rboles de juegos), o de clasicacin y/o bsqueda. o a o u La denicin recursiva de rbol es muy sencilla: Un rbol o es vac o consiste o a a o en un nodo que contiene datos y punteros hacia otros rboles. Es decir, la a representacin grca de un rbol es una de las dos que aparecen en la gura 17.9. o a a En este apartado slo trataremos con rboles binarios, que son rboles en los o a a que cada nodo tiene a lo sumo dos descendientes. En la gura 17.10 vemos la representacin grca de un rbol binario. o a a

378

Cap tulo 17. Estructuras de datos recursivas

La terminolog usada cuando se trabaja con rboles es, cuando menos, cua a riosa. En ella se mezclan conceptos botnicos como ra y hoja y conceptos a z genealgicos tales como hijo, ascendientes, descendientes, hermanos, padres, etc. o En el rbol de la gura 17.10, el nodo A es la ra del rbol, mientras que los a z a nodos D, E, F y H son las hojas del rbol; por otra parte, los hijos de la ra son a z los nodos B y C, el padre de E es B, . . . La denicin en Pascal del tipo rbol binario es sencilla: cada nodo del rbol o a a va a tener dos punteros en lugar de uno.
type tElem = char; {o el tipo que corresponda} tArbol = ^tNodoArbol; tNodoArbol = record hIzdo, hDcho: tArbol; contenido: tElem end; {tNodoArbol}

La creacin de un rbol vac a estas alturas, no representa problema alguno. o a o, Sin embargo no ocurre lo mismo para realizar una consulta, ya que debemos saber cmo movernos dentro del rbol. o a

17.4.1

Recorrido de un rbol binario a

Denir un algoritmo de recorrido de un rbol binario no es una tarea directa a ya que, al no ser una estructura lineal, existen distintas formas de recorrerlo. En particular, al llegar a un nodo podemos realizar una de las tres operaciones siguientes: (i) Leer el valor del nodo. (ii) Seguir por el hijo izquierdo. (iii) Seguir por el hijo derecho. El orden en el que se efecten las tres operaciones anteriores determinar el orden u a en el que los valores de los nodos del rbol son le a dos. Si se postula que siempre se leer antes el hijo izquierdo que el derecho, entonces existen tres formas distintas a de recorrer un rbol: a Preorden: Primero se lee el valor del nodo y despus se recorren los subrboles. e a Esta forma de recorrer el rbol tambin recibe el nombre de recorrido a e primero en profundidad. El rbol de la gura 17.10 recorrido en preorden se leer as ABDECFGH. a a :

17.4. Arboles binarios

379

Inorden: En este tipo de recorrido, primero se recorre el subrbol izquierdo, a luego se lee el valor del nodo y, nalmente, se recorre el subrbol derecho. a El rbol de la gura 17.10 recorrido en inorden se leer as DBEAFCHG. a a : Postorden: En este caso, se visitan primero los subrboles izquierdo y derecho a y despus se lee el valor del nodo. e El rbol de la gura 17.10 recorrido en postorden se leer as DEBFa a : HGCA. Ahora, escribir un procedimiento recursivo para recorrer el rbol (en cuala quiera de los tres rdenes recin denidos) es tarea fcil, por ejemplo: o e a
procedure RecorrerEnPreorden(arbol: tArbol); begin if arbol <> nil then begin Visitarnodo(arbol); {por ejemplo, Write(arbol^.contenido)} RecorrerEnPreorden(arbol^.hIzdo); RecorrerEnPreorden(arbol^.hDcho); end {if} end; {RecorrerEnPreorden}

para utilizar otro orden en el recorrido slo hay que cambiar el orden de las o acciones Visitarnodo y las llamadas recursivas.

17.4.2

Arboles de b squeda u

Como un caso particular de rbol binario se encuentran los rboles binarios a a de bsqueda (o rboles de bsqueda binaria), que son aquellos rboles en los que u a u a el valor de cualquier nodo es mayor que el valor de su hijo izquierdo y menor que el de su hijo derecho. Segn la denicin dada, no puede haber dos nodos u o con el mismo valor en este tipo de rbol. a
ee r r Obsrvese que los nodos de un rbol binario de bsqueda se pueden enue a u merar en orden creciente siguiendo un recorrido en inorden.

La utilidad de los rboles binarios de bsqueda reside en que si buscamos a u cierta componente, podemos decir en qu mitad del rbol se encuentra compae a rando solamente con el nodo ra Ntese la similitud con el mtodo de bsqueda z. o e u binaria.

380
ee r r

Cap tulo 17. Estructuras de datos recursivas


Una mejora de los rboles de bsqueda consiste en aadir un campo clave en a u n cada nodo y realizar las bsquedas comparando los valores de dichas claves u en lugar de los valores del campo contenido. De esta forma, pueden existir en el rbol dos nodos con el mismo valor en el campo contenido pero con a clave distinta. En este texto se implementan los rboles de bsqueda sin a u campo clave para simplicar la presentacin; la modicacin de la impleo o mentacin para incluir un campo clave es un ejercicio trivial. o

Operaciones bsicas a Las operaciones bsicas para el manejo de rboles de bsqueda son la cona a u sulta, la insercin y la eliminacin de nodos. Las dos primeras son de fcil o o a implementacin, haciendo uso de la natural recursividad de los rboles. La opeo a racin de eliminacin de nodos es, sin embargo, algo ms compleja, como se o o a detalla a continuacin. o B squeda de un nodo u Debido al orden intr nseco de un rbol de bsqueda binaria, es fcil implea u a mentar una funcin que busque un determinado valor entre los nodos del rbol y, o a en caso de encontrarlo, proporcione un puntero a ese nodo. La versin recursiva o de la funcin es particularmente sencilla, todo consiste en partir de la ra y o z rastrear el rbol en busca del nodo en cuestin, segn el siguiente diseo: a o u n
si arbol es vac entonces o Devolver fallo en otro caso si arbol^.contenido = dato entonces Devolver el puntero a la ra de arbol z en otro caso si arbol^.contenido > dato entonces Buscar en el hijo izquierdo de arbol en otro caso si arbol^.contenido < dato entonces Buscar en el hijo derecho de arbol

La codicacin en Pascal es directa: o


function Encontrar(dato: tElem; arbol: tArbol): tArbol; {Dev. un puntero al nodo con dato, si dato est en arbol, o a nil en otro caso} begin if arbol = nil then Encontrar:= nil else with arbol^ do if dato < contenido then

17.4. Arboles binarios


Encontrar:= Encontrar (dato, hIzdo) else if datos > contenido then Encontrar:= Encontrar (dato, hDcho) else Encontrar:= arbol end; {Encontrar}

381

Insercin de un nuevo nodo o El siguiente procedimiento inserta un nuevo nodo en un rbol binario de a bsqueda rbol; la insercin del nodo es tarea fcil, todo consiste en encontrar u a o a el lugar adecuado donde insertar el nuevo nodo, esto se hace en funcin de su o valor de manera similar a lo visto en el ejemplo anterior. El seudocdigo para o este procedimiento es:
si arbol es vac entonces o crear nuevo nodo en otro caso si arbol^.contenido > datoNuevo entonces Insertar ordenadamente en el hijo izquierdo de arbol en otro caso si arbol^.contenido < datoNuevo entonces Insertar ordenadamente en el hijo derecho de arbol

Y la implementacin en Pascal es: o


procedure Insertar(datoNuevo: tElem; var arbol: tArbol); {Efecto: se a~ade ordenadamente a arbol un nodo de contenido n datoNuevo} begin if arbol = nil then begin New(arbol); with arbol^ do begin hIzdo:= nil; hDcho:= nil; contenido:= datoNuevo end {with} end {then} else with arbol^ do if datoNuevo < contenido then Insertar(datoNuevo,hIzdo) else if datoNuevo > contenido then Insertar(datoNuevo,hDcho) else {No se hace nada: entrada duplicada} end; {Insertar}

La forma resultante de un rbol binario de bsqueda depende bastante del a u orden en el que se vayan insertando los datos: si stos ya estn ordenados el e a a rbol degenera en una lista (vase la gura 17.11). e

382

Cap tulo 17. Estructuras de datos recursivas

2 4 5 10 8 11 12 6 8 10 11 12

Datos: 6, 4, 2, 10, 5, 12, 8, 11

Datos: 2, 4, 5, 6, 8, 10, 11, 12

Figura 17.11.

Supresin de un nodo o Eliminar un nodo en un rbol de bsqueda binaria es la unica operacin que a u o no es fcil de implementar: hay que considerar los distintos casos que se pueden a dar, segn el nodo por eliminar sea u 1. Una hoja del rbol, a 2. Un nodo con un slo hijo, o o 3. Un nodo con dos hijos. Los dos primeros casos no presentan mayor problema; sin embargo, el tercero requiere un anlisis detallado para suprimir el nodo de modo que el rbol a a resultante siga siendo un rbol binario de bsqueda. En primer lugar hay que a u situarse en el nodo padre del nodo por eliminar; despus procederemos por pasos, e analizando qu se necesita hacer en cada caso: e 1. Si el nodo por eliminar es una hoja, entonces basta con destruir su variable asociada (usando Dispose) y, posteriormente, asignar nil a ese puntero. 2. Si el nodo por eliminar slo tiene un subrbol, se usa la misma idea que o a al eliminar un nodo interior de una lista: hay que saltarlo conectando directamente el nodo anterior con el nodo posterior y desechando el nodo por eliminar. 3. Por ultimo, si el nodo por eliminar tiene dos hijos no se puede aplicar la tcnica anterior, simplemente porque entonces habr dos nodos que e a

17.4. Arboles binarios

383

conectar y no obtendr amos un rbol binario. La tarea consiste en eliminar a el nodo deseado y recomponer las conexiones de modo que se siga teniendo un rbol de bsqueda. a u En primer lugar, hay que considerar que el nodo que se coloque en el lugar del nodo eliminado tiene que ser mayor que todos los elementos de su subrbol izquierdo, luego la primera tarea consistir en buscar tal nodo; a a de ste se dice que es el predecesor del nodo por eliminar (en qu posicin e e o se encuentra el nodo predecesor?). Una vez hallado el predecesor el resto es bien fcil, slo hay que copiar su a o valor en el nodo por eliminar y desechar el nodo predecesor. A continuacin se presenta un esbozo en seudocdigo del algoritmo en cuestin; o o o la implementacin en Pascal del procedimiento se deja como ejercicio indicado. o
Determinar el nmero de hijos del nodo N a eliminar u si N no tiene hijos entonces eliminarlo en otro caso si N slo tiene un hijo H entonces o Conectar H con el padre de N en otro caso si N tiene dos hijos entonces Buscar el predecesor de N Copiar su valor en el nodo a eliminar Desechar el nodo predecesor

17.4.3

Aplicaciones

Recorrido en anchura de un rbol binario a El uso de colas resulta util para describir el recorrido en anchura de un a rbol. Hemos visto tres formas distintas de recorrer un rbol binario: recorrido a en preorden, en inorden y en postorden. El recorrido primero en anchura del a rbol de la gura 17.10 nos da la siguiente ordenacin de nodos: ABCDEFGH. o La idea consiste en leer los nodos nivel a nivel, primero la ra luego (de izquierda z, a derecha) los nodos de profundidad 1, los de profundidad 2, etc. Analicemos cmo trabaja el algoritmo que hay que codicar, para ello seguio mos el recorrido en anchura sobre el rbol de la gura 17.10: a 1. En primer lugar se lee la ra de rbol: A. z a 2. Luego hay que leer los hijos de la ra B y C. z, 3. Despus, se leen D y E (hijos de B) y F y G (hijos de C). e 4. Finalmente se lee G, el unico nodo de profundidad 3.

384

Cap tulo 17. Estructuras de datos recursivas

A T1 B C T3 D E F G T2

Figura 17.12.

Teniendo en cuenta que los hijos de un nodo son las ra de sus subrboles ces a hijos se observa que existe una tarea repetitiva, la visita del nodo ra de un z a rbol. Lo unico que resta es ordenar adecuadamente los subrboles por visitar. a En este punto es donde las colas juegan su papel, pues se va a considerar una cola de subrboles pendientes de visitar, este proceso se representa en la a gura 17.12: 1. Tras leer el nodo ra A se colocan sus dos subrboles hijos en la cola de z a espera. 2. Se toma el primer rbol de la cola y se visita su ra en este caso B, tras a z, lo que se aaden sus subrboles hijos a la cola. n a 3. . . . La tabla 17.1 muestra, paso a paso, cmo va avanzando el recorrido en ano chura y cmo va cambiando el estado de la cola de espera (usando la notacin de o o la gura 17.12 para nombrar a los distintos subrboles con los que se trabaja). a Para la codicacin de este recorrido ser necesario denir el tipo cola de o a a rboles (el tipo tCola por razones de eciencia) y declarar una variable enEspera para almacenar los rboles en espera de ser visitados. a Una primera aproximacin en seudocdigo es la siguiente: o o
Crear una cola con el arbolDato en ella Procesar los rboles de esa cola a

La primera accin se rena directamente as o :

17.4. Arboles binarios Recorrido [] [A] [A,B] [A,B,C] [A,B,C,D] [A,B,C,D,E] [A,B,C,D,E,F] [A,B,C,D,E,F,G] [A,B,C,D,E,F,G,H] Cola de espera [T] [T1, T2] [T2,D,E] [D,E,F,T3] [E,F,T3] [F,T3] [T3] [H] []

385

Tabla 17.1. var arbolesEnEspera: cola de rboles a ... CrearCola(arbolesEnEspera); PonerEnCola(arbolDato, arbolesEnEspera);

donde una cola de rboles se concreta mediante el tipo tApNodo (vase el apara e tado 17.3), siendo sus elementos tElem del tipo tArbol. La segunda accin consiste en la serie de pasos de la gura 17.13. o Naturalmente, todo el peso del procedimiento recae en el procesado de los a rboles en espera. Para ello hay que leer la ra del primer rbol de la cola z a (mientras sta no est vac e e a), sacarlo de ella y aadir al nal de la cola sus n subrboles hijos. Por tanto, la accin Procesar los rboles en espera se puede a o a renar de la siguiente forma:
mientras arbolesEnEspera <> nil hacer Sacar el primer rbol de la cola a Procesar su nodo ra z Poner en cola los subrboles no vac a os

El renamiento de cada una de las tareas anteriores es directa haciendo uso de las operaciones descritas para el tipo cola.

386

Cap tulo 17. Estructuras de datos recursivas

1) Punto de partida

2) Procesado de A

A D

B E F

C G H

... 3) Procesado de B

...

4) Procesado de C

C F H G

F H

Figura 17.13.

Arboles de expresiones aritmticas e Mediante un rbol binario pueden expresarse las expresiones aritmticas a e habituales: suma, resta, producto y cociente. En tal representacin cada nodo o interior del rbol est etiquetado con el s a a mbolo de una operacin aritmtica o e (cuyos argumentos son los valores de las expresiones aritmticas que representan e sus subrboles hijos), y slo las hojas estn etiquetadas con valores numricos. a o a e Es interesante observar la relacin entre los distintos tipos de recorrido de un o a rbol binario con las distintas notaciones para las expresiones aritmticas. Por e ejemplo, la siguiente expresin dada en notacin habitual (esto es, en notacin o o o inja): 3 (4 + 5) (7 : 2) : 6 tiene el rbol sintctico de la gura 17.14. a a Si se recorre el rbol en inorden entonces se obtiene la notacin inja (habia o tual) de la expresin. Si se realiza un recorrido en postorden entonces la ordeo nacin de los nodos es la siguiente: o 345+72:6: que coincide con la expresin en notacin postja. o o

17.5. Otras estructuras dinamicas de datos

387

: 6

*
3 4

: +
5 7 2

Figura 17.14. Arbol sintctico de una expresin aritmtica. a o e ee r r El lector atento habr observado que esta notacin postja no coincide con a o la denida en el apartado 17.2.3; los argumentos de las operaciones aparecen cambiados de orden, esto no es problema con operaciones conmutativas (suma y producto) pero s en las que no lo son. Este problema se subsana deniendo un recorrido en postorden en el que se visita antes el hijo derecho que el izquierdo.

Finalmente, si se recorre el rbol en preorden se obtiene una expresin preja a o de la expresin, en este caso: o :3+45:726

17.5

Otras estructuras dinmicas de datos a

En este ultimo apartado damos una idea de otras aplicaciones de la memoria dinmica sin pasar al estudio de la implementacin de las mismas. a o Listas doblemente enlazadas La implementacin de listas dinmicas mostrada en este cap o a tulo adolece de la imposibilidad de acceder directamente al predecesor de un nodo. Este problema caus la inclusin de varios casos especiales en la implementacin de algunas o o o operaciones sobre listas, como la insercin o eliminacin de nodos intermedios o o (vase el apartado 17.1.5). e Una lista doblemente enlazada o de doble enlace se dene de modo similar al de las listas de enlace simple, slo que cada nodo dispone de dos punteros que o apuntan al nodo anterior y al nodo siguiente:

388

Cap tulo 17. Estructuras de datos recursivas

lista enlazada

Figura 17.15. Una lista doblemente enlazada.

type tElem = char; {o lo que corresponda} tListaDobleEnlace = ^tNodo; tNodo = record contenido : tElem; anterior, siguiente: tListaDobleEnlace end; {tNodo}

La gura 17.15 representa una lista doblemente enlazada. Con los nodos primero y ultimo de la lista se pueden tomar dos posturas: 1. Considerar que el anterior del primer nodo es nil, y que el siguiente al ultimo tambin es nil. e En este caso resulta util considerar la lista como un registro con dos com ponentes: principio y nal. Del mismo modo que en la implementacin de o tCola. 2. Considerar que el anterior del primero es el ultimo y que el siguiente al ultimo es el primero. En este caso, estrictamente hablando, no se obtiene una lista, sino un anillo o lista circular que carece de principio y de nal. Un anlisis ms profundo de esta estructura rebasa los l a a mites de este libro; no obstante, en el apartado de comentarios bibliogrcos se citan algunas referencias a con las que profundizar en el estudio de este tipo de datos.

17.6. Ejercicios Arboles generales

389

En el apartado 17.4 se han estudiado los rboles binarios; esta estructura se a puede generalizar a estructuras en las que los nodos pueden tener ms de dos a subrboles hijos. Esta generalizacin puede hacerse de dos formas distintas: a o 1. Pasando a rboles n-arios, en los que cada nodo tiene a lo sumo n subrboles a a hijos. Un rbol rbol n-ario se puede representar como un rbol de registros de a a a n componentes, o bien como un vector de subrboles. a 2. Considerando rboles generales, en los que no existe limitacin en el nmero a o u de subrboles hijo que puede tener. a Los rboles generales se implementan mediante un rbol de listas (puesto a a que no se sabe el nmero mximo de hijos de cada nodo). u a La eleccin de un tipo de rbol o de otro depende del problema particular o a que se est tratando: el uso de registros acelera el acceso a un hijo arbitrario; e sin embargo, un nmero elevado de registros puede consumir buena parte de la u memoria disponible. El otro enfoque, el de un rbol general, resulta apropiado a para evitar el derroche de memoria si no se usa la mayor de los campos de los a registros con la contrapartida de un mayor tiempo de acceso a los nodos. Entre las aplicaciones t picas de los rboles generales se encuentran los rboles a a de juegos o los rboles de decisin. Veamos brevemente qu se entiende por un a o e a rbol de juego (de mesa, como, por ejemplo, las damas o el ajedrez): La ra de un rbol de juegos es una posicin de las chas en el tablero; el z a o conjunto de sus hijos lo forman las distintas posiciones accesibles en un movimiento desde la posicin anterior. Si no es posible realizar ningn movimiento o u desde una posicin, entonces ese nodo no tiene descendientes, es decir, es una o hoja; como ejemplo de rbol de juegos, en la gura 17.16 se muestra un rbol a a para el juego del tres en raya, representando una estrategia que permite ganar siempre al primero en jugar. Conviene saber que no es corriente razonar sobre juegos desarrollando expl citamente todo el rbol de posibilidades, sino que suele recorrerse impl a citamente (hasta cierto punto) al efectuarse llamadas de subprogramas recursivos.

17.6

Ejercicios

1. Escriba una versin iterativa de la funcin longitud de una lista. o o 2. Se denomina pal ndromo una palabra o frase que se lee igual de izquierda a derecha que de derecha a izquierda. Por ejemplo,

390

Cap tulo 17. Estructuras de datos recursivas

Negras

Blancas

Negras

Blancas

Blancas pierden

Negras

Blancas

Blancas pierden

Negras Las negras hacen tres en raya en su siguiente turno

Figura 17.16. Estrategia ganadora para el juego del tres en raya.

17.7. Referencias bibliograficas


DABALE ARROZ A LA ZORRA EL ABAD

391

Escriba un programa que lea una cadena de caracteres (terminada por el carcter a de n de l nea) y determine si es o no un pal ndromo. (Indicacin: un mtodo o e sencillo consiste en crear una lista de letras y su inversa y compararlas para ver si son iguales). 3. Completar el programa de simulacin de colas, calculando el tiempo que permao nece la caja desocupada y el tiempo medio de espera de los clientes. 4. La funcin de bsqueda de un dato en un rbol binario de bsqueda se present o u a u o de forma recursiva. Escribir una versin iterativa de dicha funcin. o o 5. Escriba un procedimiento que halle el nodo predecesor de un nodo de un rbol bia nario de bsqueda. Usese para escribir una codicacin completa de la eliminacin u o o de un nodo en un rbol de bsqueda binaria. a u 6. Completar la codicacin en Pascal del recorrido en anchura de un rbol. Para el o a procesado de los nodos visitados lim tese a imprimir el contenido de los nodos. 7. Implemente una versin iterativa del procecimiento PonerEnCola. o 8. Escriba la versin iterativa de un procedimiento recursivo genrico. o e 9. Escriba una funcin que evale expresiones aritmticas escritas en notacin posto u e o ja.

17.7

Referencias bibliogrcas a

En [DL89] se hace un estudio detallado de de las principales estructuras dinmicas a de datos con numerosos ejemplos y aplicaciones, siguiendo el mismo estilo que en la primera parte de este texto [DW89]. Una referencia obligada es el clsico libro de N. Wirth [Wir86], que dedica su cap a tulo 4 al estudio de las estructuras dinmicas de datos. Dentro de ellas hay que destacar a el tratamiento de los distintos tipos de rboles: equilibrados, de bsqueda, generales y a u otros, con interesantes aplicaciones. Esta parte de nuestro libro debe entenderse como una introduccin, ya que las eso tructuras de datos que pueden construirse con punteros son variad simas (rboles, grafos, a tablas, conjuntos, matrices de gran tamao y bases de datos, por citar algunos) y pueden n llegar a ser muy complejos. Su estudio en detalle corresponde a cursos posteriores. Por citar algunos textos en espaol sobre el tema destacamos [AM88] y [AHU88]. n Finalmente, es obligado mencionar que la idea de los punteros con referencias indirectas de los datos es ampliamente explotada por el lenguaje C y sus extensiones. Precisamente ese uso extensivo es una razn de peso para lograr programas ecientes. Entre o las muchas referencias sobre este lenguaje, citamos [KR86].

Tema VI

Aspectos avanzados de programacin o

Cap tulo 18

Complejidad algor tmica

18.1 Conceptos bsicos . . . . . . . . . . . . . . . . . . . . . . 396 a 18.2 Medidas del comportamiento asinttico . . . . . . . . 402 o 18.3 Reglas prcticas para hallar el coste de un programa 408 a 18.4 Utiles matemticos . . . . . . . . . . . . . . . . . . . . . 418 a 18.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 18.6 Referencias bibliogrcas . . . . . . . . . . . . . . . . . . 425 a

Con frecuencia, un problema se puede resolver con varios algoritmos, como ocurre, por ejemplo, en los problemas de ordenacin y bsqueda de vectores o u (vase el cap e tulo 15). En este cap tulo se estudian criterios (presentados escuetamente en el apartado 1.3.3) que permiten al programador decidir cul de los posibles algorita mos que resuelven un problema es ms eciente. En general, la eciencia se a puede entender como una medida de los recursos empleados por un algoritmo en su ejecucin. En particular, usualmente se estudia la eciencia de un algoo ritmo en tiempo (de ejecucin), espacio (de memoria) o nmero de procesadores o u (en algoritmos implementados en arquitecturas paralelas). Como se vio en el apartado 1.3.3, el estudio de la complejidad algor tmica trata de resolver este important simo aspecto de la resolucin de problemas. o Los criterios utilizados por la complejidad algor tmica no proporcionan medidas absolutas, como podr ser el tiempo total en segundos empleado en la a ejecucin del programa que implementa el algoritmo, sino medidas relativas al o

396

Cap tulo 18. Complejidad algor tmica

tamao del problema. Adems, estas medidas son independientes del computan a dor sobre el que se ejecute el algoritmo. Nuestro objetivo en este tema es proporcionar las herramientas necesarias para el clculo de la complejidad de los algoritmos. De esta forma, en caso a de disponer de ms de un algoritmo para solucionar un problema, tendremos a elementos de juicio para decidir cul es mejor desde el punto de vista de la a eciencia. Con frecuencia, no es posible mejorar simultneamente la eciencia en tiempo a y en memoria, buscndose entonces un algoritmo que tenga una complejidad a razonable en ambos aspectos. Actualmente, y gracias a los avances de la tcnica, e quizs es ms importante el estudio de la complejidad en el tiempo, ya que a a la memoria de un computador puede ser ampliada fcilmente, mientras que el a problema de la lentitud de un algoritmo suele ser ms dif de resolver. a cil Adems, el clculo de las complejidades en tiempo y en espacio se lleva a cabo a a de forma muy similar. Por estas razones, se ha decidido dedicar este cap tulo esencialmente al estudio de la complejidad en el tiempo.

18.1

Conceptos bsicos a

El tiempo empleado por un algoritmo se mide en pasos Para medir el tiempo empleado por un algoritmo se necesita una medida adecuada. Claramente, si se aceptan las medidas de tiempo f sico, obtendremos unas medidas que dependern fuertemente del computador utilizado: si a se ejecuta un mismo algoritmo con el mismo conjunto de datos de entrada en dos computadores distintos (por ejemplo en un PC-XT y en un PC-Pentium) el tiempo empleado en cada caso diere notablemente. Esta solucin llevar a o a desarrollar una teor para cada computador, lo que resulta poco prctico. a a Por otro lado, se podr contar el nmero de instrucciones ejecutadas por el a u algoritmo, pero esta medida depender de aspectos tales como la habilidad del a programador o, lo que es ms importante, del lenguaje de programacin en el a o que se implemente, y tampoco se debe desarrollar una teor para cada lenguaje a de programacin. o Se debe buscar entonces una medida abstracta del tiempo que sea independiente de la mquina con que se trabaje, del lenguaje de programacin, del a o compilador o de cualquier otro elemento de hardware o software que inuya en el anlisis de la complejidad en tiempo. Una de las posibles medidas, que es a la empleada en este libro y en gran parte de la literatura sobre complejidad, consiste en contar el nmero de pasos (por ejemplo, operaciones aritmticas, u e comparaciones y asignaciones) que se efectan al ejecutarse un algoritmo. u

18.1. Conceptos basicos El coste depende de los datos

397

Considrese el problema de decidir si un nmero natural es par o impar. e u Es posible usar la funcin Odd predenida en Pascal, que permite resolver el o problema en tiempo constante. Una segunda opcin es emplear el algoritmo consistente en ir restando 2 o repetidamente mientras que el resultado de la sustraccin sea mayor que 1, y o nalmente comprobar el valor del resto. Es fcil comprobar que se realizan n a div 2 restas, lo que nos indica que, en este caso, el tiempo empleado depende del dato original n. Normalmente, el tiempo requerido por un algoritmo es funcin o de los datos, por lo que se expresa como tal: as escribimos T (n) para representar , la complejidad en tiempo para un dato de tamao n. n Este tamao de entrada n depende fuertemente del tipo de problema que se n va a estudiar. As por ejemplo, en el proceso de invertir el orden de los d , gitos de un nmero natural u (4351, 0) ; (435, 1) ; (43, 15) ; (4, 153) ; (0, 1534) no importa el nmero en cuestin: el dato relevante para ver cunto tiempo se u o a tarda es la longitud del nmero que se invierte. Otro ejemplo: para sumar las u componentes de un vector de nmeros reales, el tiempo empleado depende del u nmero n de componentes del vector. En este caso, se puede decir ambas cosas u sobre el coste: Que el coste de invertir un nmero, d u gito a d gito, es lineal con respecto a su longitud (nmero de cifras) u Que el coste es la parte entera de log10 (n)+1, o sea, una funcin logar o tmica, siendo n el dato. Otra situacin ejemplar se da en el caso del algoritmo de suma lenta (vase o e el apartado 1.2.1) de dos nmeros naturales a y b: u
while b > 0 do begin a:= a + 1; b:= b - 1 end {while}

Como se puede observar, el coste del algoritmo depende unicamente del se gundo parmetro, ya que se ejecutan exactamente b iteraciones del cuerpo del a bucle. Es, por tanto, un algoritmo de complejidad lineal con respecto a b, es decir, T (a, b) = b. Conclusin: no siempre todos los datos son importantes de o cara a la complejidad.

398

Cap tulo 18. Complejidad algor tmica

En denitiva, a la hora de elegir el tamao de los datos de entrada n de un n algoritmo, conviene que represente la parte o la caracter stica de los datos que inuye en el coste del algoritmo. El coste esperado, el mejor y el peor Otro aspecto interesante de la complejidad en tiempo puede ilustrarse analizando el algoritmo de bsqueda secuencial ordenada estudiado en el aparu tado 15.1.2, en el que se recorre un vector (ordenado crecientemente) desde su primer elemento, hasta encontrar el elemento buscado, o hasta que nos encontremos un elemento en el vector que es mayor que el elemento elem buscado. La implementacin en Pascal de dicho algoritmo (ya mostrada en el citado apartado) o es la siguiente:
const N = 100; {tama~o del vector} n type tIntervalo = 0..N; tVector = array[1..N] of integer; function BusquedaSecOrd(v: tVector; elem: integer): tIntervalo; {PreC.: v est ordenado crecientemente, sin repeticiones} a {Dev. 0 (si elem no est en v) i (si v[i] = elem)} a o var i: tIntervalo; begin i:= 0; repeat {Inv.: v[j] = elem j, 1 j i} i:= i + 1 until (v[i] >= elem) or (i = N); {v[i] = elem o v[j] = elem j, 1 j N} if v[i] = elem then {se ha encontrado el valor elem} BusquedaSecOrd:= i else BusquedaSecOrd:= 0 end; {BusquedaSecOrd}

Intuitivamente se puede ver que, si se tiene la buena fortuna de encontrar el elemento al primer intento, el tiempo es, digamos, de un paso (un intento). En el peor caso (cuando el elemento elem buscado es mayor o igual que todos los elementos del vector), se tendr que recorrer todo el vector v, invirtiendo n a pasos. Informalmente, se podr pensar que en un caso normal, se recorrer a a la mitad del vector (n/2 pasos).

18.1. Conceptos basicos

399

Como conclusin se puede armar que en algunos algoritmos, la complejidad o no depende unicamente del tamao del parmetro, sino que intervienen otros n a factores que hacen que la complejidad var de un caso a otro. Para distinguir e esas situaciones se habla de coste en el mejor caso, en el peor caso y en el caso medio: Tmx (n), expresa la complejidad en el peor caso, esto es, el tiempo mximo a a que un algoritmo puede necesitar para una entrada de tamao n. n Tm (n), expresa la complejidad en el mejor caso, esto es, el tiempo m nimo n que un algoritmo necesita para una entrada de tamao n. n Tmed (n), expresa la complejidad en el caso medio, esto es, el tiempo medio que un algoritmo necesita para una entrada de tamao n. Generalmente, n se suele suponer que todas las secuencias de entradas son equiprobables. Por ejemplo, en los algoritmos de bsqueda, se considerar que elem puede u a estar en cualquier posicin del vector con idntica probabilidad, es decir, o e 1 con probabilidad n . Generalmente, la complejidad en el mejor caso es poco representativa y la complejidad en el caso medio es dif de calcular por lo que, en la prctica, se cil a suele trabajar con el tiempo para el peor caso por ser una medida signicativa y de clculo factible en general. No obstante, como ejemplo, se calculan a cona tinuacin las tres medidas para el algoritmo de bsqueda secuencial ordenada. o u Para jar ideas, vamos a hallar el coste en trminos de los tiempos empleados e para las operaciones de sumar (s), realizar una comparacin (c) y realizar una o asignacin (a) por un computador cualquiera. El coste en pasos es ms sencillo, o a ya que basta con dar el valor unidad a cada una de esas operaciones. Las tres medidas de coste mencionadas se calculan como sigue: Tm : Este tiempo m nimo se alcanzar cuando v[1] elem. En tal a n caso se necesita una asignacin para iniciar la variable i, una suma y una o asignacin para incrementar el valor de i, dos comparaciones en el bucle o repeat, otro test ms para v[i] = elem y, nalmente, una asignacin a a o la funcin BsquedaSecOrd. Por lo tanto: o u Tm (n) = 3a + 3t + s n que es constante, lo que abreviamos as : Tm (n) = k n

400

Cap tulo 18. Complejidad algor tmica

Tmx : Este tiempo mximo se alcanzar cuando v[n] elem. Por lo a a a tanto: Tmx (n) = a + n(s + 2t + a) + t + a = k1 n + k2 a Tmed : Supngase que es igualmente probable necesitar 1 vuelta, 2 vuelo tas,. . . , n vueltas para encontrar el elemento buscado.1 Adems, recordea mos que el tiempo empleado por el algoritmo cuando para en la posicin o j-sima del vector es e T (j) = k1 j + k2 segn lo dicho en el apartado anterior. Entonces, se tiene que:2 u
n

Tmed (n) =
j=1 n

Tj P (parar en la posicin j esima) o (k1 j + k2 ) 1 n

=
j=1

k1 n + 1 + k2 n 2 = c1 n + c2 = de forma que tambin es lineal con respecto a n. e Por supuesto, Tmed (n) < Tmx (n), como se puede comprobar comparando a los valores de k1 y c1 , lo que se deja como ejercicio al lector. Tambin importa el gasto de memoria e Por otra parte, el estudio de la implementacin de la funcin Sumatorio o o nos sirve para ver cmo el coste en memoria tambin debe tenerse en cuenta al o e analizar algoritmos. Esta funcin, que calcula la suma de los n primeros nmeros naturales, siendo o u n el argumento de entrada, puede ser implementada de forma natural con un algoritmo recursivo, resultando el siguiente cdigo en Pascal: o
function Sumatorio(n: {PreC.: n 0} n {Dev. i=0 i} integer): integer;

1 Se tiene esta situacin, por ejemplo, cuando la secuencia ordenada de los n elementos del o vector se ha escogido equiprobablemente entre el dominio integer, as como el elemento elem que se busca. 2 Notaremos con P a la probabilidad.

18.1. Conceptos basicos


begin if n = 0 then Sumatorio:= 0 else Sumatorio:= n + Sumatorio(n-1) end; {Sumatorio}

401

Al calcular el espacio de memoria que ocupa una llamada a esta funcin ha de o tenerse en cuenta que, por su naturaleza recursiva, se generan nuevas llamadas a Sumatorio (exactamente n llamadas). En cada llamada se genera una tabla de activacin (vase el apartado 10.2) del tamao de un entero (el parmetro n), es o e n a decir, de tamao constante. En consecuencia, podemos armar que la funcin n o Sumatorio tiene un coste proporcional a n. Pero la suma de los n primeros enteros puede calcularse tambin de forma e intuitiva empleando un algoritmo iterativo. Su sencilla implementacin es la o siguiente:
function SumatorioIter(n: {PreC.: n 0} n {Dev. i=0 i} var suma, i: integer; begin suma:= 0; for i:= 0 to n do suma:= suma + i; SumatorioIter:= suma end; {SumatorioIter} integer): integer;

En este caso, una llamada a SumatorioIter, al no generar otras llamadas sucesivas, consume un espacio de memoria constante: exactamente el necesario para su parmetro y para las dos variables locales, todos de tipo integer. Piense a el lector en la diferencia de espacio requerida por ambas funciones para n = 1000, por ejemplo. Con estos ejemplos podemos concluir que, a la hora del anlisis de algoritmos, a es fundamental realizar un estudio de la eciencia, destacando como aspecto ms a importante la complejidad en tiempo, y en segundo lugar, la complejidad en espacio. Lo importante es el comportamiento asinttico o Es un hecho evidente que datos de un tamao reducido van a tener asociados, n en general, tiempos cortos de ejecucin. Por eso, es necesario estudiar el como

402

Cap tulo 18. Complejidad algor tmica

portamiento de stos con datos de un tamao considerable, que es cuando los e n costes de los distintos algoritmos pueden tener una diferenciacin signicativa. o Para entender mejor la importancia del orden de complejidad, resulta muy ilustrativo observar cmo aumenta el tiempo de ejecucin de algoritmos con o o distintos rdenes. En todos ellos, n representa el tamao de los datos y los o n tiempos estn expresados en segundos, considerando un computador que realiza a un milln de operaciones por segundo: o
T (n) n 10 50 100 103 104 105 106 log n 3.32 106 5.64 106 6.64 106 105 1.33 105 1.66 105 2 105 n 105 5 105 104 0.001 0.01 0.1 1 n log n 3.32 105 2.82 104 6.64 104 0.01 0.133 1.66 19.93 n2 104 0.0025 0.01 1 100 104 106 n3 0.001 0.125 1 1000 106 intratable intratable 2n 0.001024 intratable intratable intratable intratable intratable intratable n! 3.6288 intratable intratable intratable intratable intratable intratable

18.2
18.2.1

Medidas del comportamiento asinttico o


Comportamiento asinttico o

Como se ha visto en el apartado anterior, la complejidad en tiempo de un algoritmo es una funcin T (n) del tamao de entrada del algoritmo. Pues bien, o n es el orden de dicha funcin (constante, logar o tmica, lineal, exponencial, etc.) el que expresa el comportamiento dominante para datos de gran tamao, como se n ilustra en el ejemplo que se presenta a continuacin. o Supngase que se dispone de cuatro algoritmos para solucionar un determio nado problema, cuyas complejidades son respectivamente, lineal (8n), cuadrtica a 2 ), logar n ). En la gura 18.1 se puede (2 n tmica (20 log2 n) y exponencial (e observar cmo sus tiempos relativos de ejecucin no son excesivamente difereno o tes para datos de un tamao pequeo (entre 1 y 5). n n Sin embargo, las grcas de la gura 18.2 conrman que es realmente el orden a de la funcin de complejidad el que determina el comportamiento para tamaos o n de entrada grandes, reteniendo unicamente la parte relevante de una funcin (de o coste) para datos de gran tamao. n A la vista de esto, es evidente que el aspecto importante de la complejidad de algoritmos es su comportamiento asinttico, ignorando otros detalles menores o por ser irrelevantes. Para ello, es preciso formalizar el estudio del orden de complejidad mediante medidas que ayuden a determinar el comportamiento asinttico del coste. o

18.2. Medidas del comportamiento asintotico

403

50 40 30 20 10 1 2 3

en

2 n2 20 log 2 n 8n

Figura 18.1.

250 200 150 100 50 0

en

2 n2 8n

en 1600 1400 1200 1000 800 600 400 200 0 20

2 n2

8n

20 log 2 n 5 10 15 20 25

20 log 2 n 40 60 80 100

Figura 18.2.

404

Cap tulo 18. Complejidad algor tmica

Entre estas medidas destaca la notacin O mayscula,3 la notacin y la o u o notacin . o

18.2.2

Notacin O may scula (una cota superior) o u

Denicin: Sean f, g : Z + IR+ . Se dice que f O(g) o que f es del o Z orden de g si existen constantes n0 Z + y IR+ tales que Z f (n) g(n) para todo n n0 Con la notacin4 f O(g) se expresa que la funcin f no crece ms deo o a prisa que alguna funcin proporcional a g. Esto es, se acota superiormente el o comportamiento asinttico de una funcin salvo constantes de proporcionalidad. o o Veamos algunos ejemplos: Para el algoritmo de bsqueda secuencial ordenada, u Tmx (n) = k1 n + k2 O(n) a (lo que se ve tomando cualquier > k1 y n0 >
k2 k1 ).

Para este mismo algoritmo se tiene adems que Tmed (n) = c1 n + c2 O(n), a y para el sumatorio recursivo se cumple que S(n) = n + 1 O(n). Todas las funciones de tiempo constante son O(1): f (n) = k O(1) lo que se ve tomando = k y cualquier n0 Z + . En este caso estn Z a Tm (n) = k para la bsqueda secuencial ordenada, y S(n) = 3 para el u n sumatorio iterativo. 15n2 O(n2 ) Como esta notacin expresa una cota superior, siempre es posible apuntar o alto a la hora de establecerla. Por ejemplo, se puede decir que los algoritmos estudiados hasta ahora son O(n!). Naturalmente, esta imprecisin es perfectao mente intil, por lo que se debe procurar que la funcin g sea lo ms prxima u o a o posible a f ; es decir, se debe buscar una cota superior lo menor posible. As por , ejemplo, aunque (5n + 3) O(n2 ), es ms preciso decir que (5n + 3) O(n). a Para formalizar esta necesidad de precisin, se usan otras medidas del como portamiento asinttico. o
3 Debido a que la expresin inglesa que se utiliza para esta notacin es Big-Oh, tambin se o o e conoce como notacin O grande. o 4 En lo sucesivo, emplearemos las dos notaciones f O(g) y f (n) O(g(n)) indistintamente, segn convenga. u

18.2. Medidas del comportamiento asintotico

405

18.2.3

Notacin may scula (una cota inferior) o u

Denicin: Sean f, g : Z + IR+ . Se dice que f (g) si existen constantes o Z + + n0 Z y IR tales que Z f (n) g(n) para todo n n0

Con la notacin f (g) se expresa que la funcin f crece ms deprisa que o o a alguna funcin proporcional a g. Esto es, se acota inferiormente el comportao miento asinttico de una funcin, salvo constantes de proporcionalidad. Dicho o o de otro modo, con la notacin f (g) se indica que la funcin f necesita para o o su ejecucin un tiempo m o nimo dado por el orden de la funcin g. o En los ejemplos anteriores se puede comprobar que: Para el algoritmo de bsqueda secuencial ordenada, Tmx (n) (n). u a 3n + 1 (n). 3n + 1 (1). 15n2 (n2 ). 15n2 (n). Comparando las deniciones anteriores, se tiene que f O(g) g (f )

18.2.4

Notacin may scula (orden de una funcin) o u o

Denicin: Sean f, g : Z + IR+ . Se dice que f (g) si f O(g) y o Z g O(f ); esto es, si f O(g) (g). Al conjunto (g) se le conoce como el orden exacto de g. Con la notacin se expresa que la funciones f y g tienen el mismo grado o de crecimiento, es decir, que 0 < limx f (x) < . Ejemplos: g(x) Para el algoritmo de bsqueda secuencial ordenada, Tmx (n) (n). u a 3n + 1 (n). 15n2 (n2 ).

406

Cap tulo 18. Complejidad algor tmica

18.2.5

Propiedades de O, y

Entre las propiedades ms importantes de la notaciones O mayscula, y a u cabe destacar las que se describen a continuacin. En ellas se utiliza el s o mbolo si la propiedad es cierta para las tres notaciones, y se asume que f, g, f1 , f2 : Z + IR+ Z Reexividad: f (f ). Escalabilidad: Si f (g) entonces f (k g) para todo k IR+ . Una consecuencia de ello es que, si a, b > 1 se tiene que O(loga n) = O(logb n). Por ello, no hace falta indicar la base: O(log n)

Transitividad: Si f (g) y g (h) se tiene que f (h). Simetr Si f (g) entonces g (f ) a: (Obsrvese que las otras notaciones no son simtricas y trtese de dar un e e a contraejemplo.)

Regla de la suma: Si f1 O(g1 ) y f2 O(g2 ) entonces f1 +f2 O (mx(g1 , g2 )) a siendo mx(g1 , g2 )(n) = mx (g1 (n), g2 (n)). a a Junto con la escalabilidad, esta regla se generaliza fcilmente as si fi a : O(f ) para todo i = 1, . . . k, entonces c1 f1 + . . . + ck fk O(f ). Otra consecuencia util es que si pk (n) es un polinomio de grado k, entonces pk (n) O(nk ). Una consecuencia de ello es que, si p < q, entonces O(np ) O(nq ).
n n+1

Regla del producto: Si f1 (g1 ) y f2 (g2 ) entonces f1 f2 O(g1 g2 ). Regla del sumatorio: Si f O(g) y la funcin g es creciente, o
i=1

f (i) O

g(x) dx
1 n

. Una consecuencia util es la siguiente:


i=1

ik O(nk+1 ).

Estas propiedades no deben interpretarse como meras frmulas desproviso tas de signicado. Muy al contrario, expresan la idea de fondo de las medidas asintticas, que consiste en ver la parte relevante de una funcin de coste peno o sando en datos de gran tamao. Gracias a ello, podemos simplicar consideran blemente las funciones de coste sin peligro de prdida de informacin (para datos e o grandes, se entiende). Por ejemplo:

18.2. Medidas del comportamiento asintotico

407

50 40 30 20 10 0

n 2 + 40n

n 2 + 25

1000 800

n 2 + 40n n 2 + 25 n2

n2

600 400 200

10

15

20

25

n 2 + 40n 14000 12000 10000 8000 6000 4000 2000 0 20 40 60

n 2 + 25 n2

80 100 120

Figura 18.3.

c 1 n2 + c 2 n + c 3 c 1 n2 n 2 Efectivamente, para datos grandes las funciones con el mismo orden de complejidad se comportan esencialmente igual. Adems, en la prctica es posible a a omitir los coecientes de proporcionalidad: de hecho, no afectan a las medidas estudiadas. Las dos simplicaciones se justican en las grcas de la gura 18.3. a

18.2.6

Jerarqu de rdenes de frecuente aparicin a o o

Existen algoritmos de complejidad lineal con respecto a los datos de entrada (T (n) = c1 n + c2 ). Tambin existen algoritmos de complejidad constante e (T (n) = c), independientemente de los datos de entrada. El mtodo de intere cambio directo para ordenar arrays tiene un coste cuadrtico. Otras funciones a de coste son polinmicas de diversos grados, exponenciales, logar o tmicas, etc.

408

Cap tulo 18. Complejidad algor tmica

Estos diferentes comportamientos asintticos se pueden ordenar de menor a o mayor crecimiento. Aunque no pretendemos dar una lista exhaustiva, la siguiente cadena de desigualdades puede orientar sobre algunos de los rdenes de coste ms o a usuales:5 1 log(n) n n log(n) n2 n3 2n n!

A pesar de las apariencias, la relacin ser del orden de no es una relacin o o de orden total: existen pares de funciones tales que ninguna de las dos es una cota superior de la otra. Por ejemplo, las funciones f (n) = 1 si n es par g(n) = n si n es impar n si n es par 1 si n es impar

no verican f O(g) ni g O(f ).

18.3
18.3.1

Reglas prcticas para hallar el coste de un a programa


Tiempo empleado

En este apartado se dan reglas generales para el clculo de la complejidad en a tiempo en el peor caso de los programas escritos en Pascal. Para dicho clculo, a como es natural, se debe tener en cuenta el coste en tiempo de cada una de las diferentes instrucciones de Pascal, y esto es lo que se detalla a continuacin. o Instrucciones simples Se considera que se ejecutan en tiempo constante: La evaluacin de las expresiones aritmticas (suma, resta, producto y dio e visin) siempre que los datos sean de tamao constante, as como las como n paraciones de datos simples. Las instrucciones de asignacin, lectura y escritura de datos simples. o Las operaciones de acceso a una componente de un array, a un campo de un registro y a la siguiente posicin de un archivo. o Todas esas operaciones e instrucciones son (1).
5

La notacin o

representa la relacin de orden menor que. o

18.3. Reglas practicas para hallar el coste de un programa Composicin de instrucciones o

409

Suponiendo que las instrucciones I1 e I2 tienen como complejidades en el peor caso TI1 (n) y TI2 (n), respectivamente, entonces el coste de la composicin o de instrucciones (I1 ; I2 ) en el peor caso es TI1 ;I2 (n) = TI1 (n) + TI2 (n) que, aplicando la regla de la suma, es el mximo entre los costes TI1 (n) y TI2 (n). a Instrucciones de seleccin o En la instruccin condicional, o
if condicin then I1 else I2 o

siempre se evala la condicin, por lo que su coste debe agregarse al de la instrucu o cin que se ejecute. Puesto que se est estudiando el coste en el peor caso, se o a tendr en cuenta la ms costosa. Con todo esto, la complejidad de la instruccin a a o if-then-else es: Tcondici n (n) + mx(TI1 (n), TI2 (n)) a o Anlogamente, la instruccin de seleccin por casos a o o
case expresin of o caso1 : I1 ; ... casoL: IL end; {case}

requiere evaluar la expresin y una instruccin, en el peor caso la ms costosa: o o a Texpresi n (n) + mx(TI1 (n), . . . , TIL (n)) a o Bucles El caso ms sencillo es el de un bucle for: a
for j:= 1 to m do I

410

Cap tulo 18. Complejidad algor tmica

En el supuesto de que en I no se altera el ndice j, esta instruccin tiene el o siguiente coste:


m

m+
j=1

TIj (n)

donde la cantidad m representa las m veces que se incrementa j y la comprobacin de si est entre los extremos inferior y superior. o a En el caso de que el cuerpo del bucle consuma un tiempo jo (independientemente del valor de j), la complejidad del bucle resulta ser m(1 + TI (n)). En los bucles while y repeat no hay una regla general, ya que no siempre se conoce el nmero de vueltas que da, y el coste de cada iteracin no siempre es u o uniforme. Sin embargo, con frecuencia se puede acotar superiormente, acotando precisamente el nmero de vueltas y el coste de las mismas. u Subprogramas El coste de ejecutar un subprograma no recursivo se deduce con las reglas descritas. Por el contrario, en caso de haber recursin, hay que detenerse a diso tinguir entre los casos bsicos (los parmetros que no provocan nuevas llamadas a a recursivas) y los recurrentes (los que s las producen). Considrese como ejemplo e la versin recursiva de la funcin factorial (tomada del apartado 10.1): o o
function Fac (n: integer): integer; {PreC.: n 0} {Dev. n!} begin if n = 0 then Fac:= 1 else Fac:= n * Fac(n-1) end; {Fac}

Para calcular la complejidad TFac (n) del algoritmo se debe tener en cuenta que, para el caso bsico (n = 0), el coste es constante, (1), a TFac (0) = 1 y para los recurrentes (n > 0), el coste es de una cantidad constante, (1) ms a el de la llamada subsidiaria provocada: TFac (n) = 1 + TFac (n 1) En resumen, TFac (n) = 1 si n = 0 1 + TFac (n 1) si n > 0

18.3. Reglas practicas para hallar el coste de un programa As para n > 1, , TFac (n) = 1 + TFac (n 1) = ...

411

= 1 + 1 + TFac (n 2)

= n + TFac (0) = n+1 y, por lo tanto, el coste es lineal, o sea, TFac (n) (n).

18.3.2

Ejemplos

Una vez descrito cmo calcular la complejidad en tiempo de los programas o en Pascal, se presentan algunos algoritmos a modo de ejemplo. Producto de dos n meros enteros u Supngase que se pretende calcular el producto de dos nmeros naturales n o u y m sin utilizar la operacin de multiplicacin. Un primer nivel de diseo de un o o n algoritmo para este problema es:
prod:= 0 repetir n veces repetir m veces prod:= prod + 1

Este diseo se implementa directamente en Pascal mediante la siguiente n funcin: o


function Producto(n, m: integer): {PreC.: n,m 0} {Dev. nm} var i,j,prod: integer; begin prod:= 0; for i:= 1 to n do for j:= 1 to m do prod:= prod + 1; Producto:= prod end; {Producto} integer;

412

Cap tulo 18. Complejidad algor tmica

Como el primer bucle for se repite n veces y el segundo m veces, y puesto que el resto de las instrucciones son asignaciones (con tiempo de ejecucin constante), o se deduce que el algoritmo tiene una complejidad en tiempo T (n, m) O(nm).

Para mejorar el algoritmo, se podr utilizar un unico bucle, atendiendo al a siguiente diseo: n
prod:= 0 repetir n veces prod:= prod + m

Este diseo tambin se implementa fcilmente en Pascal mediante una funcin: n e a o


function Producto2(n, m: {PreC.: n,m 0} {Dev. nm} var i,prod: integer; begin prod:= 0; for i:= 1 to n do prod:= prod + m; Producto2:= prod end; {Producto2} integer): integer;

Se obtiene as un cdigo cuya complejidad es, claramente, O(n). o Este algoritmo se puede mejorar ligeramente si se controla que el bucle se repita n veces si n m, o m veces si m n. La implementacin de esta mejora o se deja como ejercicio al lector. Es posible conseguir una complejidad an menor utilizando el algoritmo cou nocido con el nombre de multiplicacin a la rusa.6 El mtodo consiste en multio e plicar uno de los trminos por dos mientras que el otro se divide por dos (divisin e o entera) hasta llegar a obtener un uno. El resultado se obtendr sumando aquea llos valores que se han multiplicado por dos tales que su correspondiente trmino e dividido por dos sea un nmero impar. Por ejemplo, al realizar la multiplicacin u o a la rusa de 25 11, se obtiene la siguiente tabla de ejecucin: o 25 11 50 5 100 2 200 1
Curiosamente, muchos autores dicen que este algoritmo es el que utilizaban los romanos para multiplicar.
6

18.3. Reglas practicas para hallar el coste de un programa

413

obteniendo como resultado nal 25 + 50 + 200 = 275 debido a que sus trminos e divididos correspondientes (11, 5 y 1) son impares.
ee r r La justicacin de por qu sumar slo estos valores es la siguiente: si se o e o tiene un producto de la forma k (2l), un paso del algoritmo lo transforma en otro producto igual (2k) l; sin embargo, si el producto es de la forma k (2l + 1), un paso del algoritmo lo transforma en (2k) l (debido a que (2l + 1) div 2 = l), mientras que el verdadero resultado del producto es k (2l + 1) = (2k) l + k. Por lo tanto, se tendr que sumar la cantidad a perdida k y esto es lo que se hace siempre que el nmero que se divide es u impar.

Dada la sencillez del algoritmo, se presenta directamente su implementacin o en Pascal:


function ProductoRuso(n,m: integer):integer; {PreC.: n,m 0} {Dev. nm} var prod: integer; begin prod:= 0; while m > 0 do begin if Odd(m) then prod:= prod + n; n:= n + n; {n:= n * 2, sin usar *} m:= m div 2 end; {while} ProductoRuso:= prod end; {ProductoRuso}

Como la variable m se divide por dos en cada vuelta del bucle, hasta llegar a 0, el algoritmo es de complejidad O(log m). Ordenacin de vectores o En el cap tulo 15 se presentaron varios algoritmos para la ordenacin de o vectores, viendo cmo, intuitivamente, unos mejoraban a otros en su eciencia o en tiempo. En este apartado se estudia con detalle la complejidad de algunos de estos algoritmos7 utilizando las tcnicas expuestas en los apartados anteriores. e
Dado que no es necesario tener en cuenta todos los detalles de un algoritmo para su anlisis, a se ha optado en este apartado por estudiar la complejidad sobre el seudocdigo, llegando hasta o el nivel menos renado que permita su anlisis. Los autores consideran que descender a niveles a inferiores no es necesario, ya que los fragmentos que tardan un tiempo constante (o acotado por una constante) no importa qu detalles contengan. e
7

414

Cap tulo 18. Complejidad algor tmica

Para empezar, se presenta el algoritmo de ordenacin por intercambio directo o (vase el apartado 15.2.3), que es fcil pero ineciente, como se demostrar al e a a estudiar su complejidad y compararla con la de los restantes. Como se explic en o dicho apartado, este algoritmo consiste en recorrer el array con dos bucles anidados dependientes. El primero recorre todos los elementos del vector, mientras que el segundo bucle va intercambiando los valores que estn en orden decreciente. a El boceto del algoritmo es el siguiente:
para i entre 1 y n-1 hacer Desplazar el menor valor desde vn hasta vi , intercambiando pares vecinos, si es necesario Devolver v ya ordenado

Como siempre, para determinar la complejidad es necesario contar el nmero u de veces que se ejecuta el cuerpo de los bucles, ya que las operaciones que intervienen en el algoritmo (asignaciones, comparaciones y acceso a elementos de un vector) se ejecutan en tiempo constante. El cuerpo del bucle Desplazar el menor valor. . . si es necesario requiere n-i pasos en la vuelta i-sima (uno para cada posible intercambio). Por lo tanto, el e coste del algoritmo completo es8
n i=1

(n i) =

n(n 1) 2

y, en consecuencia, su complejidad es cuadrtica (O(n2 )). a Analicemos ahora el algoritmo Quick Sort (vase el apartado 15.2.4) en el e peor caso. A grandes trazos, el algoritmo es el siguiente:
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos bloques A y B con todos los elementos de A menores que los de B n {si} Ordenar A y B usando Quick Sort Devolver v ya ordenado como concatenacin o de las ordenaciones de A y de B

donde Dividir v en dos bloques A y B consiste en


La suma que hay que calcular se corresponde con la suma de los trminos de una progresin e o aritmtica (vase el apartado 18.4). e e
8

18.3. Reglas practicas para hallar el coste de un programa


Elegir un elemento p (pivote) de v para cada elemento del vector hacer si el elemento < p entonces Colocar el elemento en A, el subvector con los elementos de v menores que p en otro caso Colocar el elemento en B, el subvector con los elementos de v mayores que p

415

Como se dijo en 15.2.4, se ha optado por elegir como pivote el primer elemento del vector. En el peor caso (cuando el vector se encuentra ordenado decrecientemente), el algoritmo Quick Sort va a tener complejidad O(n2 ). La razn es que, en tal o caso, el cuerpo del bucle para cada elemento. . . se ejecutar, en total, (n a 1) + (n 2) + . . . + 1 veces, donde cada sumando proviene de cada una de las sucesivas ordenaciones recursivas del subvector A. Esto es as porque en cada llamada se ordena un solo elemento (el pivote), y por tanto dicho subvector tendr sucesivamente longitud (n 1), (n 2), . . . , 1. Dicha suma, como se vio a anteriormente es n(n 1) 2 y por tanto el algoritmo tiene complejidad cuadrtica. En resumen, la complea jidad en el peor caso es la misma en el algoritmo anterior. Ciertamente, en el cap tulo 15 se present este ultimo mtodo como mejora o e en el tiempo de ejecucin. Lo que ocurre es que esa mejora es la que se logra en o el caso medio. Sin embargo, el correspondiente clculo rebasa las pretensiones a de este libro. Para completar este apartado se presenta el anlisis de la complejidad en a tiempo de un tercer algoritmo de ordenacin de vectores, concretamente el de o ordenacin por mezcla o Merge Sort (vase el apartado 15.2.5). El algoritmo es o e el que sigue:
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos subvectores A y B n {si} Ordenar A y B usando Merge Sort Mezclar las ordenaciones de A y B para generar el vector ordenado.

416

Cap tulo 18. Complejidad algor tmica En este caso, el paso Dividir v en dos subvectores A y B consiste en:
Asignar a A el subvector [v1 , . . . , vn div 2 ] Asignar a B el subvector [vn div 2+1 , . . . , vn ]

mientras que Mezclar las ordenaciones de A y B consiste en ir entremezclando adecuadamente las componentes ya ordenadas de A y de B para obtener el resultado buscado. El anlisis de la complejidad de este algoritmo no es complicado. Partiendo de a que la operacin de Mezclar las ordenaciones de A y B se ejecuta en un tiempo o proporcional a n (la longitud del vector por ordenar), el coste en tiempo del algoritmo completo viene dado por la siguiente relacin de recurrencia, donde ki o son cantidades constantes: T (n) = k1 si n = 1 2T ( n ) + k2 n + k3 si n > 1 2

En esta frmula k1 representa el coste del caso trivial (v de tamao 1); T (n/2) o n es el coste de cada llamada recursiva, y k2 n + k3 es el coste de mezclar los subvectores A y B, ya ordenados. Esta ecuacin se resuelve mediante sustituciones sucesivas cuando n es una o potencia de 2 (es decir, existe j, tal que n = 2j ), de la siguiente forma: n n T (n) = 2(2T ( ) + k2 + k3 ) + k2 n + k3 4 2 n = 4T ( ) + 2k2 n + k4 4 n n = 4(2T ( ) + k2 + k3 ) + 2k2 n + k4 8 4 = ... = 2j T (1) + jk2 n + kj = nk1 + k2 nlog2 n + kj Y en el caso en que n no sea una potencia de 2, siempre se verica que existe k > 0 tal que 2k < n < 2k+1 , y, por tanto, se tiene que T (n) T (2k+1 ). En consecuencia, se puede armar que, en todo caso, T (n) O(nlog2 n). Esta conclusin indica que Merge Sort es un algoritmo de ordenacin con o o una complejidad en tiempo ptima9 en el peor caso, aunque no es tan bueno en o cuanto a la complejidad en espacio, ya que es necesario mantener dos copias del vector. Existen versiones mejoradas de este algoritmo que tienen menor coste en espacio, pero su estudio excede a las pretensiones de este texto.
Hay que recordar que esta complejidad es ptima bajo la notacin O-grande, esto es, salvo o o constantes de proporcionalidad.
9

18.3. Reglas practicas para hallar el coste de un programa

417

18.3.3

Espacio de memoria empleado

Aunque el clculo de la complejidad en espacio es similar al de la complejidad a en tiempo, se rige por leyes distintas, como se comenta en este apartado. En primer lugar, se debe tener en cuenta que la traduccin del cdigo fuente o o a cdigo mquina depende del compilador y del computador, y que esto tiene o a una fuerte repercusin en la memoria. En consecuencia, y al igual que se razon o o para la complejidad en tiempo, no es recomendable utilizar medidas absolutas sino relativas, como celdas de memoria (el espacio para almacenar, por ejemplo, un dato simple: un nmero o un carcter). u a Si llamamos S(n) al espacio relativo de memoria que el algoritmo ha utilizado al procesar una entrada de tamao n, se denen los conceptos de Smx (n), Sm (n) n a n y Smed (n) para la complejidad en espacio del peor caso, el mejor caso y caso medio, de forma anloga a los conceptos respectivos de tiempo. a Con estos conceptos, y considerando, por ejemplo, que cada entero necesita una celda de memoria, el espacio necesario para la bsqueda secuencial ordenada u es: n celdas para el vector, una celda para el elemento buscado y una celda para la variable i, es decir, S(n) = n + 2 tanto en el peor caso como en mejor caso y en el caso medio. De forma anloga se ve que el algoritmo de bsqueda binaria tiene como a u complejidad en espacio S(n) = n + 4. Utilizando esta notacin, podemos armar que la funcin Sumatorio del o o apartado 18.1 tiene, en su versin iterativa, una complejidad en espacio S(n) = 3, o debida al espacio ocupado por el parmetro n y las variables locales i y suma. a La complejidad de la versin recursiva es S(n) = n + 1, puesto que cada una de o las tablas de activacin ocupa una celda para su parmetro local n. o a Para el clculo de la complejidad en espacio es preciso tener en cuenta algunos a aspectos relacionados con el manejo de subprogramas: La llamada a un subprograma tiene asociado un coste en espacio, dado que se tiene que generar la tabla de activacin (vase el apartado 10.2). Ms o e a concretamente: Los parmetros por valor necesitan un espacio igual a su tamao, al a n igual que los objetos (constantes y variables) locales. Los parmetros por variable slo necesitan una cantidad de espacio a o unitaria independientemente de su tamao. n Los algoritmos recursivos necesitan una cantidad de espacio dependiente de la profundidad de la recursin que determina el tamao de la pila de o n tablas de activacin (vase el apartado 17.2.3). o e

418

Cap tulo 18. Complejidad algor tmica Por consiguiente, cuando un subprograma recursivo origine varias llamadas, slo importar la llamada que provoque una mayor profundidad, o a pudindose despreciar las dems. e a Es un error frecuente comparar la complejidad en espacio de los algoritmos recursivos con el nmero total de llamadas. u

Ejemplo: sucesin de Fibonacci o La sucesin de los nmeros de Fibonacci (vase el apartado 10.3.1) se puede o u e hallar mediante la siguiente funcin recursiva: o
function Fib(num: integer): integer; {PreC.: num 0} {Dev. f ibnum } begin if (num = 0) or (num = 1) then Fib:= 1 else Fib:= Fib(num-1) + Fib(num-2) end; {Fib}

El coste en espacio, S(n), del algoritmo descrito es proporcional a la profundidad del rbol de llamadas; es decir, S(n) = 1 en los casos triviales n = 0 a y n = 1; en los no triviales (n 2), Fib(n) origina dos llamadas subsidiarias, Fib(n-1) y Fib(n-2), la primera de las cuales es ms profunda. Por lo tanto, a en estos casos, S(n) = 1 + mx(S(n 1), S(n 2)) = 1 + S(n 1) a En resumidas cuentas, S(n) = n, lo que indica que esta funcin tiene un requeo rimiento de espacio lineal con respecto a su argumento n.

18.4

Utiles matemticos a

Ya se ha visto en los ejemplos anteriores que, cuando se trabaja con funciones o procedimientos recursivos, la complejidad en el tiempo T (n) va a venir dada en funcin del valor de T en puntos menores que n. Por ello es util saber cmo o o calcular trminos generales de sucesiones en las que los trminos se denen en e e funcin de los valores anteriores (sucesiones recurrentes). En este apndice se o e tratan los casos ms comunes que pueden surgir a la hora del clculo de la a a complejidad en el tiempo de funciones o procedimientos recursivos.

18.4. Utiles matematicos

419

18.4.1

Frmulas con sumatorios o

Si xn es una sucesin aritmtica, esto es, xn = xn1 + r, entonces o e x1 + x2 + + xn = 1 + x + x2 + + xn1 =


i=0 i=0 i=0

(x1 + xn )n 2

1 xn . 1x 1 , siempre que |x| < 1. 1x

xi = 1 + x + x2 + = xi = ex . i! (1)i xi = log(x). i

Si cada ai (nk ), se tiene que

i=1

ai (nk+1 ).

18.4.2

Sucesiones de recurrencia lineales de primer orden

Son aquellas sucesiones en las que su trmino general viene dado en funcin e o del trmino anterior, es decir, xn = f (xn1 ). En estas sucesiones es necesario e conocer el valor de x0 . Dependiendo de la forma f se consideran los siguientes casos: Si xn es de la forma xn = cxn1 , se tiene que xn = cn x0 , ya que xn = cxn1 = c2 xn2 = . . . = cn x0 . Si xn es de la forma xn = bn xn1 para n 1, se tiene que xn = b1 b2 . . . bn x0 . Si xn es de la forma xn = bn xn1 + cn , realizando el cambio de variable xn = b1 b2 . . . bn yn en la recurrencia de xn+1 , se obtiene: b1 b2 . . . bn+1 yn+1 = bn+1 (b1 b2 . . . bn yn ) + cn+1 lo que, operando, conduce a
n

xn = (b1 b2 . . . bn ) x0 +

di
i=1

420 siendo dn =

Cap tulo 18. Complejidad algor tmica cn . (b1 b2 . . . bn ) Como ejemplo, se expone el caso particular que se obtiene cuando bn = b y cn = c, es decir, xn+1 = bxn +c. En este caso se realiza el cambio xn = bn yn y se tiene: c bn+1 yn+1 = bn+1 yn + c yn+1 = yn + n+1 b
n

yn = y 0 + lo que conduce a xn = x0 + c

i=1

c bi

= y0 + c

1 bn 1b

1 bn . (1 b)bn

Estas recurrencias son de gran importancia por s mismas, pero adems, las a recurrencias generadas por sustraccin y por divisin se reducen a ellas. o o Recurrencias generadas por sustraccin o Existen algoritmos que dan lugar a recurrencias de la forma xn = Expr(n, xnc ) conocido x0 . Mediante el cambio de variable n = kc tenemos: xn = Expr(n, xnc ) = Expr(kc, xkcc ) xkc = Expr(kc, x(k1)c ) Si ahora llamamos a la sucesin xkc = yk , tenemos: o y0 = x 0 yk = xkc = Expr(kc, yk1 ) que es lineal de primer orden. Una vez resuelta, se tiene que xn = yn/c Recurrencias generadas por divisin o Otros algoritmos dan lugar a recurrencias de la forma siguiente: xn = ...n...xn/c

18.4. Utiles matematicos conocido x0 . Mediante el cambio de variable n = ck tenemos: xn = Expr(n, xn/c ) = Expr(ck , xck /c ) xck = Expr(ck , xck1 )

421

Si ahora llamamos a la sucesin xck = yk , tenemos: o y0 = x 0 yk = x c k = Expr(ck , yk1 ) que es lineal de primer orden. Una vez resuelta, se tiene que xn = ylogc n Como ejemplo de este tipo de recurrencias, considrese el coste del algoritmo e de ordenacin por mezcla: o T (n) = k1 si n = 1 2T ( n ) + k2 n + k3 si n > 1 2

Como su resolucin sigue al pie de la letra el procedimiento descrito, se deja o como ejercicio al lector. La solucin puede compararse con la ofrecida en 18.3.2. o

18.4.3

Sucesiones de recurrencia de orden superior

Son las sucesiones generadas por subprogramas recursivos con ms de una a llamada recursiva:10 xn = f (xn1 , xn2 , . . . , xnk , n) La resolucin exacta de este tipo de recurrencias sobrepasa las pretensiones o de esta introduccin a la complejidad algor o tmica. Sin embargo, con frecuencia es posible y suciente acotar dicho coste. En efecto, es frecuente que la sucesin o xi sea creciente y que entre los tamaos de las llamadas subsidiarias se puen dan identicar el m nimo y el mximo y en general. Si llamamos xm y xmx a n a respectivamente a estos valores en la sucesin anterior, se tiene que o
k i=1 k

ai xm + f (n) xn n

ai xmx + f (n) a
i=1

Esta desigualdad nos da siempre las acotaciones y O y, cuando coincidan ambas, tendremos el orden .
10

Las distintas llamadas son xni .

422

Cap tulo 18. Complejidad algor tmica

Ejemplo: sucesin de Fibonacci o En su denicin recursiva usual, esta funcin tiene un coste dado por la o o siguiente relacin de recurrencia: o tn = k 1 tn = xn1 + xn2 + k2 si k = 0 o k = 1 si k 0

Como t es creciente, podemos acotarla entre f y g, as : fn fn gn gn = = = = k1 2fn2 + k2 k1 2gn1 + k2 si si si si k k k k =0ok=1 0 =0ok=1 0

Estas dos relaciones de recurrencia son lineales, y se resuelven fcilmente: a fn (2n/2 )gn (2n ) por lo que podemos concluir que la funcin analizada tiene un coste en tiempo o n , para una constante k sin determinar, entre 2 y 2. exponencial tn = k

18.5

Ejercicios

1. Considere las siguientes funciones (dependientes de n) de cara a estudiar su comportamiento asinttico: o n2 + 103 n + 106 n n log10 n (5n2 + 3)(3n + 2)(n + 1) log n n + log n ( 1 )n 21/n 2n 2 n 2 n 2 n loge n 3 (n+1)(n2 n+5) 1 1000 n(3+n2 ) n i n n n j=1 n i=1 n i=1 i=1 i Se pide lo siguiente: (a) Para cada una de ellas, busque una funcin sencilla que acote superiormente o su comportamiento asinttico, (usando para ello la notacin O mayscula) o o u procurando ajustarse lo ms posible. a (b) Clasique las funciones anteriores segn sean O(2n ), O(n4 ), O( n), O(log n) u o O(1). (c) Agrupe las funciones del ejercicio anterior que sean del mismo orden . 2. Compare las funciones siguientes por su orden de complejidad: 4 log log n log n (log n)2 n

18.5. Ejercicios
3. Calcule la complejidad en tiempo del siguiente algoritmo de sumar: function Suma (m, n: {PreC.: n >= 0} {Dev. m + n } var i: integer; begin for i:= 1 to n do m:= Succ (m) end; {Suma} integer): integer;

423

4. Calcule ahora el coste del siguiente algoritmo de multiplicar, function Producto (a, b: integer): {PreC.: b >= 0} {Dev. a * b} var i, acumProd: integer; begin acumProd:= 0; for i:= 1 to b do acumProd:= acumProd + a end; {Producto} en los tres casos siguientes: (a) Tal como se ha descrito. (b) Cambiando, en el cuerpo de la instruccin for, la expresin acumProd + a o o por la llamada Suma(acumProd, a). (c) Cambiando la expresin acumProd + a de antes por la llamada Suma(a, o acumProd). 5. Considerando los problemas siguientes, esboce algoritmos iterativos para resolverlos e indique su complejidad: (a) Resolucin de una ecuacin de segundo grado. o o (b) Clculo del cociente y el resto de la divisin entera mediante restas sucesivas. a o (c) Clculo de un cero aproximado de una funcin mediante el mtodo de bia o e particin. o (d) Suma de las cifras de un nmero entero positivo. u (e) Determinacin de si un nmero es primo o no mediante el tanteo de divisores. o u (f) Clculo de a
n i+1 i=1 i! ,

integer;

diferenciando dos versiones:

i. Una, en la que cada vez se halla un trmino del sumatorio. e ii. Otra, donde cada factorial del denominador se halla actualizando el del trmino anterior. e

424

Cap tulo 18. Complejidad algor tmica


(g) Ordenacin de un array de n componentes por el mtodo de intercambio o e directo: for i:= 1 to n - 1 do for j:= n downto i + 1 do Comparar las componentes j-1 y j e intercambiar si es necesario (h) Producto de dos matrices, una de m k y otra de k n.

6. Para el problema de las Torres de Hanoi de tamao n, calcule su complejidad n exacta en tiempo y en espacio, as como su orden de complejidad. Calcule el tiempo necesario para transferir los 64 discos del problema tal como se plante en su origen, a razn de un segundo por movimiento. De esta forma, o o podr saber la fecha aproximada del n del mundo segn la leyenda. a u 7. Considerando los problemas siguientes, esboce algoritmos recursivos para resolverlos e indique su complejidad: (a) Clculo del cociente de dos enteros positivos, donde la relacin de recurrencia a o (en su caso) es la siguiente: Coc(dividendo, divisor) = 1 + Coc(dividendo - divisor, divisor) (b) Clculo del mximo elemento de un array; as si es unitario, el resultado es a a , su unico elemento; si no, se halla (recursivamente) el mximo de su mitad a izquierda, luego el de su mitad derecha del mismo modo y luego se elige el mayor de estos dos nmeros. u (c) Clculo de a
n i+1 i=1 i! ,

usando la relacin de recurrencia siguiente, en su caso: o


n n1

an = an +
i=1 i=1

an n k

(d) Clculo de los coecientes binomiales a versiones: i. Iterativa, mediante el clculo de a ii. Iterativa, mediante el clculo de a

, distinguiendo las siguientes

m! n!(mn)! . m(m1)...(n+1) . (mn)!

iii. Recursiva, mediante la relacin de recurrencia conocida. o (e) Evaluacin de un polinomio, conocido el valor de la variable x, en los sio guientes casos: i. La lista de los coecientes viene dada en un array, y se aplica la frmula o coefi xi . i ii. Los coecientes estn en una lista enlazada, y usamos la regla de Horner a (vase el apartado 17.1.4). e (f) Ordenacin de un array de n componentes por el mtodo de mezcla: o e

18.6. Referencias bibliograficas


procedure MergeSort (var v: Vn (elem): iz, der); {Efecto: se ordena ascendentemente v[iz..der]} begin if iz < der + 1 then begin MergeSort (la mitad izquierda de v); MergeSort (la mitad derecha de v); Mezclar las dos mitades de v end {if} end; {MergeSort}

425

suponiendo que las mitades del vector son siempre de igual tamao, para n simplicar, y que el proceso de mezclarlas tiene un coste proporcional a su tamao. n

18.6

Referencias bibliogrcas a

La complejidad de algoritmos se estudia ms o menos a fondo en casi todos los a cursos de estructuras avanzadas de datos y de metodolog de la programacin. De las a o muchas referencias mencionables, omitiremos aqu las que tienen un contenido similar, y citaremos en cambio slo algunas de las que pueden servir para ampliar lo expuesto o aqu . Para empezar, citamos [MSPF95], una suave introduccin a los conceptos bsicos o a de la complejidad. En el clsico libro de Brassard y Bratley [BB97] (especialmente a en el cap tulo 2) se pueden ampliar las tcnicas estudiadas para resolver recurrencias. e Otro libro interesante es [Wil89], que est completamente dedicado a la complejidad a de algoritmos (incluyendo el estudio de algunos algoritmos conocidos de investigacin o operativa y de teor de nmeros utiles en criptograf a u a), as como a la complejidad de problemas. En [BKR91], se estudia el tema y su aplicacin en el desarrollo de o programas y estructuras de datos con el objetivo de minimizar el coste. En este libro se introduce adems el coste en el caso medio de algoritmos, as como el anlisis de a a algoritmos paralelos.

Cap tulo 19

Tipos abstractos de datos

19.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . 428 o 19.2 Un ejemplo completo . . . . . . . . . . . . . . . . . . . . 429 19.3 Metodolog de la programacin de TADs . . . . . . . 440 a o 19.4 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 19.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 19.6 Referencias bibliogrcas . . . . . . . . . . . . . . . . . . 448 a

A medida que se realizan programas ms complejos, va aumentando sia multneamente la complejidad de los datos necesarios. Como se explic en el a o cap tulo 11, este aumento de complejidad puede afrontarse, en primera instancia, con las estructuras de datos proporcionadas por Pascal como son, por ejemplo, los arrays y los registros. Cuando en un programa, o en una familia de ellos, el programador descubre una estructura de datos que se utiliza repetidamente, o que puede ser de utilidad para otros programas, es una buena norma de programacin denir esa estruco tura como un nuevo tipo de datos e incluir variables de este tipo cada vez que sea necesario, siguiendo as un proceso similar al de la abstraccin de procedimientos o (vanse los apartados 8.1 y 9.3.1), mediante la cual se denen subprogramas que e pueden ser reutilizados. En este proceso, denominado abstraccin de datos, el o programador debe despreocuparse de los detalles menores, concentrndose en las a operaciones globales del tipo de datos. Esto es, en trminos generales, lo que se e persigue con los tipos abstractos de datos.

428

Cap tulo 19. Tipos abstractos de datos

19.1

Introduccin o

El objetivo que se persigue en este apartado es la denicin de un conjunto o de objetos con una serie de operaciones para su manipulacin. Para resolverlo, se o utiliza la tcnica de la abstraccin de datos, que permite tratar estas deniciones e o de tipos de una forma ordenada, mantenible, reutilizable y coherente. La abstraccin de datos pone a disposicin del programador-usuario1 nuevos o o tipos de datos con sus correspondientes operaciones de una forma totalmente independiente de la representacin de los objetos del tipo y de la implementacin o o de las operaciones; de ah el nombre tipos abstractos de datos. Esta tcnica es llamada abstraccin, porque aplica un proceso consistente e o en ignorar ciertas caracter sticas de los tipos de datos por ser irrelevantes para el problema que se intenta resolver. Esas caracter sticas ignoradas son aqullas e relativas a cmo se implementan los datos, centrndose toda la atencin en qu o a o e se puede hacer con ellos. Esto es, las propiedades de un tipo abstracto de datos vienen dadas impl citamente por su denicin y no por una representacin o o o implementacin particular. o Obsrvese que los tipos de datos bsicos de Pascal (vase el cap e a e tulo 3) son abstractos en el siguiente sentido: el programador puede disponer de, por ejemplo, los enteros y sus operaciones (representados por el tipo integer), ignorando la representacin concreta (complemento restringido o autntico, o cualquiera de o e las explicadas en el apartado 2.2 del tomo I) escogida para stos. e En cambio, con los tipos denidos por el programador (por ejemplo, las colas presentadas en el apartado 17.3), los detalles de la implementacin estn a la o a vista, con los siguientes inconvenientes: El programador tiene que trabajar con la representacin de un objeto en o lugar de tratar con el objeto directamente. El programador podr usar el objeto de modo inconsistente si manipula a inadecuadamente la representacin de ste. o e En resumen, desde el punto de vista del programador-usuario se puede armar que la introduccin de los tipos abstractos de datos suponen un aumento o de nivel en la programacin, pues bastar con que ste conozca el qu, deso a e e preocupndose de las caracter a sticas irrelevantes (el cmo) para la resolucin o o del problema. Por otra parte, la tarea del programador que implementa el tipo consistir en escoger la representacin concreta que considere ms adecuada y a o a ocultar los detalles de sta en mayor o menor nivel, dependiendo del lenguaje e utilizado.
Designaremos con este trmino al programador que utiliza porciones de cdigo puestas a su e o disposicin por otros programadores (o por l mismo, pero de forma independiente). o e
1

19.2. Un ejemplo completo

429

19.2

Un ejemplo completo

Es conveniente concretar todas estas ideas mediante un ejemplo: como se vio en el apartado 11.3, la representacin de conjuntos en Pascal tiene una fuerte o limitacin en cuanto al valor mximo del cardinal de los conjuntos representados o a (por ejemplo, en Turbo Pascal el cardinal mximo de un conjunto es 256). En a el siguiente ejemplo, se pretende representar conjuntos sin esta restriccin. Poso teriormente, se utilizarn estos conjuntos ilimitados en un programa que escriba a los nmeros primos menores que uno dado por el usuario, usando el conocido u mtodo de la criba de Eratstenes (vase el apartado 11.3.3). La idea de la e o e representacin es disponer de un conjunto inicial con todos los enteros positivos o menores que el valor introducido por el usuario e ir eliminando del conjunto aquellos nmeros que se vaya sabiendo que no son primos. De acuerdo con esta u descripcin, una primera etapa de diseo del programa podr ser: o n a
Leer cota IN Generar el conjunto inicial, {2, . . . , cota} Eliminar los nmeros no primos del conjunto u Escribir los nmeros del conjunto u

Detallando un poco ms cada una de esas acciones, se tiene: Generar el a conjunto inicial se puede desarrollar as :
Crear un conjunto vac primos o Aadir a primos los naturales de 2 a cota n

Para Eliminar los nmeros no primos del conjunto, basta con lo siguiente: u
para cada elemento econjunto, entre 2 y cota Eliminar del conjunto todos los mltiplos de e u

En un nivel de renamiento inferior, se puede conseguir Eliminar del conjunto todos los mltiplos de e de la siguiente forma: u
coeficiente:= 2; repetir Eliminar e * coeficiente del conjunto coeficiente:= coeficiente + 1 hasta que e * coeficiente sea mayor que cota

Finalmente, Escribir los nmeros del conjunto no presenta problemas y se u puede hacer con un simple recorrido de los elementos del conjunto.

430

Cap tulo 19. Tipos abstractos de datos

19.2.1

Desarrollo de programas con tipos concretos de datos

Una vez detallado este nivel de renamiento, se tiene que tomar una decisin o sobre el modo de representar los conjuntos. Las posibilidades son mltiples: con u el tipo set en Pascal, con listas enlazadas, etc. En este apartado se presenta una implementacin del diseo anterior, reo n presentando un conjunto de enteros en una lista enlazada con cabecera, con los elementos ordenados ascendentemente y sin repeticiones. Desde el nivel de renamiento alcanzado en el apartado anterior se puede pasar directamente a la implementacin: o
Program CribaEratostenes (input, output); {PreC.: input = [un entero, >=2]} type tConjunto = ^tNodoEnt; tNodoEnt = record elem: integer; sig: tConjunto end; {tNodoEnt} var cota, e, coef: integer; conjunto, aux, puntPrimo, auxElim: tConjunto; begin {Leer cota:} Write(Cota: ); ReadLn(cota); {Generar el conjunto inicial, [2, ..., cota]:} New(conjunto); aux:= conjunto; for e:= 2 to cota do begin New(aux^.sig); aux:= aux^.sig; aux^.elem:= e end; {for i} aux^.sig:= nil; {Eliminar los nmeros no primos del conjunto:} u puntPrimo:= conjunto^.sig; repeat e:= puntPrimo^.elem; coef:= 2; aux:= puntPrimo; while (e * coef <= cota) and (aux^.sig <> nil) do begin if aux^.sig^.elem < coef * e then aux:= aux^.sig else if aux^.sig^.elem = coef * e then begin auxElim:= aux^.sig^.sig; Dispose(aux^.sig);

19.2. Un ejemplo completo


aux^.sig:= auxElim; coef:= coef + 1 end {else if} else if aux^.sig^.elem > coef * e then coef:= coef + 1 end; {while} puntPrimo:= puntPrimo^.sig until (e >= Sqrt(cota)) or (puntPrimo = nil); {Escribir los nmeros del conjunto:} u aux:= conjunto^.sig; while aux <> nil do begin Write(aux^.elem:4); aux:= aux^.sig end; {while} WriteLn end. {CribaEratostenes}

431

Queda claro que a partir del momento en que se ha adoptado esta representacin, quedan mezclados los detalles relativos al algoritmo (Criba de Eratstenes) o o con los relativos a la representacin (lista enlazada . . . ) y manipulacin de los o o conjuntos de enteros. Este enfoque acarrea una serie de inconvenientes, como son: El cdigo obtenido es complejo, y, en consecuencia, se diculta su coo rreccin y vericacin. o o El mantenimiento del programa es innecesariamente costoso: si, por ejemplo, se decidiese cambiar la estructura dinmica lineal por una de manejo a ms eciente, como podr ser los rboles binarios de bsqueda, se dea an a u ber rehacer la totalidad del programa. Dicho de otro modo, es muy dif a cil aislar cambios o correcciones. En el caso en que se necesitasen conjuntos sin restricciones de cardinal en otros programas, ser necesario volver a implementar en stos todas a e las tareas necesarias para su manipulacin. En otras palabras, no hay o posibilidad de reutilizar cdigo. o Estos inconvenientes son los que se pretende superar con los tipos abstractos de datos.

19.2.2

Desarrollo de programas con tipos abstractos de datos

Una forma de solucionar los problemas enunciados anteriormente es el empleo de la abstraccin de datos. As se denir un tipo abstracto de datos, entendido o , a

432

Cap tulo 19. Tipos abstractos de datos

informalmente como una coleccin de objetos con un conjunto de operaciones o denidas sobre estos objetos. Se tendr siempre en cuenta la losof de la a a abstraccin de datos: todas las caracter o sticas del tipo abstracto vienen dadas por su denicin y no por su implementacin (de la que es totalmente independiente). o o En el ejemplo anterior, es evidente que la aplicacin de la abstraccin de o o datos conducir a un tipo abstracto:2 a
type tConj = Abstracto

cuyos objetos son precisamente conjuntos con un nmero arbitrario de elementos u enteros que se podrn manipular con las siguientes operaciones:3 a
procedure CrearConj (var conj: {Efecto: conj:= } tConj);

procedure AnnadirElemConj (elem: {Efecto: conj:= conj [elem]} procedure QuitarElemConj (elem: {Efecto: conj:= conj \ [elem]}

integer; var conj:

tConj);

integer; var conj:

tConj);

function Pertenece (elem: integer; conj: tConj): boolean; {Dev. True (si elem conj) o False (en otro caso)} procedure EscribirConj (conj: tConj); {Efecto: escribe en el output los elementos de conj} function EstaVacioConj(conj: tConj): boolean; {Dev. True (si conj = ) o False (en otro caso)}

Como se dijo anteriormente, una caracter stica esencial de los tipos abstractos de datos es su independencia de la implementacin. En este momento, y sin o saber nada en absoluto acerca de la forma en que est implementado (o en que a se va a implementar) el tipo tConj, se puede utilizar de una forma abstracta, siendo ms que suciente la informacin proporcionada por la especicacin de a o o las operaciones del tipo. Adems, se podr comprobar inmediatamente cmo el a a o cdigo obtenido es ms claro, fcil de mantener y vericar. La nueva versin del o a a o programa CribaEratostenes es la siguiente:
Acptese esta notacin provisional, que se detalla en el apartado 19.2.3. e o En los casos en los que se ha considerado conveniente, se ha sustituido la postcondicin por o una descripcin algo menos formal del efecto del subprograma. o
3 2

19.2. Un ejemplo completo


Program CribaEratostenes (input, output); {PreC.: input = [un entero, >=2]} type tConj = Abstracto; var cota, e, coef: integer; conjunto : tConj; begin Write(Cota: ); ReadLn(cota); CrearConj(conjunto); for e:= 2 to cota do AnnadirElemConj(e, conjunto); for e:= 2 to Trunc(SqRt(cota)) do if Pertenece (e, conjunto) then begin coef:= 2; repeat QuitarElemConj(e * coef, conjunto); coef:= coef + 1 until e * coef > cota end; {if} EscribirConj(conjunto) end. {CribaEratostenes}

433

En el ejemplo se puede observar la esencia de la abstraccin de datos: el o programador-usuario del tipo abstracto de datos se puede olvidar completamente de cmo est implementado o de la representacin del tipo, y unicamente estar o a o a interesado en qu se puede hacer con el tipo de datos tConj que utiliza de una e forma completamente abstracta. Adems, el lector puede observar cmo se llega a una nueva distribucin a o o (mucho ms clara) del cdigo del programa: las operaciones sobre el tipo tConj a o dejan de formar parte del programa y pasan a incorporarse al cdigo propio del o tipo abstracto. Las operaciones del tipo tConj escogidas son t picas de muchos tipos abstractos de datos, y se suelen agrupar en las siguientes categor as: Operaciones de creacin: Son aqullas que permiten obtener nuevos objetos o e del tipo, como sucede en CrearConj. Estas ultimas son conocidas tambin e como operaciones constructoras primitivas. Entre stas suele incluirse una e operacin de lectura de elementos del tipo abstracto de datos. o Operaciones de consulta: Realizan funciones que, tomando como argumento un objeto del tipo abstracto, devuelven un valor de otro tipo, como hacen Pertenece o EstaVacioConj, por poner un caso. Usualmente implementan tareas que se ejecutan con relativa frecuencia. Entre stas se suele incluir e

434

Cap tulo 19. Tipos abstractos de datos una operacin que escriba objetos del tipo abstracto de datos, que en el o ejemplo ser un procedimiento EscribirConj. a

Operaciones de modicacin: Permiten, como su propio nombre indica, moo dicar un objeto del tipo abstracto de datos, como, por ejemplo, las operaciones Annadir y Eliminar. Operaciones propias del tipo: Son operaciones caracter sticas de los objetos abstra dos en el tipo de datos, como ser en el ejemplo operaciones para an calcular la unin, interseccin o diferencia de conjuntos. o o Es conveniente destacar que esta clasicacin de las operaciones no es excluo yente: en algn tipo abstracto de datos puede ser necesaria una operacin que u o pertenezca a dos clases, como, por ejemplo, una operacin que efecte una cono u sulta y una modicacin al mismo tiempo. No obstante, tal operacin no es o o adecuada desde el punto de vista de la cohesin, un criterio de calidad de softo ware aplicable a la programacin con subprogramas, que nos recomienda dividir o tal operacin en dos, una que haga la consulta y otra la modicacin. o o A modo de resumen, se puede decir que la abstraccin de datos, considerada o como mtodo de programacin, consiste en el desarrollo de las siguientes etapas: e o 1. Reconocer los objetos candidatos a elementos del nuevo tipo de datos. 2. Identicar las operaciones del tipo de datos. 3. Especicar las operaciones de forma precisa. 4. Seleccionar una buena implementacin. o Se han mostrado las tres primeras etapas tomando como gu el ejemplo de a los conjuntos de enteros. A continuacin se detalla cmo abordar la cuarta y o o ultima.

19.2.3

Desarrollo de tipos abstractos de datos

En la ultima versin del ejemplo de la criba de Eratstenes se ha utilizado o o el tipo abstracto tConj dejndolo sin desarrollar, unicamente incluyendo la paa labra abstracto. Pero, obviamente, por mucho que las caracter sticas de un tipo abstracto de datos sean independientes de la implementacin, no se puede olvidar o sta. e Lo ms adecuado para implementar un tipo abstracto de datos es recurrir a a mecanismos que permitan encapsular el tipo de datos y sus operaciones, y ocultar la informacin al programador-usuario. Estas dos caracter o sticas son esenciales

19.2. Un ejemplo completo

435

para llevar a cabo efectivamente la abstraccin de datos. A tal n, Turbo Pascal o dispone de las unidades (vase el apartado B.11). e De acuerdo con esto, se da a continuacin una posible implementacin4 del o o tipo abstracto de datos tConj, a base de listas de enteros, ordenadas ascendentemente, enlazadas con punteros:5
unit conjEnt; {Implementacin mediante listas enlazadas, con cabecera, o ordenadas ascendentemente y sin repeticiones} interface type tElem = integer; {Requisitos: definidas las relaciones = (equiv.) > (orden total)} tConj = ^tNodoLista; tNodoLista = record info: tElem; sig: tConj end; {tNodoLista} procedure CrearConj(var conj: {Efecto: conj:= } tConj);

procedure DestruirConj(var conj: tConj); {Cuidado: se perder toda su informacin} a o {Efecto: conj:= ? (ni siquiera queda vaco: comportamiento impredecible)} procedure AnnadirElemConj (elem: {Efecto: conj:= conj [elem]} procedure QuitarElemConj (elem: {Efecto: conj:= conj \ [elem]} tElem; var conj: tConj);

tElem; var conj:

tConj);

function Pertenece (elem: tElem; conj: tConj): boolean; {Dev. True (si elem conj) o False (en otro caso)}
4 Se ha optado por no detallar todas las etapas del diseo descendente de las operaciones de los n tipos abstractos, por ser stas sencillas y para no extender demasiado el texto. e Por otra parte, es conveniente que los identicadores de unidades coincidan con el nombre del archivo donde se almacenan una vez compiladas (de ah la eleccin de identicadores de, a lo o sumo, ocho caracteres). 5 Obsrvese que se ha incluido una operacin destructora, necesaria en la prctica, debido a e o a que el manejo de listas enlazadas ocasiona un gasto de memoria y sta debe liberarse cuando un e conjunto deje de ser necesario.

436

Cap tulo 19. Tipos abstractos de datos


procedure EscribirConj (conj: tConj); {Efecto: escribe en el output los elementos de conj} function EstaVacioConj(conj: tConj): boolean; {Dev. True (si conj = ) o False (en otro caso)} implementation {Representacin mediante listas enlazadas, con cabecera, o ordenadas ascendentemente y sin repeticiones} procedure CrearConj (var conj: begin New(conj); conj^.sig:= nil end; {CrearConj} tConj);

procedure DestruirConj (var conj: var listaAux: tConj; begin while conj <> nil do begin listaAux:= conj; conj:= conj^.sig; Dispose(listaAux) end {while} end; {DestruirConj}

tConj);

procedure AnnadirElemConj (elem: tElem; var conj: tConj); var parar, {indica el fin de la bsqueda, en la lista} u insertar: boolean; {por si elem ya est en la lista} a auxBuscar, auxInsertar: tConj; begin auxBuscar:= conj; parar:= False; repeat if auxBuscar^.sig = nil then begin parar:= True; insertar:= True end {then} else if auxBuscar^.sig^.info >= elem then begin parar:= True; insertar:= auxBuscar^.sig^.info > elem end {then} else

19.2. Un ejemplo completo


auxBuscar:= auxBuscar^.sig until parar; if insertar then begin auxInsertar:= auxBuscar^.sig; New(auxBuscar^.sig); auxBuscar^.sig^.info:= elem; auxBuscar^.sig^.sig:= auxInsertar end {if} end; {AnnadirElemConj} procedure QuitarElemConj (elem: tElem; var conj: tConj); var parar, {indica el fin de la bsqueda, en la lista} u quitar: boolean; {por si el elem no est en la lista} a auxBuscar, auxQuitar: tConj; begin auxBuscar:= conj; parar:= False; repeat if auxBuscar^.sig = nil then begin parar:= True; quitar:= False end {then} else if auxBuscar^.sig^.info >= elem then begin parar:= True; quitar:= auxBuscar^.sig^.info = elem end else auxBuscar:= auxBuscar^.sig until parar; if quitar then begin auxQuitar:= auxBuscar^.sig^.sig; Dispose(auxBuscar^.sig); auxBuscar^.sig:= auxQuitar end {if} end; {QuitarElemConj} function Pertenece (elem: tElem; conj: tConj): boolean; var parar, {indica el fin de la bsqueda, en la lista} u esta: boolean; {indica si elem ya est en la lista} a auxBuscar: tConj; begin auxBuscar:= conj^.sig; parar:= False; repeat

437

438

Cap tulo 19. Tipos abstractos de datos


if auxBuscar = nil then begin parar:= True; esta:= False end else if auxBuscar^.info = elem then begin parar:= True; esta:= True end else if auxBuscar^.info > elem then begin parar:= True; esta:= False end else auxBuscar:= auxBuscar^.sig until parar; pertenece:= esta end; {Pertenece} procedure EscribirConj (conj: tConj); var puntAux: tConj; begin if EstaVacioConj(conj) then WriteLn([]) else begin puntAux:= conj^.sig; Write([, puntAux^.info); while not EstaVacioConj(puntAux) do begin puntAux:= puntAux^.sig; Write(, , puntAux^.info) end; {while} WriteLn(]) end {else} end; {EscribirConj} function EstaVacioConj (conj: tConj): begin EstaVacioConj:= conj^.sig = nil end; {EstaVacioConj} end. {conjEnt} boolean;

Para nalizar, se recuerda que en el programa CribaEratostenes se hab a dejado incompleta la declaracin de tipos, as unicamente o ,
type tConj =

Abstracto;

19.2. Un ejemplo completo

439

Con los elementos de que se dispone ahora, esta declaracin se sustituir por o a la siguiente:
uses conjEnt;

que hace que el tipo tConj declarado en la unidad est disponible para su empleo e en el programa.
e e Una implementacin en Pascal estndar obligar a prescindir de las unir r o a a dades, obligando a incluir todas las declaraciones del tipo y de los subprogramas correspondientes a las operaciones del tipo abstracto tConj en todo programa en que se utilice. Con ello se perder gran parte de las ventajas an aportadas por la abstraccin de datos, como, por ejemplo, la encapsulacin, o o la ocultacin de informacin y el aislamiento de los cambios. o o

La representacin escogida para la implementacin no es la ms eciente. En o o a efecto, las operaciones de insercin, eliminacin y consulta requieren, en el peor o o caso, el recorrido del conjunto completo. Una posibilidad ms interesante consiste en representar los conjuntos mea diante arboles de bsqueda (vase el apartado 17.4.2) en vez de usar listas. No u e es necesario reparar ahora en los pormenores de esta estructura de datos; en este momento, nos basta con saber que las operaciones de modicacin y consulta o son ahora mucho ms ecientes. a Pues bien, si ahora decidisemos mejorar la eciencia de nuestra implee mentacin, cambiando las listas por rboles, no tenemos que modicar las opeo a raciones de la interfaz, sino tan slo su desarrollo posterior en la seccin imo o plementation. De este modo, se puede apreciar una de las ventajas de la abstraccin de datos: o El programador-usuario no tiene que pensar en ningn momento en la u representacin del tipo abstracto de datos, sino unicamente en su especio cacin. o Los cambios, correcciones o mejoras introducidas en la implementacin del o tipo abstracto de datos repercuten en el menor mbito posible, cual es la a unidad en que se incluye su representacin. En nuestro ejemplo, el cambio o de la implementacin basada en listas por la basada en rboles no afecta o a ni a una sola l nea del programa Criba: el programador-usuario slo se o sorprender con una mayor eciencia de su programa tras dicho cambio. a Obsrvese el efecto que hubiera tenido el cambio de representacin en la e o primera versin de Criba, y compare. o Una vez presentado este ejemplo y con l las ideas generales de la abstrace cin de datos, se dan en el siguiente apartado nociones metodolgicas generales o o acerca de esta tcnica. e

440

Cap tulo 19. Tipos abstractos de datos

19.3

Metodolog de la programacin de tipos a o abstractos de datos

En los apartados anteriores se han introducido los tipos abstractos de datos de un modo paulatino, prctico e informal. Es necesario, y es lo que se hace en a este apartado, precisar las ideas generales introducidas y presentar los aspectos necesarios para la correcta utilizacin de los tipos abstractos de datos como o mtodo de programacin. e o Siguiendo las ideas de J. Martin [Mar86], se puede denir un tipo abstracto de datos como un sistema con tres componentes: 1. Un conjunto de objetos. 2. Un conjunto de descripciones sintcticas de operaciones. a 3. Una descripcin semntica, esto es, un conjunto sucientemente completo o a de relaciones que especiquen el funcionamiento de las operaciones. Se observa que, mientras en el apartado anterior se describ los tipos an abstractos de datos como un conjunto de objetos y una coleccin de operaciones o sobre ellos, ahora se subraya la descripcin de la sintaxis y la semntica de esas o a operaciones. Esto es necesario dado que la esencia de los tipos abstractos de datos es que sus propiedades vienen descritas por su especicacin, y, por tanto, es necesario o tratar sta adecuadamente. e Adems, este tratamiento riguroso de las especicaciones de los tipos absa tractos de datos permite ahondar en la losof expuesta al comienzo del tema: a separar qu hace el tipo abstracto de datos (lo cual viene dado por la especie cacin) de cmo lo lleva a cabo (lo que se da en su implementacin). Estos dos o o o aspectos se repasan brevemente en los dos siguientes apartados.

19.3.1

Especicacin de tipos abstractos de datos o

A la hora de afrontar la especicacin de un tipo abstracto de datos se diso pone de diversos lenguajes variando en su nivel de formalidad. En un extremo, se tiene el lenguaje natural, en nuestro caso el espaol. Las especicaciones n as expresadas presentan importantes inconvenientes, entre los que destacan su ambigedad (que puede llegar a inutilizar la especicacin, por prestarse a inu o terpretaciones incorrectas y/o no deseadas), y la dicultad para comprobar su correccin y completitud. o

19.3. Metodolog de la programacion de TADs a

441

Ante estos problemas, es necesario recurrir a lenguajes ms precisos, como a puede ser el lenguaje matemtico, que posee las ventajas de su precisin, cona o cisin y universalidad (aunque en su contra se podr argumentar su dicultad o a de uso, lo cierto es que en los niveles tratados en este libro no es preciso un fuerte aparato matemtico). Por ejemplo, algunas de las operaciones del tipo abstracto a tConj se han especicado como sigue:
procedure CrearConj(var conj: {Efecto: conj:= } procedure AnnadirElemConj(elem: {Efecto: conj:= conj [elem]} tConj);

integer; var conj:

tConj);

function Pertenece (elem: tElem; conj: tConj): boolean; {Dev. True (si elem conj) o False (en otro caso)} function EstaVacioConj(conj: tConj): boolean; {Dev. True (si conj = ) o False (en otro caso)}

Obsrvese que la sintaxis de su uso viene dada por los encabezamientos de e las operaciones y que su semntica se da en el comentario que les sigue. Las a especicaciones de esta forma proporcionan un modelo ms o menos formal que a describe el comportamiento de las operaciones sin ningn tipo de ambigedad. u u Adems, se satisfacen las dos propiedades que deben cumplir las especicaciones: a precisin y brevedad. o Por ultimo, se debe resaltar la importancia de la especicacin como medio o de comunicacin entre el programador-diseador del tipo abstracto de datos, el o n implementador y el programador-usuario: como se ha repetido anteriormente, la informacin pblica del tipo abstracto de datos debe ser unicamente su especio u cacin. As una especicacin incorrecta o incompleta impedir a los implemeno , o a tadores programar adecuadamente el tipo abstracto de datos, mientras que los programadores-usuarios sern incapaces de predecir correctamente el comportaa miento de las operaciones del tipo, lo que producir errores al integrar el tipo a abstracto de datos en sus programas.

19.3.2

Implementacin de tipos abstractos de datos o

Como se indic anteriormente, la tercera etapa de la abstraccin de datos o o es la implementacin del tipo de acuerdo con la especicacin elaborada en la o o etapa anterior.

442

Cap tulo 19. Tipos abstractos de datos

La idea de partida que se ha de tomar en esta tarea es bien clara: escoger una representacin que permita implementar las operaciones del tipo abstracto o de datos simple y ecientemente, respetando, por supuesto, las eventuales restricciones existentes. Ahora bien, en el momento de llevar a la prctica esta idea, se ha de tener a presente que el verdadero sentido de la abstraccin de datos viene dado por la o separacin del qu y del cmo, de la especicacin y de la implementacin, y de o e o o o la ocultacin al programador-usuario de la informacin referente a esta ultima. o o De acuerdo con esto, y como ya se adelant en el apartado 19.2.3, resulta imo prescindible disponer en el lenguaje de programacin empleado de herramientas o que permitan la implementacin separada de los programas y los datos. Eso tas herramientas se denominan unidades, mdulos o paquetes en algunos de los o lenguajes de programacin imperativa ms extendidos. Pero estas herramieno a tas no estn disponibles en Pascal estndar, por lo que es imposible realizar la a a abstraccin de datos de una forma completa6 en este lenguaje, como se advirti o o en el apartado anterior. Sin embargo, en Turbo Pascal s que se dispone de la posibilidad de compilacin separada mediante el empleo de unidades (vase el o e apartado B.11), y esto es suciente para llevar a la prctica la abstraccin de a o datos. En otros trminos, las unidades de Turbo Pascal posibilitan una total encape sulacin de los datos, al incluir la denicin y la implementacin del tipo abso o o tracto en la misma unidad. Esta es una caracter stica positiva, al aumentar la modularidad de los programas y facilitar el aislamiento de los cambios. No obstante, las unidades permiten ocultar slo parcialmente las caracter o sticas del tipo abstracto de datos dadas por la representacin escogida. Es preciso o recalcar la parcialidad de la ocultacin de informacin, ya que es necesario incluir o o la representacin concreta en la declaracin del tipo abstracto (en el ejemplo o o tConj = ^tNodoLista; o tConj = rbol de bsqueda) contenida en la seccin a u o de interfaz de la unidad, y, por tanto, el programador-usuario conocer parte de a los detalles de la representacin.7 o Para nalizar, se puede decir que la implementacin t o pica de un tipo abstracto de datos en Turbo Pascal, siguiendo las ideas de Collins y McMillan [CM], tiene la siguiente estructura:
unit TipoAbstractoDeDatos;
En Pascal estndar habr que incluir la implementacin del tipo de datos y de sus operaciones a a o en cada programa que lo necesite, con lo cual no existe ninguna separacin entre la especicacin o o y la implementacin, que estar totalmente visible al programador-usuario. o a 7 En otros lenguajes, como, por ejemplo, Modula2, se puede conseguir la ocultacin total de o informacin mediante el empleo de los llamados tipos opacos. o
6

19.3. Metodolog de la programacion de TADs a


interface uses Otras unidades necesarias; Declaraciones de constantes, tipos, y variables necesarios para denir el tipo abstracto Encabezamientos de las operaciones del tipo abstracto de datos implementation uses Otras unidades necesarias; Informacin privada, incluyendo las implementaciones de las o operaciones del tipo abstracto de datos y de los tipos participantes, as como las constantes, tipos y variables necesarios para denir stos, y las operaciones privadas e begin Cdigo de iniciacin, si es preciso o o end. {TipoAbstractoDeDatos}

443

19.3.3

Correccin de tipos abstractos de datos o

La vericacin de un tipo abstracto de datos debe hacerse a dos niveles: o En primer lugar, se debe estudiar si la implementacin de las diferentes opeo raciones satisface la especicacin, bien mediante una vericacin a posteriori, o o o bien a travs de una derivacin correcta de programas (en los trminos pree o e sentados en el apartado 5.4) partiendo de la especicacin y desarrollando las o operaciones de acuerdo con sta. Siguiendo esta segunda opcin, por ejemplo, e o para la operacin AnnadirElemConj, se partir de la especicacin o a o {Efecto: conj:= conj [elem]}

y se llegar al cdigo a o
procedure AnnadirElemConj (elem: tElem; var conj: tConj); var parar, {indica el fin de la bsqueda, en la lista} u insertar: boolean; {por si elem ya est en la lista} a auxBuscar, auxInsertar: tConj; begin auxBuscar:= conj; parar:= False; repeat if auxBuscar^.sig = nil then begin

444

Cap tulo 19. Tipos abstractos de datos

parar:= True; insertar:= True end else if auxBuscar^.sig^.info >= elem then begin parar:= True; insertar:= auxBuscar^.sig^.info > elem end else auxBuscar:= auxBuscar^.sig until parar; if insertar then begin auxInsertar:= auxBuscar^.sig; New(auxBuscar^.sig); auxBuscar^.sig^.info:= elem; auxBuscar^.sig^.sig:= auxInsertar end {if} end; {AnnadirElemConj}

que verica la especicacin, como es fcil comprobar examinando el cdigo. o a o En efecto, la implementacin propuesta para AnnadirElemConj, hace que se o agregue elem a la representacin de conj. Ms detalladamente, en el caso del que o a elem no pertenezca al conjunto, se asigna el valor True a la variable insertar, provocando la ejecucin de la rama then de la instruccin if insertar..., que o o aade un nuevo nodo (cuya informacin es elem) en la lista que representa a n o conj. Esto demuestra informalmente que AnnadirElemConj consigue el efecto descrito en la especicacin. o Por otra parte, en los tipos abstractos de datos, se debe estudiar la correccin en una segunda direccin, ya que es necesario establecer propiedades de o o los objetos del tipo, y mediante ellas comprobar que los objetos que se manejan pertenecen realmente al tipo. Estas propiedades se formalizan en los llamados invariantes de la representacin de la forma que se explica seguidamente. El o sentido de esta segunda parte de la vericacin es el de una comprobacin de o o tipos exhaustiva: al escoger una representacin, por ejemplo, las listas en el caso o del tipo abstracto tConj, se activa la comprobacin de tipos impl o cita de Pascal, es decir, el compilador se asegura de que todo objeto del tipo tConj es una lista del tipo ^tNodoLista. Pero esta comprobacin de tipos impl o cita no es suciente, por el hecho de que no toda lista de enteros es una representacin legal de un conjunto, concreo tamente porque puede tener elementos repetidos. Por tanto, es necesaria una vericacin ms profunda que debe ser realizada por el programador-diseador. o a n Esta vericacin se llevar a cabo formalizando las propiedades pertinentes de o a los objetos en los citados invariantes de representacin. o

19.3. Metodolog de la programacion de TADs a

445

Por ejemplo, si se decide representar los conjuntos de enteros mediante listas sin repeticin, este invariante se podr formalizar como sigue: o a {Inv. i, j I, si i = j, entonces li = lj } donde I es el conjunto de ndices correspondientes a los elementos de la lista, y li es el elemento que ocupa la posicin i-sima en la lista. De esta forma se o e expresa que no deben existir dos elementos iguales en la lista. No hay normas generales para establecer el invariante de representacin, ya o que depende del tipo abstracto de datos y de la representacin escogida. Incluso, o en alguna ocasin, el invariante puede ser trivial, como ocurre en la represeno tacin de los conjuntos de enteros mediante rboles binarios de bsqueda: en o a u este caso, como en la implementacin de estos rboles no se repiten las entradas o a (vase el apartado 17.4.2), se tiene asegurado que no habr elementos duplicados, e a con lo cual el invariante de representacin se formaliza simplemente como: o {Inv. cierto} Una vez conocida la forma del invariante de representacin, es necesario o asegurarse de que lo verica la representacin particular de todo objeto del tipo o abstracto de datos. Esto es sencillo, partiendo del hecho de que todo objeto del tipo es obtenido a partir de las operaciones constructoras o mediante la aplicacin de las operaciones de seleccin o de las propias del tipo. Por ello, o o basta con comprobar (de forma inductiva) que todo objeto generado por estas operaciones verica el invariante, suponiendo que los eventuales argumentos del tipo tambin lo cumplen. As volviendo a la representacin mediante listas, la e , o operacin CrearConj lo cumple trivialmente, ya que genera el conjunto vac o o representado, valga la redundancia, por la lista vac y se verica que a, i, j I, si i = j, entonces li = lj puesto que, en este caso, I = . En cuanto a la operacin AnnadirElemConj, dado que el argumento recibido o conj verica el invariante, tambin lo vericar al completarse la ejecucin, ya e a o que en ella se comprueba expl citamente que elem no pertenece a conj. El lector puede comprobar como ejercicio que el resto de las operaciones implementadas en el ejemplo tambin preservan el invariante. e El problema de la correccin en tipos abstractos de datos puede tratarse ms o a formal y profundamente, pero ello escapa a las pretensiones de este libro. En las referencias bibliogrcas se citan textos que profundizan en este tema. a Finalmente, se debe destacar que la modularidad introducida por los tipos abstractos de datos supone una gran ayuda en el estudio de la correccin de o

446

Cap tulo 19. Tipos abstractos de datos

programas grandes. Para stos, dicho estudio resulta tan costoso que se ve muy e reducido o incluso abandonado en la mayor de los casos. Dada esta situaa cin, es muy importante disponer de una biblioteca de tipos abstractos de datos o correctos cuya vericacin se ha realizado independientemente. o

19.4

Resumen

En este apartado se enumeran algunas ideas que ayudan a jar los conceptos expuestos en este cap tulo, as como otros aspectos relacionados: Los tipos abstractos de datos permiten al programador concentrarse en las caracter sticas o propiedades deseadas para dichos tipos (o en qu servie cio deben prestar al programador), olvidndose, temporalmente, de cmo a o estn implementados (o cmo se van a implementar). Por consiguiente, a o los tipos abstractos de datos pueden considerarse como cajas negras: el programador-usuario slo ve su comportamiento y no sabe (ni le interesa o saber) qu contienen. e La denicin del tipo abstracto de datos debe expresarse como una especio cacin no ambigua, que servir como punto de partida para la derivacin o a o correcta de la implementacin o para enfrentarla a posteriori a la impleo mentacin en un proceso de vericacin formal. o o Para la aplicacin de la abstraccin de datos, es imprescindible que el o o lenguaje de programacin escogido posibilite la implementacin separada o o para poder llevar a cabo la ocultacin de informacin y la encapsulacin, o o o esenciales en esta tcnica. e La abstraccin de datos optimiza los niveles de independencia (en la l o nea de lo comentado en los cap tulos 8 y 9) del cdigo de la unidad del tipo o abstracto de datos y de los programas de aplicacin. Como consecuencia de o ello, aumenta la facilidad de mantenimiento de los programas, al aislar en la unidad de implementacin del tipo abstracto los cambios, correcciones o y modicaciones relativos a la representacin del tipo abstracto, evitando o que se propaguen (innecesariamente) a los programas de aplicacin. o La abstraccin de datos se puede considerar como precursora de la tcnica o e de orientacin a objetos (vase el apartado 5.1.3 del tomo I), en la que o e tambin se encapsulan datos y operaciones (con la diferencia de que se e aaden los mecanismos de herencia y polimorsmo, entre otros).8 n
A partir de la versin 5.5 de Turbo Pascal se permite una aplicacin limitada de esta tcnica, o o e pero su explicacin excede los propsitos de este libro. o o
8

19.5. Ejercicios

447

19.5

Ejercicios

1. Construya una unidad de Turbo Pascal para los siguientes tipos abstractos de datos. El tipo abstracto debe incluir operaciones de construccin, modicacin y o o consulta, as como operaciones propias del tipo si se considera conveniente: (a) Listas (de enteros). Consltese el apartado 17.1. u (b) Listas ordenadas (de enteros). Consltese el apartado 17.1. u (c) Pilas (de enteros). Consltese el apartado 17.2. u (d) Colas (de enteros). Consltese el apartado 17.3. u (e) Arboles binarios (de enteros). Consltese el apartado 17.4. u (f) Arboles binarios de bsqueda, conteniendo en sus nodos cadenas de caracu teres. Consltese el apartado 17.4.2. u 2. Desarrolle una implementacin alternativa para el tipo abstracto tConj, basando o la representacin en: o (a) Un vector de booleanos. Obviamente, en este caso se deben limitar los elementos posibles (supngase por ejemplo, que los elementos estn como a prendidos entre 1 y 1000, ambos inclusive). (b) Arboles binarios de bsqueda (vanse los apartados 19.2.3 y 17.4.2). u e Compare el comportamiento que tienen las operaciones en ambos tipos abstractos de datos. 3. Implemente las operaciones Union, Interseccion y Diferencia (que calculan, obviamente, la unin, interseccin y diferencia de dos conjuntos) como operaciones o o propias del tipo abstracto de datos tConj, representado mediante rboles binarios a de bsqueda. u 4. Programe un algoritmo que ordene una secuencia utilizando dos pilas. Para ello debe mover elementos de una pila a otra asegurndose de que los items menores a llegan a la cima de una de las pilas y los mayores a la cima de la otra. Utilice el tipo abstracto pila desarrollado en el ejercicio 1c. 5. Escriba una funcin para sumar dos polinomios utilizando alguno de los tipos o abstractos de datos propuestos en el ejercicio 1. 6. Escriba una unidad para la aritmtica entera (sin l e mite en sus valores mximo a y m nimo), utilizando alguno de los tipos abstractos de datos propuestos en el ejercicio 1. 7. Una cola doble es una estructura de datos consistente en una lista de elementos sobre la cual son posibles las siguientes operaciones: Meter(x,d): inserta el elemento x en el extremo frontal de la cola doble d.

Sacar(x,d): elimina y devuelve el elemento que est al frente de la cola doble a d.

448

Cap tulo 19. Tipos abstractos de datos


Inyectar(x,d): inserta el elemento x en el extremo posterior de la cola doble d. Expulsar(x,d): elimina y devuelve el elemento que est en el extremo postea rior de la cola doble d. Programar una unidad de Turbo Pascal para el tipo abstracto de datos cola doble.

19.6

Referencias bibliogrcas a

En este cap tulo se introducen los tipos abstractos de datos, eludiendo las profundidades formales que hay tras ellos y evitando tambin dar catlogos exhaustivos de los e a tipos abstractos de datos ms usuales. En [Mar86, Har89] se estudian los aspectos fora males de los tipos abstractos de datos basados en especicaciones algebraicas. Adems, a ambos textos pueden considerarse como catlogos de los tipos abstractos de datos de a ms amplia utilizacin, incluyendo la especicacin, implementaciones y aplicaciones a o o comunes de cada uno de ellos. En [Pn93] se puede encontrar un estudio actual, riguroso y completo de los tipos abstractos de datos en nuestro idioma, junto con especicaciones de los ms usuales. Igualmente, [HS90] es una lectura obligada en cuanto se aborda el a estudio de tipos abstractos de datos. Con un enfoque ms aplicado, en [LG86, CMM87] se da un tratamiento completo a de los tipos abstractos de datos bien adaptado a Pascal, ofreciendo propuestas de implementacin en este lenguaje. o Por supuesto, los tipos abstractos de datos pueden ser implementados en lenguajes de programacin diferentes de Pascal. De hecho, Pascal estndar no cuenta con mecao a nismos apropiados para manejarlos, ya que el concepto de tipo abstracto de datos es posterior a la creacin de este lenguaje. El propio N. Wirth incluy este concepto en o o su Mdula2 a travs de los mdulos, y las unidades de Turbo Pascal no son ms que o e o a un remedo (incompleto, por cierto) de este mecanismo. Otros lenguajes con facilidades para el desarrollo de tipos abstractos de datos son Ada [Bar87], que dispone de paquetes, y C++ [Str84], haciendo uso de clases. Adems, algunos lenguajes proporcionan al a programador tcnicas de compilacin separada ms adecuadas a los tipos abstractos y e o a sus necesidades que las incompletas unidades de Turbo Pascal. Finalmente, debemos indicar que el ejercicio 4 est tomado de [Mar86]. Los ejercia cios 5, 6 y 7 estn tomados de [Wei95]. a

Cap tulo 20

Esquemas algor tmicos fundamentales

20.1 Algoritmos devoradores

. . . . . . . . . . . . . . . . . . 450

20.2 Divide y vencers . . . . . . . . . . . . . . . . . . . . . . 453 a 20.3 Programacin dinmica . . . . . . . . . . . . . . . . . . . 455 o a 20.4 Vuelta atrs . . . . . . . . . . . . . . . . . . . . . . . . . . 462 a 20.5 Anexo: algoritmos probabilistas . . . . . . . . . . . . . 468 20.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470 20.7 Referencias bibliogrcas . . . . . . . . . . . . . . . . . . 473 a

Cuando se estudian los problemas y algoritmos usualmente escogidos para mostrar los mecanismos de un lenguaje algor tmico (ya sea ejecutable o no), puede parecer que el desarrollo de algoritmos es un cajn de sastre en el que se o encuentran algunas ideas de uso frecuente y cierta cantidad de soluciones ad hoc, basadas en trucos ms o menos ingeniosos. a Sin embargo, la realidad no es as muchos problemas se pueden resolver con : algoritmos construidos en base a unos pocos modelos, con variantes de escasa importancia. En este cap tulo se estudian algunos de esos esquemas algor tmicos fundamentales, su eciencia y algunas de las tcnicas ms empleadas para mee a jorarla.

450

Cap tulo 20. Esquemas algor tmicos fundamentales

20.1

Algoritmos devoradores

La estrategia de estos algoritmos es bsicamente iterativa, y consiste en una a serie de etapas, en cada una de las cuales se consume una parte de los datos y se construye una parte de la solucin, parando cuando se hayan consumido o totalmente los datos. El nombre de este esquema es muy descriptivo: en cada fase (bocado) se consume una parte de los datos. Se intentar que la parte a consumida sea lo mayor posible, bajo ciertas condiciones.

20.1.1

Descripcin o

Por ejemplo, la descomposicin de un nmero n en primos puede describirse o u mediante un esquema devorador. Para facilitar la descripcin, consideremos la o descomposicin de 600 expresada as o : 600 = 23 31 52 En cada fase, se elimina un divisor de n cuantas veces sea posible: en la primera se elimina el 2, y se considera el correspondiente cociente, en la segunda el 3, etc. y se naliza cuando el nmero no tiene divisores (excepto el 1). u El esquema general puede expresarse as :
procedure Resolver P (D: datos; var S: solucion); begin Generar la parte inicial de la solucin S o (y las condiciones iniciales) while D sin procesar del todo do begin Extraer de D el mximo trozo posible T a Procesar T (reducindose D) e Incorporar el procesado de T a la solucin S o end {while} end; {Resolver P }

La descomposicin de un nmero en factores primos se puede implementar o u sencillamente siguiendo este esquema:
procedure Descomponer(n: integer); {PreC.: n > 1} {Efecto: muestra en la pantalla la descomposicin de n o en factores primos} var d: integer;

20.1. Algoritmos devoradores


begin d:= 2; while n > 1 do begin Dividir n por d cuantas veces (k) se pueda Escribir dk d:= d + 1 end {while} end; {Descomponer}

451

20.1.2

Adecuacin al problema o

Otro ejemplo, quiz el ms conocido de este esquema, es el de encontrar el a a cambio de moneda de manera ptima (en el sentido de usar el menor nmero de o u monedas posible) para una cantidad de dinero dada:1 en cada fase, se considera una moneda de curso legal, de mayor a menor valor, y se cambia la mayor cantidad de dinero posible en monedas de ese valor. Se advierte, sin embargo, que no siempre es apropiado un algoritmo devorador para resolver este problema. Por ejemplo, si el sistema de monedas fuera de 1, 7 y 9 pesetas el cambio de 15 pesetas que ofrece este algoritmo consta de 7 monedas: 1 de 9 y 6 de 1 mientras que el cambio ptimo requiere tan slo o o 3 monedas: 2 de 7 y 1 de 1 El algoritmo presentado para el cambio de moneda resultar correcto siempre a que se considere un sistema monetario donde los valores de las monedas son cada uno mltiplo del anterior. Si no se da esta condicin, es necesario recurrir a otros u o esquemas algor tmicos (vase el apartado 20.3.3). e La enseanza que extraemos del ejemplo es la siguiente: generalmente el n desarrollo de esta clase de algoritmos no presenta dicultades, pero es complicado asegurarse de que esta tcnica es apropiada para el problema planteado. Por otra e parte, hay que sealar que este esquema se aplica con frecuencia en la resolucin n o de problemas a sabiendas de que la solucin proporcionada no es la ptima, sino o o slo relativamente buena. Esta eleccin se debe a la rapidez de la resolucin, o o o circunstancia que muchas veces hace que no merezca la pena buscar una solucin o mejor.
1

Para simplicar, supondremos que se dispone de cuantas monedas se necesite de cada valor.

452

Cap tulo 20. Esquemas algor tmicos fundamentales

20.1.3

Otros problemas resueltos vorazmente

Existen gran cantidad de problemas cuya solucin algor o tmica responde a un esquema voraz. Entre ellos, los dos siguientes son ampliamente conocidos. Problema de la mochila Se desea llenar una mochila hasta un volumen mximo V , y para ello se disa pone de n objetos, en cantidades limitadas v1 , . . . , vn y cuyos valores por unidad de volumen son p1 , . . . , pn , respectivamente. Puede seleccionarse de cada objeto una cantidad cualquiera ci IR con tal de que ci vi . El problema consiste en determinar las cantidades c1 , . . . , cn que llenan la mochila maximizando el valor n i=1 vi pi total. Este problema puede resolverse fcilmente seleccionando, sucesivamente, el a objeto de mayor valor por unidad de volumen que quede y en la mxima cantidad a posible hasta agotar el mismo. Este paso se repetir hasta completar la mochila a o agotar todos los objetos. Por lo tanto, se trata claramente de un esquema voraz. Arbol de expansin m o nimo (algoritmo de Prim) El problema del rbol de expansin m a o nimo2 se encuentra por ejemplo en la siguiente situacin: consideremos un mapa de carreteras, con dos tipos de o componentes: las ciudades (nodos) y las carreteras que las unen. Cada tramo de carreteras (arco) est sealado con su longitud.3 Se desea implantar un tendido a n elctrico siguiendo los trazos de las carreteras de manera que conecte todas las e ciudades y que la longitud total sea m nima. Una forma de lograrlo consiste en
Empezar con el tramo de menor coste repetir Seleccionar un nuevo tramo hasta que est completa una red que conecte todas las ciudades e

donde cada nuevo tramo que se selecciona es el de menor longitud entre los no redundantes (es decir, que da acceso a una ciudad nueva). Un razonamiento sencillo nos permite deducir que un rbol de expansin a o cualquiera (el m nimo en particular) para un mapa de n ciudades tiene n 1 tramos. Por lo tanto, es posible simplicar la condicin de terminacin que o o controla el algoritmo anterior.
2 3

Tambin llamado rbol de recubrimiento (del ingls, spanning tree.) e a e Este modelo de datos se llama grafo ponderado.

20.2. Divide y venceras

453

20.2

Divide y vencers a

La idea bsica de este esquema consiste en lo siguiente: a 1. Dado un problema P , con datos D, si los datos permiten una solucin o directa, se ofrece sta; e 2. En caso contrario, se siguen las siguientes fases: (a) Se dividen los datos D en varios conjuntos de datos ms pequeos, a n Di . (b) Se resuelven los problemas P (Di ) parciales, sobre los conjuntos de datos Di , recursivamente. (c) Se combinan las soluciones parciales, resultando as la solucin nal. o El esquema general de este algoritmo puede expresarse as :
procedure Resolver P (D: datos; var S: solucion); begin if los datos D admiten un tratamiento directo then Resolver P(D) directamente else begin Repartir D en varios conjuntos de datos, D1 , . . . , Dk (ms cercanos al tratamiento directo). a Resolver P (D1 ), . . . , P (Dk ) (llamemos S1 , . . . , Sk a las soluciones obtenidas). Combinar S1 , . . . , Sk , generando la solucin, S, de P (D). o end end; {Resolver P }

Como ejemplo, veamos que el problema de ordenar un vector admite dos soluciones siguiendo este esquema: los algoritmos Merge Sort y Quick Sort (vanse e los apartados 15.2.5 y 15.2.4, respectivamente). Tal como se present en el apartado antes citado, el primer nivel de diseo o n de Merge Sort puede expresarse as :
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos subvectores A y B n {si} Ordenar A y B usando Merge Sort Mezclar las ordenaciones de A y B para generar el vector ordenado.

454

Cap tulo 20. Esquemas algor tmicos fundamentales

Esta organizacin permite distinguir claramente las acciones componentes o de los esquemas divide y vencers comparndolo con el esquema general. El a a siguiente algoritmo, Quick Sort, resuelve el mismo problema siguiendo tambin e una estrategia divide y vencers: a
si v es de tamao 1 entonces n v ya est ordenado a si no Dividir v en dos bloques A y B con todos los elementos de A menores que los de B n {si} Ordenar A y B usando Quick Sort Devolver v ya ordenado como concatenacin o de las ordenaciones de A y de B

Aunque ambos algoritmos siguen el mismo esquema, en el primero la mayor parte del trabajo se efecta al combinar las subsoluciones, mientras que en el u segundo la tarea principal es el reparto de los datos en subconjuntos. De hecho, lo normal es operar reestructurando el propio vector, de modo que no es preciso combinar las subsoluciones concatenando los vectores. Adems, como se analiz a o en el apartado 18.3.2, el algoritmo Merge Sort resulta ser ms eciente en el a peor caso, ya que su complejidad es del orden de n log n frente a la complejidad cuadrtica de Quick Sort, tambin en el peor caso. a e

20.2.1

Equilibrado de los subproblemas

Para que el esquema algor tmico divide y vencers sea eciente es necesario a que el tamao de los subproblemas obtenidos sea similar. Por ejemplo, en el caso n del algoritmo Quick Sort, y en relacin con estos tamaos, se podr distinguir o n an dos versiones: La presentada anteriormente, cuya complejidad en el caso medio es del orden de n log n. La degenerada, en la que uno de los subproblemas es la lista unitaria es de tamao 1, y en la que se tiene una complejidad cuadrtica. De hecho, n a esta versin de Quick Sort es equivalente al algoritmo de ordenacin por o o insercin (vase el apartado 15.2.2). o e No obstante, hay problemas en los que el esquema divide y vencers no a ahorra coste, ni siquiera equilibrando los subproblemas. Por ejemplo, el clculo a de b i. En efecto, su versin recursiva con los subproblemas equilibrados o i=a
b

i=
i=a

1
m i=a i

b i=m+1 i,

si b < a para m = (a + b) div 2, e. o. c.

20.3. Programacion dinamica

455

tiene un coste proporcional a b a, al igual que su versin degenerada, donde o uno de los subproblemas es el trivial:
b

i=
i=a

1 a

b i=a+1 i

si b < a e. o. c.

siendo por tanto preferible su versin iterativa conocida. o

20.3
20.3.1

Programacin dinmica o a
Problemas de programacin dinmica o a

Consideremos un problema en el que se desea obtener el valor ptimo de o una funcin, y para ello se ha de completar una secuencia de etapas e1 , . . . , en . o Supongamos que en la etapa ei se puede escoger entre las opciones o1 , . . . , ok , cada una de las cuales divide el problema en dos subproblemas ms sencillos e a independientes: e1 , . . . , ei1 y ei+1 , . . . , en Entonces, bastar con resolver los k pares de subproblemas (uno por cada opcin a o para ei ) y escoger el par que ofrece el mejor resultado (para la funcin por o optimizar). Supongamos que la solucin ptima se obtiene si ei = o: o o e1 , . . . , ei1 , ei = o, ei+1 , . . . , en Entonces, sus mitades anterior y posterior e1 , . . . , ei1 , o o, ei+1 , . . . , en deben ser las soluciones ptimas a los subproblemas parciales planteados al jar o la etapa i-sima. Esta condicin se conoce como el principio de optimalidad de e o Bellman. Por ejemplo, si para ir desde el punto A hasta el punto C por el camino ms corto se pasa por el punto B, el camino m a nimo desde A hasta C consiste en la concatenacin del camino m o nimo desde A hasta B y el camino m nimo desde B hasta C. Por tanto, la resolucin de estos problemas consiste en denir una secuencia o de etapas. Por otra parte, al jar una etapa cualquiera se divide el problema en dos problemas ms sencillos (como en los algoritmos divide y vencers), que a a deben ser independientes entre s El principal inconveniente es que la eleccin . o de una etapa requiere en principio tantear varios pares de subproblemas, con lo que se dispara el coste de la resolucin de estos problemas. o

456 Ejemplo

Cap tulo 20. Esquemas algor tmicos fundamentales

Consideremos un grafo como el de la gura, organizado por fases,4 y se plantea el problema de averiguar el recorrido ms corto entre a y z, conociendo las a longitudes de los arcos: fase 0 fase i-1 fase i fase n
X $ e $$$ B $$$ $ b && b $$ & & && q & c E & X $$$ f & $$$$ & & $$$ $ qs d E g

... ... ...

... ... ... z

El problema propuesto consiste en formar una secuencia de etapas, en cada una de las cuales se opta por un arco que conduce a uno de los nodos de la siguiente, accesible desde nuestro nodo actual, determinado por las etapas anteriores. Si en la fase i-sima se puede escoger entre los nodos E, F y G, el camino e ms corto entre A y Z es el ms corto entre los tres siguientes a a ir desde a hasta e, e ir desde e hasta z ir desde a hasta f, e ir desde f hasta z ir desde a hasta g, e ir desde g hasta z de manera que, una vez jada una eleccin (por ejemplo E), las subsoluciones ir o desde A hasta E e ir desde E hasta Z componen la solucin global. Esto es, se o verica el principio de optimalidad de Bellman. En resumen, el mejor trayecto entre los puntos P y Q (en fases no consecutivas finic y ff in ), se halla as :
Elegir una etapa intermedia i (por ejemplo, (finic + ff in ) div 2) (sean {o1 , . . . , ok } los puntos en la etapa i-sima) e Elegir O {o1 , . . . , ok } tal que el recorrido MejorTray(P,O) junto con MejorTray(O,Q) sea m nimo

La recursin termina cuando los nodos a enlazar estn en etapas consecutivas, o a existiendo un unico tramo de P a Q (en cuyo caso se elige ste) o ninguno (en cuyo e
4

Este tipo de grafos se llaman polietpicos. a

20.3. Programacion dinamica

457

caso no hay trayecto posible entre los nodos a enlazar, lo que puede consignarse, por ejemplo, indicando una distancia innita entre esos puntos). Estas ideas pueden expresarse como sigue, desarrollando a la vez un poco ms el algoritmo5 descrito antes: a
procedure MejorTray (P, Q: puntos; fP , fQ : nums.fase; var Tr: trayecto; var Dist: distancia); begin if consecutivos(P,Q) then Tr:=[P, Q] Dist:=longArco(P, Q), dato del problema else begin Sea fmed (fP + fQ ) div 2, y sean {o1 , . . . , ok } los puntos de paso de la etapa i-sima e Se parte de distPQ y trP Q [ ] para todo O {o1 , . . . , ok } hacer begin MejorTray(P,O, fP , fmed ,TrPO, DistPO) MejorTray(O,Q, fmed , fQ , TrOQ, DistOQ) if DistPQ > DistPO + DistOQ then begin DistPQ:= DistPO + DistOQ; TrPQ := TrPO concatenado con TrOQ end {if} end; {para todo} Tr:= TrPQ Dist:= DistPQ end {else} end; {MejorTray}

20.3.2

Mejora de este esquema

El planteamiento anterior se caracteriza por su ineciencia debida al gran nmero de llamadas recursivas. En el caso anterior, por ejemplo, el recorrido az u puede descomponerse de mltiples formas, como se recoge en el siguiente rbol: u a
    

az

d q d e e  e  e  e e
5

ae

A   rr  j r C 

ez ...

d q d e e  e  e  e e

ae af

c r r  j r C 

fz ...

q rr  j r C  gz ag ... d q d e e  e  e  e e

ab be ac ce ad de ab bf ac cf ad df ab bg ac cg ad dg
Se advierte que este algoritmo es tremendamente ineciente y, por tanto, nada recomendable; en el siguiente apartado se vern modos mejores de afrontar esta clase de problemas. a

458

Cap tulo 20. Esquemas algor tmicos fundamentales

Sin embargo, se observa que un buen nmero de tramos se calcula repetidau mente (ab, ac, ad, . . . ), por lo que se puede mejorar el planteamiento evitando los clculos idnticos reiterados, concretamente mediante las tcnicas de tabua e e lacin. o En este apartado estudiaremos en primer lugar en qu consisten esas tcnicas, e e y despus retomaremos el algoritmo anterior para ver cmo puede mejorarse su e o comportamiento mediante la tabulacin. o Tabulacin de subprogramas recursivos o En ocasiones, una funcin f con varias llamadas recursivas genera, por difeo rentes v llamadas repetidas. La funcin de Fibonacci (vase el apartado 10.3.1) as, o e es un ejemplo clsico: a $$ 5
$$$ W $$  4  A  q z  3  A  q

d d

% s

d d

d d

Al aplicarse al argumento 5, la llamada Fib(1) se dispara cinco veces. Una solucin para evitar la evaluacin repetida consiste en dotar a la funcin o o o de memoria de modo que recuerde los valores para los que se ha calculado junto con los resultados producidos. As cada clculo requerido de la funcin se con, a o sultar en la tabla, extrayendo el resultado correspondiente si ya se hubiera a efectuado o registrndolo en ella si fuera nuevo. a El esquema es bien sencillo: basta con establecer una tabla (la memoria de la funcin) global e incluir en ella las consultas y actualizaciones mencionadas: o
function f (x: datos; var T: tablaGlobal): resultados; begin if x no est en la tabla T then begin a Hallar las llamadas recursivas f (xi , T) y combinarlas, hallando el valor f (x, T) requerido Incluir en la tabla T el argumento x y el resultado f (x, T) obtenido end; {if} f := T[x] end; {f }

Por ejemplo, la funcin de Fibonacci denida antes puede tabularse como o sigue. En primer lugar, denimos la tabla:

20.3. Programacion dinamica


type tDominio = 0..20; tTabla = array [tDominio] of record definido: boolean; resultado: integer end; {record} var tablaFib: tTabla;

459

cuyo estado inicial debe incluirse en el programa principal:


tablaFib[0].definido:= True; tablaFib[0].resultado:= 1; tablaFib[1].definido:= True; tablaFib[1].resultado:= 1; for i:= 2 to 20 do tablaFib[i].definido:= False

Entonces, la funcin Fib resulta o


function Fib(n: tDominio; var t: tTabla): integer; begin if not t[n].definido then begin t[n].definido:= True; t[n].resultado:= Fib(n-1,t) + Fib(n-2,t) end; {if} Fib:= t[n].resultado end; {Fib}

que se llama, por ejemplo, mediante Fib(14, tablaFib).


ee r r Un requisito indispensable para que una funcin se pueda tabular correco tamente es que est libre de (producir o depender de) efectos laterales, de e manera que el valor asociado a cada argumento sea unico, independiente del punto del programa en que se requiera o del momento en que se invoque. Esta observacin es necesaria, ya que en esta tcnica se utilizan variables o e globales; como se explic en el apartado 8.5.4, un uso incontrolado de estas o variables puede acarrear la prdida de la correccin de nuestros programas. e o

Tabulacin de algoritmos de programacin dinmica o o a Veamos ahora cmo las tcnicas de tabulacin descritas mejoran la eciencia o e o del algoritmo del grafo polietpico. a

460

Cap tulo 20. Esquemas algor tmicos fundamentales

En efecto, se puede alterar el procedimiento descrito de modo que cada operacin realizada se registre en una tabla y cada operacin por realizar se consulte o o previamente en esa tabla por si ya se hubiera calculado. En el ejemplo anterior, se podr anotar junto a los puntos del propio mapa el mejor trayecto que cona duce a ellos cada vez que se calcule. De este modo, no ser necesario repetir esos a clculos cuando vuelvan a requerirse. Los cambios descritos son m a nimos: Se crea una tabla global (donde registraremos los mejores trayectos y las correspondientes distancias), y se rellena inicialmente con los trayectos y distancias de los puntos consecutivos (que son los datos del problema). Entonces en vez de comprobar si un camino es directo, se har lo siguiente: a
si consecutivos(P,Q) entonces el mejor trayecto es la secuencia [P,Q] y su longitud es longArco(P,Q)

comprobaremos si est ya tabulado: a


si tabulado(P,Q) entonces extraer de la tabla el trayecto, trPQ, y su longitud, distPQ ...

Por ultimo, al nalizar un clculo, se debe aadir a la tabla la accin a n o siguiente:


Registrar en la tabla el trayecto (TrPQ) y la distancia (distPQ) hallados

Esta solucin es bastante satisfactoria. Sin embargo, este tipo de problemas o permite, en general, establecer un orden entre los subproblemas requeridos y, por consiguiente, entre los clculos necesarios. En el ejemplo anterior, los clculos a a pueden hacerse por fases: una vez hallado (y anotado) el mejor trayecto que lleva a cada uno de los puntos de una fase i, es sencillo y rpido hallar (y anotar) los a mejores caminos hasta los puntos de la fase siguiente, i+1. Ms an, como ya no a u se necesita la informacin correspondiente a la fase i-sima, es posible prescindir o e de ella, con el consiguiente ahorro de memoria.

20.3.3

Formulacin de problemas de programacin dinmica o o a

El problema del cambio de moneda (vase el apartado 20.1.2) se puede fore mular tambin de modo que los valores de las monedas no guardan relacin e o alguna: se dispone de una coleccin ilimitada de monedas, de valores v1 , . . . , vn o

20.3. Programacion dinamica

461

enteros cualesquiera, y se trata ahora de dar el cambio ptimo para la cantidad o C, entendiendo por ptimo el que requiere el menor nmero total de monedas.6 o u Una formulacin siguiendo el esquema de programacin dinmica es la sio o a guiente: si hay alguna moneda de valor igual a la cantidad total, el cambio o ptimo es precisamente con una moneda; de lo contrario, el primer paso consiste en escoger una moneda entre las de valor menor que la cantidad dada y esa eleccin debe minimizar el cambio de la cantidad restante: o
function NumMon(C: integer): integer; begin if alguna de las monedas v1 , . . . , vn es igual a C then NumMon:= 1 else NumMon:= 1 + mnvi <C (NumMon(C-vi )) end; {NumMon}

La tabulacin es sencilla: para obtener el cambio ptimo de una cantidad C, o o ser necesario consultar (posiblemente varias veces) los cambios de cantidades a menores. Por lo tanto, es fcil establecer un orden entre los subproblemas, tabua lando los cambios correspondientes a las cantidades 1, 2, . . . C, ascendentemente. Este ejemplo nos permite extraer dos consecuencias: En algunos problemas no se enuncian expl citamente las etapas que deben superarse desde el estado inicial a una solucin; ms an, a veces el proo a u blema no consta de un nmero de fases conocido de antemano, sino que u stas deben ser calculadas por el algoritmo propuesto, como se muestra e grcamente en la siguiente gura: a
   v2  I    0 d z d v3 d d Q  

v1 ... ...

...

C v1 C v2

~ z

... ...

v1

C v4

X $ $$$ B C v3

Para simplicar, supondremos que una de las monedas tiene el valor unidad, de manera que siempre es posible completar el cambio.

462

Cap tulo 20. Esquemas algor tmicos fundamentales

El ejemplo presentado obedece a un planteamiento matemtico con aplicaa ciones en campos muy diversos. Concretamente, si llamamos k1 , . . . , kn al nmero de monedas de cada valor v1 , . . . , vn respectivamente, el esquema u anterior resuelve el siguiente problema de optimizacin: o
n n

m n
i=1

ki

sujeto a que
i=1

ki v i = C

que es muy similar al planteamiento general numrico de esta clase de e problemas: hallar enteros no negativos x1 , . . . , xn que minimicen la funcin o g(k1 , . . . , kn ) denida as :
n

g(k1 , . . . , kn ) =
i=1

fi (ki ) C.

y de manera que se mantenga Nota nal

n i=1 ki vi

Es importante subrayar que un problema planteado no siempre admite una descomposicin en subproblemas independientes: en otras palabras, antes de expreo sar una solucin basada en tal descomposicin, debe comprobarse que se verica o o el principio de optimalidad. En general, la resolucin de esta clase de problemas resulta inviable sin la tao bulacin. Adems, frecuentemente resulta sencillo jar un orden en los cmputos o a o con lo que, a veces, es posible disear un algoritmo iterativo de resolucin. n o

20.4

Vuelta atrs a

Consideremos el problema de completar un rompecabezas. En un momento dado, se han colocado unas cuantas piezas, y se tantea la colocacin de una o nueva pieza. Por lo general, ser posible continuar de diversos modos, y cada a uno de ellos podr ofrecer a su vez diversas posibilidades, multiplicndose as las a a posibilidades de tanteo. La bsqueda de soluciones es comparable al recorrido u de un rbol, por lo que se le llama rbol de bsqueda (vase el apartado 17.5) o a a u e tambin espacio de bsqueda. e u Por otra parte, el tanteo de soluciones supone muchas veces abandonar una v muerta cuando se descubre que no conduce a la solucin, deshaciendo algunos a o movimientos y regresando por otras ramas del rbol de bsqueda a una posicin a u o anterior. De ah viene la denominacin de esta clase de algoritmos: vuelta atrs o a o bsqueda con retroceso.7 u
7

En ingls, backtrack o backtracking. e

20.4. Vuelta atras

463

Un ejemplo conocido de problema que se adapta bien a este esquema es el de situar ocho damas en un tablero de ajedrez de manera que ninguna est e amenazada por otra. La siguiente gura muestra una solucin: o

La gura 20.1 muestra un fragmento del rbol de bsqueda en una fase a u intermedia en que se est construyendo la solucin mostrada. Se ha usado el a o s mbolo para representar el fallo en un intento de ampliar la solucin por una o rama, y obedece a la imposibilidad de situar una dama en ciertas casillas por estar a tiro de las situadas con anterioridad. Las variantes ms conocidas a este tipo de problemas son las siguientes: a 1. Generar todas las soluciones posibles. El esquema de bsqueda de todas las soluciones, desde la fase i-sima, se u e puede resumir como sigue:
procedure P (i: numero de fase; S: solucParcial); {Efecto: genera todas las soluciones, desde la fase i-sima, a partir e de la solucin parcial S} o begin para todo pi {posibles pasos vlidos en esta fase } hacer begin a Aadir pi a S (Sea S la solucin S, dando un paso ms pi ) n o a if pi completa una solucin then o Registrar la solucin S o else Resolver P(i+1, S) end {para todo} end; {P }

Como cada dama debe estar en una la distinta, es posible representar una solucin como un vector (D1 , . . . , D8 ), donde cada componente Di es un entero o de {1, . . . , 8} que representa la columna en que se encuentra la dama i-sima. e

En particular, el procedimiento que genera todas las soluciones en el problema de las ocho damas es el siguiente:

464

Cap tulo 20. Esquemas algor tmicos fundamentales

...

& r f  r & f r r &  r & &  ff rr % & s j r a G  x w

...

r & r &  f r &  f rr &  f r &  % j & s r r a G  x w f

...

. . . d1 = 4 . . .

...

...

...

...

...

r & r  f r & &  f rr &  f r &  j % & s r r a G  x w f

...

. . . d2 = 8

& r  r f & f r r &  r & &  ff rr % & s j r a G  x w

...

d3 = 5

. . . d4 = 3 ...

Figura 20.1.

20.4. Vuelta atras


type tDominio = 1..8; ... procedure OchoDamas (i: numero de dama; S: solucParcial); var col: tDominio; begin for col:= 1 to 8 do if puede situarse la dama i-sima en la columna col sin e estar a tiro de las anteriores (1, . . . , i-1) then begin Situar la dama i-sima en la columna col e (con lo que se tiene la situacin S) o if i = 8 then Registrar esta solucin o else Resolver OchoDamas (i+1, S) end {if} end; {OchoDamas}

465

2. Tantear cuntas soluciones existen. a El esquema es una variante del anterior, estableciendo un contador a cero antes de iniciar la bsqueda de soluciones y cambiando la accin de registrar u o una solucin por incrementar el contador. o 3. Generar una solucin, si existe, o indicar lo contrario. o En este caso, la bsqueda termina cuando se halla una solucin o se agotan u o las v posibles. as Un modo fcil de conseguirlo es modicar el primer esquema aadiendo un a n parmetro (booleano) para indicar cundo se ha encontrado una solucin a a o y parando la bsqueda en caso de xito. En el caso del problema de las u e ocho damas quedar as a :

procedure OchoDamas(i: numero de dama; S: solucParcial; var halladaSol: boolean); var col: tDominio; begin halladaSol:= False; col:= 0; repeat col:= col + 1; if puede situarse la dama i-sima en la columna col sin e estar a tiro de las anteriores (1, . . . , i-1) then begin

466

Cap tulo 20. Esquemas algor tmicos fundamentales


Situar la dama i-sima en la columna col e (con lo que se tiene la situacin S) o if i = 8 then begin Registrar esta solucin; o halladaSol:= True end {then} else Resolver OchoDamas (i+1, S, halladaSol) end {then} until halladaSol or (col = 8) end; {OchoDamas}

En resumen, la tcnica de vuelta atrs ofrece un mtodo para resolver proe a e blemas tratando de completar una o varias soluciones por etapas e1 , e2 , ..., en donde cada ei debe satisfacer determinadas condiciones. En cada paso se trata de extender una solucin parcial de todos los modos posibles, y si ninguno reo sulta satisfactorio se produce la vuelta atrs hasta el ultimo punto en que an a u quedaban alternativas sin explorar. El proceso de construccin de una solucin o o es semejante al recorrido de un rbol: cada decisin ramica el rbol y conduce a o a a posiciones ms denidas de la solucin o a una situacin de fallo. a o o

20.4.1

Mejora del esquema de vuelta atrs a

El rbol de bsqueda completo asociado a los esquemas de vuelta atrs crece a u a frecuentemente de forma exponencial conforme aumenta su altura. Por ello, cobra gran importancia podar algunas de sus ramas siempre que sea posible. Los mtodos ms conocidos para lograrlo son los siguientes: e a Exclusin previa o Frecuentemente, un anlisis detallado del problema permite observar que ciertas a subsoluciones resultarn infructuosas ms o menos pronto. Estos casos permiten a a organizar el algoritmo para que ignore la bsqueda en tales situaciones. u El problema de las ocho damas es un claro ejemplo de ello: es obvio que cada dama deber estar en una la distinta, lo que facilita la organizacin de las fases a o y limita la bsqueda de soluciones. u Fusin de ramas o Cuando la bsqueda a travs de distintas ramas lleve a resultados equivalentes, u e bastar con limitar la bsqueda a una de esas ramas. a u

20.4. Vuelta atras

467

En el problema de las ocho damas, por ejemplo, se puede limitar el recorrido de la primera dama a los cuatro primeros escaques, ya que las soluciones correspondientes a los otros cuatro son equivalentes y deducibles de los primeros por simetr a.

Reordenacin de la b squeda o u Cuando lo que se busca no son todas las soluciones, sino slo una, es interesante o reorganizar las ramas, situando en primer lugar las que llevan a un subrbol de a menor tamao o aqullas que ofrecen mayores espectativas de xito. n e e

Ramicacin y poda o Con frecuencia, el mtodo de bsqueda con retroceso resulta impracticable dee u bido al elevado nmero de combinaciones de prueba que aparecen. Cuando lo u que se busca es precisamente una solucin ptima, es posible reducir el rbol o o a de bsqueda: suprimiendo aquellas fases que, con certeza, avanzan hacia soluu ciones no ptimas, por lo que se puede abandonar la bsqueda por esas v o u as 8 de esta tcnica). Como consecuencia de (sta es la idea que reeja el nombre e e esta reduccin del nmero de soluciones por inspeccionar, se puede producir una o u mejora sustancial en la eciencia de los algoritmos de bsqueda con retroceso u convirtindolos en viables. e Por ejemplo, consideremos nuevamente el problema de las ocho damas, pero con la siguiente modicacin: los escaques del tablero estn marcados con enteros o a positivos, y se trata de hallar la posicin de las damas que, sin amenazarse o entre s cubre casillas cuya suma sea m , nima. Ahora, si suponemos que se ha encontrado ya una solucin (con un valor suma S), en el proceso de bsqueda de o u las posiciones siguientes se puede podar en cuanto una fase intermedia rebase la cantidad S, ya que se tiene la seguridad de que la solucin obtenida al extender o esa solucin no mejorar en ningn caso la que ya tenemos. o a u Este ejemplo nos permite resaltar la conveniencia de reordenar la bsqueda: u es obvio que, cuanto menor sea el valor ofrecido por una solucin provisional, o mayores posibilidades se tendr de efectuar podas en las primeras fases. En a nuestro ejemplo, las posibilidades de reorganizar la bsqueda son dos: establecer u un orden entre las fases y dentro de cada fase.
8

En ingls, branch and bound. e

468

Cap tulo 20. Esquemas algor tmicos fundamentales

20.5

Anexo: algoritmos probabilistas

Por desgracia, los esquemas anteriores no siempre proporcionan soluciones a cualquier problema planteado; adems sucede a veces que, aun existiendo una a solucin algor o tmica a un problema, el tiempo requerido para encontrar una solucin es tal que los algoritmos resultan inservibles. Una posibilidad consiste o en conformarse con una solucin aproximada, o bien con una solucin que reo o sulte vlida slo casi siempre. Ambas posibilidades se basan en la aplicacin de a o o mtodos estad e sticos. En realidad, este planteamiento no constituye un esquema algor tmico, sino ms bien una trampa consistente en alterar el enunciado de un problema intraa table computacionalmente para ofrecer una respuesta aceptable en un tiempo razonable. En este anexo incluimos slo un par de ejemplos de los dos criterios ms o a frecuentemente adoptados en esta clase de algoritmos: El primero consiste en buscar una solucin aproximada, lo que es posible en o bastantes problemas numricos, sabiendo adems el intervalo de conanza e a de la solucin encontrada. La precisin de la solucin depende frecuenteo o o mente del tiempo que se invierta en el proceso de clculo, por lo que puede a lograrse la precisin que se desee a costa de una mayor inversin del tiempo o o de proceso. El segundo criterio consiste en buscar una respuesta que constituya una solucin del problema con cierta probabilidad. La justicacin es que cuando o o se trata de tomar una decisin entre, por ejemplo, dos opciones posibles, o no cabe pensar en soluciones aproximadas, aunque s en soluciones pro bablemente acertadas. Tambin aqu es posible invertir una cantidad de e tiempo mayor para aumentar, esta vez, la probabilidad de acierto. Aunque existen clasicaciones sistemticas ms detalladas de esta clase de a a algoritmos, en este apartado slo pretendemos dar una idea del principio en que o se basan, y ello mediante dos ejemplos ampliamente conocidos.

20.5.1

B squeda de una solucin aproximada u o

Deseamos hallar el rea de un c a rculo de radio 1 (naturalmente, sin usar la frmula). Un modo poco convencional de lograrlo consiste en efectuar n o lanzamientos al azar al cuadrado [1, 1][1, 1] y concluir con que la proporcin o de ellos que caiga dentro del c rculo ser proporcional a este rea. En otras a a palabras, el procedimiento consiste en generar n puntos (px , py ) aleatoriamente, extrayendo ambas coordenadas del intervalo [1, 1] uniformemente:

20.5. Anexo: algoritmos probabilistas


function NumAciertos(numPuntos: integer): integer; {Dev. el nmero de lanzamientos que caen dentro del crculo} u var i, total: integer; begin total:= 0; for i:= 1 to numPuntos do begin GenerarPunto(px , py ); {del intervalo [1, 1]2 , uniformemente} if (px , py ) c rculo de radio 1 then total:= total + 1 end; {for} NumAciertos:= total end; {NumAciertos}

469

Segn la ley de los grandes nmeros, si nmPuntos es muy grande la prou u u porcin de aciertos tender a la proporcin del rea del c o a o a rculo: numAciertos numEnsayos 4 De hecho, este algoritmo suele presentarse como un modo de estimar : 4 numAciertos numEnsayos

20.5.2

B squeda de una solucin probablemente correcta u o

Se desea averiguar si un entero n es o no primo. Supuesto n impar,9 el mtodo convencional consiste en tantear los divisores 3, 5, . . . , Trunc( n). Los e Trunc( n) 1 tanteos que requiere el peor caso, hacen este mtodo inviable cuando e 2 se trata de un n grande. Una alternativa es la siguiente: supongamos que se tiene un test10
function Test(n: integer): boolean;

que funciona aleatoriamente como sigue: aplicado a un nmero primo, resulta u ser siempre True; por el contrario, aplicado a un nmero compuesto lo descubre u 1 (resultando ser False) con probabilidad p (por ejemplo, 2 ). Por lo tanto, existe peligro de error slo cuando su resultado es True, y ese resultado es errneo con o o una probabilidad q = 1 p.
De lo contrario el problema ya est resuelto. a La naturaleza del test no importa en este momento. Bstenos con saber que, efectivamente, a existen pruebas ecientes de esta clase.
10 9

470

Cap tulo 20. Esquemas algor tmicos fundamentales Entonces, la aplicacin repetida de esa funcin o o
function RepTest(n, k: var i: integer; pr: boolean; begin pr:= False; i:= 0; repeat i:= i + 1; pr:= Test(n) until pr or (i = k); RepTest:= pr end; {RepTest} integer): boolean;

se comporta as aplicada a un primo, resulta ser siempre True, y aplicada a un : nmero compuesto lo descubre con una probabilidad 1 q k . La probabilidad de u error cuando el resultado es True, q k , puede disminuirse tanto como se quiera a costa de aumentar el nmero de aplicaciones de la funcin test. u o

20.6

Ejercicios

1. Desarrollar completamente el programa para descomponer un entero positivo en sus factores primos descrito en el apartado 20.1.1, escribiendo nalmente la solucin en la siguiente forma: o 600 300 150 75 25 5 1 2 2 2 3 5 5

2. Escribir el algoritmo de cambio de moneda descrito en apartado 20.1.2 ajustndose a al esquema devorador general. 3. Desarrolle completamente un procedimiento devorador para el problema de la mochila descrito en el apartado 20.1.3. 4. Desarrolle completamente un procedimiento devorador para el algoritmo de Prim del rbol de recubrimiento m a nimo. Tngase en cuenta la observacin hecha al e o nal del apartado 20.1.3 que nos permite simplicar la condicin de terminacin o o del bucle. 5. Se tienen dos listas A y B ordenadas. Escribir un algoritmo devorador que las mezcle produciendo una lista C ordenada.

20.6. Ejercicios

471

6. Supongamos que el vector V consta de dos trozos V1,...,k y Vk+1,...,n ordenados. Escribir un algoritmo devorador que los mezcle, produciendo un vector W1,...,n ordenado. 7. Se desea hallar el mximo valor de un vector V de n enteros. Siguiendo una a estrategia divide y vencers,11 una solucin consiste en lo siguiente: si el vector a o tiene una sola componente, sa es la solucin; de lo contrario, se procede como e o sigue: (a) se divide el vector V1,...,n en dos trozos: A = V1,...,k y B = Vk+1,...,n (b) se hallan los mximos para A y B respectivamente a (c) se combinan las soluciones parciales MA y MB obtenidas, resultando que la solucin nal es el mximo de entre MA y MB . o a Desarrolle completamente el algoritmo. 8. (a) Desarrolle otro algoritmo que halle el mximo valor de un vector, esta vez a siguiendo un esquema recursivo simple en el que se compare el primer elemento del vector con el valor mximo del resto del vector. a (b) Compare la complejidad de este algoritmo con el del ejercicio anterior. 9. Sea A una matriz cuadrada de n n. Si n es par, A puede descomponerse en cuatro submatrices cuadradas, as : A= y su determinante puede hallarse as : | A |=| A | | D | | B | | C | . Escriba un algoritmo divide y vencers para hallar el determinante de una matriz a de dimensin n, siendo n una potencia de 2. o 10. (a) Desarrolle un programa que halle Fib(15) segn la denicin recursiva usual u o de la funcin de Fibonacci, y modif o quelo para que cuente el nmero de u llamadas Fib(0), Fib(1), ... que se efectan. u (b) Implemente una variante de la funcin del apartado (a), en la que se le dote o de memoria. 11. Dena en Pascal la funcin ofuscacin de D o o jkstra, Ofusc(0) = Ofusc(1) = Ofusc(n) = 0 1 Ofusc( n ), 2 Ofusc(n + 1) + Ofusc(n 1), si n es par si n es impar , n 2 A C B D

dotndola de memoria, y averige en qu medida ha mejorado su eciencia, sia u e guiendo los pasos del ejercicio anterior.
Aunque el algoritmo descrito resuelve correctamente el problema planteado, debe advertirse que sta no es la manera ms apropiada para afrontar este problema (vase el apartado 20.2.1). e a e
11

472

Cap tulo 20. Esquemas algor tmicos fundamentales


m n

12. Redena la funcin que halla recursivamente los nmeros combinatorios o u m {0, . . . , 10}, n {0, . . . , m}, dotndola de memoria. a

, para

13. Desarrolle completamente el algoritmo que halla el camino m nimo en un grafo polietpico incorporando la tabulacin descrita en el apartado 20.3.2 a o 14. (a) Desarrolle completamente el problema del cambio de moneda descrito en el apartado 20.3.3 para el juego de monedas de 1, 8 y 9 pesetas. Dar dos versiones del mismo: una sin tabular y otra mediante tabulacin. o (b) Inserte los contadores necesarios para tantear el nmero de llamadas recuru sivas requeridas para descomponer 70 ptas. de manera ptima en las dos o versiones anteriores. 15. Desarrolle un algoritmo que d la lista de todas las descomposiciones posibles de e N (nmero natural que es un dato de entrada) en sumas de doses, treses y cincos. u 16. Supongamos que las soluciones parciales al problema de las ocho damas se concretan en dos variables: un indicador del nmero de damas situadas correctamente u (con un valor entre 0 y 8), y un array [1..8] of 1..8 cuyas primeras casillas registran las posiciones de las damas ya situadas en una subsolucin. o (a) Escriba una funcin que indique si dos damas estn a tiro. o a (b) Escriba una funcin que, conocida una subsolucin y una posicin (de 1 o o o a 8) candidata para situar la dama siguiente, indique si es ello posible sin amenazar las anteriores. (c) Desarrolle completamente el algoritmo descrito en el apartado 1, generalizado al problema de las n damas, con tres variantes: i. La generacin de todas las posiciones vlidas. o a ii. Averiguar cuntas soluciones vlidas hay. a a iii. Buscar una posicin vlida, si existe, o un mensaje de advertencia en o a caso contrario. (d) Aplique las tcnicas de poda expuestas en el apartado 20.4.1 para situar e ocho damas, libres de amenazas, en un tablero de ocho por ocho, ocupando casillas con una suma mxima, sabiendo que la casilla de la la i, columna a j, vale 108i + j puntos. 17. Utilizando las tcnicas explicadas en el apartado 20.5.1, hallar aproximadamente e el rea limitada por la funcin f (x) = log(x), el eje de abscisas y las rectas x = 1 a o 5 y x = 5. Comprese el resultado obtenido con el valor exacto de 1 log(x)dx. a

20.7. Referencias bibliograficas

473

2 1.5 1 0.5

18. Experimento de Buon Supongamos un entarimado de parquet formado por tablillas paralelas de 10 cm de ancho. (a) Hallar, mediante simulacin, la probabilidad de que, al lanzar una aguja de o 5 cm de largo, caiga sobre una l nea entre dos baldosas.
1 (b) Sabiendo que esa probabilidad es , estimar de ese modo.

20.7

Referencias bibliogrcas a

Se introduce sucintamente en este cap tulo un tema que requiere, para ser tratado cabalmente, un libro completo, por lo que los interesados en el tema necesitarn ama pliar esta introduccin. Afortunadamente, existe una enorme cantidad de referencias o de calidad dedicada al estudio del diseo de los esquemas algor n tmicos fundamentales, por lo que nos ceimos a las fuentes en castellano a las que ms debemos: los apuntes n a manuscritos de D. de Frutos [Fru84], los clsicos [AHU88], [HS90] y [BB97] y el ms a a reciente [GGSV93]. Los ejemplos escogidos aqu para explicar cada uno de los esquemas algor tmicos no son, ciertamente, originales: al contrario, su utilizacin es frecuent o sima con este n, y ello se debe a que requieren un nmero pequeo de ideas para explicar, casi por s u n mismos, los importantes conceptos que se han expuesto en este cap tulo . En [Bie93] se abunda en esta idea, llegando a proponer el uso de estos ejemplos convertidos en pequeos rompecabezas cuyo manejo de forma natural consiste en cada uno de los esquen mas algor tmicos fundamentales. Los esquemas devorador y de programacin dinmica se presentan frecuentemente o a usando como ejemplo el problema de la mochila 0-1 (o mochila entera), alternativo al del cambio de moneda. Ambos pueden considerarse formulaciones del mismo argumento con distinta ambientacin, admiten interesantes variaciones y han suscitado gran cantidad de o observaciones. Adems de las referencias citadas con carcter general, en [Wri75] pueden a a

474

Cap tulo 20. Esquemas algor tmicos fundamentales

leerse diferentes soluciones a los mismos; en [CK76] se estudian adems las condiciones a generales en que estos problemas admiten soluciones voraces. Desde que apareci el problema de las n reinas (hace ms de cien aos), se han o a n estudiado muchas de sus propiedades para tableros de distintos tamaos. En [BR75] n puede encontrarse una visin panormica del mismo y de otro problema clsico (la o a a teselacin con pentomins), ejemplicando con ambos el uso y mejora de las tcnicas de o o e vuelta atrs. a La referencia [Dew85a] es una bonita introduccin a los algoritmos probabilistas. o [BB90] ofrece una visin ms detallada y profunda. El test para descubrir nmeros o a u compuestos descrito en 20.5.2 se encuentra en casi cualquier referencia sobre algoritmos probabilistas. Entre la bibliograf seleccionada, [BB97] y [Wil89] ofrecen claras a explicaciones de los mismos.

Apndices e

Apndice A e

Aspectos complementarios de la programacin o


En este cap tulo se estudian algunos aspectos que, aunque no se pueden considerar esenciales para una introduccin a la programacin en Pascal, s resultan o o interesantes como complemento a los temas ya estudiados. En primer lugar se analiza la posibilidad de denir en Pascal subprogramas con parmetros que son, a su vez, subprogramas. Con esto se pueden conseguir a programas con un alto nivel de exibilidad. Finalmente, se estudia la utilizacin o de variables (seudo)aleatorias, que resultan especialmente utiles para desarrollar programas en los que interviene el azar.

A.1

Subprogramas como parmetros a

La utilizacin de procedimientos y funciones como parmetros eleva a un nivel o a superior la potencia y versatilidad, ya conocida, de los propios subprogramas. Supongamos, por ejemplo, que disponemos de dos funciones f, g : IR IR, y queremos generar a partir de ellas una tercera que convierta cada x IR en el mximo entre f (x) y g(x), como se muestra grcamente en la gura A.1. a a La dicultad del problema radica en que la funcin que deseamos denir o no halla el mximo de dos nmeros reales dados sino que, dadas dos funciones a u cualesquiera f y g y un nmero real x, halla f (x) y g(x) (aplicando las funciones u al real) y obtiene el mximo de esos valores: a
f (x)

si f (x) < g(x) en otro caso

MaxFuncs(f, g, x) =

g(x)

478

Apendice A. Aspectos complementarios


8 y = g(x) 6 4 1 2 y = f(x) 3 -2 -1 2 1 2 3 y = max[f,g](x)

8 6 4 2 -2 -1 -2 -4

Figura A.1.

O sea, la funcin MaxFuncs responde al siguiente esquema: o MaxFuncs : ( IR IR) ( IR IR) MaxFuncs ( f , g , IR IR x ) = ...

La novedad consiste en que, si se quiere implementar un programa que resuelva el problema, dos de los parmetros ser subprogramas (concretamente, funcioa an nes). Esto est permitido en Pascal y para ello se incluye en la lista de parmetros a a formales el encabezamiento completo del subprograma parmetro, a
function MaxFuncs (function F (x: real): real; function G (x: real): real; x: real): real; {Dev. el mximo de F(x) y G(x)} a begin if F(x) > G(x) then MaxFuncs:= F(x) else MaxFuncs:= G(x) end; {MaxFuncs}

y en la llamada, como parmetro real, se coloca el identicador del subprograma a que acta como argumento: u
y:= MaxFuncs (Sin, Cos, Pi/4)

En la ejecucin de la llamada, al igual que ocurre con cualquier tipo de o parmetros, el subprograma cticio se sustituye por el subprograma argumento, a concretndose as su accin o resultado (dependiendo de si es un procedimiento a o o una funcin, respectivamente). o Cuando el parmetro de un subprograma es otro subprograma, la consistencia a entre la llamada y la denicin requiere que los subprogramas (parmetros) o a

A.1. Subprogramas como parametros

479

formales (F y G) y los reales (Sin y Cos) tengan el mismo encabezamiento, esto es, igual nmero de parmetros y del mismo tipo. Adems, en el caso de tratarse u a a de funciones, el tipo del resultado de la funcin debe ser tambin el mismo. o e La ganancia en exibilidad es clara: no se ha denido la funcin mximo, o a punto a punto, de dos funciones jas, sino de dos funciones cualesquiera (predenidas o denidas por el usuario). As por ejemplo, si el usuario tiene denida , una funcin Cubo, la siguiente es otra llamada vlida: o a
WriteLn(MaxFuncs(Abs, Cubo, 2 * x + y))

En Pascal, los parmetros-subprograma slo pueden ser datos de entrada (no a o de salida). Adems, el paso de parmetros-subprograma no puede anidarse. a a Al ser esta tcnica un aspecto complementario de este libro, simplemente se e aaden algunos ejemplos prcticos para mostrar su aplicacin. Los ejemplos son n a o el clculo de la derivada de una funcin en un punto, la bsqueda dicotmica a o u o de una ra de una funcin dentro de un cierto intervalo y la transformacin de z o o listas.

A.1.1

Ejemplo 1: derivada

La derivada de una funcin1 en un punto se puede hallar aproximadamente o mediante la expresin o f (x + x) f (x) x tomando un x sucientemente pequeo. El clculo de la derivada se ilustra n a grcamente en la gura A.2. a Como este clculo depende de la funcin f , el subprograma en Pascal deber a o a incluir un parmetro al efecto: a
function Derivada(function F(y: real): real; x: real): real; {PreC.: F debe ser continua y derivable en x} {Dev. el valor aproximado de la derivada de de F en x} const DeltaX = 10E-6; begin Derivada:= (F(x+DeltaX) - F(x))/DeltaX end; {Derivada}

Si efectuamos una llamada a esta funcin pasndole la funcin Sin, y la o a o abscisa Pi/3, obtenemos un valor aproximado, como era de esperar: Derivada(Sin, Pi/3) ; 4.9999571274E-01
1

La funcin deber ser continua y derivable en el punto considerado. o a

480

Apendice A. Aspectos complementarios

La pendiente de la recta secante es la aproximacin dada.

f(x +

x)
La pendiente de la recta tangente es la derivada de f en x.

f(x) a x

b x + x

Figura A.2.

c3

c4

c2

c1

Figura A.3.

A.1.2

Ejemplo 2: biparticin o

Este mtodo para calcular los ceros o ra e ces de una funcin se expuso en el o apartado 6.5.1 para una funcin particular, y la generalizacin natural es denirlo o o en Pascal como un subprograma que opere con una funcin cualquiera, adems o a de efectuar la bsqueda en un intervalo cualquiera. u Las condiciones exigidas son que la funcin debe ser continua en ese intervalo o y tener distinto signo en sus extremos: digamos para simplicar que deber ser a negativa en el extremo izquierdo y positiva en el derecho, como se muestra en la gura A.3. Este clculo puede incluirse dentro de una funcin CeroBipar que reciba a o como parmetros la funcin de la que se calcula la ra y los extremos del intervalo a o z

A.1. Subprogramas como parametros en el que se busca sta. La funcin devuelve la abscisa de la ra e o z.

481

Siguiendo las ideas expuestas en el apartado 6.5.1, una primera aproximacin o al diseo puede ser n
izda:= extrIz; dcha:= extrDc; Reducir el intervalo Dar el resultado

Recordemos que la tarea reducir el intervalo se puede renar mediante un bucle while, utilizando una variable adicional puntoMedio para almacenar el valor del punto central del intervalo. F ser el identicador del parmetro que a a recoger la funcin de la que se busca la ra El subprograma nal es el siguiente: a o z.
function CeroBipar(function F(x: real): real; extrIz, extrDc: real): real; {PreC.: F continua en [extrIz, extrDc], F(extrIz) < 0 < F(extrDc) } {PostC.: F(CeroBipar(F, extrIz, extrDc)) 0 , extrIz < CeroBipar(F, extrIz, extrDc) < extrDc } const Epsilon= error permitido; var puntoMedio: real; begin {Reduccin del intervalo} o while (extrDc - extrIz) > 2 * Epsilon do begin {Inv.: F(extrIz) < 0 y F(extrDc) > 0 } puntoMedio:= (extrDc - extrIz) / 2; if F(puntoMedio) < 0 then {El cero se encuentra en [puntoMedio,extrDc]} extrIz:= puntoMedio else {El cero se encuentra en [extrIz,puntoMedio]} extrDc:= puntoMedio end; {while} CeroBipar:= puntoMedio end. {CeroBipar}

Por ejemplo, si suponemos implementado en Pascal el polinomio p(x) = x2 2 mediante la funcin P, se obtendr un cero del mismo en [0,2] efectuando la o a llamada CeroBipar(P, 0, 2)

482

Apendice A. Aspectos complementarios

A.1.3

Ejemplo 3: transformacin de listas o

Una transformacin frecuente de las listas consiste en aplicar cierta funcin o o (genrica) a todos sus elementos. Una implementacin en Pascal de esta transe o formacin es casi directa desarrollando un procedimiento AplicarALista que o tenga como argumentos la funcin a aplicar y la lista a transformar: o
procedure AplicarALista(function F(x: real): real; var lista: tListaR); {Efecto: aplica F a cada uno de los elementos de lista} var aux: tListaR; begin aux:= lista; while aux <> nil do begin aux^.valor:= F(aux^.valor); aux:= aux^.siguiente end {while} end; {AplicarALista}

Donde los elementos de la lista deben tener un tipo conocido y jo (en el ejemplo, real),
type tListaR = ^tNodoR; tNodoR = record valor: real; sig: tListaR end; {tNodoR}

y ste debe ser el mismo antes y despus de su transformacin, por lo que la e e o funcin convierte elementos de ese tipo en elementos del mismo. En este ejemplo o la lista est formada por valores reales, por lo que tambin lo son el argumento a e y el resultado de la funcin. o As por ejemplo, la llamada AplicarALista(Sqr, listaDeReales) tendr , a por efecto elevar al cuadrado todos los elementos de la lista listaDeReales.

A.2

Variables aleatorias

A menudo es necesario construir programas en los que interviene el azar: este comportamiento indeterminista es inevitable con frecuencia (por ejemplo, en procesos de muestreo), y otras veces es deseable (en programas relacionados con simulacin, juegos, criptograf etc.) o a,

A.2. Variables aleatorias

483

En este apartado se introduce el uso del azar para resolver problemas algor tmicamente.2 Empezaremos por resumir los mecanismos que ofrece Turbo Pascal para introducir el azar en nuestros programas, as como la forma de denir otros, sencillos y utiles; despus se ver un mtodo para simular variables alea e a e torias cualesquiera, para terminar dando algunos ejemplos de aplicacin. o

A.2.1

Generacin de n meros aleatorios en Turbo Pascal o u

La construccin de un buen generador de nmeros seudoaleatorios no es una o u tarea fcil; por eso y por sus importantes aplicaciones, casi todos los lenguajes a de programacin incluyen algn generador predenido. o u Para obtener los valores aleatorios en Turbo Pascal, se emplea la funcin o Random, que genera un valor seudoaleatorio y que se puede utilizar de dos modos distintos: Con un argumento entero y positivo3 n, en cuyo caso el resultado es un nmero extra uniformemente del conjunto {0, . . . , n 1}: u do Random(6) ; 2 Random(6) ; 5 Random(6) ; 0 Sin argumento, en cuyo caso el resultado es un real, extra uniformedo mente del intervalo [0, . . . , 1): Random ; 0.78593640172 Random ; 0.04816725033 Cuando estas funciones se usan en un programa, se debe usar el procedimiento Randomize (sin argumentos), para arrancar la generacin de nmeros o u aleatorios, haciendo intervenir informacin cambiante, no controlada, como deo terminadas variables del sistema (el reloj), de modo que las distintas ejecuciones de ese programa funcionen de diferente modo. Como ejemplo de uso, damos el siguiente programa, que simula el lanzamiento de un dado y de una moneda:
Hay obviamente una dicultad intr nseca para reproducir el azar (de naturaleza indeterminista) mediante algoritmos (que son procesos deterministas). De hecho, en la prctica slo se a o consigue simular un comportamiento aparentemente aleatorio seudoaleatorio aunque esto es suciente para un gran nmero de aplicaciones. u 3 En realidad, en Turbo Pascal debe ser de tipo word (vase el apartado B.3). e
2

484

Apendice A. Aspectos complementarios


Program DadoYMoneda (output); const N = 10; {nm. de lanzamientos} u var i, lanzamientos, caras, cruces: integer; begin Randomize; for i:= 1 to N do {N lanzamientos de dado} WriteLn(Random(6)+1); for i:= 1 to N do {N lanzamientos de moneda} if Random < 0.5 then WriteLn(Cara) else WriteLn(Cruz) end. {DadoYMoneda}

Como puede imaginarse, la funcin Random se puede combinar con otras opeo raciones y funciones para lograr efectos ms variados. Por ejemplo la expresin a o Random (b a) + a produce el efecto de una variable aleatoria uniforme del intervalo [a, b). De modo similar, la expresin o Random(b a + 1) + a produce el efecto de una variable aleatoria uniforme del conjunto {a, . . . , b}.

A.2.2

Simulacin de variables aleatorias o

Aunque las variables aleatorias uniformes bastan en un gran nmero de situau ciones, muchas veces se requiere generar otras variables siguiendo otras funciones de distribucin. En este apartado se introduce el mtodo de la transformada o e 4 que permite generar variables aleatorias arbitrarias. En los ejercicios inversa, se describen sin justicar algunos mtodos particulares. e En primer lugar se presenta este mtodo para variables aleatorias continuas. e Si F : IR [0, 1] es una funcin de distribucin cualquiera, el mtodo consiste o o e en generar la variable aleatoria y U nif [0, 1), y hallar el x tal que F (x) = y (vase la gura A.4). En otras palabras, si y U nif [0, 1), entonces F 1 (y) F . e Por ejemplo, supongamos que deseamos generar una variable aleatoria uniforme en el intervalo [a, b). Como su funcin de distribucin es, o o F (x) =
4

0 1

xa ba

si x a si a x b si b < x

Tambin conocido como look up-table. e

A.2. Variables aleatorias

485

1 0.8 0.6 0.4 0.2

-2

-1

Figura A.4.
1 ...

Figura A.5.

para y [0, 1) se tiene F 1 (y) = a + y (b a). Por lo tanto, si se genera y U nif [0, 1) (mediante y:= Random simplemente, por ejemplo), basta con hacer x:= a+y*(b-a) para obtener la variable aleatoria x U nif [a, b), como se ve en la gura A.5. Naturalmente, no siempre es tan fcil invertir F , e incluso a veces es imposible a hacerlo por medios anal ticos. Sin embargo, la cantidad x = F 1 (y), conocida y, siempre puede hallarse como el cero de la funcin F (x) y (vase el aparo e tado 6.5.3), de la que sabemos que es decreciente, por lo que es idneo el mtodo o e de biparticin (vase el apartado 6.5.1). o e El mtodo expuesto es sencillo y se adapta bien a variables discretas. Sea e la funcin de probabilidad P rob(x = i) = pi , para 1 i n; su funcin de o o k pi para 1 k n, y expresa la probabilidad distribucin es P (k) = o i=1 de que x i. Entonces, el mtodo consiste en generar la variable aleatoria e y U nif [0, 1), y hallar el m nimo k tal que P (k) y (vase la gura A.6). e

486

Apendice A. Aspectos complementarios

1
)

...

. ..
) )

y 0
)

) )

...

Figura A.6.

Por ejemplo, supongamos que se desea trucar un dado de forma que d las e cantidades 1, . . . , 6 con probabilidades 015, 01, 015, 015, 03, 015 respectivamente. Para ello, se hallan las cantidades P (k), que resultan ser 015, 025, 040, 055, 085, 100, respectivamente; luego se genera y U nif [0, 1), mediante y:= Random, y nalmente, basta con hallar el m n{k tal que P (k) y}. Si las cantidades P (k) se almacenaron en un array, esta bsqueda se puede realizar por u 5 que tiene el siguiente contenido: inspeccin de la tabla, o P rob(x = i) 015 01 015 015 03 015 P (k) 015 025 040 055 085 100 i 1 2 3 4 5 6

Si y:= Random genera el valor 0 75, por ejemplo, hay que buscar el menor P (k) 0 75, y a partir de l localizar en la tabla de bsqueda la cara del dado e u que le corresponde (el cinco, en este caso). La inspeccin se puede hacer secuencialmente o mediante bsqueda dicotmio u o ca (vase el apartado 15.1). El desarrollo se deja como ejercicio (vase el ejercie e cio 15).

A.2.3

Ejemplos de aplicacin o

Las aplicaciones de los algoritmos no deterministas son mltiples y variadas. u Se muestran en este apartado dos de ellas como botn de muestra, remitiendo o
5

As se explica su denominacin look up-table en ingls. o e

A.2. Variables aleatorias

487

al lector interesado a los ejercicios, donde se indican otras, y a los comentarios bibliogrcos. a Como ejemplo inicial, considrese que un supermercado desea hacer un sorteo e entre sus clientes, de forma que sus probabilidades de ser premiados sean proporcionales al dinero gastado en la tienda. Trivialmente se ve que esta situacin o es un caso particular del dado trucado, explicado al nal del apartado anterior, por lo que no requiere mayor explicacin (vase el ejercicio 15). o e Un ejemplo t pico es el acto de barajar, que consiste sencillamente en
Dado un array A de n elementos, Situar en A1 uno cualquiera, escogido aleatoriamente entre A1 y An . ... Situar en An1 uno cualquiera, escogido aleatoriamente entre An1 y An .

donde la eleccin entre Aizda y Adcha se efecta mediante la asignacin o u o posic:= variable aleatoria uniforme del conjunto {izda, . . . , dcha} referida en el apartado A.2.1 y en el ejercicio 12c de este cap tulo. Finalmente, esbozamos el mtodo del acontecimiento cr e tico, para simular una cola (una ventanilla de un banco, por ejemplo), donde llega un cliente cada 1 unidades de tiempo y tarda en ser atendido 2 unidades de tiempo, por trmino e 6 medio. Un paso intermedio del diseo de este programa puede ser el siguiente: n
{Dar valores iniciales a los datos} reloj:= 0; longCola:= 0; sigLlegada:= 0; {tiempo hasta la siguiente llegada} sigSalida:= 0; {tiempo hasta la siguiente salida} repetir sin parar if cola vac o sigLlegada < sigSalida then begin a {llegada de cliente} reloj:= reloj + sigLlegada; sigSalida:= sigSalida - sigLlegada; sigLlegada:= ExpNeg(1 ); longCola:= longCola + 1; EscribirSituacion(reloj, longCola) end {then} else begin {salida de cliente}
El tiempo entre llegadas (resp. salidas) es una variable aleatoria exponencial negativa de parmetro 1 que se supone desarrollada, ExpNeg(lambda) (vase el ejercicio 14). a e
6

488

Apendice A. Aspectos complementarios


reloj:= reloj + sigSalida; sigLlegada:= sigLlegada - sigSalida; sigSalida:= ExpNeg(2 ); longCola:= longCola - 1; EscribirSituacion(reloj, longCola) end {else} Fin repetir

A.3

Ejercicios

2. Halle un cero del polinomio p(x) = x2 2 en [0, 2], siguiendo el proceso indicado en el apartado A.1.2.

1. Desarrolle un programa completo que utilice la funcin MaxFun que aparece en el o texto, y trace la grca de la funcin MaxFun(Sin, Cos, x) en el fragmento del a o plano XY = [0, 3] [0 5, 1 5].

3. Aplique la funcin Derivada en un programa al polinomio del ejercicio anterior. o Desarrolle un programa para comprobar la diferencia entre el resultado obtenido por el programa y el calculado de forma anal tica, p (x) = 2x, confeccionando una tabla con los valores de ambos, para diferentes valores de la variable x (por ejemplo, x {0 0, 0 1, . . . , 1 0}) y de la constante DeltaX (por ejemplo, {1, 0 5, 0 25, 0 125, 0 0625}). 4. Desarrolle un subprograma para hallar el cero de una funcin dada siguiendo el o mtodo de Newton-Raphson explicado en el apartado 6.5.2. e
sup 5. Desarrolle un subprograma que halle o i=inf ai , para una sucesin cualquiera a : IN IR. 1 Apl quese al clculo de n i! , siendo n un dato dado por el usuario. a i=1

6. Desarrolle un subprograma que halle

b a

f (x)dx, siguiendo los mtodos siguientes: e

(a) iterativamente, mediante la correspondiente descomposicin en rectngulos o a (vase el ejercicio 11 del cap e tulo 6). (b) recursivamente (vase el ejercicio 7 del cap e tulo 10). 7. Desarrolle una nueva versin del subprograma AplicarALista del apartado A.1.3, o siguiendo una estrategia recursiva. 8. Construya un procedimiento AplicarAArbol que aplique una funcin a los eleo mentos de un rbol, transformndolos, de la misma forma que AplicarALista lo a a hace con las listas. 9. Desarrolle un subprograma que ltre los datos de una lista segn una funcin u o lgica, eliminando los que no cumplan ese test. Si, por ejemplo, se trata de una o lista de enteros y el test es la funcin Odd, el efecto del ltro consiste en suprimir o los pares. 10. Desarrolle una versin recursiva del subprograma del ejercicio anterior. o

A.3. Ejercicios

489

1 0.8 0.6 0.4 0.2 5 -0.2 10 15 20

Figura A.7. 11. Construya un procedimiento general de ordenacin en el que el criterio por el que o se ordenan los elementos sea un subprograma que se suministra como parmetro. a El encabezamiento del procedimiento es type tVector = array [1..n] of tElemento; procedure Ordenar(var v:tVector; function EsAnterior(x, y:tElemento):boolean); Con este procedimiento se consigue una gran exibilidad, al poder ordenar el vector en la forma que ms interese al usuario, con llamadas a Ordenar en las que a EsAnterior se particularice con las relaciones de orden <, OrdenAlfabtico, e MejorCalicacin, etc. o 12. Dena funciones aleatorias para simular lo siguiente: (a) Una variable aleatoria del intervalo [a, b). (b) Una variable aleatoria del intervalo (a, b]. (d) Un dado que da un seis con probabilidad 1/2, y un nmero del uno al cinco u con probabilidad uniforme. (e) Una moneda trucada, que cae de cara dos de cada tres veces. 13. Genere nmeros naturales de forma que aparezca un 1 con probabilidad 1/2, un u 2 con probabilidad 1/4, un 3 con probabilidad 1/8, etc. 14. Empleando el mtodo de la transformada inversa, genere una variable aleatoria e exponencial negativa de parmetro , cuya funcin de densidad f viene dada por a o f (x) = ex , y que se representa grcamente en la gura A.7). a Se recuerda que, previamente, debe hallarse la funcin F de distribucin correso o pondiente. (c) Una variable aleatoria del conjunto {a, . . . , b}.

490

Apendice A. Aspectos complementarios

15. Mediante el mtodo de la transformada inversa, desarrolle un dado trucado que e d las cantidades 1 a 6 con probabilidades arbitrarias dadas como dato. e 16. Dena una funcin que genere una variable aleatoria a partir de una funcin de o o distribucin arbitraria F denida sobre el intervalo [a, b]. o

A.4

Referencias bibliogrcas a

Este apartado se ha centrado en el uso del azar, suponiendo que las herramientas necesarias estn incorporadas en nuestro lenguaje de programacin, lo que es el caso a o habitual. Sin embargo, el desarrollo de buenos generadores de nmeros aleatorios no u es una tarea fcil, e incluso es frecuente encontrar en el mercado y en libros de texto a generadores con defectos importantes. En [Yak77, PM88, Dan89, War92], entre otras referencias, se estudian los principios de su diseo, explicando el generador ms difunn a dido actualmente (el de Lehmer) y examinando algunos de los errores de diseo ms n a frecuentes; en [War92, Sed88] se describen dos pruebas de calidad (el test espectral y el de la chi-cuadrado). La simulacin de variables aleatorias no uniformes se estudia en multitud de texo tos. El mtodo de la transformada inversa se puede estudiar en [Yak77, Mor84, PV87], e entre otros textos, junto con otros mtodos generales (el del rechazo y el de la come posicin). Algunos mtodos particulares para la simulacin de la normal se pueden o e o encontrar en las dos ultimas referencias. En [Dew85a] se vincula la simulacin de va o riables aleatorias con algunas aplicaciones de los algoritmos no deterministas a travs e de amenos ejemplos y situaciones. De esta ultima referencia se ha tomado el mtodo e del acontecimiento cr tico, y otras aplicaciones prcticas de la simulacin se pueden a o encontrar en [PV87]. Finalmente, debemos indicar que en el apartado 20.5 se introducen otras aplicaciones de los algoritmos no deterministas. En este apndice hemos tocado muy supercialmente la posibilidad de que los e parmetros de los subprogramas sean subprogramas a su vez. Esta posibilidad es ampliaa mente explotada en otros modelos de programacin como el funcional, por lo que el leco tor interesado puede consultar cualquer referencia sobre el mismo, por ejemplo [BW89]. Los cierto es que, en programacin imperativa, este mecanismo se usa en muy contadas o ocasiones.

Apndice B e

El lenguaje Turbo Pascal


El lenguaje Turbo Pascal posee numerosas extensiones con respecto al lenguaje Pascal estndar, que, por una parte, le coneren una mayor potencia y a capacidad, pero por otra merman la posibilidad de transportar sus programas a otros computadores. Es interesante conocer estas extensiones por las siguientes razones: Porque ampl la capacidad para manejar otros tipos numricos del lenan e guaje Pascal, superando las limitaciones de los tipos estndar y facilitando a el intercambio de este tipo de valores con programas escritos en otros lenguajes. Porque existen en muchos otros lenguajes de programacin, y por ello han o pasado a ser algo admitido y utilizado, siendo un estndar de facto. Por a ejemplo, el tipo cadena (string) con sus operaciones asociadas. Porque son imprescindibles para la utilizacin de ciertos tipos de datos del o Pascal estndar, como ocurre con los archivos, en los que la conexin con a o su implementacin f o sica no est denida en Pascal. a Porque permiten reforzar ciertas caracter sticas deseables en todo lenguaje evolucionado. La modularidad de Pascal se refuerza mediante las unidades que nos permiten denir, por ejemplo, los tipos abstractos de datos (vase e el cap tulo 19), aunque con limitaciones con respecto a otros lenguajes como Modula2. En los prximos apartados se explican brevemente las particularidades ms o a interesantes de Turbo Pascal, siguiendo el orden en el que se han presentado en el texto los aspectos del lenguaje con los que se relacionan.

492

Apendice B. El lenguaje Turbo Pascal

B.1

Elementos lxicos e

En primer lugar estudiaremos las diferencias ms signicativas en la forma a de escribir los identicadores y ciertos s mbolos especiales. La longitud de los identicadores en Turbo Pascal slo es signicativa en o sus 64 primeros caracteres, mientras que en Pascal son signicativos todos los caracteres.1 En Turbo Pascal el signo @ es un operador diferente de ^, mientras que en Pascal se pueden usar indistintamente. El signo @ en Turbo Pascal permite que un puntero seale a una variable existente no creada como referente del puntero. n En Turbo Pascal un comentario debe comenzar y terminar con el mismo par de s mbolos { y } o (* y *). En Pascal, un s mbolo de un tipo puede cerrarse con el del otro.

B.2

Estructura del programa

El encabezamiento del programa en Turbo Pascal es opcional, por lo que puede omitirse en su totalidad. Sin embargo, si se escribe la palabra program deber ir acompaada del identicador de programa. Los nombres de archivo a n como input y output tambin son opcionales, pero si se escriben, deber hacerse e a correctamente. Las diferentes secciones de declaraciones y deniciones se abren con las palabras reservadas correspondientes como en Pascal estndar. No obstante, en a Turbo Pascal se puede alterar el orden de las diferentes secciones y abrirlas repetidas veces.

B.3

Datos numricos enteros e

Turbo Pascal introduce dos tipos numricos naturales predenidos llamados e byte y word. Sus dominios incluyen solamente valores enteros positivos, siendo para el tipo byte {0, ..., 255} y para el tipo word {0, ..., 65535}.

Un valor perteneciente al tipo byte ocupa precisamente un byte en memoria (de aqu su nombre), mientras que uno del tipo word ocupa dos bytes.

El tipo entero integer se complementa con dos nuevos tipos predenidos denominados shortInt y longInt. Sus dominios son enteros positivos y negaAunque en el User Manual & Report (segunda edicin, pg. 9) [JW85] se dice textualmente: o Implementations of Standard Pascal will always recognize the rst 8 characters of an identier as signicant.
1

B.4. Datos numericos reales

493

tivos, siendo para el tipo shortInt {128, ..., 127}, mientras que el de longInt es {214483648, ..., 2147483647}. El tipo shortInt ocupa un byte, el tipo integer ocupa 2 y el longInt 4 bytes. Estos tipos son especialmente utiles para realizar clculos enteros con valores a grandes, como el clculo del factorial, para los que el tipo integer resulta muy a limitado. A estos tipos numricos se les pueden aplicar los mismos operadores y opee raciones que al tipo entero, e incluso se pueden asignar entre s siempre que los valores asignados estn comprendidos dentro de los respectivos dominios. e Existe un tercer tipo llamado comp que es un h brido entre entero y real: se almacena en la memoria como un entero (en complemento a dos), pero se escribe como un real (en notacin cient o ca). A pesar de su implementacin como entero o no es un tipo ordinal. Dado que no se le pueden aplicar las operaciones de tipo entero, lo consideraremos y utilizaremos como real. Se utiliza en aquellas aplicaciones que necesiten valores grandes, con precisin entera, pero donde o no haya que aplicar operaciones enteras. En Turbo Pascal la expresin n mod m se calcula como n - (n div m) * o m y no produce error si m es negativo, mientras que en Pascal estndar no est a a denido si m es cero o negativo.

B.4

Datos numricos reales e

Turbo Pascal dispone de tres tipos de datos reales (codicados en punto otante) que complementan al tipo estndar real, denominados single, double a y extended, adems del ya comentado comp. Sus diferentes caracter a sticas se muestran en la siguiente tabla:
Tipo single double extended comp real Dominio {1.5E 45, ..., 3.4E38} {5.05E 324, ..., 1.7E308} {1.9E 4951, ..., 1.1E4932} {263 , ..., 263 1} {2.9E 39, ..., 1.7E38} Cifras signicativas 7u8 15 16 o 19 20 o 19 20 o 11 12 o Ocupacin o de memoria 4 8 10 8 6

Los tipos single y double cumplen el estndar IEEE 754, que es el ms a a utilizado en representacin en punto otante, lo que los hace idneos para el o o intercambio de datos reales con programas escritos en otros lenguajes. Es curioso que el tipo estndar real de Turbo Pascal no sea estndar en su codicacin a a o interna.

494

Apendice B. El lenguaje Turbo Pascal

Los tipos reales adicionales, incluyendo el tipo comp, admiten todos los operadores y operaciones del tipo real, e incluso son asignables entre s dentro de , sus respectivos dominios.2

B.5

Cadenas de caracteres

Es muy frecuente construir programas que precisen cadenas de caracteres para formar nombres, frases, l neas de texto, etc. En Pascal estndar, este tipo de datos hay que denirlo como un array de a caracteres con una longitud ja. Si la secuencia de caracteres tiene una longitud menor que la longitud del array, la parte nal de ste queda indenido. Con el e n de evitar posibles errores, es conveniente almacenar la longitud utilizada del array para no acceder a la parte sin denir. En Turbo Pascal existe un tipo de datos espec co predenido llamado string, que podemos traducir como cadena de caracteres. Este tipo es similar a un array de caracteres, pero su longitud es gestionada automticamente por el compilaa dor, hasta un cierto l mite. Adems Turbo Pascal dispone de las funciones y a procedimientos necesarios para procesar las cadenas.

B.5.1

Declaracin de cadenas o

En la declaracin de una variable de cadena se dene la longitud mxima de o a la cadena, lo que se conoce como su longitud f sica.
var cadena:

string[20];

Con esta declaracin la variable cadena podr tener a lo sumo 20 caracteres, o a es decir, este tamao es un l n mite mximo, ya que la cadena puede tener menos. a Para saber cuntos tiene en realidad, junto con la cadena se guarda un a ndice que contiene la longitud real de la cadena, lo que se denomina su longitud lgica. o Si al leer la variable cadena con la instruccin: o
ReadLn(cadena)

Para su utilizacin debe estar activada la opcin o o [Options][Compiler][Numericprocessing][X]8087/80287

(vase el apartado C.3.3). e

B.5. Cadenas de caracteres o asignar un valor con:


cadena:= Lupirino

495

escribimos menos de 20 caracteres, el ndice almacenar el nmero real de caa u racteres escritos. Si escribimos 20 o ms, el a ndice valdr 20, pero en el ultimo a caso se perder los caracteres posteriores al vigsimo, truncndose la cadena. an e a Tambin es posible declarar su longitud mxima, en cuyo caso la cadena e a toma una longitud f sica de 255 caracteres. Por ejemplo:
var nombre: string;

Se puede acceder a los caracteres de una cadena por sus ndices, como en un array, siendo el elemento de ndice 0 el que almacena la longitud lgica de la o cadena. Por ejemplo, mediante las siguientes instrucciones:
longitud:= Ord(cadena[0]) inicial:= cadena[1]

se asigna a la variable longitud la longitud de la cadena y el primer carcter de a sta a la variable inicial . e

B.5.2

Operadores de cadenas

Dos cadenas se pueden comparar entre s con los operadores usuales de re lacin, considerndose menor (anterior) aquella cadena que precede a la otra o a por orden alfabtico.3 Es decir, la comparacin se realiza carcter a carcter, e o a a hasta que una cadena diera de la otra en uno, siendo menor aqulla que tiene e menor ordinal en el carcter diferenciador. Puede suceder que una de las dos caa denas termine sin haber encontrado un carcter distinto, en cuyo caso la cadena a ms corta se considera la menor. a Adems de los operadores de relacin, se puede aplicar el operador +, llamado a o de concatenacin, que une dos cadenas para formar otra. Por ejemplo, haciendo: o
cadena1:= Pepe; cadena2:= Luis; cadena:= cadena1 + cadena2

la variable cadena tendr el valor PepeLuis. a


3 Lamentablemente, el orden entre cadenas se basa en el cdigo ASCII, por lo que no funciona o del todo de acuerdo con el orden alfabtico espaol (acentos, ees). Adems, las maysculas e n n a u preceden a las minsculas, por lo que no pueden compararse entre s de acuerdo con el orden u alfabtico. e

496

Apendice B. El lenguaje Turbo Pascal

B.5.3

Funciones de cadenas

Turbo Pascal proporciona un conjunto de funciones predenidas para procesar las cadenas. Estas funciones se presentan a continuacin:4 o Concat: S S . . . S S.

Concatena las cadenas argumento para producir una cadena resultado. Produce el mismo resultado que el operador +.

Length: S Z.

Halla la longitud lgica (entero) de la cadena argumento. La funcin o o Length equivale al ordinal del carcter 0 de la cadena argumento. a

Pos (S1 , S2 ) Z.

Indica la posicin (un entero) de la primera cadena dentro de la segunda, o o el valor 0 si no se encuentra. Esta funcin es especialmente util para o buscar un texto dentro de otro. string; z1, z2: integer): string;

Copy(s:

Extrae de s una subcadena formada por z2 caracteres a partir del z1-simo e (incluido)

Veamos algunos ejemplos, con sus salidas: var cadena1, cadena2, cadena3: string[40]; ... cadena1:= Alg.; cadena2:= y estr. de datos; cadena3:= Concat(cadena1, cadena2); WriteLn(cadena3); Alg. y estr. de datos 21 WriteLn(Length(cadena3)); WriteLn(Pos(cadena2, cadena3)); 5 estr WriteLn(Copy(cadena3, 8 ,4))

B.5.4

Procedimientos de cadenas

Como complemento de las funciones predenidas, Turbo Pascal tambin dise pone de un conjunto de procedimientos de gran utilidad para el manejo de cadenas. Estos procedimientos son los siguientes:
Dada la diversidad de tipos de algunos procedimientos y funciones de cadenas, se ha optado por dar sus encabezamientos en lugar de su denicin funcional. o
4

B.5. Cadenas de caracteres Delete(var s: string; z1, z2: integer)

497

Borra z2 caracteres de la cadena s a partir del z1-simo (incluido). Al utie lizarlo, la cadena reduce su longitud en el nmero de caracteres eliminados. u string; var s2: string; z: integer)

Insert(s1:

Inserta en s2 la cadena s1 a partir de la posicin z. El procedimiento o Insert, por el contrario, aumenta la longitud de la cadena en el nmero u de caracteres insertados. real ; var s: string)

Str(r:

Convierte el valor real r (tambin puede ser un entero z) en la cadena e s. Str convierte un valor numrico en su representacin como cadena de e o caracteres, lo que permite, por ejemplo, medir la longitud en caracteres de un nmero. Se utiliza tambin en aplicaciones grcas, donde es obligatorio u e a escribir cadenas. string; var r: real; var z: integer) Convierte la cadena s en el valor real r (tambin puede ser un entero) y e devuelve un cdigo entero z, que es 0 si se puede hacer la conversin, y en o o caso contrario seala la posicin del error. Este procedimiento es quizs el n o a ms interesante de todos, al efectuar la conversin de una cadena formada a o por d gitos y aquellos s mbolos permitidos para formar un nmero (tales u como la letra E mayscula o minscula y los s u u mbolos punto, ms y menos) a en su valor numrico. Si por error la cadena no tiene forma correcta de e nmero, se seala la posicin del carcter causante del fallo, lo que permite u n o a corregirlo. De esta forma se puede robustecer el proceso de introduccin o de nmeros, que es una de las causas principales de errores de ejecucin. u o

Val(s:

Veamos algunos ejemplos, tomando como punto de partida las asignaciones del ejemplo anterior. Delete(cadena3, 11, 14); WriteLn(cadena3); Insert( simples, cadena3, 20); WriteLn(cadena3); valNum1:= 123; Str(valNum1, cadena1); WriteLn(cadena1); Val(12A, valNum1, error); WriteLn(valNum1, ,error); Val(cadena1, valNum1, error); WriteLn(valNum1, ,error) Algoritmos de datos Algoritmos de datos simples

123 0 3 123 0

498

Apendice B. El lenguaje Turbo Pascal

B.6

Tipos de datos estructurados

Las particularidades de Turbo Pascal en cuanto a estos tipos de datos son escasas: en lo referente a arrays, se debe sealar que los procedimientos Pack y n Unpack no estn denidos en Turbo Pascal. a

B.7

Instrucciones estructuradas

Las diferencias con Pascal en cuanto a instrucciones estructuradas se limitan al tratamiento de la instruccin case, que en Turbo Pascal presenta tres o variaciones, que son las siguientes: En Turbo Pascal el valor de la expresin selectora de un case puede no o coincidir con alguna de sus constantes sin que se produzca error, como suceder en Pascal estndar. En este caso, en Turbo Pascal contina la a a u ejecucin del programa en la instruccin que sigue a case. o o La instruccin case en Turbo Pascal puede disponer de una parte else que o se ejecuta cuando la expresin selectora no coincide con ninguna de sus o constantes. Dentro de las constantes de la instruccin case, Turbo Pascal permite la o denicin de intervalos, lo que no est permitido en Pascal estndar. o a a A continuacin se muestra un pequeo ejemplo de las diferencias anteriores o n y su sintaxis.
program ClasificacionDeCaracteres (input, output); var car: char; begin Write(Introduzca un carcter: ); a ReadLn(car); case car of a..z:WriteLn(Es minscula); u A..Z:WriteLn(Es mayscula); u 0..9:WriteLn(Es nmero) u else WriteLn(Es un smbolo) end {case} end. {ClasificacionDeCaracteres}

B.8. Paso de subprogramas como parametros

499

B.8

Paso de subprogramas como parmetros a

Turbo Pascal diere de Pascal estndar en la forma de realizar la declaracin a o y paso de subprogramas como parmetros. En Turbo Pascal los subprogramas a deben ser obligatoriamente de un tipo con nombre para poder ser pasados como parmetros. a Por ejemplo, en la denicin de la funcin Derivada (vase el apartado A.1.1) o o e se utilizaba como parmetro una funcin de argumento real y resultado tambin a o e real. El tipo de esta funcin se dene en Turbo Pascal como se muestra a o continuacin: o
type tMatFun = function (x:

real):

real;

Para un procedimiento con dos parmetros enteros se escribir a a:


type tProcInt = procedure (a, b:

integer);

Los identicadores utilizados en estos encabezamientos se utilizan a efectos de la denicin del tipo sin que tengan ninguna repercusin sobre el resto del o o programa. Para declarar la funcin como parmetro formal, la declaramos del tipo o a tMatFun dentro del encabezamiento de la funcin ejemplo. De esta forma: o
function Derivada (Fun: tMatFun; x: real): real; const DeltaX = 10E-6; begin Derivada:= (Fun(x + DeltaX) - Fun(x))/DeltaX end; {Derivada}

Dentro del programa principal efectuamos la llamada a la funcin Derivada o pasndole cualquier funcin denida por el usuario que encaje con el tipo tMatFun. a o Suponiendo denida una funcin Potencia, una posible llamada ser o a:
WriteLn(La derivada es: , Derivada(Potencia, x))

La utilizacin de subprogramas como parmetros requiere, dentro del esqueo a ma de gestin de memoria de Turbo Pascal, la realizacin de llamadas fuera o o del segmento de memoria donde reside el programa. Estas llamadas lejanas se activan marcando la opcin o

500

Apendice B. El lenguaje Turbo Pascal

[F10] [Options] [Compiler] [Code generation] [X] [Force far calls]

dentro del entorno integrado (vase el apartado C.3.3). Tambin se puede hacer e e desde el programa fuente utilizando las denominadas directrices de compilacin, o que son unas marcas que delimitan la parte del programa que debe compilarse con esta u otras funciones activadas. En el caso de las llamadas lejanas, la directriz de activacin es {$F+} y la desactivacin es {$F-}. o o

B.9

Archivos

Para poder trabajar con archivos en Turbo Pascal es necesario relacionar el archivo externo, existente en el dispositivo de E/S y nombrado segn el sistema u operativo, con la variable de tipo archivo denido en nuestro programa. Una vez establecida esta relacin se puede proceder a utilizar las instrucciones Reset o o ReWrite y posteriormente las de lectura o escritura respectivamente, que tendrn a efecto sobre el archivo externo relacionado. Esta conexin se establece mediante el procedimiento Assign de Turbo Paso cal que tiene como parmetros el identicador de la variable archivo y una cadena a que representa el nombre del archivo externo expresado en el lenguaje de comandos del sistema operativo. Por ejemplo, la instruccin: o
var archivo: text; ... Assign(archivo, C:\CARTAS\CARGEST.DOC)

vincula la variable archivo con el archivo llamado CARGEST.DOC existente en la unidad C:, directorio CARTAS.5 La otra instruccin adicional de Turbo Pascal para el proceso de archivos es o Close:
Close(archivo)

que sirve para cerrar un archivo una vez que se termina de procesarlo. Al ejecutarse la instruccin Close, aquellas operaciones de lectura o escritura que o pudieran quedar pendientes son completadas, quedando el archivo externo actualizado. A continuacin el archivo argumento es cerrado y se pierde la conexin o o establecida por Assign.
Si se omite la unidad se tomar la actual, al igual que si se omiten los directorios. En cualquier a caso deber gurar un nombre de archivo, no estando permitido el uso de comodines (vase el a e apartado A.1.2 de [PAO94]).
5

B.10. Memoria dinamica

501

En Turbo Pascal las variables de archivo no tienen asociada la variable intermedia (cursor) denida por el operador ^ propia de Pascal estndar. Al escribir a el s mbolo ^ detrs de un variable de archivo se produce un error. Esta diferena cia es importante, pues impide que en Turbo Pascal se pueda inspeccionar una componente del archivo sin leerla. Por esta razn ciertos programas que emplean o este tipo de acceso deben ser modicados. Los procedimientos Get y Put, para el manejo de archivos, tampoco estn a denidos en Turbo Pascal. Aquellos programas que los utilizan deben modicarse. En Turbo Pascal la lectura de una marca de n de l nea, en un archivo de texto, devuelve el carcter ASCII 13 (retorno de carro), y si contina la lectura, a u el caracter ASCII 10 (alimentacin de l o nea). En Pascal estndar la lectura de a una marca de f de l n nea se realiza como la de un unico carcter y devuelve un a espacio en blanco. Turbo Pascal dispone de numerosas extensiones para el tratamiento de archivos. Algunas realizan llamadas a las funciones del sistema operativo permitiendo, por ejemplo, cambiar el directorio de trabajo, borrar un archivo, etc. Otras permiten efectuar un acceso directo a las componentes de los archivos mejorando el tratamiento secuencial de Pascal estndar. Tambin se permiten cheros sin tipo a e para realizar operaciones a bajo nivel. Para su estudio remitimos a la bibliograf a complementaria.

B.10

Memoria dinmica a

Las diferencias en cuanto al manejo de memoria dinmica residen en que en a Turbo Pascal los procedimientos New y Dispose slo reciben una variable de tipo o puntero, mientras que en Pascal estndar se permiten parmetros adicionales. a a Recordemos, adems, que en Turbo Pascal el operador @ tiene un signicado a diferente de ^ (vase el apartado B.1). e

B.11

Unidades

Las unidades consisten en conjuntos de objetos, tales como constantes, tipos, variables, procedimientos y funciones que pueden ser denidos o declarados e incluso iniciados en la propia unidad. Estos objetos normalmente estn relacioa nados entre s y se orientan a la resolucin de ciertos problemas o tareas. o Con las unidades se puede ampliar el repertorio de instrucciones del lenguaje de una forma modular, agrupndolas por acciones, sin tener que mostrar cmo a o se realizan estas acciones, que quedan ocultas en una parte privada.

502

Apendice B. El lenguaje Turbo Pascal

Hay dos tipos de unidades en Turbo Pascal, aunque ambas son utilizadas de idntica forma. Estos dos tipos son: e Unidades predenidas, que se dedican a tareas concretas como, por ejemplo, la interaccin con el sistema operativo, el tratamiento de la pantalla o de texto o la creacin de grcos. o a Unidades denidas por el programador, para resolver otros problemas no previstos en las unidades predenidas. Cada unidad es compilada por separado y es incorporada a un programa mediante una llamada a la misma, realizada al comienzo del programa (antes de las deniciones y declaraciones), en una clasula u
uses unidad

Las unidades se incorporan a los programas, que, de esta forma, pueden acceder a los objetos que forman la unidad y utilizarlos en la resolucin de o dichos problemas o tareas. Las unidades son una forma apropiada (en Turbo Pascal) para construir bibliotecas de subprogramas para realizar clculos o procesos concretos. Tambin a e se utilizan para la denicin de tipos abstractos de datos (vase el cap o e tulo 19).

B.11.1

Unidades predenidas de Turbo Pascal

El lenguaje Turbo Pascal incorpora un conjunto de unidades que le dan una mayor potencia y exibilidad. Son las siguientes: System: Esta unidad incluye todas las instrucciones predenidas de Pascal estandar. Es incorporada de forma automtica en todos los programas, a por lo que no es necesario nombrarla en la clusula uses. a DOS: En esta unidad se pueden encontrar los equivalentes en Pascal de las principales llamadas al sistema operativo. Crt: Contiene funciones y procedimientos para trabajar con la pantalla de texto. Printer: Es una pequea unidad que facilita el trabajo con la impresora. n En ella se trata a la impresora como un archivo de texto llamado lst. Un procedimiento Write o WriteLn que se dirija al archivo lst, tendr como a efecto el env de la salida a la impresora. Veamos un ejemplo: o

B.11. Unidades
uses printer; ... WriteLn (lst, texto)

503

Graph3: Es una unidad para la compatibilidad con los grcos de tortuga6 a de la versin 3.0 de Turbo Pascal. Depende de la unidad Crt, por lo que o sta debe ser llamada previamente. e Turbo3: Es una unidad para compatibilidad con ciertas instrucciones de la versin 3.0. Al igual que Graph3 tambin depende de Crt. o e Graph: Es la unidad donde se denen las rutinas grcas necesarias para a usar la pantalla en los modos grcos de alta resolucin. a o Los contenidos particulares de cada una de ellas pueden consultarse en la bibliograf complementaria. a

B.11.2

Unidades denidas por el usuario

De la misma forma que al escribir nuevos procedimientos y funciones un programador ampl las ya existentes en el lenguaje y puede utilizarlas en su a programa, se pueden escribir nuevas unidades, y aadirlas a las existentes en n Turbo Pascal. Una vez compiladas se pueden incorporar a aquellos programas que las necesiten slo con nombrarlas en la clusula uses. o a Una de las principales aplicaciones de las unidades denidas por el usuario es la creacin de nuevos tipos de datos complejos, cuyas deniciones y operaciones o asociadas a los mismos son incluidas en una unidad. Estos tipos abstractos de datos pueden incorporarse en la forma descrita anteriormente a aquellos programas que los precisen (vase el cap e tulo 19). Las unidades tienen dos partes: una pblica, llamada interface, donde se u denen los objetos que la unidad ofrece para que puedan ser usados por los programas, y otra privada llamada implementation, donde se concretan y desarrollan los objetos mencionados en la parte pblica y donde se denen otros u objetos locales privados que quedan ocultos y a los que no es posible acceder desde los programas que utilicen la unidad. Para escribir una unidad tenemos que conocer su estructura y sta es: e
Los grcos de tortuga fueron desarrollados por Seymour Papert en el MIT dentro del lenguaje a Logo y consisten en un paradigma de una tortuga que se desplaza un cierto nmero de pasos en u una direccin o que gira un cierto ngulo, y que va dejando un rastro que forma el grco. o a a
6

504

Apendice B. El lenguaje Turbo Pascal


unit nombre de la unidad; interface uses lista de unidades; deniciones y declaraciones pblicas; u implementation deniciones y declaraciones privadas; procedimientos y funciones; begin cdigo de iniciacin o o end.

En primer lugar aparece la palabra reservada unit, seguida por el nombre de la unidad, de forma similar al nombre de un programa. La palabra reservada interface abre las deniciones y declaraciones pblicas. u Si la unidad en cuestin depende de otras unidades, debe situarse en primer lugar o la clusula uses seguida por la lista de unidades necesitadas. A continuacin se a o deben denir constantes, tipos, y declarar variables, y los encabezamientos de procedimientos y funciones que sern visibles al programa que utilice la unidad. a Los cuerpos de los procedimientos y funciones declarados no se incluyen aqu . La palabra reservada implementation inicia la parte privada, en la que deben desarrollarse los procedimientos y funciones cuyos encabezamientos se han declarado en la parte interface. Para ello deben repetirse en esta parte los encabezamientos, si bien pueden abreviarse eliminando sus parmetros si los a tienen. La parte implementation puede completarse con otros objetos enteramente privados, incluso otros procedimientos y funciones, que pueden ser utilizados por los pblicos pero que no queremos que sean visibles. Estos subprogramas debern u a tener su encabezamiento completo. Todos los objetos denidos o declarados en la parte de interface son visibles en implementation. Despus de esta parte se puede ubicar lo que se denomina cdigo de iniciacin, e o o que consiste en un conjunto de instrucciones para dar valores iniciales a aquellas estructuras variables utilizadas por la propia unidad, en este caso se coloca un begin, como se mostr en el esquema anterior. o En el cap tulo 19 pueden encontrarse ejemplos de denicin y utilizacin de o o unidades dedicadas a tipos abstractos de datos. Una vez escrita y depurada la unidad, sta se compila dando lugar a un e archivo con extensin TPU. Cuando se compila un programa que contiene una o clusula uses seguida por el nombre de la unidad, el compilador busca el archivo a *.TPU correspondiente, agrega sus deniciones y declaraciones a las del propio programa, y enlaza el cdigo de la unidad y del programa. Dado que la unidad o ha sido compilada previamente, la conexin entre ambos es bastante rpida. o a

B.11. Unidades

505

B.11.3

Modularidad incompleta de Turbo Pascal

La utilizacin de unidades en Turbo Pascal refuerza los aspectos modulares o del lenguaje Pascal estndar siendo equivalentes, con pequeas limitaciones, a a n los mdulos existentes en otros lenguajes. o Las unidades permiten solucionar ciertos problemas de jerarqu modular a como, por ejemplo, las llamadas a subprogramas desde otros varios, lo que obligaba a situar los subprogramas llamados por encima de su verdadero nivel para hacerlos accesibles a dos o ms subprogramas diferentes. La solucin a este a o problema se alcanza incorporando una unidad con los subprogramas llamados. Las unidades tiene una modularidad de acciones completa, al estar separadas las partes pblica y privada de los subprogramas, lo que les permite alcanzar u una verdadera ocultacin de la informacin. Sin embargo, la modularidad de o o los datos no es completa, al no permitir mencionar pblicamente tipos con una u implementacin privada (oculta) como en otros lenguajes, por ejemplo, Modula2 o o Ada. Por ello, cuando utilizamos las unidades de Turbo Pascal para la denicin o de tipos abstractos de datos, su declaracin y denicin tienen que estar en la o o parte pblica interface. u

Apndice C e

El entorno integrado de desarrollo


Turbo Pascal c es un producto comercial desarrollado por la empresa Borland International, Inc., cuyo uso est muy extendido.1 a Turbo Pascal ha ido evolucionando a lo largo del tiempo de acuerdo con las necesidades del mercado. Esta evolucin se ha concretado en la aparicin de suo o cesivas versiones del producto. Los principales saltos cualitativos se producen en el paso de la versin 3.0 a la 4.0, al introducirse un entorno integrado de desarroo llo, y en el paso de la versin 5.0 a la 5.5 permitiendo algunas caracter o sticas de la programacin orientada a objetos. La evolucin posterior tiende a completar o o las posibilidades del entorno y facilitar su utilizacin (uso del ratn en la versin o o o 6.0, resaltado de palabras reservadas en la 7.0, etcetera). La versin ms reciente o a en el momento de escribir estas l neas es la 7.01, que contiene la ayuda traducida al castellano. Adems, existe una versin para Windows c denominada Delphi c . a o El contenido de este apndice corresponde a la versin 7.0, si bien se puede e o aplicar con muy pequeas variaciones desde la versin 4.0. n o

C.1

Descripcin del entorno o

Turbo Pascal no es slo un compilador de un lenguaje de programacin, sino o o un completo entorno integrado de desarrollo compuesto por todos los componentes necesarios para desarrollar programas, entre otros: Un potente editor, que permite escribir y modicar programas (y texto en general), con la posibilidad de cortar, copiar, pegar, buscar y reemplazar texto.
1

El uso leg timo de Turbo Pascal requiere la correspondiente licencia.

508

Apendice C. El entorno integrado de desarrollo

Un compilador del lenguaje Turbo Pascal que cumple, salvo pequeas n excepciones, la sintaxis y semntica de Pascal estndar. Existe la posia a bilidad de compilar en memoria o en disco. La primera opcin permite o alcanzar una gran velocidad de compilacin, mientras que la segunda se o utiliza para crear los programas ejecutables. Un depurador que permite realizar un seguimiento de los programas, ejecutndolos paso a paso, deteniendo la ejecucin del programa e inspeccioa o nando sus objetos. Una ayuda a la que se puede acceder desde el entorno, que permite la consulta rpida de la sintaxis y semntica de Turbo Pascal. a a Desde el entorno se puede acceder al DOS, para realizar tareas propias del sistema operativo, sin tener que abandonar el trabajo en curso. Este entorno est controlado por mens, es decir, el programador puede elegir a u en cualquier momento entre una serie de opciones organizadas jerrquicamente. a As en algunos casos, al escoger una opcin se abre un submen que muestra , o u las nuevas (sub)opciones disponibles. El entorno est basado en ventanas que pueden estar asociadas a programas a (pudiendo trabajar con varios a la vez, transriendo informacin de unos a otros), o mensajes u operaciones. Pero para conocerlo, lo mejor es practicar, y eso mismo es lo que proponemos en el apartado siguiente.

C.2

Desarrollo completo de un programa en Turbo Pascal

En este apartado vamos a describir la forma adecuada y eciente para escribir, almacenar y modicar un programa, para compilarlo y ejecutarlo y para depurarlo.

C.2.1

Arranque del entorno

En primer lugar tenemos que arrancar el entorno, para ello pasamos al directorio donde se encuentre, por ejemplo PASCAL. En la versin 7.0, el compilador o se encuentra dentro del directorio \BIN que a su vez est dentro del directorio a \PASCAL. Hacemos: C:\> CD PASCAL

C.2. Desarrollo completo de un programa en Turbo Pascal

509

Figura C.1.

o C:\> CD PASCAL\BIN para la versin 7.0 o A continuacin arrancamos el entorno tecleando TURBO: o C:\PASCAL\BIN> TURBO Aparece la pantalla inicial, mostrada en la gura C.1. La l nea superior es el men, es decir, el conjunto de opciones que se pueden ejecutar. En el u centro aparece la ventana de edicin, con un nombre de archivo por defecto, y o debajo una l nea que muestra los atajos disponibles, o sea, aquellas teclas que nos permiten efectuar ciertas acciones con una o dos pulsaciones. Para acceder a las opciones del men se pulsa [F10] y a continuacin su u o inicial (o la letra resaltada en su caso). Para salir de la barra de mens y editar u (crear o modicar) nuestro programa pulsamos [Esc], entonces el cursor pasa a la parte interior de la ventana de edicin, que es donde vamos a escribir nuestros o programas. Todas las operaciones pueden realizarse igualmente utilizando el ratn. o Los nmeros de la esquina inferior izquierda expresan la la y columna en u que se encuentra el cursor. El nmero de la esquina superior derecha expresa u la ventana que est activa. Turbo Pascal puede tener varios programas abiertos a

510

Apendice C. El entorno integrado de desarrollo

a la vez en distintas ventanas. Tambin se utilizan ventanas para realizar el e seguimiento y depuracin de nuestros programas, y para el env de mensajes. o o Inicialmente Turbo Pascal asigna un nombre al chero de trabajo, que para la ventana 1 es NONAME00.PAS

C.2.2

Edicin del programa fuente o

Se realiza escribiendo las sucesivas l neas del programa, terminando cada l nea pulsando la tecla . El alineamiento se realiza la primera vez con la tecla de tabulacin y a partir de entonces se hace automticamente en cada salto de o a l nea. Para corregir o modicar texto se utilizan las teclas habituales:
teclas de cursor [Del] o [Supr] tecla de Retroceso [Insert] [F10] Para moverse por la ventana. Borra la letra que tiene el cursor debajo. Borra retrocediendo hacia la izquierda. Elige el modo de insercin o Sale de la ventana de edicin. o

Si se elige el modo de insercin, lo que escribamos va desplazando el texto o escrito a la derecha desde la posicin de insercin (si es que lo hay). En el modo o o de no insercin, el texto que escribamos ocupa el lugar del texto escrito, por lo o que ste ultimo se pierde. e Utilizando las teclas citadas anteriormente hemos escrito el programa para el clculo recursivo del factorial (vase el apartado 10.1), que aparece en la a e gura C.2. Dentro del men principal existe una opcin Edit (Editar) que ofrece algunas u o posibilidades complementarias del editor que permiten copiar y pegar fragmentos de texto. Sus opciones trabajan sobre bloques de texto que hay que marcar pulsando la tecla de maysculas y las del cursor. u Existe un almacenamiento temporal de texto llamado el portapapeles (en ingls clipboard ) donde se guardan los bloques de texto cortados (Cut) o copiados e (Copy) hasta que se peguen (Paste) en otro sitio o se corte o copie un nuevo bloque.

C.2.3

Grabar el programa fuente y seguir editando

Para no perder el trabajo que se va realizando, es necesario ir grabando el texto escrito peridicamente. Para ello se utiliza la opcin Save del men o o u Edit, o bien la tecla [F2]. La primera vez que se hace esto, Turbo Pascal

C.2. Desarrollo completo de un programa en Turbo Pascal

511

Figura C.2.

muestra la ventana de la gura C.3 (aunque con otra lista de archivos, con toda probabilidad) en la que se nos invita a asignar un nombre al programa. Para movernos por las distintas opciones de la ventana usamos la tecla [Tabulador], salimos con [Esc] y elegimos con la tecla . Supongamos que queremos llamar a nuestro archivo FACT y queremos grabarlo en el directorio ra de la unidad A. Entramos en el apartado Save file z as y escribimos el nombre que queremos darle: A:\FACT No es necesario darle extensin, ya que por defecto se le asigna .PAS. Si no o ponemos unidad y directorio, Turbo Pascal toma los que tiene asignados como directorio de trabajo. Al pulsar la tecla se graba el programa.

A partir de este momento, cada vez que queramos actualizar en el disco el archivo con el programa, usando el mismo nombre, bastar con pulsar [F2]. Se a recomienda actualizar el programa cada diez o veinte minutos y, en cualquier caso, siempre antes de compilar, para evitar la prdida accidental de parte de e nuestro trabajo. Al hacerlo, se graba una copia del programa primitivo con la extensin .BAK y de la nueva versin con la extensin .PAS. o o o La tecla [F2] es un atajo de la opcin File (Archivo) del men principal o u donde se encuentran aquellas opciones necesarias para crear, almacenar e imprimir archivos, salir al DOS y terminar una sesin de trabajo. Por su inters las o e hemos resumido a continuacin: o

512

Apendice C. El entorno integrado de desarrollo

Figura C.3. New Open... (F3) Save (F2) Save as... Save all Change dir... Print Printer setup... DOS shell Exit (Alt+X) Abre una nueva ventana de trabajo.

Lee un archivo.
Almacena el texto en un archivo con el nombre actual. Almacena el texto en un archivo con un nuevo nombre. Almacena en archivos todos los textos del entorno. Cambia el directorio de trabajo. Imprime el programa activo. Congura la salida para diferentes impresoras. Sale al DOS (se vuelve al entorno escribiendo exit). Finaliza la ejecucin de Turbo Pascal. o

La diferencia entre las opciones New, Open y Save es que la primera sirve para abrir una nueva ventana de edicin, la segunda para abrir una ventana con o un archivo que fue creado con anterioridad, y que es le desde el disco, y la do tercera para guardar un archivo. Para cambiar el nombre del programa con el que estamos trabajando, por ejemplo, cuando se van a introducir cambios que no se sabe si sern denitivos, usamos Save as. a

C.2.4

Compilacin o

En esta fase del desarrollo del programa vamos a realizar la traduccin de o nuestro programa fuente en Pascal (usualmente almacenado en un archivo con extensin PAS) a un programa objeto, ejecutable, que al compilarse en el disco o tendr la extensin EXE (vase el apartado 5.3 de [PAO94]). a o e

C.2. Desarrollo completo de un programa en Turbo Pascal Activamos el men Compile haciendo: u
[Alt] + [C] o bien [F10] [C] y despus pulsamos [C] e

513

o, de otro modo, ms cmodamente a o


[Alt] + [F9]

Si el programa se compila con xito, aparece el mensaje: e


Compile successful: Press any key

si no, habr que corregir los errores que vayan apareciendo. Estos son mostrados a por el compilador mediante mensajes de error en la ventana de edicin. o Veamos un ejemplo: si por un descuido olvidamos declarar la variable global n, al intentar leerla, se produce un error:
Error 3: Unknown identifier. (identicador desconocido)

situndose el cursor en la l a nea en que se ha detectado el error, debajo de la instruccin ReadLn(n). o El compilador no siempre es capaz de sealar la posicin del error con pren o cisin, por lo que en ciertos casos hay que indagar su origen por encima de donde o se seala. n El men Compile consta de varias opciones interesantes que resumimos a u continuacin: o
Compile (Alt+F9) Make (F9) Build Destination Memory Primary file... Clear primary file Information... Compila el programa fuente en curso Compila los archivos de programa modicados Compila todos los archivos de programa Permite elegir destino, memoria o disco Dene el programa primario para Make y Build Borra el programa primario Muestra informacin sobre la compilacin en curso o o

Con Make y Build se compila el archivo de programa y en su caso aquellos otros archivos de programa que dependan de l o a los que haga referencia, e como, por ejemplo, las unidades denidas por el programador (vase el apare tado B.11.2). Durante la depuracin, la compilacin se puede efectuar almacenando el o o programa ejecutable en memoria RAM, lo que permite una mayor velocidad.

514

Apendice C. El entorno integrado de desarrollo

Una vez que el programa est totalmente depurado puede compilarse en disco, e crendose el archivo ejecutable. Para esto hay que cambiar el destino de la a compilacin de la siguiente forma: abrimos el men Compile y pulsamos [D] o u activndose la orden Destination que tiene dos opciones, Memory y Disk. a Seleccionando Disk las ulteriores compilaciones se dirigirn siempre a disco. a El programa objeto ejecutable tendr el mismo nombre que el archivo en el a que se encuentre el programa fuente, pero con la extensin .EXE. o Una vez que hemos compilado (en memoria o en disco) el programa con xito e ya se puede ejecutar.

C.2.5

Ejecucin o

Para ejecutar el programa se activa el men Run con [Alt] + [R] o [F10] u [R] y se selecciona la orden Run volviendo a pulsar [R], o directamente con [Ctrl] + [F9]. Desde el entorno integrado, al terminar el programa se vuelve a la ventana de edicin sin tiempo para ver las salidas. Podemos ver la ventana de salida o tecleando [Alt] + [F5]. Veamos un ejemplo de ejecucin del programa Fact: o
Turbo Pascal Version 7.0 Copyright (c) 1983,92 Borland International Escriba un nmero natural peque~o: 5 u n El factorial de 5 es 120

C.2.6

Depuracin o

Durante el proceso de compilacin y ejecucin de un programa es frecuente o o que se originen errores que no sean fciles de corregir. Para ayudar al prograa mador en este caso, el depurador integrado de Turbo Pascal permite analizar el funcionamiento de nuestro programa, ejecutarlo paso a paso, examinar y modicar variables y jar puntos de ruptura (en los que se detiene la ejecucin o del programa de forma condicional o incondicional, permitiendo inspeccionar el estado del mismo). En primer lugar, tenemos que activar el depurador desde el men de opciones u haciendo [F10] [Options] [Debugger] y marcando la opcin Integrated. o Para que el compilador genere la informacin necesaria para el depurador, o hemos de asegurarnos de que la opcin Debug information est marcada, y si o a tenemos objetos locales, comprobar tambin la opcin Local symbols dentro e o de las opciones de la pantalla de opciones del depurador: [F10] [Options] [Compiler] [Debugging] (vase el apartado C.3.3). e

C.2. Desarrollo completo de un programa en Turbo Pascal

515

Figura C.4.

A continuacin recompilamos el programa y, al ejecutarlo, el depurador o asume el control del programa. Para ejecutar el programa paso a paso, sin entrar en las llamadas a la funcin, o elegimos la opcin [F10] [Run] [Step over] o simplemente pulsamos repetidao mente [F8], viendo cmo las l o neas del programa se van iluminando y ejecutando sucesivamente. La pantalla alterna entre la ventana de edicin y la de salida. o Podemos ver que las llamadas recursivas a la funcin se resuelven en un solo o paso. Para ejecutar paso a paso, pero entrando en las llamadas a subprogramas, elegimos la opcin [F10] [Run] [Trace into] o pulsamos repetidamente [F7], o viendo cmo las l o neas de la funcin se iluminan al irse ejecutando. En el ejemplo o del factorial, la funcin se repite varias veces debido a las llamadas recursivas. o Hay que tener cuidado y no pasarse del nal del programa, ya que en tal caso se vuelve a comenzar. La ejecucin paso a paso tiene su complemento perfecto con la inspeccin de o o constantes, variables y parmetros, que nos permitir examinar los valores que a a tienen estos objetos tras la ejecucin de cada instruccin. Para ello hay que abrir o o una ventana de inspeccin llamada Watches, en la que, en nuestro ejemplo, o colocaremos los identicadores n y num. Hacemos [F10] [Debug] [Watch], con lo que se abre la ventana, y despus [F10] [Debug] [Add Watch] o simplemente e [Ctrl]+[F7], lo que permite introducir los identicadores que se deseen. Si se ejecuta el programa paso a paso, se vern los valores adoptados en cada a momento por los objetos incluidos en la ventana de inspeccin. En el ejemplo, o al comenzar el programa tanto n como num son etiquetados como identicadores desconocidos (Unknown identifier), para cambiar posteriormente, adoptando n el valor introducido por el usuario, y reducindose num en cada llamada recure siva. En la gura C.4 vemos la apariencia de la ventana Watches en un punto intermedio del proceso, donde las sucesivas llamadas recursivas han ido reduciendo el valor del argumento de la funcin hasta alcanzar el caso base. o

516

Apendice C. El entorno integrado de desarrollo

Figura C.5.

Una opcin interesante en casos como ste es la de Call stack (llamada o e a la pila) que permite ver la sucesin de llamadas realizadas. Recordemos que o los objetos locales de las llamadas a subprogramas son almacenados en una estructura de datos del tipo pila (vase el apartado 17.2.3). Esta opcin se e o activa haciendo [F10] [Debug] [Call stack]. En la gura C.5 se muestra la serie de llamadas producidas para calcular el factorial de 4, empezando por el propio programa, dentro de la ventana Call stack. El depurador permite tambin detener la ejecucin de un programa para e o la inspeccin de objetos sin necesidad de ejecutar paso a paso. Esto se hace o estableciendo un punto de ruptura Breakpoint. En el ejemplo, jaremos dicho punto al nal de la funcin, de forma condicioo nal para un valor de num = 0. De esta forma se efectuarn las sucesivas llamadas a a la funcin, detenindose el programa cuando la condicin se haga verdadera. o e o Haciendo [F10] [Debug] [Breakpoints], aparece la ventana de edicin de o los puntos de ruptura en la que, eligiendo la opcin [Edit], podremos escribir el o nmero de l u nea y la condicin. A continuacin salimos de la ventana y ejecutao o mos el programa. Este se detiene en el punto de ruptura y muestra un mensaje, lo que nos permite inspeccionar los valores de sus variables y parmetros, as a como las llamadas existentes en la pila.

C.2.7

Salida de Turbo Pascal

Para terminar una sesin de trabajo con el entorno integrado de desarrollo, se o teclea [F10] [File] [Exit]. Es importante no olvidarse de actualizar el programa en el disco si hemos hecho cambios.

C.3. Otros menus y opciones

517

C.3

Otros men s y opciones u

En este apartado se hace un breve resumen de otros mens y opciones inteu resantes de Turbo Pascal que no se han expuesto en el apartado anterior. La explicacin completa de todos los mens y opciones de Turbo Pascal o u rebasa los objetivos de este apndice, por lo que debe acudirse a la bibliograf e a complementaria.

C.3.1

Search (B squeda) u

Las opciones correspondientes a este men se utilizan para buscar y sustituir u caracteres, palabras o frases, y para buscar l neas, errores y procedimentos o funciones. Son un complemento del propio editor de texto de Turbo Pascal.

C.3.2

Tools (Herramientas)

El men Tools permite la utilizacin simultnea de Turbo Pascal y de otros u o a programas complementarios no integrados en el mismo. Estos otros programas pueden servir, por ejemplo, para realizar bsquedas de texto, programar en u lenguaje ensamblador, usar un programa de depuracin ms potente o para o a analizar y optimizar el funcionamiento de los programas.

C.3.3

Options (Opciones)

El men Options es uno de los ms importantes para el correcto funcionau a miento del entorno, pues permite congurar muchos de sus parmetros, que de a ser incorrectos dan lugar a errores que impiden la compilacin. o Las opciones se seleccionan con la tecla de tabulacin y se activan o desactivan o pulsando la tecla de espacio (aparece una cruz o quedan en blanco). Dentro del men de opciones nos interesan especialmente algunas de las cou rrespondientes al compilador (incluidas en el submen Compiler), que exponeu mos a continuacin: o [ ] Force far calls

Permite llamadas fuera del segmento actual de instrucciones. Debe marcarse al usar procedimientos y funciones como parmetros (vase el apara e tado A.1).

[ ] Range checking

Comprueba si los ndices de arrays y cadenas se encuentran dentro de sus l mites, y si las asignaciones a variables de tipo escalar no estan fuera de

518

Apendice C. El entorno integrado de desarrollo sus intervalos declarados. Debe activarse cuando puedan aparecer errores de este tipo.

[ ] Overflow checking Comprueba errores de desbordamiento despus de efectuar las siguientes e operaciones: +, -, *, Abs, Sqr, Succ y Pred. Debe activarse cuando puedan aparecer errores de este tipo. [ ] Complete boolean eval Realiza la evaluacin completa de las expresiones booleanas sin optimizaro las, es decir, la evaluacin del circuito largo (vase el apartado 3.5) en caso o e de estar activada y del circuito corto en caso contrario. [ ] Debug information Genera informacin de depuracin imprescindible para ejecutar el proo o grama paso a paso y para jar puntos de ruptura. Debe activarse para depurar. [ ] Local symbols Genera informacin de los identicadores locales necesaria para examinar o y modicar las variables locales de un subprograma y para ver las llamadas producidas hasta llegar a un determinado subprograma con la opcin o Debug Call Stack. Debe activarse para depurar dentro de un subprograma. [ ] 8087/80287 Genera cdigo para el coprocesador numrico y debe estar activada para o e poder utilizar los tipos reales de Turbo Pascal. Memory sizes Fija los tamaos de memoria de la pila y el mont n culo, que son dos estructuras necesarias para el funcionamiento de los programas. El tamao de la n pila (Stack size) puede ser insuciente en programas (normalmente recursivos) con gran numro de llamadas a subprogramas, como la funcin de e o Ackermann (vase el apartado 10.3.3). En dichos casos puede aumentarse e su tamao hasta un valor mximo de 65535. n a Debugger En esta opcin se jan ciertos parmetros del depurador, entre otros, si se o a usa el depurador integrado o independiente.

C.4. Ejercicios Directories

519

Aqu se jan los directorios de trabajo de los diferentes archivos que se utilizan en Turbo Pascal. Para situar ms de un directorio en la lista, se a separan mediante punto y coma. Environment Abre un submen donde se pueden jar muchos de los parmetros de funu a cionamiento del entorno integrado, parmetros de pantalla, del editor, del a ratn, al arrancar, colores, etc. Esta conguracin se guarda en el archivo o o TURBO.TP

C.3.4

Window (Ventana)

El men Window abre un submen para el control de las ventanas del u u entorno integrado. Se puede elegir su disposicin, modicar el tamao y posicin o n o y elegir la ventana activa, entre otras opciones.

C.3.5

Help (Ayuda)

El entorno integrado de Turbo Pascal dispone de una extensa ayuda integrada que incluye la explicacin de todos sus mandatos, los del lenguaje Pascal (con o las extensiones de Turbo Pascal) y otros aspectos como unidades y directivas de compilacin. o El texto de la ayuda tiene formato de Hipertexto, es decir, ciertas palabras aparecen resaltadas, y basta con situar el cursor sobre ellas y pulsar la tecla para acceder a informacin concreta sobre el concepto indicado. o La ayuda de Turbo Pascal es dependiente del contexto, o sea, se abre en el apartado correspondiente al mandato con el que estamos trabajando o en la palabra sealada por el cursor. No obstante, si queremos buscar otros temas, n podemos acudir al men de ayuda. u Adems, la ayuda dispone de informacin sobre los mensajes de error, sobre el a o uso (sintaxis y semntica) de los identicadores predenidos, palabras reservadas, a etc., constituyendo un verdadero manual del usuario.

C.4

Ejercicios

1. Invitamos al lector a que utilice el entorno de Turbo Pascal para desarrollar gradualmente los programas que se han propuesto como ejercicios en los sucesivos cap tulos del libro.

520

Apendice C. El entorno integrado de desarrollo

2. Utilice los tipos numricos enteros de Turbo Pascal para el clculo del factorial. e a Determine cul es el nmero mayor del que se puede calcular el factorial, en los a u tipos byte, integer, word, shortint y longint. 3. Calcule el valor de y del nmero e, con la mxima precisin posible utilizando u a o el tipo real extended y la suma de las series que se presentan a continuacin: o 1 1 1 1 4 = 1 3 + 5 7 + 9 + ... 1 1 1 e = 1 + 1! + 2! + 3! + . . . 4. Utilice las cadenas de caracteres para comprobar si una frase dada forma un pal ndromo. 5. Escriba una procedimiento que haga ms robusta la entrada de valores numricos a e enteros utilizando el procedimiento Val. Para ello el procedimiento leer el nmero a u como una cadena de caracteres. Si se produce algn error en la introduccin u o deber mostrar la parte correcta y pedir el resto del nmero. a u 6. Compile los programas ejemplo con paso de subprogramas como parmetros (vese a a el apartado A.3) en Turbo Pascal. 7. Escriba un programa en Turbo Pascal que pida la unidad, directorio, nombre y extensin de un archivo de texto, lo abra para escritura y lea repetidamente una o cadena desde teclado y la escriba en dicho archivo, aadiendo un salto de l n nea al nal de la misma, nalizando la introduccin cuando se escriba una cadena vac o a. 8. Escriba un programa en Turbo Pascal que pida la unidad, directorio, nombre y extensin de un archivo de texto existente y muestre su contenido en pantalla, o incluyendo los saltos de l nea. Asimismo, al terminar la lectura del archivo, el programa mostrar un resumen indicando el total de caracteres le a dos, cuntos a son respectivamente maysculas, minsculas, nmeros u otros s u u u mbolos, y cuntos a saltos de l nea ten a.

C.5

Referencias bibliogrcas a

La explicacin ms directa, sobre el funcionamiento del entorno y del lenguaje Turbo o a Pascal la podemos encontrar en su propia ayuda interactiva, donde acudiremos por su inmediatez para solventar aquellas dudas que pueden aparecer mientras utilizamos el entorno. No obstante, no es recomendable utilizar la ayuda como texto para el aprendizaje de Turbo Pascal, siendo conveniente acudir a otras obras estructuradas de una forma ms pedaggica. a o Para utilizar un compilador tan completo y extenso como Turbo Pascal no hay nada mejor que disponer de los manuales originales [Bor92b], [Bor92d], [Bor92a] y [Bor92c]. El libro [ON93] est concebido como un completo manual de referencia de Turbo a Pascal cubriendo todos los aspectos del entorno, con ejemplos completos. El texto [Joy90] es un manual de programacin en Turbo Pascal que ofrece una o visin completa del mismo en sus versiones 4.0, 5.0 y 5.5. o El libro [CGL+ 94] esta orientado a un primer curso de programacin estructurada o y orientada a objetos utilizando como lenguaje el Turbo Pascal y cuenta con numerosos ejercicios.

Bibliograf a

[AA78] [AHU88] [AM88] [Arn94] [Bar87] [BB90] [BB97] [Ben86] [Bie93] [BKR91] [Bor92a] [Bor92b] [Bor92c]

S. Alag y M.A. Arbib. The design of well-structured and correct c programs. Springer Verlag, 1978. A. V. Aho, J. E. Hopcroft, y J. Ullman. Estructuras de datos y algoritmos. Addison-Wesley Iberoamericana, 1988. F. Alonso Amo y A. Morales Lozano. Tcnicas de programacin. e o Paraninfo, 1988. D. Arnow. Teaching programming to liberal arts students: using loop invariants. SIGCSE Bulletin of the ACM, 3:141144, 1994. J. G. P. Barnes. Programacin en Ada. D de Santos, 1987. o az G. Brassard y P. Bratley. Algor tmica (concepcin y anlisis). Maso a son, 1990. G. Brassard y P. Bratley. Fundamentos de algoritmia. Prentice-Hall, 1997. J. Bentley. Programming pearls. Addison-Wesley Publishing Company, 1986. M. J. Biernat. Teaching tools for data structures and algorithms. SIGCSE Bulletin of the ACM, 25(4):911, Dic. 1993. L. Banachowski, A. Kreczmar, y W. Rytter. Analysis of algorithms and data structures. Addison-Wesley, 1991. Borland International Inc. Turbo Pascal 7.0 Library Reference, 1992. Borland International Inc. Turbo Pascal 7.0 Programmers Guide, 1992. Borland International Inc. Turbo Pascal 7.0 TurboVision Guide, 1992.

522 [Bor92d] [BR75] [BW89]

Bibliograf a Borland International Inc. Turbo Pascal 7.0 Users Guide, 1992. J. R. Bitner y M. Reingold. Backtrack programming techniques. Communications of the ACM, 18(11):651655, Nov. 1975. R. Bird y P. Wadler. Introduction to functional programming. Prentice Hall International, 1989.

[CCM+ 93] J. Castro, F. Cucker, F. Messeguer, A. Rubio, Ll. Solano, y B. Valles. Curso de programacin. McGraw-Hill, 1993. o [CGL+ 94] J. M. Cueva Lovelle, M. P. A. Garc Fuente, B. Lpez Prez, M. C. a o e Luengo D y M. Alonso Requejo. Introduccin a la programacin ez, o o estructurada y orientada a objetos con Pascal. Editado por los autores, 1994. [CK76] [CM] L. Chang y J. F. Korsh. Canonical coin changing and greedy solutions. Journal of the ACM, 23(3):418422, Jul. 1976. W. Collins y T. McMillan. Implementing abstract data types in Turbo Pascal.

[CMM87] M. Collado, R. Morales, y J. J. Moreno. Estructuras de datos. Realizacin en Pascal. D de Santos, S. A., 1987. o az [Col88] [Dan89] [dat73] W.J. Collins. The trouble with for-loop invariants. SIGCSE Bulletin of the ACM, pages 14, 1988. R. L. Danilowicz. Demostrating the dangers of pseudo-random numbers. SIGCSE bulletin of the ACM, 21(2):4648, Jun. 1989. Datamation. Nmero especial dedicado a la programacin estructuu o rada, Dic. 1973.

[DCG+ 89] P. Denning, D. E. Comer, D. Gries, M. C. Mulder, A. B. Tucker, A. J. Turner, y P. R. Young. Computing as a discipline. Communications of the ACM, 32(1):923, 1989. [DDH72] [Dew85a] O. J. Dahl, E. W. Dijkstra, y C. A. R. Hoare. Structured Programming. Academic Press Ltd., 1972. A. K. Dewney. Cinco piezas sencillas para un bucle y generador de nmeros aleatorios. Investigacin y Ciencia, 105:9499, Jun. 1985. u o

[Dew85b] A. K. Dewney. Ying y yang: recurrencia o iteracin, la torre de hanoi o y las argollas chinas. Investigacin y Ciencia, 100:102107, En. 1985. o

Bibliograf a [Dij68] [DL89] [DM84] [DW89] [ES85] [For82] [Fru84] [FS87]

523

E.W. Dijkstra. Goto statement considered harmful. Communications of the ACM, 11(3), Mar. 1968. N. Dale y S. Lilly. Pascal y estructuras de datos. McGraw-Hill, 1989. B. P. Demidovich y I. A. Maron. Clculo Numrico Fundamental. a e Paraninfo, Madrid, 1984. N. Dale y C. Weems. Pascal. McGraw-Hill, 1989. G. G. Early y D. F. Stanat. Chinese rings and recursion. SIGCSE Bulletin of the ACM, 17(4), 1985. G. Ford. A framework for teaching recursion. SIGCSE Bulletin of the ACM, 14(2):3239, July 1982. D. Frutos. Tecnolog de la programacin. Apuntes de curso. Maa o nuscrito, 1984. G. Fernndez y F. Sez. Fundamentos de Informtica. Alianza Edia a a torial. Madrid, 1987.

[GGSV93] J. Galve, J. C. Gonzlez, A. Snchez, y J. A. Velzquez. Algor a a a tmica. Diseo y anlisis de algoritmos funcionales e imperativos. Ra-ma, n a 1993. [GL86] L. Goldschlager y A. Lister. Introduccin moderna a la Ciencia de o la Computacin con un enfoque algor o tmico. Prentice-Hall hispanoamericana. S.A. Mjico, 1986. e N. E. Gibbs y A. B. Tucker. A model curriculum for a liberal arts degree in computer science. Communications of the ACM, 29(3), march 1986. R. Harrison. Abstract data types in Modula-2. John Wiley and sons, 1989. B. Hayes. Altibajos de los nmeros pedrisco. A la bsqueda del u u algoritmo general. Investigacin y Ciencia, 90:110115, Mar. 1984. o T.F. Higginbotham. The integer square root of n via a binary search. SIGCSE Bulletin of the ACM, 23(4), Dic. 1993. E. Horowitz y S. Sahni. Fundamentals of data structures in Pascal. Computer Science Press, 3 edicin, 1990. o

[GT86]

[Har89] [Hay84] [Hig93] [HS90]

524 [Joy90] [JW85] [KR86] [KSW85] [LG86] [Mar86] [McC73] [Mor84]

Bibliograf a L. Joyanes Aguilar. Programacin en Turbo Pascal Versiones 4.0, o 5.0 y 5.5. Mc Graw-Hill, 1990. K. Jensen y N. Wirth. Pascal user manual and report. Revised for the ISO. Springer Verlag, 1985. B.W. Kernighan y D.M. Ritchie. El lenguaje de programacin C. o Prentice-Hall, 1986. E. B. Koman, D. Stemple, y C. E. Wardle. Recomended curriculum for cs2, 1984. Communications of the ACM, 28(8), aug. 1985. B. Liskov y J. Guttag. Abstraction and interpretation in program development. MIT Press, 1986. J. J. Martin. Data types and structures. Prentice Hall, 1986. D. D. McCracken. Revolution in programming: an overview. Datamation, Dic. 1973. B. J. T. Morgan. Elements of simulation. Chapman and Hall, 1984.

[MSPF95] O. Mart anchez y C. Pareja-Flores. A gentle introduction to aln-S gorithm complexity for CS1 with nine variations on a theme by Fibonacci. SIGCSE bulletin of the ACM, 27(2):4956, Jun. 1995. [ON93] [PAO94] [PJ88] [PM88] S. K. OBrien y S. Namero. Turbo Pascal 7, Manual de referencia. Osborne Mc Graw-Hill, 1993. C. Pareja, A. Andeyro, y M. Ojeda. Introduccin a la Informtica o a (I). Aspectos generales. Editorial Complutense. Madrid, 1994. M. Page-Jones. The practical guide to structured design. PrenticeHall, 1988. S. K. Park y K. W. Miller. Random number generators: good ones are hard to nd. Communications of the ACM, 31(10):11921201, Oct. 1988. R. Pea. Diseo de programas. Formalismo y abstraccin. Prentice n n o Hall, 1993. R. S. Pressman. Ingenier del Software. Un enfoque prctico. a a McGraw-Hill, 1993. L. Pardo y T. Valds. Simulacin. Aplicaciones prcticas en la eme o a presa. D de Santos, S. A., 1987. az

[Pn93] [Pre93] [PV87]

Bibliograf a [RN88] [Sal93] [Sed88] [Str84] [Tam92] [Tur91]

525

L. R y R. D. Nelson. Adventures with your computer. Penguin ade Books Ltd., Middlesex, Gran Bretaa, 1988. n W. I. Salmon. Introduccin a la computacin con Turbo Pascal. o o Addison-Wesley Iberoamericana, 1993. R. Sedgewick. Algorithms. Addison-Wesley, 1988. B. Stroupstrup. The C++ programming language. Addison Wesley, 1984. W. C. Tam. Teaching loop invariants to beginners by examples. SIGCSE Bulletin of the ACM, pages 9296, 1992. A. J. Turner. Computing curricula (a summary of the ACM/IEEECS joint curriculum task force report). Communications of the ACM, 34(6):6984, 1991. J. S. Warford. Good pedagogical random number generators. Communications of the ACM, pages 142146, 1992. M. A. Weiss. Estructuras de datos y algoritmos. Addison-Wesley Iberoamericana, 1995. S. Wiedenbeck. Learning recursion as a concept and as a programming technique. Communications of the ACM, 1988. H. S. Wilf. Algorithmes et complexit. Masson, 1989. e N. Wirth. Algoritmos + Estructuras de datos = Programas. Ediciones del Castillo. Madrid, 1986. N. Wirth. Recollections about the development of Pascal. ACM Sigplan Notices, 28(3):119, 1993. J. W. Wright. The change making problem. Journal of the ACM, 22(1):125128, Jan. 1975. S. J. Yakowitz. Computational probability and simulation. AddisonWesley Publishing Company, 1977.

[War92] [Wei95] [Wie88] [Wil89] [Wir86] [Wir93] [Wri75] [Yak77]

Indice alfabtico e
Abs, 30, 32 abstraccin, 193 o abstraccin de datos, 428, 431 o Ackermann, funcin de, 219 o acontecimiento cr tico, 487 agrupamiento, 135 alcance de un identicador, 180 aleatoria (variable), 482 aleatorios (nmeros), 483 u algoritmo, 3, 4 complejidad de un, 15 comprobacin de un, 14 o correccin de un, 14 o formalmente, 8 informalmente, 6 vericacin de un, 14 o algoritmos de programacin dinmica, 455 o a de vuelta atrs, 462 a de backtracking, 462 devoradores, 450 divide y vencers, 453 a probabilistas, 468 a mbito de validez, 175 reglas de, 178 anchura (recorrido en), 383 and, 36 anidamiento de bucles, 96 de instrucciones de seleccin, 90 o anillo, 388 apuntador, 336 a rbol, 377 binario, 377 de bsqueda, 379 u de decisin, 389 o de juego, 389 general, 389 hoja de un, 378 n-ario, 389 nodo hijo en un, 378 nodo padre en un, 378 ra de un, 378 z recorrido de un, 378 archivo, 285 con tipo, 287 creacin, 289 o de texto, 294 escritura de un, 289 externo, 500 lectura de un, 291 ArcTan, 32 array, 253 ASCII, 35 asercin, 14 o asignacin (instruccin), 52 o o Assign, 500 autodocumentacin, 69 o B, 36 bsqueda u binaria, 304 dicotmica, 149 o en archivos, 320 en archivos arbitrarios, 321 en archivos ordenados, 321 en arrays, 301

528 secuencial, 148, 302 secuencial ordenada, 304 backtracking, 462 begin, 62 biblioteca de subprogramas, 181 bloque, 175 Bolzano, teorema de, 113 boolean, 36 bottom-up, 139, 202 bucle ndice de un, 101 cuerpo del, 94, 98 instrucciones, 94 postprobado, 98, 100 preprobado, 95 burbuja algoritmo, 329 byte, 492 C, 35 cabecera, 59 cabeza de una lista, 352 cadenas funciones, 496 operadores, 495 campo, 272 selector, 276 case, 92, 133, 498 caso base, 213 recurrente, 213 char, 35 chi-cuadrado, 490 Chr, 36 ciclo de vida, 18 circuito corto, 39 largo, 39 circunejo (^), 337 clave en la bsqueda, 321 u en la ordenacin, 321 o

Indice alfabetico Close, 500 cdigo reutilizable, 201 o cola, 370 coma otante, 493 comp, 493 compilacin en Turbo Pascal, 512 o complejidad, 15 clculo, 408 a comportamiento asinttico, 402 o de un algoritmo, 396 de un programa, 408 en el caso medio, 399 en el mejor caso, 399 en el peor caso, 399 en espacio, 400 en tiempo, 396 composicin, 85 o comprobacin, 14 o computabilidad, 11 Concat, 496 conjuntos, 244 const, 61 constante, 52 annima, 52 o con nombre, 52 conversin de tipos, 34, 36, 58 o Copy, 496 correccin, 14 o de un programa, 73 parcial, 14 total, 15 Cos, 32 coste de ejecucin, 396 o de un programa, 396 cuerpo de un bucle, 94 de un programa, 62 de un subprograma, 169 cursor, 288, 501 dato, 28

Indice alfabetico declaracin, 240 o global, 175 local, 161 denicin, 60 o de subprograma, 161 Delete, 497 depuracin, 10, 14, 514 o de un programa, 74 descripcin, 240 o desigualdad de conjuntos, 246 diagrama, 129 BJ, 131 de Bhm y Jacopini, 131 o de ujo, 17, 125 limpio, 129 privilegiado, 131 propio, 129 diagramas equivalentes, 135 diferencia, 245 diferenciacin nita, 146 o dimensin, 255 o directrices de compilacin, 500 o diseo n ascendente, 139, 202 descendente, 71, 134, 139 con instrucciones estructuradas, 141 con subprogramas, 193 Dispose, 339 div, 28 divide y vencers, 453 a do, 94 double, 493 downto, 101 efectos laterales, 182 eciencia, 11 else, 89 encabezamiento, 59 de un programa, 59 de un subprograma, 169 end, 62 enumerado, 235 EoF, 96, 288 EoLn, 96, 295 escritura (instruccin), 54 o con formato, 55 especicacin, 4, 78 o estado, 8, 74 nal, 9 inicial, 9 estructura de datos, 233 estructura jerrquica, 193 a estructurada (programacin), 85 o Exp, 32 expresin, 31 o extended, 493 factorial, 212 False, 36 Fibonacci, sucesin de, 216 o FIFO, 370 le, 287 n de archivo, 57, 285, 288 de l nea, 58, 294 for, 100 formato de salida de datos, 55 forward, 223 funcin, 159 o binaria, 30 inja, 30 interna, 30 monaria, 30 preja, 30 recursiva, 212 function, 159 Get, 291, 501 global,declaracin, 175 o goto, 49 hipertexto, 519

529

530 hoja de un rbol, 378 a Horner, regla de, 357 identicador, 49, 52 a mbito, 175 alcance, 180 global, 175 local, 175 oculto, 175 predenido, 49 visible, 175 if, 88, 89, 132 igualdad de conjuntos, 246 implementation, 503 in, 246 inclusin, 246 o independencia de subprogramas, 193 indeterminista algoritmo, 482 comportamiento, 482 ndice, 257 de un array, 255 de un bucle, 101 ingenier del software, 18 a inorden, recorrido en, 379 input, 58, 492 Insert, 497 instruccin, 52 o de seleccin, 88 o de asignacin, 52 o de escritura, 54 de lectura de datos, 57 de repeticin, 94 o iterativa, 94 integer, 28 interface, 503 interfaz, 10, 192, 193 interseccin, 245 o invariante de representacin, 444 o de un bucle, 14, 111

Indice alfabetico inversin, 136 o iteracin (instruccin), 94 o o label, 49 lectura (instruccin), 57 o Length, 496 LIFO, 362 lista, 352 cabeza de una, 352 circular, 388 de doble enlace, 387 doblemente enlazada, 387 enlazada, 352 literal, 51 llamada a un subprograma, 161, 170 llamadas lejanas, 499, 517 Ln, 32 local, declaracin, 161 o longInt, 493 look up-table, 484 matriz, 260, 263 MaxInt, 28 memoria dinmica, 335 a men, 93 u Merge Sort, 316 complejidad, 415 mod, 28, 493 modelo de von Neumann, 10 secuencial, 10 modularidad, 190 mdulo, 189 o New, 338 Newton-Raphson, mtodo de, 115 e nil, 343 nodo de una lista, 352 hijo, 378 padre, 378 not, 36

Indice alfabetico notacin o cient ca, 32 exponencial, 32 O mayscula, 404 u mayscula, 405 u polaca inversa, 369 postja, 369 mayscula, 405 u objeto global, 200 no local, 200 ocultacin de la informacin, 193 o o Odd, 38 of, 49 O mayscula, 404 u mayscula, 405 u or, 36 Ord, 36 ordenacin o burbuja, 329 de archivos, 322 de arrays, 306 insercin directa, 309 o intercambio directo, 310, 414 complejidad, 414 Merge Sort, 316 complejidad, 415 ordenacin rpida, 312 o a por mezcla, 316, 322, 415 complejidad, 415 Quick Sort, 312, 414 complejidad, 414 seleccin directa, 307 o ordinales, 236 organigrama, 125 output, 57, 492 overow, 30 Pack, 498 packed, 49 pal ndromo, 389

531 palabra reservada, 48 parmetro, 161 a de formato, 55 cticio, 165 formal, 165 paso de, 170 por referencia, 166 por variable, 166 por direccin, 166 o por valor, 166 real, 165 pedrisco, nmeros, 13, 119 u pertenencia, 246 pila, 362 recursiva, 215 pointer, 336 Pos, 496 postcondicin, 78 o postorden, recorrido en, 379 precedencia, 35 precondicin, 78 o Pred, 30, 36 preorden, recorrido en, 378 principio de autonom de subprogramas, a 181 de mxima localidad, 181 a procedimiento, 159 procedure, 159 profundidad, recorrido en, 378 Program, 60 programa estructurado, 134 programacin o con subprogramas, 157 dinmica, 455 a estructurada, 85 puntero, 336 Put, 289, 501 Quick Sort, 312 complejidad, 414

532 R, 32 ra de un rbol, 378 z a Random, 374, 483 Randomize, 483 Read, 57 ReadLn, 57 real, 32 record, 272 recursin, 211 o cruzada, 222 mutua, 222 renamiento por pasos sucesivos, 73 registro, 271 con variantes, 276 campo selector, 276 parte ja, 276 repeat, 98, 133 repeticin, 86 o Reset, 291 ReWrite, 289 Round, 34 s mbolo de decisin, 126 o de entrada de datos, 126 de procesamiento, 126 de salida de datos, 126 terminal, 125 salto de l nea ( ), 57 secuencial, 285 segmentacin, 190 o seleccin, 85 o instruccin, 88 o selector, 92 set, 244 seudoaleatoria (variable), 482 seudoaleatorios (nmeros), 483 u seudocdigo, 17 o shortInt, 493 simulacin o de colas acontecimiento cr tico, 487

Indice alfabetico de variables aleatorias, 484 Sin, 32 single, 493 sobrecarga, 34, 37 Sqr, 30, 32 SqRt, 32 Str, 497 string, 494 subprograma denicin de un, 161 o llamada a un, 161, 170 tabla de activacin de un, 215 o subrango, 238 subrutina, 161 Succ, 30, 36 sucesiones de recurrencia de primer orden, 419 de orden superior, 421 tabla de activacin, 215 o de seguimiento, 515 tamao de los datos, 397 n teorema de Bolzano, 113 test espectral, 490 text, 294 then, 88 mayscula, 405 u tipo abstracto de datos, 428 correccin, 443446 o especicacin, 440441 o implementacin, 434, 441443 o invariante de representacin, 444 o tipo de datos, 28, 52 annimo, 242 o bsico, 28 a con nombre, 240 enumerado, 235 estndar, 28 a estructurado, 233 ordinal, 39 predenido, 28

Indice alfabetico to, 101 top-down, 139 torres de Hanoi, 216 transformada inversa, 484 transicin, 8 o funcin de, 9 o trazado de un programa, 74 True, 36 Trunc, 34 Turbo Pascal entorno, 507 lenguaje, 491 type, 240 unidades, 435, 501 implementation, 503 interface, 503 union, 245 unit, 504 Unpack, 498 until, 98 uses, 504 Val, 497 valor, 52 variable, 52 aleatoria, 482 continua, 484 exponencial negativa, 489 simulacin de, 484 o uniforme, 484 de control, 101 puntero, 336 referida, 336 seudoaleatoria, 482 variantes, registro con, 276 vector, 260, 261 vectores paralelos, 318 vericacin, 14, 106 o visibilidad de un identicador, 180 vuelta atrs, 462 a while, 94, 132 with, 275, 278 word, 492 Write, 54 WriteLn, 54 Z, 28

533

S-ar putea să vă placă și