Vous êtes sur la page 1sur 342

CD interactivo

Mihaela Juganaru Mathieu en esta edicin


Subido por:

Libros de Ingeniera Qumica y ms

https://www.facebook.com/pages/Interfase-
IQ/146073555478947?ref=bookmarks

Si te gusta este libro y tienes la posibilidad,


cmpralo para apoyar al autor.
Introduccinalaprogramacin
Mihaela Juganaru Mathieu

PRIMERA EDICIN EBOOK

Mxico, 2014
Direccin editorial: Javier Enrique Callejas
Coordinadora editorial: Estela Delfn Ramrez
Supervisor de prepensa: Gerardo Briones Gonzlez
Diseo de portada: Juan Bernardo Rosado Sols
Fotografas: Thinkstockphoto

Revisin tcnica: Fabiola Ocampo Botello


Jos Snchez Jurez
Roberto de Luna Caballero
Escuela superior de Cmputo
Instituto Politcnico Nacional
Roland Jgou
Ecole Nationale Suprieure des Mines de St. Etienne

Introduccin a la programacin
Derechos reservados:
2014, Mihaela Juganaru Mathieu
2014, Grupo Editorial Patria, S.A. de C.V.
Renacimiento 180, Colonia San Juan Tlihuaca,
Delegacin Azcapotzalco, Cdigo Postal 02400, Mxico, D.F.

Miembro de la Cmara Nacional de la Industria Editorial Mexicana


Registro nm. 43

ISBN ebook: 978-607-438-920-3

Queda prohibida la reproduccin o transmisin total o parcial del contenido de la presente obra en cualesquiera
formas, sean electrnicas o mecnicas, sin el consentimiento previo y por escrito del editor.

Impreso en Mxico
Printed in Mxico

Primera edicin ebook: 2014


*VU[LUPKV
Agradecimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
Presentacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
Prlogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi

Captulo 1
Del algoritmo al programa
1.1 Programa, algoritmo, lenguaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Anlisis y comprensin de un problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Programas y paradigmas de programacin y lenguajes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Transformacin de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Variables, tipos y expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Tipos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Apuntadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.3 Pseudocdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Nociones bsicas: variables, tipos y expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Estructura general del pseudocdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Estructuras componentes del pseudocdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Uso de los arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Funciones y procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.4 Diagrama de ujo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Sntesis del captulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Captulo 2
Programacin en lenguaje C: conceptos bsicos
2.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.2 Mi primer programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.3 Estructura de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Declaraciones y deniciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.4 Variables y expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Identicadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Tipos y variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Expresiones con operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
2.5 Control de ujo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Proposiciones y bloques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Estructuras alternativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Estructuras iterativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Otras proposiciones de control de ujo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
2.6 Problemas resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Ecuacin de primer grado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

iii
Contenido

Clculo aproximado del nmero ureo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76


Clculo de una raz de ecuacin de tercer grado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Clculo de la fecha del da siguiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Ternas pitagricas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Juego de la bsqueda de un nmero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Sntesis del captulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Referencias de Internet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

Captulo 3
Variables, apuntadores, funciones y recursividad
3.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.2 Variables y apuntadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Variables locales y variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Variables dinmicas y variables estticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Apuntadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Tipo void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
3.3 Funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Denicin de una funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Llamadas de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Prototipo de una funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Un ejemplo completo: clculo de mximo comn divisor y de mnimo comn mltiplo . . . . . . . . . . . . . . . . . . . 111
Transmisin de los parmetros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
3.4 Funciones estndares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Algunas de las funciones de <stdlib.h> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
3.5 Funciones de entrada/salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
3.6 Recursividad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
3.7 Ejemplos de uso de funciones recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Escritura de un nmero entero positivo en base 2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Escritura de un nmero fraccionario en base 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Nmero en espejo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
3.8 Apuntadores de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
3.9 Funciones con nmero variable de parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Sntesis del captulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Referencias de Internet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

Captulo 4
Arreglos, cadenas de caracteres, archivos
4.1 Arreglos y matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Arreglos unidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Asignacin dinmica de memoria para los arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Problemas resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Juego del gato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
4.2 Caracteres y cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Tipo carcter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
iv
Contenido

Funciones estndares para el manejo de caracteres y cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183


Problema resuelto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
4.3 La funcin main. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
4.4 Archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Sntesis del captulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

Captulo 5
Estructuras de datos. Tipos abstractos

5.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202


5.2 Tipos de datos denidos por el usuario. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Nombramiento de los tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Denicin de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Trabajo con variables de tipo estructurado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Apuntadores de los tipos compuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Tipos estructurados referenciados por otros tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Tipos estructurados auto-referenciados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Tipo enumeracin y tipo unin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Problema resuelto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
5.3 Estructuras de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Listas circulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Listas doblemente ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
5.4 Tipos abstractos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Pilas, colas, dobles colas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Conjunto matemtico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Grafos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
5.5 Problemas resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Criba de Eratstenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Problema de Josephus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
Sntesis del captulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262

Captulo 6
Bsqueda. Seleccin. Ordenamiento

6.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270


6.2 Fundamentos tericos y descripcin de problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Relacin de orden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Marco general de estudio y estructuras de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Bsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Seleccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Ordenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
6.3 Arreglos y listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
Bsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
v
Introduccin a la programacin

Seleccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
Mantenimiento: insercin y eliminacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Ordenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
6.4 Montculos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Denicin y propiedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Implementacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Insercin y eliminacin de elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Ordenamiento por montculo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Sntesis de captulo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
Bibliografa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Ejercicios y problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320

Documentos adicionales que se ecuentran en el CD-ROM

Apndice 1
Representacin interna de la informacin

Apndice 2
Uso del software Raptor para la elaboracin de diagramas de ujo

Apndice 3
Del cdigo hasta el ejecutable: Compilacin y uso de libreras externas

Apndice 4
Complejidad de algoritmos: una corta introduccin

vi
(NYHKLJPTPLU[VZ

A mis hijos Marceline, Virgile y Guilhem; mi luz, mi energa y mi razn de vivir. Les pido perdn por el tiempo que no
estuve con ustedes para ir a caminar o para pasear en bicicleta; pero, el resultado es este libro.
Este libro es hoy una realidad y fue posible porque la ingeniera Estela Delfn Ramrez me dio la oportunidad de ha-
cerlo y me brind toda su conanza. Le agradezco mucho eso y que haya tenido mucha paciencia durante los meses que
tard el libro en estar listo.
Gracias a mi mam, quien me da el coraje necesario en cada llamada que me hace desde Rumania.
Tambin agradezco mucho a tres valiosos compaeros de trabajo: a los profesores Jean-Jaques Girardot y Roland
Jgou, por su apoyo y ayuda en la consulta de la bibliografa y en el intercambio de informacin acerca de los lenguajes
de programacin y la complejidad de los algoritmos, a Jean-Franois Tchebanoff, por su apoyo tcnico en varias ocasiones
durante la preparacin de este libro.
Agradezco de una forma muy especial a los profesores Fabiola Ocampo Botello, Roberto de Luna y Jos Snchez Ju-
rez de la ESCOM-IPN y a la profesora Irma Ardn de la UAM-Azcapotzalco, porque sus sugerencias, comentarios y obser-
vaciones fueron de gran ayuda para la realizacin de este proyecto. Una mencin especial al profesor Nicols Domnguez
Vergara, quien durante su gestin como jefe del Departamento de Sistemas, en la Universidad Autnoma Metropolitana,
unidad Azcapotzalco, me ofreci la inmensa oportunidad de trabajar en Mxico.
Agradezco tambin a la institucin que me form en Francia: Ecole Nationale Suprieure des Mines de St. Etienne.
Un pensamiento para todos mis alumnos, tanto a los que han estado conmigo en las aulas de Francia y como en las
aulas de Mxico, durante todos los aos de mi labor docente, quienes ayudaron con sus dudas, detalles tcnicos, gene-
ralidades y detalles tericos. Preguntar es un buen camino para demostrar, no que el alumno no sabe, sino para que el
profesor sepa conducirse y pueda lograr una mejor transmisin de sus conocimientos.
Gracias a mi esposo, mi hermana y a algunos de mis amigos, quienes soportaron y toleraron mis ausencias y mi falta
de disponibilidad.

vii
7YLZLU[HJP}U

Nadie nace sabiendo programar computadoras!

La programacin es un conocimiento que se aprende, como se aprende hacer reacciones qumicas en un laboratorio,
resolver ecuaciones matemticas o andar en bicicleta. El principal objetivo de este libro es mostrar que el aprendizaje de
la programacin puede ser fcil, si se empieza desde lo bsico y se contina de manera gradual, hasta que se es capaz
de escribir un programa que resuelve un problema.
La primera dicultad del aprendizaje de la programacin radica en la necesidad de aprender dos cosas bastante
diferentes de manera simultnea:

1. Un lenguaje para transmitir a la mquina las rdenes que se le quieren dar; esto es, el lenguaje de programacin
y una manera de pensar y concebir dar rdenes a la computadora.
2. El algoritmo traducido en programa.

El uso de un lenguaje siempre debe respetar un conjunto de reglas de sintaxis y de semntica; sin embargo, un
programa que es correcto desde el punto de vista del lenguaje no siempre va a realizar la tarea o a resolver el problema
que se quiere solucionar.
En el proceso de aprendizaje de un programa, si ya se conoce programar con algn otro lenguaje de programacin,
resulta ms fcil aprender otro lenguaje, sin muchas explicaciones, mejor an si el segundo lenguaje pertenece al mismo
paradigma de programacin. Por esta razn, considero que el lenguaje C es el ms ptimo de aprender como primer
lenguaje, ya que es un lenguaje imperativo, que permite un manejo muy preciso de conceptos importantes de la progra-
macin, como las variables, los apuntadores, las funciones y los arreglos, entre otros aspectos. Cualquier otro lenguaje de
programacin, como Pascal, los lenguajes de los software MATLAB o SCILAB o el lenguaje R, los lenguajes C++ o Java,
debern aprenderse despus de que se conoce y se maneja de manera ptima el lenguaje C que, como ya se dijo antes,
es mucho ms fcil de entender y manejar.
Este libro consta de seis captulos que lo guiarn en su aprendizaje en el conocimiento de la programacin; des-
de los aspectos bsicos, las bases de la programacin hasta llegar a los conceptos ms difciles de la programacin,
como los arreglos, las cadenas de caracteres y los archivos, las estructuras avanzadas de datos, y lo ms importante
de la programacin: el ordenamiento, la bsqueda y la seleccin de los lenguajes ms complejos. Toda esto se
describe y analiza en cada uno de los seis captulos de los que consta el libro:
1. Del algoritmo al programa
2. Programacin en Lenguaje C: Conceptos
3. Variables, apuntadores, funciones y recursividad
4. Arreglos, cadenas de caracteres, archivos
5. Estructuras de datos. Tipos abstractos
6. Bsqueda, Seleccin Ordenamiento
Para complementar el aprendizaje de la programacin, el libro est acompaado de un CD-ROM de apoyo, el cual con-
tiene mucha informacin adicional, que le ser de gran utilidad, como: la descripcin de todos los programas que se
estudian en el libro y funciones o fragmentos de cdigos. Cabe resaltar que en el CD-ROM se aborda cada uno de los
captulos del libro de manera independiente. Asimismo, tambin contiene informacin adicional, como algunas anima-
ciones escritas en Java por los algoritmos de ordenamiento. Tambin incluye cuatro apndices en los que se abordan y
resuelven problemas conexos tratados en el libro, como: la representacin de la informacin, el uso de un software para
realizar diagramas de ujo, detalles prcticos del uso softwares libres para programar en lenguaje C y un ltimo apndice
sobre la complejidad de los algoritmos.
ix
Introduccin a la programacin

La realizacin de este libro es la sntesis de mi trabajo como profesora de computacin desde el ao 2000 hasta
el 2008 en Francia y de los tres trimestres que curs en la Universidad Autnoma Metropolitana, unidad Azcapotzalco.
Quiero agradecer una vez ms a todo el equipo de Grupo Editorial Patria por su conanza y su apoyo continuo, as
como por su eciencia.
Este libro est dirigido, en primer lugar, a aquellos alumnos de las escuelas de carreras tcnicas y cientcas, quienes
tienen que cursar obligatoriamente la asignatura de introduccin a la programacin, y en segundo lugar a los alumnos
universitarios que cursan una carrera de computacin, para ellos, la lectura a profundidad de los ltimos tres captulos les
ser de gran utilidad.
El libro puede ser utilizado con toda conanza por los profesores de computacin como un soporte metodolgico o
como una coleccin de problemas resueltos, con el n de utilizarlos en clase o como una amplia coleccin de problemas
propuestos para tareas o exmenes de evaluacin.

La autora

x
7Y}SVNV

El libro Introduccin a la Programacin ofrece las generalidades bsicas que deben conocer aquellas personas que
deseen introducirse en la programacin de computadoras; pues, mediante aspectos visuales, como la presentacin de
diagramas de ujo, y de aspectos secuenciales, como los algoritmos, la autora expone al lector la lgica de la programa-
cin estructurada, para luego, de manera ordenada, explicar, las generalidades bsicas del lenguaje de programacin C,
a travs de ejemplos concretos, as como los temas que resultan esenciales, bsicos y necesarios para el desarrollo de
programas de computadoras en este lenguaje de programacin. Asimismo, introduce al lector en el estudio de temas
de programacin de computadoras ms avanzados, como las estructuras de datos, las formas de ordenamiento, la selec-
cin y la bsqueda.
La motivacin que da surgimiento a este libro, deriva de la experiencia y el deseo de la autora de ofrecer un material
educativo que incorpore desde los aspectos algortmicos hasta la puesta en marcha de un programa de computadora
escrito en lenguaje C, a travs de ejemplos expresados de forma didctica, lo cuales permiten a estudiantes y profesores
practicar la ejecucin de los diagramas de ujo desarrollados, as como los resultados de los programas de computadora
escritos en lenguaje C, mediante una explicacin clara y sucinta de los ejemplos resueltos a lo largo del libro.
Una de las principales asignaturas en las que puede ser utilizado este libro es: Introduccin a la programacin de
computadoras, debido a que presenta, de manera general, la lgica de desarrollo de diagramas de ujo y algoritmos,
proporcionando la referencia de una herramienta computacional, que permite a los estudiantes el anlisis de la lgica de
solucin de problemas, antes de la codicacin. Otra de las asignaturas en la que resulta de gran utilidad este material
es: Programacin en lenguaje C, ya que aborda desde las estructuras bsicas hasta los temas esenciales que son parte
fundamental de este lenguaje, como los apuntadores y el manejo de ujos de entrada y salida.
La introduccin a la comprensin de las estructuras de datos, mtodos de seleccin, ordenamiento y bsqueda, son
aspectos necesarios para el estudio de las estructuras de datos. Asimismo, incorpora temas y ejemplos relacionados con
los sistemas numricos, necesarios para la comprensin de las formas de almacenamiento y el manejo de la memoria
dinmica de la computadora.

El contenido de este libro se estructura en seis captulos:


1. Del algoritmo al programa
2. Programacin en lenguaje C: Conceptos bsicos
3. Variables, apuntadores, funciones y recursividad
4. Arreglos, cadenas de caracteres, archivos
5. Estructuras de datos. Tipos abstractos
6. Bsqueda. Seleccin. Ordenamiento

En cada uno de los seis captulos que conforman este libro, la autora expone los aspectos tericos y prcticos, me-
diante ejemplos resueltos y completos, adems de que tambin presenta una gua de estudio para el estudiante para
el desarrollo de programas, que le facilita el aprendizaje de la programacin de computadoras y reforzar los aspectos
tericos y prcticos expuestos en esta obra.

Fabiola Ocampo Botello


Departamento de Ingeniera en Sistemas Computacionales
Escuela Superior de Cmputo
Instituto Politcnico Nacional
Mxico, D.F.

xi
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB
1 +LSHSNVYP[TVHSWYVNYHTH

Contenido
1.1 Programa, algoritmo, lenguaje  $IAGRAMADEmUJO
Algoritmo Sntesis del captulo
Anlisis y comprensin de un problema Bibliografa
Programas y paradigmas de programacin y lenguajes Ejercicios y problemas
Transformacin de un programa
1.2 Variables, tipos y expresiones
Variables
Tipos
Apuntadores
Objetivos
Expresiones
Funciones s #OMPRENDERQUELACOMPUTADORAFUNCIONACONPROGRAMASY
QUEADEMSESCAPAZDEREALIZARPROGRAMAS
1.3 Pseudocdigo
Nociones bsicas: variables, tipos y expresiones s #ONOCERYCOMPRENDERLASNOCIONESFUNDAMENTALESDELA
Estructura general del pseudocdigo programacin: variable, tipo y funcin.
Estructuras componentes del pseudocdigo s %STUDIARLOSPRINCIPALESPARADIGMASDELAPROGRAMACIN
Uso de los arreglos s 0ROPONERALGORITMOSSIMPLESPARALARESOLUCINDEDIVERSASTAREAS
Funciones y procedimientos s % XPRESARALGORITMOSCONDIAGRAMASDEmUJOOCON
pseudocdigo.
1
*OUSPEVDDJOBMBQSPHSBNBDJO

1.1 Programa, algoritmo, lenguaje

De acuerdo con la naturaleza del funcionamiento de las computadoras, se dice que estas siempre ejecutan rdenes en
un formato que les resulta inteligible; dichas rdenes se agrupan en programas, conocidos como software, el cual, para
su estudio, a su vez, se divide en dos partes: el formato de representacin interno de los programas, que constituye el
lenguaje mquina o cdigo ejecutable, y el formato de presentacin externa, que es un archivo o un conjunto de archi-
vos, que puede o no estar en un formato que puede ser visto/ledo por el usuario (es decir, en un formato que respeta
las reglas).
Para ejecutar lo que el usuario desea hacer en su computadora, o bien para resolver un problema especco, este pre-
cisa buscar un software que realice o ejecute con exactitud la tarea que se ha planteado o elaborar y desarrollar (escribir)
un programa que la realice. El trabajo de elaboracin de un programa se denomina programacin. Pero la programacin
no es solo el trabajo de escritura del cdigo, sino todo un conjunto de tareas que se deben cumplir, a n de que el cdigo
que se escribi resulte correcto y robusto, y cumpla con el objetivo o los objetivos para los que fue creado.
Las armaciones que se derivan de lo anterior son varias:
s Conocer las herramientas, los formalismos y los mtodos para transformar un problema en un programa escrito en
un lenguaje (que posiblemente no ser el lenguaje mquina), y para que dicho programa pueda ser transformado
en un cdigo ejecutable.
s Saber transformar el problema inicial en un algoritmo y luego en un programa.
La primera armacin es genrica y se considera para varias categoras de problemas para resolver. Por su parte, la se-
gunda es especca de un problema determinado que se tiene que resolver, para lo cual existen diversas metodologas
especcas de resolucin para este tipo de problemas. Para los casos de problemas muy generales, en ocasiones existen
mtodos conocidos que solo se adaptan a un problema en particular. El mtodo es, por lo general, un algoritmo o una
tcnica de programacin.

Algoritmo

Un algoritmo constituye una lista bien denida, ordenada y nita de operaciones, que permite encontrar la solucin a un
problema determinado. Dado un estado inicial y una entrada, es a travs de pasos sucesivos y bien denidos que se
llega a un estado nal, en el que se obtiene una solucin (si hay varias) o la solucin (si es nica).
Ejemplo
Problema: Gestionar la lista de compras que una empresa realiza durante un mes.

Solucin
Para resolver este problema de gestin muy general, se cuenta con las herramientas que se utilizan en otros problemas
que ya tienen una resolucin en la empresa (por ejemplo, un programa en Java con los datos en una base de datos).
As, para la solucin del problema planteado, se proponen dos opciones:

s Usar la base de datos de trabajo para guardar, tambin en esta, la lista de productos que se requiere comprar.
s Guardar una lista en entradas que se actualiza cada vez que se captura o se incluye un nuevo producto que la
empresa necesita, y que se borra o elimina al momento que el producto ya est abastecido, y en salidas, cada vez
que algn empleado necesite una impresin de dicha lista.

En este ejemplo, el algoritmo global de resolucin se compone de diversos pasos sucesivos de dilogo con el usuario
(un empleado de la empresa), para mantener actualizada la lista de productos necesarios; asimismo, en pasos si-
guientes se precisa hacer una insercin y/o una eliminacin o borrado de los productos (elementos) de la lista o una
impresin en una forma legible.
2
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Un algoritmo puede ser expresado en:


s Lenguaje natural (a veces, este no resulta muy claro, pero es muy til para problemas simples)
s Pseudocdigo
s Diagramas de ujo
s Programas
El uso de algn elemento de la lista anterior para la expresin de un algoritmo, se hace segn el nivel de descripcin de
dicho algoritmo. Es evidente que el lenguaje natural es de mayor utilidad para transmitir las ideas del algoritmo. Al con-
trario, un programa es difcil de entender por simple lectura, aun por una persona que conoce el lenguaje del programa,
e imposible para aquellas que no lo conocen.
El pseudocdigo y los diagramas de ujo, en cambio, se sitan en un punto intermedio de comprensin, entre
el lenguaje natural y un programa. Estas dos herramientas poseen un poder de expresin equivalente; no obstante, los
diagramas de ujo tienen la ventaja de ser ms grcos y visuales.
Con base en el ejemplo anterior, se puede armar que la parte de solucin expresada en lenguaje natural tiene algu-
nas ambigedades para el usuario que no es el programador; por ejemplo, qu signica la expresin de pasos sucesivos
de dilogo con el usuario? Aunque, en ocasiones, tambin presenta ambigedades hasta para el propio programador;
por ejemplo, cules son los datos en una base de datos?, una base de datos es relacional o de otro modelo?, cul
interfaz?, cmo se manejan las lecturas/escritura en dicha base de datos?
Las respuestas a las interrogantes anteriores se expresan de la siguiente forma:

s La primera ambigedad (pasos sucesivos) se debe expresar lo ms detallada posible por el destinatario del pro-
grama (el usuario).
s Los otros cuestionamientos son de detalles tcnicos.

La descripcin de un algoritmo usualmente se realiza en tres niveles:

1. Descripcin de alto nivel. El primer paso consiste en la descripcin del problema; luego, se selecciona un
modelo matemtico y se explica el algoritmo de manera verbal, posiblemente con ilustraciones, pero omitiendo
detalles.
2. Descripcin formal. En este nivel se usa un pseudocdigo o diagrama de ujo para describir la secuencia de
pasos que conducen a la solucin.
3. Implementacin. Por ltimo, en este nivel se muestra el algoritmo expresado en un lenguaje de programacin
especco, o algn objeto capaz de llevar a cabo instrucciones.

Para llegar a la implementacin, primero se deben tener descripciones de alto nivel o formalmente explcitas, sobre todo
cuando el trabajo de desarrollo de un algoritmo se hace en grupo.

Anlisis y comprensin de un problema

En el ejemplo que se present acerca de la necesidad de una empresa de gestionar la lista de compras que
efecta durante un mes, se realiz, de forma informal y muy esquemtica, la presentacin de un problema
que ocurre comnmente, y se indic, de forma muy literal, cmo se puede resolver, aunque sin bastantes detalles. No
obstante, tambin es claro que para la resolucin de este problema debemos saber cmo insertar, borrar u ordenar los
elementos de una lista. As, para cada aspecto del problema se debe buscar un algoritmo que lo resuelva; por ejem-
plo, un algoritmo de insercin, otro para borrar de la lista un elemento y, si la lista no est explcita en la memoria de la
computadora, un algoritmo para ordenar los elementos en una forma deseada.
Por lo general, un problema se descompone en subproblemas; por tanto, un algoritmo expresa la resolucin de un
problema (elemental o no).
3
*OUSPEVDDJOBMBQSPHSBNBDJO

Las etapas de desarrollo de un algoritmo, con base en la lgica, son las siguientes:

1. Denicin. En esta etapa se especica el propsito del algoritmo y se ofrece una denicin clara del problema
por resolver. Adems, aqu tambin se establece lo que se pretende lograr con su solucin.
2. Anlisis. En este punto se analiza el problema y sus caractersticas, y se determinan las entradas y salidas del
problema. De igual modo, tambin se realiza una investigacin sobre si ya se conoce alguna o varias soluciones
de este. En el caso de que ya se conozcan varias soluciones, entonces se determina cul es la ms conveniente
para el problema que estamos tratando. Si no se conoce ninguna, o no nos satisfacen las soluciones existentes,
se propone una nueva.
3. Diseo. Aqu es donde se plasma la solucin del problema. Con ese n, se emplea una herramienta de diseo,
que consiste en el diagrama de ujo y el pseudocdigo.
4. Implementacin. En este ltimo paso es donde se realiza o se ve concretado el programa y, por ende, se hacen
varias pruebas.

En cada una de las etapas especicadas antes, se utiliza un tipo de descripcin conveniente e inteligible para cada uno
de los participantes en el proceso de concepcin y realizacin del algoritmo. Hoy en da, existe una rama de las ciencias de
la computacin que se ocupa del manejo de los proyectos de desarrollo de programas: la ingeniera de software.1
En el ejemplo citado antes, en el que se plantea el desarrollo de un programa de lista de compras, el enunciado
constituye la denicin del problema, mientras que la fase de anlisis pone en evidencia las entradas y las salidas, el
modo operativo, el formato de la base de datos y su ubicacin, los dispositivos de acceso a los datos contenidos y algunos
datos de volumetra.
Entre los principales datos del ejemplo, que seran tratados en esta fase, destacan: el tamao mximo posible de la
lista de compras y el tamao del catlogo de productos, en donde el usuario deber buscar el producto que se inserta en
la lista de compras. En el diagrama de la gura 1.1 se representa un diagrama de la actividad posible del usuario.

Imprimir la lista

Borrar toda la lista


Borrar
Borrar un elemento

Insertar un elemento

Figura 1.1
Asimismo, en esta fase es fcil distinguir la necesidad de desarrollar una manera de guardar la lista de compras de mane-
ra permanente; idealmente, esta puede generarse en un da. As, al iniciar las labores del da se puede cargar o capturar
la lista y durante el transcurso de la jornada laboral se pueden hacer diversas actualizaciones (insercin y borrado). En
esta fase de anlisis, tambin se indica el funcionamiento global del programa.
Para el mismo ejemplo, en la fase de diseo se plantear un diagrama de ujo de la totalidad del programa; adems
de que aqu tambin se pone en evidencia la solucin que se eligi para guardar los nuevos datos (la lista de compra) y
qu formato se utilizar.

Ejemplo

Supngase un nmero entero N del que se requiere probar si es divisible o no entre 3.

1
Para mayor informacin vase el libro Sommerville, Ian, Ingeniera de software, 6a. ed., Pearson Educacin, Mxico, 2001.
4
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

En este caso, la denicin del problema es el enunciado mismo: Probar si un nmero entero N es o no divisible
entre 3. Este caso se trata de un problema muy simple de aritmtica.
En la etapa de anlisis, identicamos las entradas y las salidas:
Entrada: Un nmero entero N.
Salida: Una respuesta (S o No).
Para la resolucin del problema de este ejemplo, conocemos la denicin de la divisibilidad: un nmero N es divisible
entre otro nmero k, si la divisin N k es exacta (o el resto es 0).
Asimismo, existen mtodos que presentan diferentes grados de dicultad para un ser humano:
s Realizar la divisin n k y comprobar si es exacta.
s Efectuar la suma de las cifras que componen el nmero en base 10 y vericar si el nmero es divisible entre 3.
De acuerdo con la naturaleza del ser humano, l puede aplicar con mayor facilidad el segundo mtodo, de-
bido a que la divisin de la suma de las cifras y el clculo mismo de la suma son ms simples que la divisin
inicial; sin embargo, para la computadora es lo mismo realizar la divisin de 78564589 entre 3 o la divi-
sin de 52 entre 3. En el segundo caso, en cambio, es necesario hacer la extraccin de las cifras y luego la suma de las
cifras; entonces, la resolucin del problema es simple, como lo establecen los siguientes pasos:

1. Se hace la lectura del nmero N.


2. Se toma el resto de la divisin de N entre 3 (la operacin mdulo N %3).
3. Segn el valor del resto, se escribe: S o No.

En la etapa de n de anlisis, los pasos a seguir resultan muy claros; en tanto, en el paso de diseo se formalizan an
ms y lo describen sin ninguna ambigedad. Durante la implentacin (la ltima etapa), es preciso saber cmo introducir
los valores de entrada en la computadora y cmo hacer el programa.
En el siguiente apartado se estudia cules son dichos valores de entrada, qu es un lenguaje de programacin, qu
signica programa y cmo se transforma un programa en cdigo mquina.

Programas y paradigmas de programacin y lenguajes

Un programa informtico se dene como un conjunto de instrucciones que, una vez ejecutado, realiza una o varias
tareas en una computadora. De esta forma, sin programas, una computadora no puede realizar las actividades para las
que fue diseada y creada.
El conjunto general de programas que posee una computadora se denomina software, trmino que se utiliza para
denir al equipamiento o soporte lgico de una computadora.
Un programa se escribe con instrucciones en un lenguaje de programacin, el cual, a su vez, est denido por su
sintaxis, que establece e indica las reglas de escritura (la gramtica), y por la semntica de los tipos de datos, instrucciones,
deniciones, y todos los otros elementos que constituyen un programa.
Un lenguaje de programacin es un caso particular del lenguaje informtico; este ltimo permite hacer programas,
pero tambin describir datos, conguraciones fsicas y protocolos de comunicacin entre equipos y programas.

C, C++, Pascal, ADA, FORTRAN, LISP, SCHEME,


PROLOG, SQL, Xquery, Java, entre otros
Lenguaje de programacin

HTML, XML, RDF, Latex,


Lenguaje informtico SVG, entre otros

Figura 1.2 Tipos de lenguajes.


5
*OUSPEVDDJOBMBQSPHSBNBDJO

Si un programa est escrito en un lenguaje de programacin comprensible para el ser humano, se le llama cdigo fuente.
A su vez, el cdigo fuente se puede convertir en un archivo ejecutable (cdigo mquina) con la ayuda de un compilador,
aunque tambin puede ser ejecutado de inmediato a travs de un intrprete.
A su vez, un paradigma de programacin provee (y determina) la visin y los mtodos de un programador en la
construccin de un programa o subprograma. Existen diferentes paradigmas que derivan en mltiples y variados estilos
de programacin y en diferentes formas de solucin de problemas:
s 0ARADIGMAIMPERATIVO
s En este paradigma se impone que cualquier programa es una secuencia de instrucciones o comandos que se
ejecutan siguiendo un orden de arriba hacia abajo; este nico enlace del programa se interrumpe exclusivamente
para ejecutar otros subprogramas o funciones, despus de lo cual se regresa al punto de interrupcin.
s 0ARADIGMAESTRUCTURADO
s Este paradigma es un caso particular de paradigma imperativo, por lo que se imponen nicamente algunas es-
tructuras de cdigo, prohibiendo una continuacin del clculo de manera catica. Por ejemplo, se impone que las
instrucciones sean agrupadas en bloques (procedimientos y funciones) que comunican; por tanto, el cdigo que
se repite tiene la forma de un ciclo (loop, en ingls), gobernado por una condicin lgica.
s 0ARADIGMADECLARATIVO
s Un programa describe el problema a solucionar y la manera de resolverlo, pero no indica el orden de las acciones
u operaciones que se deben seguir. En este caso, hay dos paradigmas principales:
0ARADIGMAFUNCIONAL Conforme a este, todo se describe como una funcin.
0ARADIGMALGICO De acuerdo con este, todo se describe como un predicado lgico.
Un problema a resolver se expresa como una llamada de una funcin o un predicado lgico, y su resolucin depende de
la descripcin introducida en las funciones o los predicados.

s 0ARADIGMAORIENTADOAOBJETOS
s Existen tres principios fundamentales que gobiernan este tipo de programacin:
%NCAPSULACIN En este principio se encapsulan datos, estados, operaciones y, en ocasiones, tambin eventos,
en objetos. El cdigo sera ejecutado, entonces, segn la ocurrencia de eventos o de creacin/destruccin de
instancia de objetos.
0ROTOTIPOS CLASESYHERENCIAS El prototipo y la clase son las abstracciones del objeto; otros prototipos se
denen de acuerdo con un prototipo existente.
4IPIlCACINYPOLIMORlSMO Constituyen la comprobacin del tipo con respecto a la jerarqua de las clases.
s 0ARADIGMADEPROGRAMACINPOREVENTOS
s Un programa se concibe como una iteracin innita con dos objetivos: detectar los eventos y establecer el clculo
capaz de tratar el evento.
s 0ARADIGMASPARALELO DISTRIBUIDOYCONCURRENTE
Un programa no se realiza con una sola unidad de cmputo, sino que emplea varias unidades de clculo (reales en caso
paralelo y distribuido), las cuales pueden ser procesadores o computadoras y/o unidades centrales del mismo procesa-
dor. En el caso de este paradigma, el programa se corta en subprogramas o rutinas que se ejecutan de manera indepen-
diente sobre otras unidades de cmputo, ya sea de modo sncrono o asncrono, compartiendo o no la misma memoria.
Un lenguaje de programacin puede vericar uno o ms paradigmas. Por ejemplo, el lenguaje Java comprueba el paradig-
ma orientado a objetos y el cdigo que compone la parte de mtodos de los objetos verica el paradigma estructurado.
Por su parte, el lenguaje de programacin de pginas de Internet, JavaScript, funciona/trabaja conjuntamente con las p-
ginas y el servidor del sitio; por tanto, es un lenguaje, inspirado por Java, que comprueba el paradigma de programacin
orientado a objetos, al tiempo que tambin funciona segn el paradigma de la programacin por eventos.
Algunos ejemplos de lenguajes de programacin imperativos son: lenguaje mquina, lenguaje ensamblador, C, For-
tran, Cobol, Pascal, Ada, C++, C#, Java. A excepcin del lenguaje mquina y el lenguaje ensamblador, los otros constituyen
lenguajes estructurados.
6
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Entre los lenguajes declarativos ms conocidos son: LISP (Scheme), Prolog, SQL, Smalltalk, Datalog. Asimismo, el
lenguaje Java tambin puede ser considerado como un lenguaje declarativo.
Como lenguajes orientados a objetos existen: Simula, C++, Java, C#(.Net), Python.
Histricamente, las primeras computadoras se programaban manualmente (de forma fsica), cambiando los disposi-
tivos fsicos del equipo de cmputo; por ejemplo, la mquina analtica de Charles Babbage, programada por Ada Byron,
o la computadora ENIAC.

Al principio, en los albores de la computacin, se introdujo el lenguaje ensamblador, que codica, con cdigos li-
terales, las operaciones del procesador, los registros y las direcciones de memoria. En la actualidad, algunas mquinas
virtuales an se pueden programar en un lenguaje ensamblador adaptado. Otro dominio actual, por el cual se utiliza el
lenguaje ensamblador, es el desarrollo de interfaces especcas con dispositivos de entrada/salida de los datos. La prin-
cipal ventaja del lenguaje ensamblador es un cdigo ecaz, muy cercano al lenguaje mquina. En tanto, las principales
desventajas o defectos que presenta el lenguaje ensamblador son, en principio, su verbosidad, esto es, para escribir
clculos, que parecen simples, se escriben pginas y pginas en el lenguaje ensamblador, y la dicultad de corregir los
errores que pueden parecer errores de concepcin del programa o errores de compilacin.
Un gran avance en materia de programacin fue la aparicin de los lenguajes de programacin de alto nivel, por
medio de los cuales se simplic la escritura de cdigo.
En el siguiente esquema se observa un fragmento de un programa escrito en lenguaje C, una parte del cdigo en
lenguaje ensamblador y una imagen de la memoria que contiene el cdigo mquina.

int main() LFB2: 0x100000f20 <main+4>: 0x0afc45c7 0xc7000000 0x0023f845 0x458b0000


{ pushq %rbp

int a=10; LCFI0: 0x100000f30 <main+20>: 0xfc4503f8 0xb8f44589 0x00000001 0x25ffc3c9

int b, c; movq %rsp, %rbp 0x100000f40 <dyld_stub_exit+2>: 0x000000f4 0xe51d8d4c 0x41000000


0xd525ff53
b = 35; LCFI1:

c = a + b; movl $10, -4(%rbp) 0x100000f50 <stub helpers+12>: 0x90000000 0x00000068 0xffe6e900


0x0000ffff
return 1; movl $35, -8(%rbp)
0x100000f60: 0x00000001 0x0000001c 0x00000001 0x00000020
} movl -8(%rbp), %eax
0x100000f70: 0x00000000 0x00000020 0x00000002 0x00000000
addl -4(%rbp), %eax
0x100000f80: 0x00000000 0x00000038 0x00000038 0x00001001
movl %eax, -12(%rbp)

movl $1, %eax 0x100000f90: 0x00000000 0x00000038 0x00000003 0x0003000c

leave

ret

En la corta historia de la computacin (corta en comparacin con otras ciencias y reas del conocimiento humano), han
sido propuestos varios lenguajes, pero solo algunos cuantos han sido utilizados en realidad.2

En la gura 1.3 se observa una lista de lenguajes de programacin, ordenados cronolgicamente (en azul se desta-
can los lenguajes de descripcin de datos ms importantes y el protocolo fundamental de Internet):3

2
Algunos lenguajes, como ALGOL, para la programacin, o SGML, para la descripcin de los datos, fueron propuestos; sin
embargo, tcnicamente, nunca se desarrollaron el compilador ni las herramientas necesarias para trabajar con la versin completa.
Estos lenguajes se consideran importantes por su incursin en la historia de la computacin y porque constituyen el origen de
otros lenguajes de programacin (como PASCAL) o HTML y XML.
3
Consultar la pgina http://oreilly.com/news/graphics/prog_lang_poster.pdf, para observar un esquema que aborda la historia de los
lenguajes, sus versiones y su liacin.
7
*OUSPEVDDJOBMBQSPHSBNBDJO

Java
Script
FORTRAN COBOL APL 1968 PROLOG SQL Eiffel HTTP Ruby PHP XML
1954 1959 1962 logo 1972 1978 1985 1991 1993 1995 1997

1950 1958 1964 1969 1973 1983 Ada 1987 1991 1994 2000 2001
Lenguaje LISP BASIC SGPIL C C++ PERL Python Common C# Kylix
ensamblador ALGOL PL/I Objective C Java Lisp

Figura 1.3 Lnea de tiempo de los lenguajes de programacin.

Esta proliferacin y riqueza de lenguajes de programacin tiene su origen en:

s El importante desarrollo de software, el cual, cada dos aos, ofreci un poder de clculo multiplicado y de almace-
namiento de datos por n, por el mismo precio.
s La diversicacin de los campos de aplicacin. En un principio, la mayor necesidad de los lenguajes de progra-
macin era tratar grandes volmenes de datos e importantes clculos numricos; sin embargo, las necesidades
cambiaron, por lo que despus aparecieron aplicaciones de inteligencia articial, de manejo de bases de datos, de
tratamiento y de generacin de imgenes.
s La teora de la computacin en un amplio sentido. Por ejemplo, los dos casos siguientes:
La teora de Codd, de lgebras relacionales (creada en la dcada de 1970), que permiti el desarrollo del len-
guaje SQL para el manejo de las bases de datos relacionales.
El trabajo de MacCarthy (1956) sobre las funciones recursivas, que permitieron el desarrollo del lenguaje LISP.
s Las nuevas metodologas de ingeniera de software. Aqu, lo ms importante es el uso extendido del paradigma
orientado a objetos.
s La implementacin. El uso prctico de un lenguaje permite distinguir las limitaciones de uso e impulsa las nuevas
proposiciones para su mejoramiento.

Hoy en da, an se trabaja en el desarrollo de lenguajes de programacin, pero desde dos perspectivas bsicas: proponer
nuevas soluciones a los problemas actuales4 y mejorar algunos de los lenguajes actuales, proponiendo nuevos estndares.
En la actualidad, el uso de un lenguaje de programacin est condicionado por:

s El conocimiento del lenguaje en cuestin; es decir, su sintaxis y la semntica de los conceptos y las instrucciones
que lo componen.
s El tipo de problema a resolver. Por ejemplo, para consultar datos que se guardan en un formato especco en
una base de datos o en una base de conocimiento se utilizan, comnmente, los lenguajes de tipo declarativo,
donde se caracterizan los datos que se esperan en salida, como SQL para la base de datos relacional, PROLOG
para la base de conocimiento, XQuery y XSLT para colecciones de datos en el formato XML. En otro ejem-
plo, para dar las rdenes de instalacin de software, es conveniente escribir programas en el shell del sistema
operativo.
s El derecho y la posibilidad material de utilizar un compilador o intrprete de dicho lenguaje, ya que estos tipos de
software (compilador, taller de desarrollo, intrprete) suelen tener un costo monetario o licencias restrictivas.
s La conguracin fsica que est disponible. Por ejemplo, si est disponible una arquitectura multiprocesador, sera
ms conveniente utilizar un lenguaje de tipo C o FORTRAN, por medio de los cuales se abstendra de realizarse el
clculo paralelo, o emplear herramientas de paralelizacin automtica. En el caso de que el programa tuviera que

4
Por ejemplo, un grupo de trabajo de W3C an trabaja en el desarrollo de un lenguaje de manejo y actualizacin de colecciones de
archivos XML.
8
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

explorar y comunicar con una interfaz de un equipo raro, como una mquina de produccin o un dispositivo de
medicin, es preferible escribirlo en un cdigo del lenguaje ensamblador.
s La conguracin del software que est disponible o que se impone por la construccin del programa y el uso ulte-
rior del producto nito. Por ejemplo, para aprender la programacin es mejor iniciar con un lenguaje de alto nivel
del paradigma imperativo de tipo C o PASCAL. En el caso de que el destinatario del programa utilizara el sistema
operativo de plataforma mvil con sistema MAC OS, las herramientas para desarrollar aplicaciones imponen usar el
framework COCOA o XCode y el lenguaje de programacin Objective C.

Tambin es posible que al interior de un programa sean introducidas algunas otras funciones de diferente naturaleza,
las cuales son escritas en otros lenguajes de programacin o en fragmentos de cdigos de otro lenguaje (por lo gene-
ral, en un lenguaje declarativo de interrogacin de base de datos). En un proyecto de desarrollo de programa, se elige
al menos un lenguaje de programacin, pero resulta tcnicamente posible elegir otro u otros lenguajes.

Transformacin de un programa
Un programa de usuario recorre el siguiente camino hasta su ejecucin:
s %DICIN
s Con un editor de texto se escribe el programa en el lenguaje elegido.
s #OMPILACIN
s En lenguaje de alto nivel, el cdigo fuente se transforma en instrucciones para la mquina (cdigo objeto o cdigo
ejecutable).
s %NLAZADO
s Un ejecutable se construye con cdigos objeto (uno o ms) y libreras de funciones, entre otros.
El resultado de este proceso es un cdigo ejecutable directo para la mquina.
Pero tambin existe el modo interpretacin de ejecucin, en el cual cada frase, instruccin, orden o consulta, escritos
en cdigo fuente, se transforma, poco a poco, en rdenes, ya sea directamente por el procesador, por otro software o por
la mquina abstracta. Este es el caso del intrprete del lenguaje PROLOG, del Shell y del motor de resolucin de consultas
(SQL, por las bases de datos). En el mismo caso tambin se encuentra el lenguaje Java en modo interpretado, en donde
el cdigo transformado (clases o archivos) es interpretado por la Mquina Virtual Java.

Editor

Cdigo fuente
Editor

Compilador (y ms)
Cdigo fuente

Cdigo ejecutable Ejecucin

Ejecucin

Figura 1.4

En gran parte de los casos, el compilador o el intrprete realiza algunas transformaciones a los programas (optimizacin
de cdigo, detecciones de n de programa, paralelizacin de cdigo, etc.), para obtener un cdigo mquina ms rpido
o ms adaptado a la mquina a la cual est destinado.
9
*OUSPEVDDJOBMBQSPHSBNBDJO

Para la mayora de los lenguajes, hay herramientas completas que permiten, en ambientes amigables, la edicin y la
realizacin de todos los pasos hasta la construccin del ejecutable de una manera implcita.
Es muy probable que un programa que se compila y se ejecuta por primera vez tenga errores de compilacin. Tam-
bin es probable que, despus de un tiempo de ejecucin, el programa tenga errores lgicos de ejecucin; en este caso,
se regresa a la edicin del cdigo fuente inicial, con el n de corregir los errores, y luego se desarrollan las otras etapas,
hasta la construccin del ejecutable (vase gura 1.5).

Editor

Cdigo fuente

Compilador (y ms)
Si hay
errores
Cdigo ejecutable

Ejecucin

Figura 1.5

A lo largo de este captulo se presentan el pseudocdigo y los diagramas de ujo como herramientas para el diseo de
los algoritmos. Por su parte, el lenguaje C, se aborda con amplitud ms adelante en otros captulos, ya que se trata de un
lenguaje imperativo y estructurado, considerado un lenguaje de alto nivel.

1.2 Variables, tipos y expresiones

El objetivo general de un programa es transformar datos en resultados tiles para el usuario. Los datos estn almacenados
en la memoria principal o en la memoria secundaria, ya sea de manera temporal (durante toda la ejecucin del programa
o durante una parte del tiempo de ejecucin) o de manera permanente. En la mayora de los lenguajes de programacin,
los datos son de diferentes tipos, aparecen en expresiones o en las llamadas de funciones y se manejan a travs del uso
de variables.

Variables
El formato de representacin y de estructuracin de los datos depende del paradigma del lenguaje de programacin y de
la opcin que el programador ha elegido para representar los datos. En el paradigma imperativo y en el caso de algunos
otros paradigmas (por ejemplo, lenguaje PROLOG) existe una nocin bsica comn para el manejo de los datos: la no-
cin de variable. La ventaja de las variables es que almacenan datos de entrada, de salida o intermedios. No obstante,
existen lenguajes de tipo SQL o XPath que no implementan la nocin de variable.
Por lo general, en cada programa aparece al menos una variable, lo que signica que en cada programa hay una
zona de memoria con un tamao jo que contiene un valor de tipo preciso; por ejemplo, un entero representado en
forma binaria de Ca25 sobre 4 bytes, o una cadena de caracteres de un tamao mximo de 255.

5
Vase el apndice 1 en el CD-ROM.
10
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Cada variable debe tener:

s Un tamao de memoria ocupada y un modo de representacin interna. Por ejemplo, un punto otante simple
precisin sobre 4 bytes o cadenas de caracteres de 100 + 1 caracteres.
s Un conjunto de operadores y de tratamientos especcos que pueden aplicarse a la variable. Si las variables son, por
ejemplo, de tipo lgico, se aplican operadores lgicos; pero, si las variables son numricas, se aplican operadores
de clculo numrico (suma, producto, entre otros).

El nombre de una variable debe ser nico y no ambiguo. La unicidad del nombre de la variable durante su ciclo de vida,
asegura una semntica correcta de las operaciones (expresiones, rdenes o proposiciones) que implican a la variable. De
esta forma, el nombre de una variable es un identicador diferente de cualquier palabra clave utilizada en el lenguaje
o nombre de una funcin externa. Generalmente, los nombres de las variables inician con una letra y son sucesiones
de letras y cifras y el smbolo _ (guin bajo). Para la cualidad del programa, es preferible que el nombre de una variable
sea sugestivo al tratamiento y de un largo de tamao aceptable, ya que un nombre de variable muy largo puede generar
errores de tecleo al momento de la edicin del programa, lo que produce prdidas de tiempo para su correccin. En la
determinacin del nombre de la variable, tambin se sugiere utilizar nicamente letras sin acento, para una mejor porta-
bilidad del cdigo o porque la sintaxis del lenguaje no lo permite.
Algunos ejemplos de nombres de variables son los siguientes: a, a1, area, suma. Tambin lo son: a1b159 y
a2b158; sin embargo, la lectura de un programa con nombres de este tipo sera difcil. Por lo que respecta a la extensin
del nombre, una variable llamada nueva_suma_valores_quantidades, tomara mucho ms tiempo escribirla.
Se considera que variables de nombre i, j, k, indican variables enteras usadas para ndices; en tanto, las variables de
nombre a, b y c, por lo general se utilizan para valores numricos reales (punto otante); las variables llamadas p y q se
emplean para apuntadores; las variables llamadas n y m son variables que contienen valores de tamaos de arreglos.
No es obligatorio que la variable tenga un valor al saberse que la zona de memoria dedicada a la variable sea ocu-
pada. En algunos momentos, es posible que la variable no tenga ningn valor; en estos casos, se dice que la variable es
no-inicializada (por ejemplo, lenguaje PASCAL) o libre (por ejemplo, lenguaje PROLOG). Si la variable posee un valor en
un instante T del programa, dicho valor solo es nico para ese instante T. A lo largo de la vida de la variable, el valor que
tenga esta puede cambiar; la nica condicin es que los valores guardados sean del mismo tipo de la variable. El cambio
de valor de la variable se hace alrededor de una operacin explcita de asignacin o por efecto secundario,6 como el
clculo de una funcin o el tratamiento de un recurso externo de tipo archivo.

Las variables son de varios tipos; en la mayora de los lenguajes de programacin imperativa predominan los siguien-
tes tipos:

s Variables simples. Son propias de los tipos bsicos, para los datos enteros, otantes, caracteres y lgicos (pero no
en el lenguaje C).
s Variables compuestas. La denicin del tipo de una variable compuesta depende de la sintaxis del lenguaje de
programacin y de su poder semntico.
s Arreglos de variables de tipo simple o tipo compuesto. Los arreglos sirven para almacenar una sucesin de
valores del tipo indicado.

En la mayora de los lenguajes en los que cada variable tiene una declaracin, se indica el nombre y el tipo. En
ocasiones, tambin se indica si la variable es esttica o dinmica o si el acceso al contenido de la variable es p-
blico o privado (por lo general, en lenguajes orientados a objetos). Si la semntica del lenguaje impone la declara-
cin de cualquier variable que se usa, la ausencia de dicha declaracin genera un error de compilacin. Del mis-
mo modo, si existen variables que estn declaradas, pero que no se utilizan, la mayora de los compiladores envan
mensajes explcitos de advertencia, que no son errores, pero s informaciones realizadas por el programador. Tam-
bin existen casos de lenguajes en los que ninguna variable se declara; en estos lenguajes, a cada aparicin de la
variable se considera que dicha variable tiene un tipo por defecto. Este es el caso del lenguaje PROLOG, en el cual
nicamente una variable sirve para la evaluacin de expresiones lgicas o del lenguaje M. Tambin hay lenguajes

6
Efecto secundario (side effect en ingls).
11
*OUSPEVDDJOBMBQSPHSBNBDJO

en los cuales si una variable se usa sin denicin, se considera que es una variable simple de un tipo indicado por su
nombre (por ejemplo, el lenguaje FORTRAN). La sintaxis de las declaraciones de variables es diferente de un lenguaje a
otro; sin embargo, un elemento comn es que en todos los casos se indican el tipo de la variable y su nombre.

Ejemplos
integer A, I; signica dos variables de nombre A e I y de tipo entero.

double Rayo; signica una variable de nombre Rayo y de tipo punto otante doble precisin.

La nocin de variable es generalmente la misma para la mayora de los lenguajes de programacin de tipo imperativo;
no obstante, de un lenguaje a otro, o de una computadora a otra, la implementacin puede ser diferente. Por ejemplo,
segn la computadora, un tipo entero se implementa con un tipo de representacin binaria y sobre 2, 4 u 8 bytes. As,
en el lenguaje M, del software MATLAB, todas las variables tienen el tipo de doble precisin (64 bits).
Una variable simple tiene un nombre nico y posee un solo valor de tipo elemental; dicho tipo est declarado ex-
plcita o implcitamente. En el ejemplo anterior, las tres variables son simples. A una variable le corresponde una zona de
memoria que contiene el valor, donde escribiendo el nombre de la variable se accede a su valor.

Ejemplo
Si despus de las declaraciones precedentes se escribe A+I, esto representa una expresin aritmtica que usa los valo-
res de las variables A e I.

Un arreglo es una variable que tiene un nombre y posee un cierto nmero de valores del mismo tipo (simple o com-
puesto), los cuales se encuentran almacenados, uno despus del otro, en una zona de memoria contigua. El tipo de cada
valor del arreglo tambin es implcito o explcito. En la declaracin de un arreglo, ms que el tipo de los elementos del
arreglo y el nombre de este, se indica la dimensin.

Ejemplo
integer YX[10]; signica un arreglo que contiene 10 valores enteros.

Segn los lenguajes de programacin, se puede trabajar o no con todo el arreglo en un solo comando o expresin, o (el
caso ms comn) trabajar con un solo valor del arreglo a la vez.

Ejemplo
Si se trabaja con el lenguaje M, sum(YX) signica la suma de todos los valores y 5*YX signica un arreglo temporario
que contiene los valores del arreglo YX multiplicados por 5. En el lenguaje C, estas expresiones no signican nada, a
menos que el usuario dena una funcin especial sum(...) capaz de tratar arreglos de tipo entero.

Un valor que compone el arreglo se llama elemento. Un elemento se identica con el nombre del arreglo y con su posi-
cin al interior del arreglo, llamada ndice.

Ejemplo
Por la declaracin precedente, YX[1] es el elemento de ndice 1 del arreglo YX; este valor se puede utilizar en cualquier
expresin aritmtica o instruccin.
Si tomamos en cuenta la denicin de una variable entera I, entonces YX[I] es el elemento con el ndice del valor
de la variable I del arreglo YX.
Segn los lenguajes de programacin, los ndices de un arreglo empiezan en 1 (lenguaje M o FORTRAN o PASCAL), o en
0 (lenguaje C o Java).
La discusin sobre las variables compuestas y los apuntadores est muy extendida y es muy dependiente del len-
guaje de programacin. En el caso del lenguaje C, que es el que se va a presentar en este texto, se trata con detalle en
12
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

los captulos 2 y 3. En tanto, en el siguiente apartado se estudian los diferentes tipos de variables; normalmente, para
cualquier tipo conocido por el programa se puede denir una variable.
El ciclo de vida de una variable inicia en el momento de la ASIGNACINDELAZONADEMEMORIA, conforme a su
denicin. La asignacin es realizada por el compilador, si la variable es global (es decir, si el lenguaje es compilado), o
por el intrprete, durante la ejecucin del programa, si la variable es local o dinmica. El tamao de la zona de memoria
asignada es, por lo general, el tamao del tipo por las variables simples o el producto del tamao del tipo del nombre
de los elementos (es decir, la dimensin) del arreglo. En el caso de algunos lenguajes de programacin que manejan
colecciones de datos, el tamao de memoria asignado es variable. Sin embargo, la reserva de memoria puede ser ja o
variable, segn el lenguaje de programacin, el tipo de clculo que se hace o el ambiente de ejecucin.
Si la variable es local o aparece en una parte de cdigo que termina o si el programador lo indica (con una funcin
free(X) en el lenguaje C, por ejemplo), se hace la liberacin de la zona de memoria ocupada por la variable.
Una variable es local si su contenido es accesible nicamente en una parte del programa (por ejemplo, un bloque
en el lenguaje C, la resolucin de un predicado en PROLOG o el cuerpo de una funcin que se ejecuta en la mayora de
los lenguajes). En sentido opuesto, tambin hay variables globales; as, una variable global es visible desde cualquier
lugar del programa.

Ejemplo

#include<stdlib.h>
#include<stdio.h>
int a;
void funcion_impresion(int x)
{

int b;
b = sizeof(int);
printf(Se necesita %d bytes para guardar el valor %d.\n, b, x);
printf(Se necesita %d bytes para guardar el valor %d.\n, (int)sizeof(x+a),
x+a);
}

int main()
{
int b;
b = 15;
a = 25;
funcion_impresion(b);
}

Este programa en C tiene una variable global que es visible desde las dos funciones: main y funcion_impresion. Tam-
bin, hay dos variables locales con el mismo nombre, b, en cada una de las dos funciones; cada una de estas variables
tiene un contenido diferente.

Las variables locales pueden clasicarse en estticas o dinmicas, pero esta clasicacin nicamente aplica en algunos
lenguajes de programacin, por lo que su semntica es diferente de un lenguaje a otro, para indicar el modo de asigna-
cin de memoria. La nocin se usa en el caso de funciones recursivas, por las cuales las variables estticas son nicas
para todas las llamadas de una misma funcin. Una variable esttica tiene su espacio de memoria asignado fuera de las
variables dinmicas en lenguajes como C, C++ o VisualBasic. En el ejemplo anterior todas las variables son dinmicas.
Toda vez que una variable tiene su espacio de memoria asignado, su contenido puede ser consultado en lectura o
en lectura escritura.
La lectura del contenido de una variable se hace a cada aparicin del nombre de la variable, pero si la variable no
contiene nada (es decir, no fue inicializada), su lectura produce un error.
13
*OUSPEVDDJOBMBQSPHSBNBDJO

En la mayora de los casos y de los lenguajes de programacin (excepto en algunos lenguajes del paradigma de-
clarativo), la escritura (cambio) de un contenido se hace con un operador de asignacin de valor. Dicho operador de
asignacin tiene una aridad de dos y generalmente se expresa con la sintaxis siguiente:

variable operador_asignacion expresin

El operador de asignacin cambia segn el lenguaje de programacin del que se trate; as, es = para los lenguajes C,
C++ o Java, e: = para los lenguajes PASCAL o SET y algunos lenguajes declarativos (por ejemplo, LISP, SCHEME, XSLT).
En el operador de asignacin, la parte izquierda (el primer operando) constituye la variable y la parte derecha es una
expresin del mismo tipo o un tipo compatible por el cual el valor sera convertido al tipo de la variable, si la conversin es
posible. Sin embargo, el funcionamiento es siempre el mismo: primero se evala la expresin y luego se hace la escritura
del valor obtenido en la zona de memoria asignada por la variable.
La expresin que aparece en una asignacin debe ser correcta sintctica y semnticamente (escritura correcta y uso
correcto del tipo). Pero, esta vericacin de correccin no es una garanta de que la expresin est correcta al momento
de la ejecucin de la asignacin.
Los errores de evaluacin pueden aparecer como la divisin con cero, un valor de ndice que est fuera del rango
permitido o una operacin aritmtica que se hace con desbordamiento aritmtico. Segn el lenguaje del que se trate, si
el clculo de la expresin se hace con errores o excepciones, es posible integrar un cdigo general de tratamiento de la
excepcin o un cdigo particular (lenguajes ADA, Java o Smalltalk). Tambin es posible que el compilador que introduce
vericaciones de correccin, paso a paso, durante la evaluacin de la expresin de la parte derecha, seale explcitamen-
te la causa del error, como en el lenguaje Java. Pero estos lenguajes tambin son considerados lenguajes sin ninguna
vericacin de este tipo y los errores de clculo de la expresin pueden ser fatales, como la divisin con 0, o el clculo
contina con un valor incorrecto. En el lenguaje C, si el error es fatal, la asignacin se interrumpe y el programa tambin.

Tipos

Un tipo informtico es el atributo de cualquier dato (constante, variable o dato almacenado en la memoria interna o en
la memoria externa) guardado por el programa de manera implcita o explcita. Por lo general, el tipo indica la forma fsica
del contenido del dato. As, un tipo induce naturalmente una representacin interna de los datos; entonces, el tamao
tambin induce en la semntica del lenguaje un conjunto de operadores que se aplican a los valores pertenecientes a
este tipo.
Los tipos son caractersticas de los lenguajes de programacin y se clasican en:
s Tipos predenidos.
s Tipos denidos por el usuario.
Un tipo predenido es un tipo propuesto por el lenguaje con una semntica explcita y un conjunto preciso de operado-
res. Por su parte, un tipo predenido puede ser:
s Un tipo bsico, el cual traduce tipos de representacin interna de los datos en lenguaje de programacin, como
enteros, reales (con representacin en punto otante), lgicos, carcter (cdigo ascii, Unicode, entre otros) y cade-
na de caracteres (menos frecuente).
s Un tipo complejo, el cual traduce un tipo abstracto de datos7 o un tipo de datos que responde a una necesidad
en el paradigma de programacin; por ejemplo, el tipo enumerado, el semforo (en programacin concurrente),
el mensaje en la programacin distribuida asncrona, etctera.

7
Un tipo abstracto es un tipo de datos concebido de manera terica explicitando la semntica del tipo: cmo funciona y cules son
las operaciones con este tipo. Por ejemplo, podemos denir un tipo abstracto para modelar la nocin matemtica de conjunto. El
tipo abstracto conjunto tiene denidos los operadores entre conjuntos (reunin, interseccin y diferencia) y el operador de
pertenecia. Un tipo abstracto se implementa despus en su lenguaje de programacin.
14
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Ejemplo
En el lenguaje C, los tipos bsicos predenidos son enteros o reales: char, short, int, long, float, double.

Un carcter se asimila como un entero representado en 1 byte, y los valores lgicos se consideran por interpre-
tacin de los valores que adquiere; es decir, cualquier valor diferente de 0 es verdad, ya que el valor 0 es falso. Las
cadenas de caracteres se conciben como arreglos de caracteres con un carcter especial al nal. Los tipos complejos
parecen denidos en las libreras estndares: FILE*, para trabajar con archivos; clock y time, para trabajar con el
tiempo del sistema o absoluto; socket para trabajar con los sockets, etctera.

Muchos lenguajes permiten al programador la denicin de sus propios tipos, los cuales son ms cercanos al pro-
blema que se pretende resolver. El tipo compuesto es un conjunto ordenado de variables con tipos conocidos (struct
en C, record en PASCAL o PL/SQL).

En el paradigma de la programacin orientada a objetos, tambin hay nociones de tipo jerrquico y de tipo opaco
con respecto a la visibilidad o la herencia.

La comprobacin de tipicacin constituye la operacin de vericacin de compatibilidad de los tipos al interior de


una expresin. La tipicacin puede ser de dos tipos:

s Esttica. Hecha al momento de la compilacin.


s Dinmica. Hecha al momento de la ejecucin del programa.
Esta fase de comprobacin de los tipos es necesaria para garantizar la correccin del cdigo y evitar los errores de des-
bordamiento. Cuando se hace una operacin entre tipos diferentes, antes es posible hacer una conversin de un tipo a
otro (por lo general, el ms dbil se convierte en el ms fuerte), y luego se realiza la operacin. La compatibilidad de los
tipos est indicada en la parte de la semntica del lenguaje. La tipicacin es fuerte cuando solo son aceptadas las trans-
formaciones para el tipo ms fuerte (por ejemplo, el lenguaje PASCAL); en caso contrario, la tipicacin se considera dbil.

Apuntadores

Un puntero o apuntador es una variable capaz de referenciar una variable del programa o una direccin de memoria. Este
se dene como una variable, pero nada ms se indica que el tipo es una referencia (a veces una ubicacin) de un tipo
conocido (estndar o denido por el programador) o de cualquier otro tipo. Este modo de acceso a un contenido, pasando
primero por su direccin (ubicacin de memoria), permite realizar tratamientos por los cuales los operandos no son cono-
cidos completamente al momento de la ejecucin del programa.
No todos los lenguajes implementan esta nocin; por ejemplo, M (del MATLAB), R o FORTRAN. La semntica y el uso
de apuntadores son muy diferentes de un lenguaje a otro.
Los lenguajes ensambladores implementan esta nocin de manera natural, con el modo de direccionamiento indi-
recto por medio de un registro.

Ejemplo
A continuacin se presenta un ejemplo de uso de apuntadores en C y en PASCAL, por el cual el contenido de una va-
riable es accesible y se modica usando un apuntador sin hacer referencia al nombre de la variable:

#include<stdlib.h>
#include<stdio.h>
int main()
{
int a = 12, b = 5;
int *p;
if (a > b) p = &a;
15
*OUSPEVDDJOBMBQSPHSBNBDJO

else
p = &b;
printf( El contenido inicial de mi variable preferida : %d\n, *p);
a = a + 67;
printf( El contenido final de mi variable preferida : %d\n, *p);
*p = 100;
printf(El valor de a est ahora : %d.\n, a);
}

PROGRAM codigo_apuntador_ejemplo;
VAR
a,b : integer;
p : ^integer;
BEGIN
a := 12;
b := 5;
if a > b then p := @a
else p := @b;
writeln(El contenido inicial de mi variable preferida :,p^);
a := a + 67;
writeln(El contenido final de mi variable preferida :,p^);
p^ = 100;
writeln(El valor de a est ahora ::,a);;
END.

Memoria

Direccin A Contenido A

Ensamblador:
Ensamblador: mover Direccion A, registro2
code_Op direccion A code_Op *registro2

0
1

Direccin A

Contenido A registro 2

2
Registro operacin

Contenido A

Registro operacin

Acceder al contenido de la variable A por una operacin Op usando el acceso


directo y el acceso indirecto

Figura 1.6
16
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Expresiones

En programacin, una expresin es la traduccin en lenguaje informtico de un clculo aritmtico, lgico o de otra natu-
raleza. La nocin de la expresin fue inspirada de la nocin de expresin matemtica, por lo que su semntica es similar:
la evaluacin de una expresin se hace tomando en cuenta los valores que intervienen y aplicando los operadores. En las
expresiones, los operadores tienen un orden de evaluacin y prioridades. Una expresin contiene, entonces:

s Valores constantes
s Variables
s Operadores
s Parntesis

La escritura de una expresin en un cdigo implica la evaluacin de esta al momento de la ejecucin del cdigo. La eva-
luacin se hace tomando en cuenta la prioridad de los operadores.
Los operadores estn denidos por la sintaxis del lenguaje, al tiempo que la parte de semntica indica el tipo de
los operandos y el tipo del resultado. Por lo general, los operadores del lenguaje de programacin son de aridad 1 (un
solo operando) o de aridad 2 (la mayora); el caso de aridad superior a 2 (es decir, de 3 o ms operandos) es menos
comn. La mayora de los lenguajes de programacin usan la forma de injo para la escritura de las expresiones, que es
la escritura en el orden siguiente:
operador_aridad_1 operando
operando1 operador_aridad_2 operando2
Ejemplo
1
La expresin matemtica 2 mv2 + mhg tiene como posible rbol de evaluacin el siguiente (la operacin de multipli-
cacin es asociativa, entonces hay varias maneras de hacer el clculo):

* *

* * m *

/ m v v h g

1 2

Figura 1.7 rbol de evolucin de la expresin.

Esta expresin se escribe 1/2*m*v*v+m*h*g en la forma de injo. En el lenguaje LISP se usa la forma polaca (o forma
de prejo): (* (/ 1 2) m v v) (* m h g)).

Por los tipos numricos, se usan las cuatro operaciones aritmtica conocidas:
17
*OUSPEVDDJOBMBQSPHSBNBDJO

s Suma (adicin) +
s Diferencia (sustraccin o resta) -
s Producto (multiplicacin) *
s Divisin /
En algunos lenguajes hay un operador por el resto de la divisin entera (el mdulo), % (en el lenguaje C), o un operador
para la potencia, ^ (en el lenguaje BASIC).
Por tipos que no son numricos y segn el lenguaje, tambin hay operadores; por ejemplo, por las cadenas de ca-
racteres (si el lenguaje se considera cadena de caracteres como un tipo bsico) hay un operador de concatenacin (unir)
para dos cadenas: + en el lenguaje C++ o | en el lenguaje SQL.
Segn la semntica de cada lenguaje de programacin, los operadores que corresponden a operaciones aritmticas
/ lgicas o de transformacin se aplican a operandos:
s de tipos similares o compatibles, obteniendo un resultado del mismo tipo (por ejemplo, las operaciones aritm-
ticas se hacen entre elementos de tipo numrico) o de otro tipo (por ejemplo, el tipo lgico).
s de tipos diferentes; por ejemplo, en el lenguaje C, la adicin y la sustraccin de un apuntador y de un entero;
el resultado signica un nuevo apuntador para la direccin calculada, segn el apuntador y el segundo operando.
Otra clase de operadores son los operadores de orden, que sirven para comparar el orden de dos valores de tipos nu-
mricos, con el n de regresar un valor de tipo lgico. En matemticas, los operadores de orden ms comunes son: < ,
) , > , * , = , &. En la mayora de los lenguajes, estos operadores se traducen en programas con los siguientes smbolos:
< , <=, >, > =, = y < > o ! =.
Asimismo, en la mayora de los lenguajes de programacin, tambin se implementan los operadores lgicos de la l-
gica de primer orden: la negacin (operacin de aridad 1), la conjuncin y la disyuncin (operaciones de aridad 2). Estos
operandos lgicos corresponden a las palabras no, y, o. En muchos de los lenguajes, corresponden a los operadores
NOT, AND y OR. Las tablas de verdad de estos operadores son:

p NOT p
V F
F V

p Q P!.$Q
V V V
V F F
F V F
F F F

p Q P/2Q
V V V
V F V
F V V
F F F

En programacin, los operadores aritmticos tienen la misma prioridad que en matemticas; as, las operaciones de *
y / tienen la misma prioridad alta, que las operaciones de + y -. En las operaciones aritmticas, los operadores tienen
18
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

una prioridad mayor que los operadores de orden; en tanto, los operadores lgicos tienen una prioridad ms baja que
los otros.
Por ejemplo, si las variables a, b y c tienen valores numricos, para vericar que a, b y c pueden ser las aristas de un
tringulo, en lenguaje matemtico se impone que a, b y c seran valores positivos y que cada nmero verica la siguiente
desigualdad triangular: x + y < z.
En lenguaje de programacin, estas seis condiciones lgicas que deben cumplirse se escriben con la expresin:
a > 0 AND b > 0 AND c > 0 AND a < b + c AND b < a + c AND c < a + b
En la expresin anterior no son necesarios los parntesis, sino que nicamente se utilizan para dar mayor claridad, por lo
que cada operacin de orden se puede escribir entre parntesis, as:
(a > 0) AND (b > 0) AND (c > 0) AND (a < b + c) AND (b < a + c) AND (c < a + b)

Funciones

Se considera que una funcin es una parte de cdigo capaz de realizar una tarea y/o de transformar valores para obtener
otro valor. Una funcin se dene por:
s El nombre. Este no debe ser ambigo; segn el lenguaje, el nombre debe ser nico con respecto a las variables
globales y a otras funciones.
s El tipo de valor que la funcin regresa.
s El nmero jo (o variable) de parmetros y la lista ordenada de tipo aceptable por los parmetros.
s El cdigo. Este es nico para cada funcin.
Cercanas a la nocin de funcin (o idnticas por el lenguaje C), se encuentran las nociones de procedimiento, rutina o
subrutina, las cuales signican una parte del programa encargada de realizar una tarea sin regresar expresamente un valor.
Los valores calculados o transformados por el cdigo se regresan en la lista de los parmetros.
Si el lenguaje permite la redenicin de las funciones, por ejemplo, los lenguajes orientados a objetos, como C++ o
Java, o que las funciones tengan varias listas de parmetros, el cdigo de una funcin no es nico. En el primer caso se
toma en cuenta la ltima denicin de la funcin, mientras que en el segundo se hace la correspondencia entre la lista
de parmetros actuales y las listas de parmetros.
Una vez que la funcin est denida (y si no tiene restricciones de acceso; por ejemplo, no es privada, como en el
caso del lenguaje Java, o no es una funcin interna de otra funcin, como en el lenguaje C), en todo el cdigo se pueden
hacer una o varias llamadas a la funcin, con la nica restriccin de que la lista de los parmetros reales (especicados
en la llamada) correspondan en nombre y tipo con los parmetros formales (que aparecen en la denicin de la fun-
cin).
La llamada de la funcin se hace especicando:
s El nombre de la funcin.
s La lista de los parmetros reales, los cuales pueden ser expresiones que se evalan o variables.
Por su parte, la sintaxis de una llamada de funcin es casi la misma para prcticamente todos los lenguajes (a excepcin de
algunos lenguajes funcionales, como LISP o SCHEME) y es inspirada en la notacin matemtica:

Nombre_funcin(parametro1, parametro2, )

La sintaxis de la denicin de una funcin vara considerablemente de un lenguaje a otro, al igual que la semntica de la
denicin y el modo de ejecucin de las llamadas. En el caso de las llamadas de funciones, estas tienen varias semnticas,
segn el paradigma del lenguaje de programacin. Por el paradigma imperativo y por las funciones que regresan valores,
las llamadas se comportan como expresiones del tipo regresado.
19
*OUSPEVDDJOBMBQSPHSBNBDJO

De acuerdo con el lenguaje de programacin, los parmetros pueden modicarse o no en el cdigo de la funcin,
donde el valor del parmetro a la salida de la funcin es cambiado. O se indica expresamente si los parmetros son de
entrada (cuando sus valores no cambian) o de salida (por ejemplo, el lenguaje PL/SQL) si los valores van a cambiar.
Un parmetro real que no es de salida puede ser cualquier expresin posible del parmetro formal.
En el lenguaje C solo existe la nocin de parmetro de entrada y de salida, lo cual depende de la forma en que se
transmite: el valor indicado por una variable o una expresin; o un apuntador al contenido de una variable. Solo un apun-
tador transmitido como parmetro puede cambiar el contenido de la memoria. Hablamos de parmetros transmitidos
por valor o por referencia.
Ejemplo
Una funcin que calcula la suma de los valores de dos elementos o de una lista de elementos.

Por la suma de dos elementos, implantamos las funciones en los lenguajes C, PASCAL y PL/SQL, y por la suma de
una lista realizamos las implementaciones en los lenguajes PROLOG y LISP.

El concepto de lista no tiene un tipo predenido en los tres lenguajes antes mencionados, por lo que es muy dife-
rente en PROLOG y en LISP. Por su parte, en el lenguaje PROLOG no existe la nocin de funcin regresando cualquier
tipo de valor, sino que las funciones (llamadas predicados) regresan valores de verdad.

Denicin de la funcin suma #DIGODELASLLAMADAS


s ,ENGUAJE#nDOSVERSIONES s ,ENGUAJE#
int sumaC1(int a, int b) int suma;
{ printf(suma1:%d\n,sumaC1(12,56));
return a + b; sumaC2(12,56, &suma);
} printf(suma 2: %d\n, suma);
s ,ENGUAJE0!3#!,
void sumaC2(int a, int b, int *valor)
{ VAR valor_suma : integer;
*valor = a + b; BEGIN
} valor_suma := SUMA2(12, 67);
WRITELN(La suma es:, valor_suma);
s ,ENGUAJE0!3#!,
END.
FUNCTION SUMA2(a : integer; b : INTE-
s ,ENGUAJE0,31,
GER) : INTEGER;
BEGIN DECLARE
SUMA2 := a+b; VAR VALOR_SUMA INTEGER;
END; BEGIN
SUMA2(12, 67, VALOR_SUMA);
s ,ENGUAJE0,31,
END.
CREATE OR REPLACE PROCEDURE SUMA2(a IN
s ,ENGUAJE02/,/'
integer, b in INTEGER, s OUT INTEGER)
BEGIN ?- suma(8, [1, 2, 4]).
s := a+b; false.
END; ?- suma(XX, [11, 2, 45]).
XX = 58 .
s ,ENGUAJE02/,/'
s ,ENGUAJE,)30
suma(0, []).
suma(X, [X]). >(suma ())
suma(S,[X|L]):-suma(Y,L),S is X +Y. 0
>(suma (1 2 3 4 5))
15

20
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

s ,ENGUAJE,)30
(defun suma (lista)
Calculo de la suma de dos elemen-
tos de la lista
(if (null lista) 0
(+ (first lista) (suma (rest
lista))
)
))

En ocasiones, las funciones tienen efectos de bordo, transformando contenidos en otras zonas de la memoria o cambian-
do los estados de los dispositivos de entrada/salida. Por ejemplo, una funcin clear( ) sin parmetros en lenguaje C
o la rutina ClrScr en PASCAL, que borra la pantalla de trabajo.
En todos los lenguajes, ms que las funciones denidas por el usuario, se utilizan funciones que provienen de libre-
ras externas, estndares (anexadas al compilador o al intrprete) o funciones adicionales. La funcin citada, que borra la
ventana de trabajo, proviene de una librera estndar para los dos lenguajes.

1.3 Pseudocdigo

Un pseudocdigo (falso lenguaje) est formado por una serie de palabras con un formalismo muy sencillo, que permite
describir el funcionamiento de un programa. Se usa tanto en la fase de diseo como en la fase de anlisis.
El pseudocdigo describe un algoritmo utilizando una mezcla de frases en lenguaje comn, instrucciones de progra-
macin y palabras clave que denen las estructuras bsicas. Su objetivo es permitir que el programador se centre en los
aspectos lgicos de la solucin de un problema.
El pseudocdigo utiliza expresiones matemticas, expresiones lgicas y la nocin de variable (sencilla, arreglo,
pila, cola, conjunto, etctera). El pseudocdigo se puede extender para expresar tipos complejos y operaciones entre
variables y constantes de este nuevo tipo.

Nociones bsicas: variables, tipos y expresiones

Una variable es un contenido de memoria que contiene un valor que podemos cambiar; es decir, que vara. Una variable
tiene un nombre (jo y nico) y un valor (variable durante la ejecucin del algoritmo).
Las expresiones matemticas contienen los operadores conocidos, constantes y funciones matemticas. Por ejemplo:
X 1, 2 + 16 + 18, sen(2)* cos(x)
Una expresin lgica contiene expresiones matemticas, operadores de comparacin y operadores lgicos.
s Los operadores de comparacin son: =,,>,<,,.
s Los operadores lgicos son: AND, OR, NOT.
Ejemplo
X 1 = 4, [ + 16 5, sen(x)* cos(x)0.2, x < y AND y < z
En este caso, las primeras tres expresiones contienen nicamente un operador de comparacin, mientras que la ltima
expresin es la traduccin de la expresin matemtica: x < y < z.

Una variable contiene valor de entrada o de salida (resultados) o clculos intermediarios.


21
*OUSPEVDDJOBMBQSPHSBNBDJO

Para cambiar o dar un valor a una variable, se utiliza una lectura o una asignacin. La lectura de una variable se realiza
de la siguiente forma:
Lectura (variable)
El funcionamiento de una variable es conforme al siguiente orden:
1. El usuario del cdigo entrega un valor de buen tipo y este valor se guarda en variable.
Ejemplos
Lectura (alfa).
Se lee (entrega) un valor por la variable alfa.
Lectura (alfa, beta).
Se leen dos valores que se entregan luego: primero a la variable alfa, segundo a la variable beta.
2. Despus de una lectura, y hasta un nuevo cambio de valor, el valor contenido en la memoria para una variable
no cambia.
La asignacin de un valor a una variable se realiza de esta forma:
variable expresin (matemtica o lgica)
La expresin (matemtica o lgica) puede contener la variable misma, mientras que su tipo es el mismo que el tipo de
la variable.
La signicacin de la asignacin se efecta realizando el clculo de la expresin y, luego, el contenido de la variable
se cambia con el valor de ese clculo; por tanto, el valor anterior de la variable se pierde.
Ejemplos
x 4 + 22
alfa beta gamma
i i + 1
En la primera asignacin aparece el smbolo matemtico , el cual posee una signicacin muy precisa, pero en el
momento de la traduccin del pseudocdigo en un lenguaje de programacin, debemos buscar cmo se implementa
o se puede implementar esta constante.
Por su parte, la ltima asignacin tiene el siguiente funcionamiento: con el valor actual de la variable i se hace el
clculo de i + l, cuyo valor sera guardado en la variable i. Por ejemplo, si la variable i vale 3, despus de esta asignacin
el contenido de la variable es 4.
La escritura de un contenido se hace de una manera muy simple:

Escritura (variable)
Escritura (constante)

El funcionamiento es evidente; el valor de la variable o de la constante se pone en la salida del pseudocdigo.


La principal funcin del pseudocdigo consiste en decir qu debe hacer el cdigo y cmo; por esta razn, la escritura
es muy simple, sin indicar el formato de salida, y con un cierto nmero de cifras despus del punto decimal; por ejemplo,
con una poltica de caracteres o determinando en qu lugar de la pantalla se escribe.
Ejemplos
Escritura (Aqu termina el programa)
Escritura (i)
Escritura (j)
La primera escritura, Aqu termina el programa, es una constante de tipo cadena de caracteres.
Las siguientes escrituras se colocan en la salida de los valores de las variables i y j, en ese orden.
22
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Para ser ms especco, antes de una lectura o de una escritura, se puede poner una Escritura (mensaje).
Ejemplos
Escritura (Indica por favor el valor de a:)
Lectura (a)

Escritura (Mi calculo final es:)
Escritura (X)

Estructura general del pseudocdigo

Un pseudocdigo se escribe para dar las grandes lneas del clculo; su objetivo es compartir con los dems programado-
res su visin de la resolucin del problema. Hay dos principios en la escritura de un pseudocdigo:
s Al inicio se escriben todas las variables que se usan en pseudocdigo; cada una con su nombre y su tipo.
s Las lneas del pseudocdigo que siguen son rdenes (instrucciones o estructuras) que se ejecutan de arriba hacia
abajo; primero una orden y despus otra, as sucesivamente.
El pseudocdigo que se utiliza para la descripcin de un algoritmo o para indicar los pasos de resolucin de un problema
contiene estructuras de control, las cuales se utilizan para describir las instrucciones de los algoritmos. Hay cuatro tipos
de estructuras:
s Secuencial
s Selectiva
s Iterativa
s Anidamiento

Estructuras componentes del pseudocdigo


Estructura secuencial
La estructura de control secuencial tiene la siguiente forma:

instruccin^
1
instruccin^
2

instruccin^
k

Las instrucciones se ejecutan en el orden indicado por los ndices: de arriba hacia abajo y una despus de la otra.
Ejemplo
Primer pseudocdigo
Lectura de x y clculo del cuadrado de x:
Real x, y;
Lectura (x)
y x x
Escritura (y)

Observaciones:

s La primera lnea contiene la declaracin de las variables; x y y son las nicas variables y tienen el mismo tipo.
23
*OUSPEVDDJOBMBQSPHSBNBDJO

s Las instrucciones se ejecutan en el siguiente orden: lectura, asignacin, escritura.


s No hay ambigedades en cada instruccin y pueden ejecutarse.
s Las variables que se usan tienen un valor correcto al inicio o al n de cada instruccin y en todo el pseudocdigo.
La estructura secuencial es la base del pseudocdigo, pero no es suciente para resolver todos los problemas.

Estructura selectiva
Las estructuras selectivas permiten expresar las elecciones que se hacen durante la resolucin del problema. Hay varios
tipos de estructuras selectivas:

s Selectiva simple.
s Selectiva doble (alternativa).
s Selectiva mltiple.
s Selectiva casos (mltiple).

La estructura selectiva simple es de la siguiente forma:

si expresin lgica entonces


instrucciones fin si

En esta estructura, primero se hace el clculo de la expresin lgica; si el valor de esta expresin es cierto (no falso) se eje-
cutan las instrucciones (puede ser una sola o ms de una). Si el valor de la expresin lgica es falso, no se ejecuta nada.
Las palabras si, entonces y n si, son palabras clave que permiten estructurar y dar un sentido a las instrucciones. Por
otro lado, es posible escribir la estructura anterior como:
si expresin lgica entonces
instrucciones

n si
Esta escritura no es tan clara. Dnde inicia y dnde termina la estructura si?, dnde empiezan las instrucciones que se
ejecutan cuando la expresin es cierta? Por estas razones, es mejor que las partes que componen una estructura (en
nuestro caso selectiva, aunque tambin aplica para todas las estructuras selectivas e iterativas) se escriban con algunos
espacios y que la parte si se alinee con la parte n si.
La estructura selectiva alternativa es de esta forma:

si expresin lgica entonces


instrucciones1
si no
instrucciones2
fin si

Primero, se hace el clculo de la expresin lgica. Si el valor de esta expresin es cierto (no falso) se ejecutan las instruc-
ciones1. Si no, se ejecutan las instrucciones2.

24
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Ejemplo
Vericar si un nmero entero es o no divisible entre 3.
La entrada es el nmero n, la salida es un mensaje de tipo S o No.

Integer N
Lectura (N)
resto @ N%3
si resto = 0 entonces
Escritura (S)

sino
Escritura (NO)

fin si
La estructura selectiva mltiple es usada para anidar condiciones lgicas mutuamente excluyentes. Su forma es la
siguiente:

si expresin lgica1 entonces


instrucciones1
sino si expresin lgica2 entonces
instrucciones2
sino si expresin lgica3 entonces
instrucciones3

sino
instruccionesn
fin si

Esta estructura se ejecuta de la siguiente manera:


Se hace el clculo de la expresin lgica1, si el resultado es cierto se ejecutan instrucciones1 y la instruccin
selectiva se termina. Si no, se hace el clculo de la expresin lgica2; si el resultado es cierto se ejecuten instrucciones2
y la instruccin selectiva se termina Si todas las expresiones lgicas son falso, entonces se ejecutan instruccionesn.

Ejemplo
Resolver la ecuacin de primer grado que tiene su forma matemtica ms general:
ax + b = 0
La entrada est formada por los dos parmetros de la ecuacin, a y b, que sern guardados en dos variables de tipo
punto otante. La salida ser un mensaje sobre la raz de la ecuacin y, en algn caso, su valor.
Los casos que pueden aparecer y que deben tratarse de manera diferente sern:

s a = 0 y b = 0: cualquier nmero real es una solucin.


s A = 0 y b & 0: no hay ninguna solucin.
s a & 0: la raz es nica y de valor a/b.

Una proposicin de pseudocdigo es la siguiente:


Real a, b
25
*OUSPEVDDJOBMBQSPHSBNBDJO

Real x
Lectura (a)
Lectura (b)
si a 0 entonces
Escritura (Hay una nica raz)
x @ b/a
Escritura (x)
sino si b 0 entonces
Escritura (No hay ninguna raz)
sino
Escritura (Hay una infinidad de races)
fin si

Este pseudocdigo contiene una estructura selectiva mltiple con condiciones lgicas exclusivas. La resolucin se puede
hacer con un pseudocdigo que contiene dos estructuras selectivas alternativas, una aadida a la otra:

Real a, b, x
Lectura (a)
Lectura (b)
si a = 0 entonces
si b = 0 entonces
Escritura (Hay una infinidad de races)
sino
Escritura (No hay ninguna raz)
fin si
sino
Escritura (Hay una nica raz)
x @ b/a
Escritura (x)
fin si

Las dos soluciones propuestas son semnticamente equivalentes, por lo que se realiza el mismo tratamiento; depende
nicamente de la manera de escribir y leer el cdigo y de que los participantes en el desarrollo de la solucin del pro-
blema preeran una u otra.
En la segunda versin, en lugar de dos declaraciones de variables, tambin tenemos una sola para las tres variables
del cdigo; sin embrago, la primera versin permite nada ms una separacin lgica entre las variables de entrada
y la nica variable de salida.
La estructura selectiva mltiple-casos se usa cuando un mismo valor se compara con varios valores. Su forma es la
siguiente:

seleccionar expresin
caso valor1
instrucciones1
caso valor2
instrucciones2

en otro caso
instruccionesn
fin seleccionar

La expresin puede ser una sola variable. Primero, se obtiene el valor de esta expresin y se compara con cada valori;
si hay expresin = valori, se ejecutan las instruccionesi. Si ningn valor corresponde, se ejecuta la parte en otro caso,
instruccionesn.

26
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Ejemplo
Segn el valor de una variable de entrada, se escribe:

s FALSO si el valor de la variable es 0.


s CIERTO si el valor de la variable es 1.
s INDEFINIDO si el valor de la variable es 1.
s ERROR en otros casos.

El pseudocdigo es muy simple, est estructurado en dos partes: la lectura de la variable y una estructura selectiva de
tipo caso:

Integer x
Lectura (x)
seleccionar x
caso 0
Escritura (FALSO)
caso 1
Escritura (CIERTO)
caso 1
Escritura (INDEFINIDO)
en otro caso
Escritura (ERROR)
fin seleccionar

Observacin: El orden de tratamiento de los casos es libre; se puede poner cualquier permutacin de los valores 1,
0 y 1, nicamente al nal se pone en otro caso.

Estructura iterativa
Las estructuras iterativas abren la posibilidad de ejecutar un grupo de instrucciones ms de una vez; es decir, sirven para
ejecutar varias veces una misma parte de un cdigo. Hay varios tipos de estas:
s Bucle mientras
s Bucle repetir
s Bucle para (cada)
La estructura iterativa mientras (while) tiene la siguiente forma:

mientras expresin lgica hacer


instrucciones
fin mientras

Su ejecucin es la siguiente:
Se calcula la expresin lgica y, si su valor es cierto, se ejecutan las instrucciones y se hace un nuevo clculo de la ex-
presin lgica. Entones, en total, las instrucciones se ejecutan 0 o varias veces, dependiendo del valor de la expresin
lgica.

Ejemplo _#UIDADO!LINTERIORDELASINSTRUCCIONESHAY
QUEHACERMODIlCACIONESDELASVARIABLESQUE
Buscar el entero m ms pequeo, pero que sea mayor que un nmero real positivo componen la expresin lgica PORQUELAEX
x, con x * 1. presin siempre sera cierta y la estructura no
se terminara nunca.

27
*OUSPEVDDJOBMBQSPHSBNBDJO

La entrada del problema es el nmero x y la salida es el nmero entero m. La idea es incrementar en 1 una variable
m iniciada con el valor 0 hasta que la variable m sea ms grande que la variable de entrada x. El pseudocdigo es:
Real x
Integer m
Lectura (x)
m @ 0
mientras m<x hacer
m @ m + 1
fin mientras
Escritura (m)

Ejemplo
Buscar el nmero entero ms grande de forma 2k que sea menor que un nmero real positivo x, con x * 1.
La entrada del problema es el nmero x y la salida es el nmero m = 2k; asimismo, podemos considerar que la
variable k es tambin una salida.
La resolucin de este problema es parecida a la del problema anterior:
Se calcula la potencia ms pequea de 2(2j), que sera ms grande que x; despus, se hace un paso detrs. El
pseudocdigo es de la siguiente forma:

Real x
Integer m, k, j
Lectura (x)
m @ 1
j @ 0
mientras m x hacer
m @ m*2
j @ j + 1
fin mientras
m @ m/2
k @ j1
Escritura (El numero que se busca es:)
Escritura (m) Escritura (es la potencia 2 de)
Escritura (k)

_#UIDADO4AMBINALINTERIORDEESTAESTRUC En este ejemplo de pseudocdigo, se muestra que antes y despus de la estructura re-
tura repetitiva, durante las instrucciones HAY petitiva mientras, entre las variables m y j, siempre existe la relacin m = 2j.
QUEHACERACTUALIZACIONESDELASVARIABLESQUE
componen la expresin lgica DEOTRAMA La estructura iterativa repetir (repeat) tiene la siguiente forma:
nera, el valor de la expresin no cambia y
la estructura repetir SE EJECUTARA INlNIDAD
de veces.
repetir
instrucciones
hasta que expresin lgica

Su ejecucin es la siguiente:
Se ejecutan las instrucciones y se hace el clculo de la expresin lgica. Si su valor es falso, se ejecutan de nuevo las
instrucciones y se hace un nuevo clculo de la expresin lgica.
En resumen, las instrucciones se ejecutan una o ms veces, dependiendo del valor de la expresin lgica.

28
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Una estructura repetir es equivalente a:

instrucciones
mientras NOT (expresin lgica)
instrucciones
fin mientras

Ejemplo
Para un nmero real x entre 0 y 1 (0 <x< 1), una base de numeracin b (b ) 10) y un nmero entero positivo k, buscar
las k primeras cifras despus del punto decimal de la representacin de x en base de b.
Las entradas del problema son tres: el nmero x, la base b y el nmero de cifras k. Mientras que la salida sera una
sucesin de cifras en base b (un nmero entre 0 y b 1). De manera ms formal:

x(10) = 0.c1c2ck1ck(b)

El algoritmo de transformacin de un nmero en base 10 en otra base se aborda en el apndice 1; la idea es hacer
multiplicaciones con la base b, tomando despus la parte entera de cada multiplicacin. As, el pseudocdigo:

Real x
Integer b, k
Real y, p
Integer i, c
Lectura (x)
Lectura (b)
Lectura (k)
y @ x
i @ 0
repetir
p @ y*b
c @ parte_entera(p)
y @ parte_fraccionaria(p)
Escritura (c)
i @ i + 1
hasta que i k

Este pseudocdigo reviste inters por varias razones:


s Hasta ahora las salidas de los algoritmos se han colocado al nal de todos los clculos; desde que se obtiene una
cifra de la representacin fraccionaria, esta cifra contenida en la variable c, se escribe y a cada paso se calcula
un nuevo valor en la iteracin siguiente.
s Se ejecutan exactamente k iteraciones; la variable i indica, de manera muy precisa, cuntas iteraciones se ejecu-
taron.
s La manera de trabajar con la variable i no es nica, tambin se puede escribir as:

i @ 1
repetir
p @ y*b
c @ parte_entera(p)
y @ parte_fraccionaria(p)
Escritura (c)
i @ i + 1
hasta que i > k
29
*OUSPEVDDJOBMBQSPHSBNBDJO

El sentido de la variable i es ahora: se est ejecutando la iteracin nmero i.


s Se requiere el clculo de las partes entera y fraccionaria; entonces, se indica con claridad la llamada de funcio-
nes, que normalmente estn implementadas en casi todos los lenguajes de programacin.
s La variable p es intermediaria y sirve para hacer una sola vez la multiplicacin de y por b. El uso de esta variable
no es obligatorio, se puede escribir directamente y en el siguiente orden:
c @ parte_entera(y*b)
y @ parte_fraccionaria(y*b)
Nota: Nada ms que la misma multiplicacin se hace dos veces.
s Otra variable intermediaria es y, que guarda los valores intermediarios por el clculo de la representacin de x; al
inicio, esta variable toma el valor de x. Es posible usar directamente x en los clculos; pero, al nal de la estructura
repetitiva, el valor de x se desnaturaliza y el valor inicial se pierde.
Una estructura iterativa que toma en cuenta la nocin de variable-contador es la estructura iterativa para (for), la cual
tiene la siguiente forma:
para i de inicio hasta fin [paso p] hac-
er
instrucciones
fin para
Donde i es una variable (simple) e inicio, n, p, son valores numricos. Si el paso no es declarado, su valor es 1.
Su ejecucin es repetitiva y su funcionamiento es el siguiente:
La i recibe el valor inicio y se ejecutan las instrucciones; luego, i se incrementa el valor de p (el paso) y se reejecutan
las instrucciones, si el valor de i es menor que n.
En resumen, las instrucciones se ejecutan 0 o varias veces, dependiendo de los valores de inicio, n y el paso.
El valor de la variable i se puede usar al interior de las instrucciones, pero no puede ser modicado.
Esta estructura es equivalente a:

i @ inicio
mientras i fin hacer
instrucciones
i @ i + p
fin mientras

Ejemplo
Obtener todas las potencias de un nmero a desde a1 hasta ak, donde a y k son valores de entrada; a es un nmero
real y k es un entero positivo.
El uso de una estructura repetitiva para cada es evidente:
Real a, p
Integer k, i
Lectura (a)
Lectura (k)
p @ 1
para cada i de 1 hasta k
p @ p*a
Escritura (p)
fin para
30
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Es muy fcil vericar que al nal de cada iteracin la variable p contiene el valor de ak. El pseudocdigo funciona tam-
bin por un nmero k entregado, que es 0 o un valor negativo; en este caso, no se ejecuta nada.
Ejemplo
Obtener la representacin fraccionaria de un nmero. El pseudocdigo del ejemplo anterior se puede escribir con una
estructura iterativa para cada:

Real x
Integer b, k
Real y, p
Integer i, c
Lectura (x)
Lectura (b)
Lectura (k)
y @ x
para cada i de 1 hasta k paso 1
p @ y*b
c @ parte_entera(p)
y @ parte_fraccionaria(p)
Escritura (c)
fin para

En los ltimos dos ejemplos, si se quieren guardar los valores de salida, las potencias de a y las cifras en base b del uso
de las variables simples no son sucientes, por lo que es indispensable usar los arreglos.

Uso de los arreglos

En la seccin 1.2 presentamos las nociones de variable en general y de arreglo. Como se vio, un arreglo se caracteriza por
un tipo bsico (entero, cadena de caracteres, ), su nombre (como cualquier variable) y su tipo.
En el pseudocdigo tambin es til poder usar los arreglos. As, en la parte de la declaracin debemos establecer que
la variable es un arreglo, indicando su dimensin. Por ejemplo:

Integer B, A[10]
Real X [200]

Aqu, podemos ver que B es una variable simple, A es un arreglo de valores enteros y X es un arreglo de valores en punto
otante. Los dos arreglos tienen una dimensin ja: 10 por el arreglo A y 200 por el arreglo X. Tambin, es posible declarar
arreglos con una dimensin variable, pero se debe tomar en cuenta, en la escritura de los programas, porque en algunos
lenguajes de programacin la denicin de arreglo es ms complicada que una simple declaracin. Por ejemplo:

Integer N
Integer A[N]

En este caso, el arreglo A no se puede usar si la variable N no est denida; as, es preferible que durante la ejecucin del
cdigo, el valor de esta variable no cambie.

Ejemplo
La suma de los elementos de un arreglo que se lee de entrada:
Integer N, suma, i
Integer A[N]
Lectura (N)
para cada i de 1 hasta N hacer
31
*OUSPEVDDJOBMBQSPHSBNBDJO

Lectura (A[i])
fin para
/* ahora el arreglo est lleno */
suma @ 0
para cada i de 1 hasta N hacer
suma @ suma + A[i]
fin para
Escribir (suma)

Las partes entre /* y */ son comentarios, que ayudan al lector a entender mejor el cdigo.
El arreglo A tiene una dimensin variable. Como un consejo de programacin podemos indicar que cuando la di-
mensin posible no es demasiado grande, conviene hacer la declaracin con una constante, a n de evitar trabajo de
programacin ms difcil.

Ejemplo
El clculo de las potencias de a, desde a1 hasta ak. Sin reducir la calidad de la solucin ni su generalidad, podemos
suponer que k ) 30. En esta versin, los nmeros de forma ai seran guardados en un arreglo C[30].
El pseudocdigo es, por tanto:
Real a, C[30]
Integer k, i
Lectura (a)
Lectura (k)
p @ 1
para cada i de 1 hasta k
p @ p*a
C[i] p
fin para
para cada i de 1 hasta k
Escritura (C[i])
fin para

Funciones y procedimientos

Las funciones y los procedimientos son partes de un programa que van a ser ejecutadas, una o varias veces, con los
valores transmitidos en los parmetros:
s Funcin. Recibe parmetros (uno o varios) y calcula un valor de regreso.
s 0ROCEDIMIENTO Recibe parmetros, pero no regresa explcitamente ningn valor.
Los parmetros son de tipos conocidos (simples o arreglos) y pueden ser de entrada o de salida. Un parmetro de
entrada sirve para introducir los valores necesarios al clculo y un parmetro de salida va a cambiar su valor durante la
ejecucin de la funcin o del procedimiento, para entregar resultados. La denicin de un parmetro es la siguiente:
[IN/OUT] tipo_parametro nombre_parametro
Con la palabra IN se indica un parmetro de entrada y con la palabra OUT se indica un parmetro de salida. Los pa-
rmetros de entrada siempre deben tener valores; al contrario de los parmetros de salida.
Las funciones y los procedimientos se declaran una sola vez y pueden ejecutarse varias veces. Una ejecucin se
denomina llamada.
La declaracin de un procedimiento es:

32
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

procedimiento nombre_procedimiento (lista de definiciones de parametros)


inicio

fin procedimiento

La llamada de un procedimiento es de la siguiente forma:

nombre procedimiento(parametro1, parametro2, )

Donde los parametros1 son variables o arreglos, expresiones o constantes. Hay casos en los que los parmetros corres-
ponden en nmero y tipo con la denicin.

Ejemplo
La denicin de un procedimiento para la lectura de un arreglo:

procedimiento lectura_arreglo (IN Integer dimension, OUT Integer X[])


inicio
Integer i
para cada i de 1 hasta dimension hacer
Lectura (X[i])
fin para
fin procedimiento
Ejemplo de dos llamadas:
lectura_arreglo(100, A)
lectura_arreglo(N, X)

Una funcin se dene como:

funcin tipo nombre_funcion (lista de definiciones de parmetros)


inicio
.
regresa valor
fin funcin

La funcin tiene que regresar o devolver un valor del mismo tipo de la funcin. Una llamada de funcin es parecida a una
llamada de procedimiento y puede ser escrita en una expresin.
Ejemplo
La denicin de una funcin para la suma de los elementos de un arreglo es la siguiente:

funcin Integer suma_arreglo(IN Integer dimension, IN Integer X[ ])


inicio
Integer i, ss
ss @ 0
para cada i de 1 hasta dimension hacer
ss @ ss + X[i]
fin para
regresa ss
fin funcion

El programa completo para calcular la suma de los elementos de un arreglo puede ser el siguiente:
33
*OUSPEVDDJOBMBQSPHSBNBDJO

Integer N, suma
Integer A[N]
inicio
Lectura (N)
lectura_arreglo (N, A)
Escribir(suma (N, A))
fin

1.4 Diagrama de ujo

Los diagramas de ujo son comunes en varios dominios tcnicos y se usan para poner en orden los pasos a seguir o las
acciones a realizar. Su principal ventaja es que tienen la capacidad de presentar la informacin con gran claridad, adems
de que se necesitan relativamente pocos conocimientos previos para entender los procesos y/o el objeto del modelado.
Por ejemplo, en el siguiente diagrama se presentan los pasos a seguir cuando alguien sale de vacaciones:

Start

Cierre todas las


ventanas

Cierre agua, gas


y luz

Cierre la puerta
de entrada

NO S
Hay un vecino
de confianza?

Deje un duplicado
de las llaves al
vecino

Stop

Figura 1.8
34
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

En la descripcin de los algoritmos o de los programas existen varios formalismos. Pero, de una manera sinttica, las reglas
comunes a todos para expresar algoritmos, segn el paradigma de la programacin estructurada, son:
s Un diagrama de ujo se lee de arriba hacia abajo.
s Un diagrama se compone de bloques entre los cuales existen echas que indican el sentido de lectura o de eje-
cucin.
s Tanto al inicio como al nal hay un solo bloque, START y STOP, respectivamente (vase gura 1.9).

Start Stop

Figura 1.9

s Para las operaciones de entrada o de salida se utilizan los bloques con la forma de un paralelogramo (vase gura
1.10).

Lectura(x) Escritura(x)

Figura 1.10

s Los bloques para hacer asignaciones son rectangulares o cuadrados (vase gura 1.11).

variable @ expresion

Figura 1.11

s Una decisin tomada con base en una expresin lgica se expresa con un bloque en forma de rombo (vase gura
1.12).

NO S
expresin
lgica

Figura 1.12

La mayora de las estructuras de la programacin estructurada presentadas tienen una transcripcin evidente e inmediata
en diagramas de ujo:
s La estructura selectiva simple y la estructura selectiva alternativa:

35
*OUSPEVDDJOBMBQSPHSBNBDJO

S NO
expresin
lgica

instrucciones 1 instrucciones 2

Figura 1.13

Nota: Cualquier rama (S o NO) puede dejarse vaca.

s Estructura iterativa mientras:

expresin
S
lgica

NO
instrucciones

Figura 1.14

s Estructura iterativa repetir:

instrucciones

expresin
lgica
NO
S

Figura 1.15
36
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

En cada una de estas estructuras, instrucciones signica cualquier construccin correcta de diagrama de ujo formada
para uno solo o ms bloques.
Las variables (simples, arreglo o de otro tipo) de un diagrama de ujo pueden considerarse implcitamente declara-
das desde sus primeras apariciones, o bien pueden declararse de manera explcita y detallada en un documento anexo
al diagrama. De cualquier forma, la primera aparicin de una variable debe ser en bloque de entrada o escrita en el lado
izquierdo de una asignacin. Por ejemplo:

Start Start

Lectura (i) j @ k + 3

Lectura (i)
j @ i + 3

Stop Stop

Figura 1.16

El primer diagrama de ujo es correcto, porque en el momento del clculo de la variable j, la variable i es conocida y
contiene un valor; pero en el segundo diagrama, el uso de la variable k del lado derecho de la primera asignacin no es
correcto, porque aqu la variable k aparece por primera vez y no contiene ningn valor.
Los ejemplos de diagramas de ujo que aparecen enseguida, son la resolucin de los problemas y ejercicios mostra-
dos como ejemplos a lo largo de todo el captulo. El diagrama de ujo es, en la mayora de los casos, una traduccin el
del pseudocdigo. Los diagramas de ujo fueron concebidos y probados con el software Raptor.8 En el apndice 2 que se
encuentra en el CD-ROM, se presenta este software, la manera cmo hacer los diagramas de ujo y la forma de probarlos.
Ejemplo 1
Leer una variable x y calcular el cuadrado de x.

Start

Su variable:
GET x

y@ x * x

PUT El cuadrado
de + x +es +
y

End

Figura 1.17

8
Raptor es un software libre disponible en: http://raptor.martincarlisle.com/
37
*OUSPEVDDJOBMBQSPHSBNBDJO

Este diagrama de ujo es lineal y muy simple: lectura, clculo y escritura.


Ejemplo
Vericar si un nmero entero es o no divisible entre 3.

Start

Su variable:
GET I

resto @ I%3

S NO
resto = 0

PUT S, es di- PUT NO. No es di-


visible entre 3 visible entre 3

End

Ejemplo 3 Figura 1.18

Resolver la ecuacin de primer grado que tiene su forma matemtica ms general:


ax + b = 0
Start

Su valor:
GET n

S NO
x = 0

PUT FALSO S NO
x = 1

PUT CIERTO S NO
x = 1

PUT INDEFINIDO PUT ERROR

End
Figura 1.19
38
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Ejemplo 4
Segn el valor de una variable de entrada, se escribe:
s FALSO si el valor de la variable es 0.
s CIERTO si el valor de la variable es 1.
s INDEFINIDO si el valor de la variable es 1.
s ERROR en otros casos.

Start

Su valor:
GET n

S NO
n = 0

PUT FALSO S NO
n = 1

PUT CIERTO S NO
n = 1

PUT INDEFINIDO PUT ERROR

End

Figura 1.20

/BSERVACIN En el software Raptor no se dispone de formalismos para modelar la estructura selectiva casos; entonces,
se utiliza la estructura alternativa clsica; esto es, existe la libertad de poner los bloques de decisin en cualquier orden,
aunque es mejor seguir el enunciado del problema.
Ejemplo 5
Buscar el nmero entero ms pequeo, m que es mayor que un nmero real positivo x, con x * l.

39
*OUSPEVDDJOBMBQSPHSBNBDJO

Start

Su variable real > l:


GET x

S NO
x = l

PUT La entrada
m @ 0
no es correcta

Loop

S
m > x

NO

m A m + l

PUT Se obtiene:
+ m + l > + x

End

Figura 1.21

/BSERVACIN En el diagrama introducimos de manera suplementaria la prueba para certicar si la entrada es correcta; a
saber, si x * l.
Ejemplo 6
Buscar el nmero entero ms grande de forma 2k que sea menor que un nmero real positivo x, con x * 1.

40
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

Start

Su valor x:
GET x

m @ 1

Loop

m @ m * 2

S
m > x

NO

m @ m/2

PUT m = + m +
< x = + x

End

Figura 1.22

/BSERVACIN Por razones de tamao, en este diagrama de ujo hemos renunciando al clculo de j con m = 2j.

Ejemplo 7
Para un nmero real x entre 0 y 1 (0 < x < 1), una base de numeracin b (b ) 10) y un nmero entero po-
sitivo k, buscar las k primeras cifras despus del punto decimal de la representacin de x en base b.

41
*OUSPEVDDJOBMBQSPHSBNBDJO

Start

Su nmero entre
0 y 1: GET x

La base de numeracin:
GET b

Nmero de cifras en base


+ b + : GET k

y @ x

i @ 0

Figura 1.23

Loop

p @ y * b

c @ floor (p)

y @ p floor (p)

PUT c

i @ i + 1

S
i > = k

NO

End

Figura 1.24
42
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

/BSERVACIN Con respecto al pseudocdigo, cambiamos el orden de obtencin de c al interior de la estructura iterativa
por una razn pedaggica: disponemos de la funcin floor() para el clculo de la parte entera inferior; entonces, obte-
nemos la parte fraccionaria por diferencia entre el nmero y su parte entera. Vase el CD-ROM de apoyo que acompaa
este libro, donde se incluye una versin en la que se usa un arreglo para guardar las cifras en base b con una escritura
ms legible del resultado.
Ejemplo 8
Obtener todas las potencias de un nmero a, desde a1 hasta ak, donde a y k son valores de entrada, a es un nmero
real y k es un entero positivo.

Start

El nmero real:
GET a

El coeficiente
mximo: GET k

i @ 1

p @ 1

Figura 1.25

Loop

S
i > k

NO

p @ p * a

c[i] @ p

i @ i + 1

Figura 1.26
43
*OUSPEVDDJOBMBQSPHSBNBDJO

i @ 1

Loop

PUT in +
+ c[i]

i @ i + 1

S
i > k

NO

End

Figura 1.27

/BSERVACIN El diagrama de ujo implementa (expresa) la versin con las potencias de a almacenadas en un arreglo
que se escribe al nal, durante otra estructura repetitiva.
Ejemplo 9
Calcular la suma de los elementos de un arreglo que se lee de entrada.

Start

lectura_arreglo
(M, B)

suma (M, B, suma)

escribir (suma, la
suma de los elemen-
tos del arreglo)

End

Figura 1.28
44
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

/BSERVACIN La herramienta que empleamos, nicamente nos permite el uso de procedimientos; entonces, la solucin
del problema la expresamos como tres llamadas de procedimientos:
1. Por la lectura del arreglo:

Start
(out N, out A)

La dimensin del
arreglo: GET N

i @ 1

Loop

Elemento +i+:
GET A[1]

I @ I + 1

S
i > N

NO

End

Figura 1.29

45
*OUSPEVDDJOBMBQSPHSBNBDJO

2. Por el clculo de la suma de los elementos del arreglo:

Start (in N,
in A, out s)

s @ 0

i @ 1

Loop

s @ s + A [i]

i @ i +1

S
i > N

NO

End

Figura 1.30

3. Por la escritura de un valor y de un mensaje:

Start (in valor,


in mensaje)

PUT valor + +
mensaje

End

Figura 1.31

46
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

/BSERVACIN Aqu se puede observar que el nombre del arreglo es X y que el nombre de su dimensin es M. Estos
nombres se usan en las llamadas de los procedimientos de lectura y de clculo de suma. As, podemos denir los par-
metros de los procedimientos con los nombres que deseamos; en este caso, N por el parmetro de dimensin y A por el
parmetro que guarda el arreglo. Algunos de los parmetros son: de entrada, cuando se calcula la suma de los elementos
del arreglo, o de salida, que es el valor calculado de esta suma.

Sntesis del captulo

La computadora siempre ejecuta rdenes en un formato inteligible para ella; dichas rdenes estn agrupadas en un pro-
grama o software. Un programa est escrito en un lenguaje de programacin de alto o bajo nivel y traducido en cdigo
ejecutable. Por su parte, un software es un conjunto de programas.
El trabajo de realizacin de un software que resuelve un problema o que responde a una situacin est basado en la
elaboracin de algoritmos. Un algoritmo sigue un proceso de elaboracin que pasa por las siguientes fases:
1. Denicin. Se especica el propsito del algoritmo.
2. Anlisis. Se analizan el problema y sus caractersticas; se determinan las entradas y las salidas del problema, y se
elige la solucin ms conveniente, si hay varias, o se propone una nueva.
3. Diseo. Se plasma la solucin del problema; aqu se emplea una herramienta de diseo: el diagrama de ujo y
el pseudocdigo.
4. Implementacin. Se realiza el programa y se hacen varias pruebas; el programa se edita con editores de texto y
se compila o se interpreta a n de crear el ejecutable o ejecutar el cdigo.
Los programas se escriben en un lenguaje de programacin. Hay varios paradigmas de programacin y una multitud de
lenguajes de programacin que tienen uno o varios paradigmas. La eleccin del lenguaje de programacin depende prin-
cipalmente del tipo de problema a resolver, de la computadora y de otros dispositivos fsicos que se utilizarn.
Los programas contienen variables. Una variable tiene un tipo y un nombre que debe ser nico. Segn los paradig-
mas el lenguaje, la asignacin de una zona de memoria para la variable se hace en la memoria (memoria central, en la
mayora de los lenguaje) de manera esttica o dinmica.
El pseudocdigo y los diagramas de ujo son herramientas de diseo de algoritmos ms o menos equivalentes, que
tienen el paradigma de programacin imperativa y estructurada.
Las estructuras de paradigma que se conocen son:
s Estructura secuencial.
s Estructura alternativa.
s Estructura iterativa.
s Funciones y procedimientos.
Las variables que se usan en el pseudocdigo o en el diagrama de ujo pueden ser simples, de tipo arreglo o de otro tipo,
descrito por el programador. Cada variable posee un nombre nico y no ambiguo y tiene reservada una zona de memoria
en la cual se almacena el valor de la variable.

47
*OUSPEVDDJOBMBQSPHSBNBDJO

Bibliografa
s Knuth, Donald, El arte de programar ordenadores, Vol. I, Algoritmos fundamentales, Editorial Revert, Barcelona,
Bogot, Mxico, 1986.
s Cedano Olvera, Marco Alfredo y otros, Fundamentos de computacin para ingenieros, Grupo Editorial Patria, Mxi-
co, 2010.
s Alfred V., Sethi, Ravi y Ullman, Jeffrey D., Compilers: Principles, Techniques, and Tools, Addison-Wesley, Estados
Unidos, 1986.

Ejercicios y problemas

1. Cul es la diferencia entre un programa y un algoritmo?

2. Es posible escribir directamente un programa para la resolucin de un problema? Es til?

3. Construir el rbol de evaluacin y luego escribir la expresin en lenguaje informtico en forma de injo de:

a) ab + bd

b) a2 103
a b 2 4ac
c)
2b
d) a + b < c y 2c < 4a + 3b

4. Escribir la expresin lgica que corresponde a la expresin a & 0 y b2 4ac > 0 matemtica siguiente:

5. Proponer nombres de variables para resolver una ecuacin de segundo grado.

6. Si los coecientes algebraicos de una ecuacin de segundo grado son nmeros reales, proponer tipos para
las variables elegidas en el problema anterior.

7. Si los coecientes de la ecuacin x2 by2 = 0 son nmeros enteros, proponer nombres y tipos de variables
para buscar soluciones enteras.

8. Por qu la denicin de una funcin parece tan diferente entre los lenguajes C, PASCAL y PL/SQL, por un
lado, y PROLOG o LISP, por el otro? Por qu los primeros ejemplos son tan parecidos (lenguajes C, PASCAL
y PL/SQL)?

48
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

9. Cul paradigma de programacin gobierna a los diagramas de ujo y al pseudocdigo?

10. Hacer una tabla con columnas asociadas a los paradigmas de programacin y con lneas asociadas de al me-
nos cinco lenguajes de programacin citados en el captulo. Indicar con el smbolo de paloma si el lenguaje
sigue o no el paradigma enunciado.

11. Cul es el orden de ejecucin de las operaciones en la expresin siguiente?

A* 3000 + 45 > A+45 * 3000

12. Si las variables a, b y c tienen los valores 1, 2 y 3, respectivamente, cul es el valor de la siguiente expresin?

NOT((a <= b) AND (a + b <= c))

%LABORARELPSEUDOCDIGOOELDIAGRAMADEmUJOCONELSOFTWARE2APTORPARALOSSIGUIENTESPROBLEMAS

13. #LCULODELASUPERlCIEREA de un crculo, cuando conocemos el radio del crculo.

14. #LCULODELASUPERlCIEDEUNCRCULO cuando conocemos el dimetro del crculo.

15. #LCULOSOBREUNCILINDRO cuando conocemos el radio del crculo y la altura.

Figura 1.32

Queremos saber:

s El volumen.

s El rea de la supercie cilndrica.

49
*OUSPEVDDJOBMBQSPHSBNBDJO

16. #LCULODELAFECHADE0ASCUA Para un ao entre 1900 y 2100 la fecha de Pascua se determina segn las
siguientes frmulas:

M = 25, N = 4 constantes, AN es el ao

a = AN mod19, b = AN mod 4, c = AN mod7

d = (19a + M) mod 30, e = (2b + 4c + 6d + N) mod7

si d + e> 9, la fecha es el da de d + e 9 de abril

si no, la fecha es d + e + 22 de marzo.

17. %SCRIBIR EL NOMBRE DEL DA Lunes o Martes, o..., cuando conocemos el nmero del da de la semana
(1, 2, ..., 7).

18. #LCULODELVALORABSOLUTODEUNNMERO El valor absoluto de un nmero es denido como:

a) Crear el pseudocdigo que lo calcula.

b) Escribir una funcin con un parmetro de entrada y de regreso, del valor absoluto de su parmetro.

19. Conversin de una temperatura expresada en grados Fahrenheit a grados Celsius segn la frmula:

C = (5/9)(F 32)

Hacer dos pseudocdigos:

a) Uno para la conversin de grados Celsius a grados Fahrenheit.

b) Uno para la conversin de grados Fahrenheit a grados Celsius.

20. Resolver una ecuacin de segundo grado.

Una ecuacin de segundo grado, en su forma ms general, es:

ax2 + bc + c = 0

21. Clculo de N! con N entero, positivo y bastante pequeo (N ) 8).

22. Ecuacin diofntica. Resolver la ecuacin x2 by2 = 0 con a y b nmeros enteros. Aqu nos interesan ni-
camente las soluciones de parejas de nmeros enteros y positivos.

50
$BQUVMPt%FMBMHPSJUNPBMQSPHSBNB

23. #LCULO DEL MXIMO COMN DIVISOR DE DOS NMEROS ENTEROS Aplicar el algoritmo de Euclides para
calcular el divisor comn ms grande. El algoritmo tiene dos versiones: con sustracciones sucesivas o con
divisiones.

24. Leer un arreglo y construir su imagen en espejo:

s En otro arreglo.

s En el mismo arreglo.

25. Clculo del ms pequeo y el ms grande de un nmero variable N de nmeros.

26. 0OTENCIASDE Vericar si un arreglo contiene nicamente valores de tipo 2k, con K entero positivo.

27. Vericar si un arreglo tiene sus valores en orden creciente.

Es decir que para un arreglo X de dimensin N:

X[ i ] ) X[i + 1], i = 1, N 1

28. Si un arreglo tiene sus valores en orden creciente, calcular su valor mediano.

29. Decimos que un arreglo contiene una ZONAPLANA si tiene al menos dos valores sucesivos iguales. Por
ejemplo, el arreglo siguiente tiene tres zonas planas:

1 2 3 33 4 4 5 6 7 8 20 20 20 20 20 20

que tienen los valores 3, 4 y 20.

Si el arreglo contiene zonas planas, indicar el tamao por la zona plana ms amplia y el valor que se repite.
Por ejemplo, en el arreglo indicado, la zona plana ms amplia tiene el tamao de 5 y contiene el valor 20.

51
Introduccin a la programacin 6OJEBEt%FMBMHPSJUNPBMQSPHSBNB
2 7YVNYHTHJP}ULUSLUN\HQL*!
JVUJLW[VZImZPJVZ

Contenido
Ecuacin de primer grado
2.1 Introduccin #LCULOAPROXIMADODELNMEROUREO
2.2 Mi primer programa #LCULODEUNARAZDEECUACINDETERCERGRADO
#LCULODELAFECHADELDASIGUIENTE
2.3 Estructura de un programa Ternas pitagricas
Directivas *UEGODELABSQUEDADEUNNMERO
Comentarios 3NTESISDELCAPTULO
Declaraciones y deniciones "IBLIOGRAFA
2.4 Variables y expresiones 2EFERENCIASDE)NTERNET
Identicadores Ejercicios y problemas
Tipos y variables
Constantes Objetivos
Expresiones con operadores
s %NTENDERELFUNCIONAMIENTODEUNPROGRAMAENLENGUAJE#
2.5 Control de ujo s #ONOCERLASINTAXISDELLENGUAJE#
Proposiciones y bloques s #OMPRENDERYUTILIZARESTRUCTURASALTERNATIVASEITERATIVAS
Estructuras alternativas s %SCRIBIRPROGRAMASENLENGUAJE#
Estructuras iterativas s %LABORARPROGRAMASBSICOS
Otras proposiciones de control de ujo s -ANEJARVALORESNUMRICOS ENTEROSYDEPUNTOmOTANTEEN
2.6 Problemas resueltos lenguaje C.
52 52
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

2.1 Introduccin

Un programa traduce un algoritmo en un lenguaje de programacin; dicho algoritmo est muy claro en la mente del
programador o se expresa en un pseudocdigo o con la ayuda de diagramas de ujo. Por tanto, para poder escribir
un programa, necesitamos conocer y utilizar un lenguaje de programacin.
En este segundo captulo comenzaremos con el aprendizaje de un lenguaje de programacin de alto nivel: el lenguaje
C, el cual fue propuesto al inicio de la dcada de 1970 por Dennis M. Ritchie y ha pasado por varias etapas de evolucin.
La ms notable es la versin del lenguaje propuesta por Dennis M. Ritchie y Brian Kernighan en la primera edicin del libro
El lenguaje de programacin C, que es la principal referencia para aprenderlo. La norma ANSI C o C89 (denominacin
exacta: ANSI X3.159-1989) es una extensin del C de Kernighan y Ritchie. En este texto vamos a usar la norma ANSI C, y
el cdigo que se presenta fue probado con el compilador gcc.
El lenguaje C es un lenguaje de alto nivel (signicativo para el programador), que sigue el paradigma de la programa-
cin imperativa y estructurada, lo que le permite trabajar las llamadas de funciones, la recursividad y mucho ms en un
nivel alto, al igual que funcionar a un nivel bastante bajo, tipo ensamblador, para trabajar directamente el contenido de la
memoria, manejar entradas y salidas desde equipos especiales y realizar trabajo sobre la red.

2.2 Mi primer programa

Un programa fcil de entender y realizar es el de calcular el rea de un crculo cuando se conoce su radio. Se tiene la
siguiente frmula matemtica:

rea =  radio2

Una versin de este cdigo en lenguaje C es la siguiente:


/* Area de un circulo */
#include <stdio.h>
#include <stdlib.h>
#define PI 3.141592653
int main(int argc, char *argv[])
{
float radio = 10.5;
float area;
area = PI*radio*radio;
printf(El area de un circulo de radio %f es\t %f.\n, radio, area);
exit(0);
}

Este cdigo se almacena en un archivo.c. Por ejemplo, primer.c; el archivo se escribe con un editor de texto y se
procesa en un compilador, por ejemplo el gcc,1 utilizando la siguiente instruccin:
gcc -o primer primer.c

Para ejecutar el programa primer en gcc se utiliza la siguiente instruccin:


./primer

1
Vase el apndice 3 en el CD-ROM para ms detalles sobre la compilacin.
53
Introduccin a la programacin

El cual produce como salida:

El area de un circulo de radio 10.500000 es 346.360596.

Nuestro primer programa C se compone de:

s Un comentario
/* Area de un circulo */
s Una parte de directivas

#include <stdio.h>
#include <stdlib.h>
#define PI 3.141592653

s Un bloque principal que se llama main.


int main(int argc, char *argv[])
{
float radio = 10.5;
float area;
area = PI*radio*radio;
printf(El area de un circulo de radio %f es\t %f.\n, radio, area);
exit(0);
}

El comentario no incluye informacin para la ejecucin misma del cdigo, pero cuenta con datos importantes para el
programador o un lector externo del cdigo (otra persona).
Las directivas que se usaron en el ejemplo anterior son de dos tipos:

s De inclusin de bibliotecas externas: #include


s De denicin de valores constantes: #define, que es en realidad una sustitucin que se hace al inicio de la com-
pilacin (el preprocesamiento).

El bloque main contiene una parte de declaracin de variables (las dos variables radio y area de tipo otante):

float radio = 10.5;


float area;

y una parte de proposiciones:

area = PI*radio*radio;
printf(El area de un circulo de radio %f es\t %f.\n, radio, area);
exit(0);

Dos de las tres proposiciones anteriores son funciones estndares: una para escribir un mensaje printf y la ltima
para terminar el programa exit. La tercera proposicin es una asignacin, en la cual el valor inicial y desconocido de
la variable area cambia con el valor del clculo 3.141592653 * radio*radio. La asignacin es la transposicin en
lenguaje de programacin de la frmula matemtica presentada, excepto que para la constante matemtica  usamos
una aproximacin.2
En el bloque main tambin aparecen los identicadores de variables (radio y area), la palabra clave del tipo de dato
(float) para los valores otantes de tipo numrico (10.5) y un mensaje textual entrecomillado (El area ... n).

2
Hay la posibilidad de usar la biblioteca externa math.h que contiene la constante M_PI denida por un valor ms
preciso de /. En el CD-ROM se incluye el programa segundo .c con esta modicacin.
54
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

2.3 Estructura de un programa

Un programa en lenguaje C se compone de un conjunto de archivos que se almacenan con la extensin .c o .h. Los
archivos con la extensin .h contienen declaraciones de variables y funciones globales. Los archivos .c contienen la
declaracin y las deniciones de las funciones. La presencia de la funcin main en uno de los archivos .c es la nica
restriccin que se impone; por lo dems, el programador tiene la libertad de usar cualquier nombre y cualquier tipo para
sus funciones y variables.
La estructura de un archivo en el lenguaje C es la siguiente:

# include.. directivas por el


... procesador
# define..
...

int N; declaraciones de variables


...
declaraciones de funciones
wid imp()
... deniciones de funciones

Por el momento trabajaremos con un programa formado por un solo archivo .c. En este caso la estructura es la siguiente:

# include.. directivas por el


... procesador
# define..
...

int N; declaraciones de variables


...
declaraciones de funciones
voi d imp()
... deniciones de funciones
int main()
(main obligatoria)

Entonces, un archivo de C (.c o .h) contiene los siguientes elementos:

s Directivas de preprocesador (opcional).


s Comentarios (opcional).
s Declaraciones de variables o de funciones.
s Bloques de denicin de las funciones.
s Al interior de los bloques, se encuentran proposiciones simples u otros bloques.

Un archivo en lenguaje C se escribe nicamente usando caracteres que pertenecen al cdigo ASCIII que no contiene
ninguna letra con tilde. Las letras con tilde se pueden usar nicamente al interior de las cadenas de caracteres, pero no
puede garantizarse que se impriman correctamente, esto depender del sistema operativo de la computadora y los par-
metros del sistema donde se ejecute el cdigo.
Al interior de las cadenas de caracteres que inician y terminan con comillas dobles (mensajes textuales) se colocan,
para efectos de impresin, los caracteres ASCII (o codicaciones) de la tabla 2.1.

3
Vase en el apndice 1 la tabla de los caracteres que se encuentra en el CD-ROM.
55
Introduccin a la programacin

Tabla 2.1 Caracteres especiales para formato de impresin.

\n salto de lnea
\\ el carcter \
\t una tabulacin horizontal
\v una tabulacin vertical
\ el carcter
\ el carcter
\b una seal audible
\ooo un nmero en octal
\xhh un nmero en hexadecimal

Es importante resaltar que el lenguaje C es sensible a la diferencia entre las letras minsculas y las maysculas. Por ejem-
plo, if es una palabra clave e IF es un identicador denido por el programador.

Directivas
Las directivas indican reglas de sustitucin o la inclusin de bibliotecas externas con declaracin de variables y funciones
globales que se utilizan en las funciones denidas por el usuario.
La directiva de inclusin #include tiene dos formas:

#include <archivo.h>
#include archivo.h

Indica en ambos casos que se incluye el archivo de declaraciones archivo.h. La primera forma indica que la biblio-
teca es estndar y su ubicacin es denida por el nivel de conguracin de sistema; la segunda forma, indica que el
archivo.h fue construido por el programador (o su grupo de trabajo) y que se ubica en la misma carpeta donde se
encuentra el archivo actual, o bien, en otra carpeta, en cuyo caso se debe indicar la ruta relativa a dicho archivo.
Ejemplos

#include <stdlib.h>
#include <sys/socket.h>

Los archivos se encuentran en una carpeta estndar, en este caso:

/usr/include para stdlib.h y /usr/include/sys para socket.h


#include bibliotecalocal.h
#include grafo/impresion.h

Aqu las bibliotecas se ubican en la carpeta de trabajo del usuario, al mismo nivel para la bibliotecalocal.h y en
la subcarpeta grafo para impresion.h.
Por su parte, las directivas de sustitucin tienen la siguiente forma:

#define NOMBRE valor-de-sustitucion

Donde define e include son palabras clave del lenguaje C, mientras que NOMBRE y valor-de-sustitucion
son denidos por el usuario. El funcionamiento es el siguiente: cada vez que se encuentra el identicador NOMBRE se
sustituye con valor-de-sustitucion.

Ejemplo
En el primer programa presentado:
56
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

/*El Area de un circulo */


..
#define PI 3.141592653
int main(int argc, char *argv[])
{
..
area = PI*radio*radio;
...

El preprocesador hace la sustitucin siguiente:

area = 3.141592653*radio*radio;

Nota: El valor de sustitucin puede ser cualquier sucesin de caracteres o cadena de caracteres.

Ejemplo
#include <stdio.h>
#include <stdlib.h>
#define PI 3.141592653
#define OP *
int main(int argc, char *argv[])
{
float radio = 10.5;
float area;
area = PI OP radio OP radio;
printf(El area de un circulo de radio %f es\t %f.\n, radio, area);
exit(0);
}

En el programa anterior se hacen dos sustituciones: PI y el identicador OP.


El resultado de las sustituciones es:

area = 3.141592653 * radio * radio;

Es posible tambin construir sustituciones con parmetros o macrosustituciones.

Ejemplo

#include <stdio.h>
#include <stdlib.h>
#define PI 3.141592653
#define PROD(x,y) x * y
#define CUADRADO(x) x * x
int main(int argc, char *argv[])
{
float radio= 10.5;
float area;
area = PROD(CUADRADO(radio), PI);
printf(El area de un circulo de radio%f es\t %f.\n, radio, area);
exit(0);
}

En el programa anterior se hacen tres sustituciones: PI, PROD con 2 parmetros y CUADRADO con un solo parmetro.
El resultado de las sustituciones es el siguiente:

57
Introduccin a la programacin

area = radio* radio* 3.141592653;

Nota: Las directivas se escriben en una sola lnea.

Comentarios

En cualquier lenguaje de programacin, el papel de un comentario es ayudar a la comprensin de cualquiera que lea el
cdigo y no modica en nada la ejecucin del programa.
En el lenguaje C, los comentarios se indican entre /* y */ y pueden escribirse en lneas sucesivas. Tambin existe
la forma //que indica el inicio del comentario que se termina al nal de una sola lnea. La forma // tambin se utiliza
en C++.
Un consejo es insertar comentarios al inicio del archivo en C para indicar qu hace el cdigo y quin lo hizo, al lado
de cada funcin del usuario, al interior de los bloques (para indicar qu se hace) o cada vez que el programador piense
que es necesario.

Ejemplo
El primer programa tiene un solo comentario. A continuacin se presenta una versin con ms comentarios:

/* El area de un circulo */
/* fecha de creacion : 10 de marzo de 2011
autor : Mihaela Juganaru de Mathieu
*/
#include <stdio.h>
#include <stdlib.h>
#define PI 3.141592653
int main(int argc, char *argv[])
{
float radio = 10.5; //este valor se cambia por otro radio
float area;
area = PI*radio*radio;
printf(El area de un circulo de radio%f es\t %f.\n, radio, area);
exit(0); // 0 : ejecucion correcta
}

Los ejecutables que se generen sern idnticos, sin importar la cantidad de comentarios.
Nota: Al interior de los comentarios tambin se pueden usar caracteres del cdigo ASCII (que incluyen letras con tilde).

Declaraciones y deniciones

Otra parte de un archivo .c (la ms larga) contiene las declaraciones de variables, las declaraciones de las funciones y las
deniciones de estas funciones. La forma general de una declaracin de funcin o de variable es:

tipo nombre_entidad
tipo nombre_entidad(parametros);

Donde tipo es un tipo estndar del lenguaje C o un tipo denido por el programador y nombre_entidad es un
identicador a eleccin del usuario.

58
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

2.4 Variables y expresiones

Como la mayora de los lenguajes de programacin imperativa, el lenguaje C implementa la nocin de variable en un
sentido simple como la zona de memoria que se asigna para contener un valor de un tipo preciso.

Identicadores

Por identicador se entiende un nombre que es nico en un ambiente preciso; este nombre es una sucesin de letras
maysculas o minsculas, cifras y el carcter _ (guin bajo) y no puede iniciar con una cifra. Por ejemplo: a, AA, A2_
tablero, _mi_constante son identicadores, pero 2sumas no puede ser un identicador.
Un identicador sirve como nombre para las variables del programa, para las funciones y tipos denidos por el usuario
y para las etiquetas de programas.
El lenguaje C se basa en un conjunto de reglas de sintaxis que permite denir el programa mismo con sus declara-
ciones, deniciones y proposiciones. La sintaxis impone el uso de palabras clave, por esta razn no deben utilizarse los
siguientes trminos:

Tabla 2.2 Palabras reservadas del lenguaje C.

sizeof register break


do if static
void case else
int struct volatile
char enum long
switch while const
extern return typedef
continue float short
union default for
signed unsigned

Existen tambin identicadores estndar denidos en las bibliotecas externas del lenguaje C y es mejor evitar el uso del
mismo nombre. Por ejemplo, FILE, time y printf son nombres de tipos y de una funcin de varias bibliotecas es-
tndares.
Al interior de un bloque, los identicadores deben ser nicos. Por ejemplo, no es posible tener dos variables denidas
con el mismo nombre, ni una etiqueta y una variable homnimas.
Un consejo sera seleccionar identicadores nemnicos (nombres expresivos) para una lectura fcil y una escritura
de cdigo elegante.

Tipos y variables

Una variable tiene un nombre (o identicador) y un tipo. Al momento de la compilacin (y si el programa es correcto
sintcticamente) o durante la ejecucin del programa se hace la asignacin de la zona de memoria para la variable.
En lenguaje C las variables se declaran con alguna de las siguientes tres formas:

tipo_datos identificador;
59
Introduccin a la programacin

tipo_datos identificador1, identificador2, ..., identificadorn;

tipo_datos identificador = valor;

La tercera de estas formas permite dar un primer valor a la variable al inicio del pro-
_#UIDADO #UALQUIER VARIABLE UTILIZADA EN EL grama.
programa debe ser declarada antes.
Nota: Un tipo de datos puede ser: tipo estndar o tipo denido por el usuario o
que se encuentre en una biblioteca externa.
Los tipos estndar escalares (que adems contienen un solo valor) son:

Palabra reservada Contenido Tamao


int entero 2 bytes
long entero 4 bytes
short entero 1 byte
char Cdigo ASCII 1 byte
float otante 4 bytes
double otante 8 bytes

Los enteros tienen una presentacin binaria y guardan un valor (sin fraccin decimal) con su signo. El tipo char puede
almacenar un entero o un cdigo ASCII.
Adems, los enteros pueden ser precedidos por la palabra unsigned, en cuyo caso su valor se considera positivo,
aumentando el rango de valores que se pueden representar.
El compilador gcc utiliza cuatro bytes para representar el tipo long y representa a los otantes con la norma
IEEE-754. Vase el apndice 1 para los detalles de representacin binaria de los tipos enteros y del tipo otante.
El lenguaje C no implementa un tipo particular para los valores lgicos: se considera que si un valor (el valor de una
expresin) es cero, se asimila el valor lgico FALSO, de lo contrario es CIERTO.

Constantes

Una constante aritmtica se declara de dos maneras:


1. Como una variable precedida de la palabra const y seguida de la asignacin de un valor (el cual no se puede
modicar en el resto del cdigo).

Ejemplo

const int limita = 10;

2. Con la directiva #define de sustitucin al inicio del programa.

Ejemplo

#define limita 10

Los valores de las constantes pueden ser:


60
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

s Enteros: Formados por cifras y, eventualmente, un signo.

Ejemplo

123, +45, 34

s Flotantes: Formados por signo, parte entera, punto decimal, parte fraccionaria y, eventualmente, un exponente con
la letra e o E y un entero.

Ejemplo
0., 123., 0.89, 0.123E+3, 67.0e-23

Podemos usar tambin valores constantes sin ninguna declaracin.

Expresiones con operadores


Una expresin aritmtica contiene: variables, constantes, operadores aritmticos y parntesis. Los operadores aritmti-
cos bsicos del lenguaje C son los siguientes:

Tabla 2.3 Operadores aritmticos bsicos.


+ Suma
Resta o el negativo de un nmero
* Multiplicacin
/ Divisin
% Residuo de la divisin entera

Nota: La prioridad de los operadores es la misma que en las expresiones matemticas, las expresiones entre parntesis
se evalan primero y en orden descendente los operadores:
s *, / y %.
s + y .

Adems, si tenemos una sucesin de operadores de la misma prioridad, el clculo se realiza de izquierda a derecha.

Ejemplo

(1+2)*34%6 y (1+2*34)%6 son expresiones diferentes.


Los parntesis se representan por dos y se abren y cierran en el orden normal.

Ejemplo
x+1 + y+z
2 3 .
La expresin: ((x+1)/2+(y+2)/3)/(x+y)) es correcta y corresponde a la expresin matemtica: x+y
En una expresin aritmtica podemos tener valores enteros o otantes. Dicha expresin se ejecuta como operacin de
enteros, si sus argumentos son enteros, o como operacin de otantes, si son otantes.

Ejemplo

int i= 13, j = 5;
printf(una division: %d, i/j);
61
Introduccin a la programacin

printf(otra division %f, 1.0*i/j);


produce:
una division: 2
otra division 2.600000
Dado que la primera divisin i/j se hace entre enteros, entonces el resultado es entero. En el caso de la segunda ex-
presin 1.0*i/j, como la primera operacin es la multiplicacin de un otante con un entero, el entero se convierte
en otante y luego se hace la divisin entre dicho otante y el entero, dando como resultado nal una divisin exacta.
Si queremos cambiar el tipo de las expresiones o variables, se utiliza una conversin de tipo (o casting):
(tipo)variable
(tipo)(expresion)
Ejemplo
Cuidado! La conversin (float) En el ejemplo anterior la divisin exacta puede hacerse de la siguiente manera:
(i/j) no produce como resultado la
(float)i/(float)j.
divisin exacta porque primero se calcula
LADIVISINENTREENTEROSYLUEGOSEHACE
DICHACONVERSIN Nota: Una conversin de tipo tiene una alta prioridad.

_#UIDADO,ACONVERSINDEUNTIPOMSAM
Ejemplo
PLIOAUNOMSESTRECHOPUEDEPRODUCIRUNA
PRDIDADEVALOR
(float)6 produce 6.0 pero (char)3000 produce un valor falso porque un valor
de tipo char contiene valores entre 128 y 127 (256 = 28 un byte).

En el lenguaje C, la asignacin de un valor a una variable es considerada como un operador y tiene la siguiente forma:

variable = expresin;

Esta operacin se ejecuta de la siguiente manera: se calcula la expresin y su resultado se asigna a la variable. Si
la expresin y la variable no tienen el mismo tipo, se hace la conversin del valor calculado de la expresin al tipo de la
variable. A continuacin se dan algunos ejemplos de asignacin.

Ejemplo

i = 56;
x = sqrt((float)i);
j = (i=3);
a = (i = 5) + (j = 6);

Las dos primeras asignaciones son fciles de entender: el valor de i cambia a 56 y el valor de x al valor de. La tercera expre-
sin contiene dos asignaciones: primero, el valor de i cambia a tres, y luego, el valor de j cambia a tres. La ltima
expresin tiene tres asignaciones y, al nal, los valores de las variables i, j, y a son cinco, seis y once, respectivamente.
Por su parte, los operadores de relacin del lenguaje C comparan los valores de la parte izquierda con el valor de
la parte derecha. Estos operadores son:
<, <=, >, >=,
== y !=
El sentido es evidente: menor <, menor o igual , mayor >, mayor o igual , equivalencia = = y no equivalencia !=.
La prioridad de los operadores de relacin es ms baja que la de los operadores aritmticos, esto signica que prime-
ro se evala la parte izquierda, seguida de la parte derecha, y luego se hace la comparacin.
Ejemplo

i < m+2

Primero se ejecuta la adicin y luego se comparan los dos valores.


62
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

No hay valores especcos para el manejo de los valores lgicos de True (Verdad) o False (Falso). El lenguaje C
toma el valor cero para Falso (expresin falsa) y cualquier valor diferente de cero como para Verdad (expresin verda-
dera).
Existen tambin operadores lgicos. Estos son los siguientes:

s ! para la negacin (NOT) -operador unario


s && para la conjuncin (AND) -operador binario
s || para la disyuncin (OR) -operador binario.

El operador ! convierte un valor que no es cero en cero y cero en uno.


El operador && regresa cero, si al menos uno de los dos operandos vale cero (es falso) o un valor diferente de cero
si no es as (los dos operandos son verdaderos).
El operador || regresa cero, si los dos operandos valen cero (son falsos) o un valor diferente de cero si no es as
(al menos un operando es verdadero).
En una expresin lgica que contiene && y ||, se ejecutan primero las operaciones de && y luego las de ||, esto es,
el operador && tiene una prioridad ms alta.
Una sucesin de operaciones de && o de || se ejecuta de izquierda a derecha, pero las evaluaciones de las ex-
presiones se hacen una a una hasta que se conoce el resultado, es posible entonces que la evaluacin de la expresin
lgica inicial se haga sin evaluar todos los operandos. Es decir, para el operador && que regresa verdadero si todos los
operandos son ciertos, si se encuentra que el primer operando es falso se termina la evaluacin, sin calcular el segundo.
Para el operador ||, si el primer valor es diferente de cero, equivalente a verdadero, no se evala ms, porque toda la
operacin es verdadera.

Ejemplo

i<n && a[i]==2


Si la expresin izquierda i<n es falsa, el clculo termina sin vericar la equivalencia de la derecha y el valor nal es
falso. nicamente si i<n es verdad, se hace el clculo de a[i]==2 y se determina el valor nal de toda la expresin.
Por su parte, en la expresin i<n || a[i]==2, si i<n es verdadero, el clculo se termina con el valor verdadero.
nicamente si i<n es falso, se hace el clculo de a[i]==2 y se detecta el valor nal de toda la expresin.
Durante los programas, muchas veces se requiere aumentar o disminuir en uno el valor de una variable (generalmente
un ndice de arreglo), para ello, el lenguaje C implementa dos operadores unarios (unitarios), denominados operadores
de incremento y decremento, los cuales se muestran a continuacin:

++

Estos operadores se pueden usar de dos formas diferentes: como prejo ++i o como sujo i++. En ambos casos se
cambia el valor de la variable i, pero si la expresin ++i o i++ (--j o j--) aparece en una expresin ms larga, es
necesario tomar en cuenta los siguientes puntos:
s ++i (o --j) primero se hace la agregacin/disminucin en uno y luego se usa el resultado en el clculo.
s i++ (o j--) primero se usa el valor de la variable en el clculo y luego se hace la agregacin/disminucin en uno.

Ejemplo
n = 3; m = 3;
x = n++; y = ++m;

Al nal, x vale tres, mientras que, y, n y m valen cuatro.


63
Introduccin a la programacin

El lenguaje C ofrece la posibilidad de trabajar directamente con el contenido de la memoria y cuenta con operadores
para el manejo de bits.
Estos operadores solo se aplican sobre valores de tipo entero (char, short, int, long) signados o no (unsig-
ned) y son los siguientes:

Tabla 2.4 Operadores para el manejo de bits.

& AND de bits


| OR inclusivo de bits
^ OR exclusivo de bits
<< corrimiento a la izquierda
>> corrimiento a la derecha
~ complemento a uno

Para &, | y ^ se trabaja con los bits de los dos operandos. El operador ~ es unario, y el resto son operadores binarios.
Para los corrimientos, el cdigo binario del operando de la izquierda se recorre el nmero de posiciones indicadas
por el operando de la derecha.

Ejemplo
x >> 3 hace un corrimiento de tres bits a la derecha por los bits del valor de x.

La operacin ~ es unaria. Las operaciones de &, | y ^ son binarias; tanto la unaria como las binarias trabajan solo con
operandos enteros.

Ejemplo
Para a=121 y b=15, las representaciones binarias y el efecto de las operaciones de manejo de bits son:

Expresin Representacin del nivel de bits


a = 121 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1
~a 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 0
a<<3 0 0 0 0 0 0 1 1 1 1 0 0 1 0 0 0
a>>2 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0
a = 121 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1
b = 15 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
a&b 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
a|b 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
ab 0 0 0 0 0 0 0 0 0 1 1 1 0 1 1 0

Adems del operador de asignacin = visto antes, en lenguaje C hay otros operadores de asignacin. Estos son de tipo
op=, donde op es un operador binario. La expresin:

expr1 op = expr2

es equivalente a:

(expr1) = (expr1) op (expr2)


64
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

El operador op puede ser: +, , *, /, %, >>, <<, &, | o ^. Cuidado! x*=y10 signica


x=x*(y10) y no x=x*y10.
Ejemplo
i+=2 signica i = i+2

Tambin, en el lenguaje C hay un operador ternario ?: (con tres operandos) que propone un clculo alternativo. La ex-
presin condicional se escribe como:

expr1 ? expr2 : expr3

Misma que se evala de la siguiente manera: se evala primero el valor de expr1, si este es verdadero (diferente de
cero) se lleva a cabo la expr2, de lo contrario, la expresin que se lleva a cabo es expr3.
Ejemplo
abs = (x>0) ? x : -x;
Al nal, la variable abs contiene el valor absoluto de x.

Para todos estos operadores (hemos presentado los que trabajan con valores numricos), si tenemos una expresin con
los mismos operadores varias veces o con una mezcla de ellos, la tabla 2.5 nos indica la precedencia y asociatividad
de operadores. La asociatividad nos indica el orden en que se evala una expresin que contiene un solo operador. La
precedencia (o prioridad) nos indica el orden en que se efectan las operaciones cuando tenemos una sucesin de varios
operadores. La lectura de la tabla4 se hace de arriba hacia abajo, esto es, los operadores que aparecen primero tienen
una prioridad ms alta.

Tabla 2.5 Precedencia de operadores.

Operadores Asociatividad
() []-> izquierda a derecha
! ~ ++ --+ -* & (tipo) sizeof derecha a izquierda
* /% izquierda a derecha
+ izquierda a derecha
<<= >>= izquierda a derecha
== != izquierda a derecha
& izquierda a derecha
^ izquierda a derecha
| izquierda a derecha
&& izquierda a derecha
|| izquierda a derecha
?: derecha a izquierda
= += -= *= %= /= &= ^= |= <<= >>= derecha a izquierda
, izquierda a derecha

Se puede ver que la prioridad ms baja es la del operador de coma, indicando que primero se evala todo lo que est
entre comas. La asignacin tambin tiene una prioridad muy baja indicando que primero se evala la parte derecha de la
asignacin (la parte izquierda no se evala porque debe ser una variable).
En caso de duda y en ausencia de esta lista, se recomienda poner parntesis para indicar el orden conveniente para
el programa.

4
Por el momento no introducimos los operadores color azul, pero sern presentados en los captulos que siguen.
65
Introduccin a la programacin

Ejemplo
4 < y + 5 && x > 8 * a
El orden de evaluacin es el siguiente: la suma y + 5, la comparacin 4 < el resultado de (y + 5), si este resultado
es falso se termina el clculo con el valor cero (falso), de lo contrario, se hace la multiplicacin, la siguiente comparacin
x > el resultado de (8*a) y segn el valor de esta ltima comparacin se calcula el valor nal de la expresin.
En la escritura de las expresiones aritmticas y de comparacin hay algunos errores frecuentes, en los cuales se confun-
den algunos operadores:
s La expresin de igualdad a == b con la expresin de asignacin a = b.
s Las expresiones lgicas a && b y a || b con las expresiones de clculo al nivel de bits a & b y a | b.

Escribir esto no genera errores de compilacin, pero s errores de ejecucin del programa porque en la mayora de los
casos las expresiones lgicas se utilizan en estructuras alternativas o repetitivas y el efecto puede ser contrario.
En el lenguaje C se tiene la ventaja de poder usar el operador de asignacin como un operador que regresa un valor
y de que cualquier valor se interpreta como un valor lgico. Sin embargo, en algunos casos la escritura puede parecer
ambigua:

if (x = y)
printf( Cambie x con el valor de y que es cero);
else
printf( Cambie el x con el valor de y que NO es cero);

El lector del cdigo puede imaginar (creer) que es un error de parte del programador y que sera correcto escribir x
== y, pero primero se hace la asignacin y la interpretacin del resultado para distinguir los casos de y == 0 y su
contrario. La escritura es correcta, pero para tener un programa ms legible es mejor escribir:

if ((x = y) ==0)

Una ltima observacin: en la expresin ((x = y) ==0) los parntesis son necesarios porque el operador == tiene
una prioridad ms alta que el operador de asignacin =.
En caso de tener dudas sobre la escritura de una expresin, se puede consultar la tabla de prioridades, o bien, se
coloca entre parntesis la parte de la expresin que se quiere evaluar primero.

2.5 Control de ujo

El ujo de ejecucin de un programa en lenguaje C es de arriba hacia abajo al interior del bloque de la funcin principal
main. En dicho interior se pueden encontrar:
s Proposiciones y bloques
s Estructuras alternativas: if-else, else-if, switch.
s Estructuras repetitivas: while, for, do-while.
s break, continue, goto

Proposiciones y bloques

Un bloque se compone de una sucesin de declaraciones y de proposiciones; a su vez, un bloque es un caso particular
de proposicin.
66
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Una expresin se convierte en una proposicin cuando va seguida de un punto y coma ;. A continuacin se pre-
senta un ejemplo de sucesin de proposiciones:
Ejemplo
www=3;
i++;
printf(%d%d\n, www, i);
Nota: Una llamada a la funcin printf() es una expresin.

Un bloque es una proposicin compuesta que inicia con el smbolo de una llave de apertura { y naliza con la llave de
cierre }, dentro de las cuales hay declaraciones y proposiciones. Enseguida se muestra un ejemplo de bloque.
Ejemplo
{
www=3;
i++;
printf(%d%d\n, www, i);
}

Nota: En el primer programa que se present, el bloque de main es de esta forma.

Estructuras alternativas

Las estructuras alternativas introducen la eleccin de procesamiento segn el valor de una expresin o de una variable.
Existen tres estructuras alternativas en el lenguaje C: if-else, else-if y switch.

Proposicin if-else

Esta proposicin es para las decisiones segn el valor de verdad de una expresin. Su forma es:

if (expresin)
proposicin1
else
proposicin2

Donde la parte else es optativa. La proposicin if-else se ejecuta de la siguiente manera: se hace la evaluacin
de la expresin, si es verdadera (no es cero) se ejecuta la proposicin1, pero si la expresin es falsa, se ejecuta la
proposicin2.
Las dos proposiciones, proposicin1 y proposicin2, pueden ser simples o compuestas (bloques).
La expresin se puede escribir de la siguiente forma:

if ( expresion )

en lugar de:

if ( expresion != 0 )
Ejemplos

if(x<y)
min = x;
else
67
Introduccin a la programacin

min = y;

if (x)
{
z=10/x;
teta = cos(z);
}

Cuidado! Una rama else se asocia con el Una de las proposiciones alternativas puede ser otra proposicin if. Hablamos enton-
LTIMOif abierto. ces de una secuencia de if anidada.

Ejemplo

if (n>0)
if (m<0)
z=m
else
z=n;

En esta caso, la asignacin z=n se lleva a cabo cuando n>0 es verdadero y m<0 es falso. Si queremos hacer z=n
nicamente si n>0 es falso, tenemos que forzar la asociacin construyendo un bloque alrededor del if ms interno:

if (n>0)
{
if (m<0)
z=m
}
else
z=n;

Proposicin else-if

Esta proposicin es para tratar decisiones mltiples, donde en la rama else de if hay otro if. Su forma es la siguiente:

if (expresin1)
proposicin1
else if (expresin2)
proposicin2
else if (expresin3)
proposicin3
...
else
proposicink

Las expresiones se ejecutan en el siguiente orden: si una expresin es verdadera, se ejecuta la proposicin correspon-
diente y toda esta proposicin else-if se termina. Si ninguna expresin es cierta, es decir, todas son falsas, se ejecuta
la ltima proposicink.
Ejemplo
Escribir el nombre del da que corresponde al nmero del da de la semana.
if(dia==1)
printf(lunes\n);

68
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

else if(dia==2)
printf(martes\n);
else if(dia==3)
printf(miercoles\n);
else if(dia==4)
printf(jueves\n);
else if(dia==5)
printf(viernes\n);
else if(dia==6)
printf(sabado\n);
else if(dia==7)
printf(domingo\n);
else
printf(error\n);

Proposicin switch
Esta proposicin tambin es para tomar decisiones mltiples donde el valor entero de la expresin se compara con ex-
presiones de valores constantes enteros:

switch (expresin) {
case const_expresion1:
proposiciones1
case const_expresion2:
proposiciones2
...
default:
proposicionesk
}

Cada etiqueta case contiene uno o ms valores constantes enteros. Si el valor de la expresin es uno de estos va-
lores, se ejecutan las proposiciones de la etiqueta y todas las que le siguen. Si el valor de la expresin no coincide con
ninguna etiqueta case se ejecutan las proposiciones de la etiqueta default.

Ejemplo
El nombre del da cuando se conoce su nmero:
switch(dia)
{
case 1:
printf(lunes\n);
break;
case 2:
printf(martes\n);
break;
case 3:
printf(miercoles\n);
break;
case 4:
printf(jueves\n);
break;
case 5:
printf(viernes\n);
break;

69
Introduccin a la programacin

case 6:
printf(sabado\n);
break;
case 7:
printf(domingo\n);
break;
default:
printf(error\n);
}

La proposicin break produce una salida despus del n de switch. Esto es, en ausencia de break se ejecutan las
siguientes proposiciones de otras etiquetas case.

Ejemplo
En el cdigo siguiente:

switch(dia)
{
case 1:
printf(lunes\n);
case 2:
printf(martes\n);
case 3:
printf(miercoles\n);
case 4:
printf(jueves\n);
case 5:
printf(viernes\n);
case 6:
printf(sabado\n);
case 7:
printf(domingo\n);
default:
printf(error\n); }

Si el valor de la variable dia es 5, se ejecutan cuatro llamadas a la funcin printf() y como salida aparece:

viernes sabado domingo error.

Estructuras iterativas

Las estructuras iterativas se usan para los procedimientos que son repetitivos. En el lenguaje C se tienen tres estructuras
iterativas: while, for y do-while.

Proposicin while

La forma de la proposicin iterativa while es la siguiente:


while (expresin)
proposicin

Se evala la expresin y, si su valor es verdadero (diferente de cero) se ejecuta la proposicin. Luego, se reevala
la expresin y se hace la prueba. La proposicin while termina cuando la expresin toma el valor de cero (falso).
70
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Ejemplo
Clculo de los cuadrados menores a un valor lmite.

/* programa : se calculan todos los cuadrados enteros hasta un limite*/


#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int limita = 25;
int i;
i = 1;
while (i*i <= limita)
{
printf(i=%d, cuadrado=%d\n, i, i*i);
i += 1;
}
exit(0);
}

El programa escribe:

i=1, cuadrado=1
i=2, cuadrado=4
i=3, cuadrado=9
i=4, cuadrado=16
i=5, cuadrado=25

Proposicin for

La proposicin iterativa for es similar a la proposicin while, en la cual, antes de todo clculo, se lleva a cabo una pri-
mera expresin (por lo general, es una parte de la inicializacin), se evala otra expresin lgica y si esta es verdadera se
ejecuta la proposicin, al nal se evala otra expresin y se itera con la evaluacin de la expresin de condicin. La forma
de la proposicin for es la siguiente:

for (expresion1; expresion2; expresion3)


proposicin

La expresion1 es la expresion de inicializacin, la expresion2 es la expresion de condicin y la expresion3 se


ejecuta al nal de una iteracin.
Esta forma de for es equivalente a:

expresion1;
while (expresion2)
{
proposicin
expresion3;
}

Las expresiones que componen la proposicin for no son obligatorias (pueden faltar). Normalmente, expresin1 y
expresin3 son llamadas o asignaciones y expresin2 es una expresin de relacin.

Ejemplo
Clculo de los cuadrados menores a un valor lmite con una proposicin for:
71
Introduccin a la programacin

/* programa : calculo de todos los cuadrados enteros hasta un limite*/


#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int limita = 25;
int i;
for (i= 1; i*i <= limita; i++)
printf(i=%d, cuadrado=%d\n, i, i*i);
exit(0);
}
En este ejemplo, la proposicin for contiene una sola proposicin, la llamada a la funcin printf().

Ejemplo
Vericar si un nmero n es primo. Por denicin, un nmero es primo si no tiene ms divisores que el nmero 1 y l
mismo. De tal manera que, si el nmero no es divisible entre ningn otro nmero desde 2 hasta n-1, podemos decir
que es primo. Por otro lado, vericar hasta n-1 parece mucho; para evitar este posible inconveniente, podemos vericar
nicamente hasta n . Por otra parte, si el nmero no es divisible entre dos, entonces no sera divisible entre cualquier
nmero par (4, 6, ...). Por tanto, es suciente probar la divisibilidad con 2 y con todos los nmeros impares de 3 hasta n .
El programa en lenguaje C es el siguiente:

/* Programa que verifica si un numero es primo */


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
int n, d;
short es_primo;
n = 6751;
for (d=2, es_primo = 1; d<=sqrtl(n) && es_primo == 1;)
if (n%d == 0)
es_primo = 0;
else if (d==2)
d = 3;
else
d = d + 2;
if (es_primo)
printf(%d es primo\n, n);
else
printf(NO, %d no es primo\n, n);
exit(0);
}

La proposicin interna de for es un else-if. La expresin de relacin es una expresin lgica con el operador &&. La
tercera expresin del for es vaca.
La proposicin for y la proposicin if (es_primo) son proposiciones al interior del mismo bloque.
Por ejemplo, para n = 6751 la respuesta del programa es No, 6751 no es primo. Si se desea vericar la primalidad
de algn otro nmero, se cambia el valor de la asignacin y se compila otra vez el programa.5

5
En el captulo siguiente vamos a introducir las funciones de entrada de un programa.
72
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Proposicin do-while

En la proposicin iterativa do-while primero se ejecuta la parte del cdigo que se encuentra entre do y while y luego
se verica la condicin para salir (o no) de la estructura. La sintaxis de esta proposicin iterativa es:

do
proposicin
while (expresin);

Primero se ejecuta la proposicin, y luego se evala la expresin. Si la expresin tiene un valor diferente de
cero (es verdadero) se ejecuta de nuevo la proposicin y el clculo de la expresin. La proposicin dowhile
termina cuando la expresin se hace falsa (o cero).

Ejemplo
n
i
El clculo de i=1

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i, n, suma;
n = 16;
i = 1;
suma = 0;
do
{
suma += i;
i++;
}
while (i <= n);
printf (la suma hasta %d es %d\n, n, suma);
exit(0);
}

La suma de los nmeros de 1 hasta n se calcula de manera progresiva: a cada paso i que se aumenta la suma es parcial.

Otras proposiciones de control de ujo

A veces necesitamos salir de una estructura, ya sea de ciclo (iterativa) o alternativa. Para hacerlo, tenemos dos proposi-
ciones: break y continue. Adems de la proposicin goto que permite la continuacin del ujo con cualquier otra
proposicin.

Proposiciones break y continue

La proposicin break produce la salida de la proposicin que la contiene y el programa contina con la siguiente pro-
posicin.
Por su parte, la proposicin continue se usa solo con una estructura de ciclo (iterativa) y su efecto es que se ter-
mina el paso corriente, pero se contina con el siguiente paso de la estructura repetitiva que la contiene.

Ejemplo
La prueba de si un nmero es o no primo (dos versiones, una con break y otra con continue):

73
Introduccin a la programacin

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
int n, d;
short es_primo;
n = 49;
for (d=2, es_primo = 1; d<=sqrtl(n);)
if (n%d == 0){
es_primo = 0;
break;
}
else if (d==2)
d = 3;
else
d = d + 2;
if (es_primo)
printf(%d es primo\n, n);
else
printf(NO, %d no es primo\n, n);
exit(0);
}

El efecto de break es visible porque cuando se detecta un divisor d de n se sale completamente del for.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
int n, d;
short es_primo;
n = 99;
for (d=2, es_primo = 1; d<=sqrtl(n) && es_primo == 1;)
{
if (n%d == 0)
{
es_primo = 0;
continue;
}
if (d==2)
d = 3;
else
d = d + 2;
}
if (es_primo)
printf(%d es primo\n, n);
else
printf(NO, %d no es primo\n, n);
exit(0);
}

En esta versin, cuando se detecta que d es un divisor se contina directamente con la prueba de la expresin de relacin.
74
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Proposicin goto y etiquetas

Esta proposicin se utiliza cuando se necesita ir a una parte del programa que no es la proposicin siguiente.

goto etiqueta ;
etiqueta :
...

donde etiqueta es un identicador del programa


Nota: Se debe hacer una sola denicin de dicha etiqueta. _#UIDADO%SMUYDIFCILCOMPRENDERYCORRE
gir un programa con proposiciones goto
Normalmente esta proposicin se utiliza para el tratamiento de errores. ASQUENOSERECOMIENDASUUSO

Ejemplo
La prueba del nmero primo usando la proposicin goto.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
int n, d;
short es_primo;
n = 99;
for (d=2, es_primo = 1; d<=sqrtl(n) && es_primo == 1;)
if (n%d == 0)
goto noprimo;
else if (d==2)
d = 3;
else
d = d + 2;
if (es_primo)
printf(%d es primo\n, n);
else
noprimo:
printf(NO, %d no es primo\n, n);
exit(0);
}

Cuando se detecta un divisor, el salto se hace a la impresin del mensaje NO.

2.6 Problemas resueltos

Ecuacin de primer grado

Una ecuacin de primer grado tiene la siguiente forma general:


ax + b = 0
Esta ecuacin admite una solucin nica cuando a  0. Si a = 0, la ecuacin no tiene soluciones cuando b  0 tiene
una innidad de soluciones cuando el diagrama de ujo es presentado como en el captulo 1 (ejemplo de la pgina 38,
gura 1.19).
75
Introduccin a la programacin

El programa en C es el siguiente:
/* programa para resolver ecuacion primer grado */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float a, b, x;
a=0.5;
b=-1.5;
if (a==0)
if (b==0)
printf( Hay infinidad de soluciones\n);
else
printf( No tiene solucion\n);
else
{
x = -b/a;
printf(Solucion unica %f\n, x);
}
exit(0); _#UIDADO ,A IGUALDAD MATEMTICA A   SE
} traduce con el operador ==.

En la ltima rama else hay dos proposiciones a realizar: el clculo de la raz y la salida de mensaje: Solucion unica, en-
tonces se forma un bloque o secuencia de ms de una instruccin, de una proposicin compuesta.
Para los valores a=0.5 y b=-1.5 el programa escribe:
Solucin nica 3.000000
Para otros valores de entrada (a y b) se cambia el programa, se compila y se ejecuta.

Clculo aproximado del nmero ureo

El nmero ureo6  es un nmero que expresa una proporcin perfecta. Ha sido muy estudiado por los matemticos
desde la antigedad. Su valor exacto es:

= 1+ 5

2
El programa en lenguaje C que utiliza esta frmula es muy simple:

/* calculo exacto del numero aureo */


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[])
{
float fi;
fi = (1 + sqrt(5.))/2;
printf( El valor exacto del numero aureo es : %20.18f\n, fi);
exit(0);
}

y produce el mensaje :
El valor exacto del numero aureo es: 1.618034005165100098

76
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

El programa usa la funcin estndar sqrt de la biblioteca matemtica math.h del lenguaje C.
Queremos obtener un valor aproximado del nmero ureo  sin usar la funcin sqrt para obtener 5 . El valor de
 se puede calcular como una fraccin innita:

= 1
1+ 1
1+ 1
1+1

porque  es la raz de la ecuacin:
=1+ 1

La idea sera calcular valores sucesivos:
k + 1 = 1 + 1
k
hasta que converja.
El pseudocdigo de tal procesamiento sera:
repetir

2 1 + 1/
dif |2 |
 2

hasta que dif


donde es un nivel de aproximacin, por ejemplo = 1010 si queremos diez decimales exactos.
El programa en C es el siguiente:

/* calculo aproximado del numero aureo */


#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float eps = 1.E-10;
float fi, fi2, dif, div;
fi = 2;
do
{
fi2 = 1 + 1/fi;
dif = fi2 -fi;
div = dif > 0 ? dif : -dif;
fi = fi2;
}
while (div > eps);
printf( El valor aproximado del numero aureo es : %20.18f\n, fi);
exit(0);
}

6
Vase la pgina de Wikipedia http://es.wikipedia.org/wiki/Nmero_ureo
77
Introduccin a la programacin

Tomamos un valor inicial fi = 2. En el bloque de la estructura do-while reconocemos el clculo de 2, dif es la
diferencia 2  y div es el valor absoluto. La estructura C do-while tiene la condicin de n como la negacin de
la condicin de la estructura repetir.
El programa produce el resultado:
El valor aproximado del numero aureo es: 1.618034005165100098

Clculo de una raz de ecuacin de tercer grado

Calcular la raz de la siguiente ecuacin:

x3 5x2 + 14x 2 = 0

que se encuentra en el intervalo [0,1].


Para una ecuacin de tercer grado, no conocemos frmulas que se apliquen de forma directa para resolverla.
Si consideramos la funcin f (x)= x3 5x2+14x 2, es fcil vericar que f(0)< 0 y f(1)>0. Es decir que
f (0) f (1) < 0. El cambio del signo de la funcin nos indica que seguramente hay una raz en este intervalo.
La idea del algoritmo es considerar un intervalo [iz, dr] en el cual la funcin cambia de signo. Tomamos x como
la mitad del intervalo. Si f (x) = 0, es perfecto: encontramos la raz, pero podemos aceptar tambin que x est muy
cerca de la raz:

|f (x)| <

Si al contrario, |f (x)| > , el intervalo [iz, dr] cambia: si f (x) y f (iz) tienen el mismo signo es iz lo
que cambia en x, sino es el lmite derecho dr lo que cambia en x. De esta manera se guarda la propiedad f(iz)
f(dr)< 0y el nuevo intervalo [iz, dr] es ms estrecho.
El programa que traduce esta idea es:
/* busqueda de solucion entre 0 y 1 */
/* de la ecuacion x*x*x -5*x*x + 14*x -2 = 0 */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float iz, dr;
float x;
float fiz, fdr, fx, abs;
const float eps = 0.1e-8;
iz = 0.; dr = 1.;
fiz = iz*iz*iz -5*iz*iz + 14*iz -2;
fdr = dr*dr*dr -5*dr*dr + 14*dr -2;
if (fiz * fdr > 0)
{
printf( no se pueden determinar soluciones entre %f y %f.\n, iz, dr);
exit(1);
}
do
{
x = (iz + dr) /2;
fx = x*x*x -5*x*x + 14*x -2;
abs = fx >= 0 ? fx : -fx;
if (abs < eps) break;
78
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

if (fx * fiz < 0)


{
dr = x;
fdr = fx;
}
else
{
iz = x;
fiz = fx;
}
}
while (1);
printf( La raiz aproximada es : %15.10f\n, x);
exit(0);
}
En la estructura iterativa do-while se hace una salida con break cuando se detecta un valor cerca de la solucin.
La condicin de repeticin es 1, esto signica que la estructura iterativa se termina nicamente con la proposicin break.
El programa calcula la raz siguiente:
La raiz aproximada es: 0.1507262737

Clculo de la fecha del da siguiente

Un problema muy simple de la vida diaria es calcular la fecha que sigue a una fecha conocida. Por ejemplo, para el 30
de abril de 2010, la fecha que sigue es el 1 de mayo de 2010.
La primera pregunta sera cmo representamos una fecha en lenguaje C. Con los conocimientos que tenemos hasta
hoy, una solucin bastante simple es trabajar con tres variables enteras (de tipo int) que contienen respectivamente el
nmero de la fecha en el mes, el nmero del mes y el ao, por ejemplo:

int dia = 30;


int mes = 4;
int annio = 2010;

La fecha que nos interesa se obtiene entonces por el clculo: primero se aumenta en uno la variable dia, si se obtiene
un nmero que no es coherente con el nmero de mes (por ejemplo 31 para la variable dia cuando la variable mes
vale 6 pues el 31 de junio no existe) se aumenta en uno el nmero de mes. Si jams se obtiene 13 para la variable
mes, se modica la variable annio.
El programa debe tomar en cuenta todos los casos posibles para el valor de la variable mes. Adems, se da por hecho
que la fecha inicial es coherente.
Una versin del programa es la siguiente;

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int dia = 31;
int mes = 12;
int annio = 2010;

printf(Dia inicial : %d de %d de %d\n, dia, mes, annio);

79
Introduccin a la programacin

dia++;
switch(mes)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
if (dia == 32)
{
dia = 1; mes++;
}
break;
case 2:
if ((dia == 29 && annio%4 != 0) || (dia == 30 && annio %4 == 0))
{
dia = 1; mes = 3;
}
break;
case 4:
case 6:
case 9:
case 11 :
if (dia == 31)
{
dia = 1; mes++;
}
break;
case 12 :
if (dia == 32)
{
dia = 1; mes = 1;
annio++;
}
break;
}
printf(Dia siguiente: %d de %d de %d\n, dia, mes, annio);
exit(0);
}

La salida de este programa (por los valores indicados de las variables dia, mes y annio) es:

Dia inicial: 31 de 12 de 2010


Dia siguiente: 1 de 1 de 2011

En la escritura de esta versin del programa se preere una estructura alternativa switch porque esta estructura de pro-
gramacin es ms compacta y legible, lo que permite el tratamiento comn de los casos de meses de 31 das, excepto
el mes de diciembre, para el cual se cambia tambin de ao y el tratamiento comn de los meses de 30 das.
Otra versin con las mismas variables y la misma manera de escribir los mensajes para el usuario, se hizo con estruc-
turas anidadas else-if y con proposiciones goto.

#include <stdlib.h>
#include <stdio.h>

80
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

int main(int argc, char *argv[])


{
int dia = 31;
int mes = 12;
int annio = 2010;

printf(Dia inicial : %d de %d de %d\n, dia, mes, annio);

dia++;
if (mes ==1 || mes == 3 || mes == 5 || mes == 7 || mes == 8 || mes == 10)
{
if (dia == 32)
{
dia = 1; mes++;
}
goto fin;
}
else if (mes == 2)
{
if ((dia == 29 && ano%4 != 0) || (dia == 30 && ano %4 == 0))
{
dia = 1; mes = 3;
}
goto fin;
}
else if (mes == 4 || mes == 6 || mes == 9 || mes ==11)
{
if (dia == 31)
{
dia = 1; mes++;
}
goto fin;
}
else
if (dia == 32)
{
dia = 1; mes = 1;
annio++;
}
fin:
printf(Dia siguiente : %d de %d de %d\n, dia, mes, annio);
exit(0);
}

Ternas pitagricas

Una terna pitagrica es un triplo de nmeros enteros positivos (x, y, z), que pueden ser las aristas de un tringulo rectn-
gulo, los cuales, a saber, cumplen la siguiente condicin:

x2 + y2 = z2

La terna pitagrica ms conocida es: (3, 4, 5). Otras ternas pitagricas son: (8, 15, 17), (7, 24, 25), (9, 40, 41), (6, 8, 10).
81
Introduccin a la programacin

Hay una innidad de ternas pitagricas.


Para hacer un programa capaz de generar ternas con x, y < D (con D como lmite) una solucin brutal sera: generar
todas las ternas de enteros (x, y, z) con x y y hasta D y z hasta D 3z .
El programa se escribe de una manera muy simple:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[])


{
int x, y , z;
int D = 9000;

for (x = 2; x <= D; x++)


for (y = 2; y <= D; y++)
for(z = 2; z <= D * sqrt(2.); z++)
if ( x*x + y * y == z*z)
printf( Una terna pitagorica : %d %d %d\n, x, y, z);
exit(0);
}

La salida del programa es de forma:

Una terna pitagorica : 2288 4290 4862


Una terna pitagorica : 2288 5166 5650
Una terna pitagorica : 2288 6084 6500
Una terna pitagorica : 2288 7260 7612
Una terna pitagorica : 2288 7575 7913
Una terna pitagorica : 2289 2180 3161
Una terna pitagorica : 2289 3052 3815
Una terna pitagorica : 2289 5720 6161
..

Para un valor D lmite de 300 la respuesta es muy rpida, pero para D igual a 9000 el programa necesita ms de una
hora para ejecutarse (64 min 49 s), es enorme! La explicacin de este tiempo de ejecucin es muy sencilla: se generan
D332 ternas que es un valor muy alto.
Para mejorar el desempeo del programa debemos tomar en cuenta dos observaciones:
s Si el programa genera una terna (x, y, z) como terna pitagrica, no es necesario generar tambin (y, x, z).
Es suciente generar ternas con x < y < z.
s Para dos valores de x y y, si la terna (x, y, z) es pitagrica, el valor de z es nico y se calcula con :

z = 3x2 + y2

La traduccin de la primera condicin x < y es que, en lugar de generar y desde dos hasta D, lo generamos desde x +
1 (el primer entero mayor que x) hasta D. Por construccin tenemos la segunda condicin y < z.
La segunda condicin requiere que el valor de 3x2 + y2 sea entero. Para el clculo de la raz cuadrada usamos la
funcin sqrt7 que regresa un valor otante que se convierte automticamente a un valor entero (su parte entera).
La nueva versin del programa es:

7
Vase la captulo 3 para ms detalles sobre esta funcin.
82
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[])


{
int x, y , z;
int D = 9000;

for (x = 2; x <= D; x++)


for (y = x +1; y <= D; y++)
{
z = sqrt(x*x+y*y);
if ( x*x + y * y == z*z)
printf( Una terna pitagorica : %d %d %d\n, x, y, z);
}
exit(0);
}

Esta versin del programa tiene un tiempo de ejecucin muy atractivo: 0.65 segundos8 para D = 9000 y 55.3 segundos
para D = 90000, incomparable con el tiempo de la primera versin (64 minutos para D = 9000).
Es simple ver que si (x, y, z) es una terna pitagrica, entonces (ax, ay, az) tambin lo es para cualquier valor a entero.
Una terna pitagrica se dice primitiva si x y y son primos entre s (co-primos): y el nico divisor comn es 1.
Si queremos generar todas las ternas pitagricas primitivas, podemos modicar la segunda versin para probar pri-
mero la co-primalidad (primalidad relativa) de los nmeros generados x y y.
Un problema secundario a resolver sera probar si x y y son primos entre s. Una solucin sera calcular el mximo co-
mn divisor con el algoritmo de Euclides y probar si es o no 1. Otra solucin sera probar si hay algunos divisores posibles.
Si trabajamos con la ltima solucin,9 generaremos todos los nmeros entre 2 y m in (x, y,). Una mejora a esta prueba
sera tomar el divisor 2 y luego los valores 3, 5, 7, (todos los impares).
El programa es el siguiente:

/* programa que verifica si x y y son primos entre si */


#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int x = 299, y = 29901;
int d, primos, dmax;

primos = 1;
dmax = (x < y ? x : y);
for ( d = 2; d <= dmax; )
{
if ( x %d == 0 && y %d == 0)
{
primos = 0;
break;
}
if (d == 2)

8
Estos tiempos de ejecucin dependen de la computadora que se utilice pero la proporcin es la misma.
9
El algoritmo de Euclides se presenta en el captulo 3.
83
Introduccin a la programacin

d = 3;
else
d += 2;
}
if ( primos == 0)
printf( No, %d y %d no son primos entre ellos\n, x, y);
else
printf( SI, %d y %d son primos.\n, x, y);
}

La variable dmax contiene el valor ms grande posible para un divisor comn de los dos nmeros y la lnea de inicializa-
cin de este valor corresponde al clculo del mnimo. La condicin de if traduce la vericacin de la divisibilidad con d;
el lenguaje C es muy conveniente porque si la primera condicin es falsa, no se realiza el clculo de la segunda.
La salida del programa para los valores de x y y indicados es:

SI, 299 y 29901 son primos entre si.

Para el problema de generacin de las ternas pitagricas primitivas, podemos integrar para cada generacin de x y y la
prueba de primalidad antes de vericar si el valor de z es entero.

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
int x, y , z;
int D = 9000;
int dmax, d, primos;
for (x = 2; x <= D; x++)
for (y = x +1; y <= D; y++)
{
primos = 1;
dmax = x;
for ( d = 2; d <= dmax; )
{
if ( x %d == 0 && y %d == 0)
{
primos = 0;
break;
}
if (d ==2)
d = 3;
else
d += 2;
}
if ( primos == 0)
{
z = sqrt(x*x+y*y);
if ( x*x + y * y == z*z)
printf( Una terna pitagorica : %d %d %d\n, x, y, z);
}
}
exit(0);
}

Una parte de la salida es :


84
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Una terna pitagorica : 705 992 1217


Una terna pitagorica : 707 5076 5125
Una terna pitagorica : 708 3445 3517
Una terna pitagorica : 711 3080 3161
Una terna pitagorica : 712 7905 7937
Una terna pitagorica : 715 1428 1597
Una terna pitagorica : 715 2052 2173

El tiempo de ejecucin del programa para el lmite D = 9000 es de 2 min 31 s, ms que el tiempo de
la versin de generacin de todas las ternas pitagricas primitivas o no, que fue de 55 segundos. El aumento del
tiempo de ejecucin se debe a las pruebas de co-primalidad.
Otro mtodo de generacin de las ternas pitagricas primitivas es utilizar algunos de los resultados de la teora de los
nmeros que dicen que (x, y, z) es una terna pitagrica primitiva si y solo si existen dos nmeros enteros m y n, m > n,
que son primos entre s y de paridad diferente (uno es par y el otro impar) que cumplen las siguientes tres relaciones:10

x = m2 n2
y = 2mn
z2 = m2 + n2

El programa que se deduce de estas aserciones matemticas toma en cuenta:

s la generacin de y y la vericacin de que uno es par y el otro impar.


s la prueba de primalidad entre m y n.
s el clculo de y, sin vericar que x2 + y2 = z2.
s la vericacin de que y < D.

Para vericar la primalidad utilizaremos el mismo cdigo que precede, adaptado para las variables m y n. La generacin de
las parejas (n, m) se puede hacer aumentando a cada paso n en uno y tomando a desde +1 (para asegurar que > n) y
aumentarlo de dos en dos; este aumento asegura que los dos nmeros conserven diferente paridad. La ltima condicin,
y < D, sera vericada explcitamente dentro de la estructura for donde se genera el valor.
El programa de generacin de ternas pitagricas primitivas que se apoya en este mtodo de generacin es el siguien-
te:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
int x, y , z;
int D = 9000;
int dmax, d, primos;
int m, n;
for (n = 1; 2*n*n <= D; n++)
for (m = n +1; (x = m*m - n*n) <= D && (y = 2*m *n) <= D; m += 2)
{
primos = 1;
dmax = n;
for ( d = 2; d <= dmax; )

10
Dejamos como ejercicio al nal del captulo hacer la demostracin matemtica de esta proposicin (directa e inversa).
85
Introduccin a la programacin

{
if ( n %d == 0 && m %d == 0)
{
primos = 0;
break;
}
if (d ==2)
d = 3;
else
d += 2;
}
if ( primos != 0)
{
z = m * m + n * n;
printf( Una terna pitagorica : %d %d %d\n, x, y, z);
}
}
exit(0);
}

Una parte de la salida de este programa es:


Una terna pitagorica : 8277 364 8285
Una terna pitagorica : 8645 372 8653
Una terna pitagorica : 7 24 25
Una terna pitagorica : 55 48 73
Una terna pitagorica : 91 60 109
Una terna pitagorica : 187 84 205

Las ternas pitagricas generadas no estn en el mismo orden que la primera versin, pero las dos versiones generan
correctamente el mismo nmero de ternas pitagricas primitivas: 1604 para el lmite D = 9000.
El tiempo de ejecucin de esta ltima versin del programa es de 0.012 s, esta versin es ms rpida que la primera.
En conclusin de este ejemplo de generacin de ternas pitagricas, tenemos que el trabajo para obtener un progra-
ma puede requerir la escritura de varias versiones y que puede ser til un anlisis preliminar.
El tiempo de ejecucin de un programa es un criterio esencial para la calidad del trabajo de programacin.

Juego de la bsqueda de un nmero

Este juego lo jugamos todos cuando fuimos nios: en la mano o en un vasito se esconden algunas piedritas o granos de
maz y se pregunta al compaero de juego cuntas piedritas cree que hay guardadas. Una versin del juego la encon-
tramos tambin hoy en da en la televisin con los juegos de atnale al precio, en los cuales el jugador indica un precio
y una voz le indica: correcto, es ms o es menos. El objetivo del juego es encontrar lo ms rpido posible el valor
exacto.
Siempre hay dos jugadores: J1 y J2, por ejemplo, el jugador uno (J1) elige el nmero y el jugador dos (J2) intenta
adivinarlo. Podemos suponer tambin que el nmero elegido siempre est en un intervalo de valores; de no ser as sera
imposible terminar el juego en un nmero nito de pasos.
86
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Ejemplo
A continuacin se presenta un ejemplo de dilogo entre jugadores:

J1: Listo.
J2: Es 10?
J1: Menos.
J2: Es 9?
J1: Menos.
J2:Es 2?
J1: Ms.
J2: Es 5?
J1: Correcto.

El papel ms difcil es para el J2, quien tiene que hacer las propuestas. Para J1 es bastante fcil, pues solo elige el n-
mero y retroalimenta cada propuesta de J2.
Un programa para la computadora, qu puede aportar a este juego? Existen varias posibilidades, el programa puede
ser un jugador (J1 o J2) o bien, puede ayudar a uno de ellos.
A continuacin se presentan tres propuestas:

s El programa es el jugador uno (J1) que elige el nmero y que retroalimenta cada proposicin del jugador humano
J2, con una de las tres respuestas posibles: correcto, menos o ms.
s El programa es el jugador dos (J2) que hace las propuestas tomando en cuenta el intervalo de valores posibles y
tambin las respuestas conocidas.
s El programa es una ayuda para el J2 antes que haga una propuesta, le sugiere un valor o un intervalo de valores.

En el primer programa el principio de funcionamiento es: generar un nmero aleatorio y despus, hasta que la respuesta
del J2 sea correcta, leer y retroalimentar proposiciones.

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[])


{
int a = 1, b = 200;
int elegido;
int propuesta;

srand(time(NULL));
elegido = a + rand()%(b - a +1);
printf( Busca el numero !\n);
while(1)
{
scanf(%d, &propuesta);
if ( propuesta == elegido)
{
printf( correcto !\n);
break;
}
else if (propuesta < elegido)
printf( Mas.\n);
else
87
Introduccin a la programacin

printf( Menos.\n);
}
exit(1);
}

En este programa la llamada de funcin srand(...) genera una serie de nmeros aleatorios; dicha serie es diferente
en cada ejecucin del cdigo. La generacin del valor elegido se hace en el intervalo [a, b].
La estructura repetitiva es una estructura while con la condicin lgica siempre cierta. La salida de la es-
tructura se hace con una proposicin break cuando se acierta al nmero generado aleatoriamente. En
la estructura iterativa while se encuentra por primera vez la llamada a una funcin que hace la lectura de un valor. Esta
funcin se detallar en el captulo 3.
Un ejemplo de ejecucin del programa es la siguiente:

Busca el numero !
100
Mas.
150
Menos.
125
Menos.
112
Menos.
106
Mas.
109
Correcto !

Para la versin donde J2 es la computadora, el programa debe proponer un nmero y esperar la respuesta del juga-
dor humano J1. Podemos utilizar los siguientes cdigos para las respuestas posibles:

s 0 para correcto
s 1 para El numero es mas grande
s -1 para El numero es menos grande

Una solucin muy fcil sera proponer todos los nmeros iniciando con 1 y, a cada paso, incrementarla en uno, con
la desventaja de que si el intervalo es muy largo, se harn muchos pasos.
Otra solucin sera proponer al principio el valor central del intervalo (reduciendo as la cantidad de valores posibles).
Si

p= a+b
2

es la propuesta y no es correcta, entonces el nuevo intervalo sera [p, b]; si no, el intervalo sera [a, p]. Podemos reducir
un poco ms los dos intervalos a [p + 1, b] y [a, p 1] porque sabemos que no es la solucin. Para los siguientes pasos,
se reduce cada vez el intervalo.
El programa es el siguiente:

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
88
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

int a = 1, b = 200;
int sa, sb, propuesta, respuesta;

printf( Elige un numero !\n);


sa = a;
sb = b;
while(1)
{
propuesta = (sa + sb)/2;
printf(Mi propuesta :%d\n, propuesta);
scanf(%d, &respuesta);
if (respuesta == 0)
break;
else if (respuesta == -1)
sb = propuesta - 1;
else
sa = propuesta + 1;
}
exit(1);
}

Las variables sa y sb contienen en cada paso los lmites del intervalo. En la escritura del programa no tratamos ningn
caso de respuestas errneas y suponemos tambin que el jugador J1 siempre dice la verdad.
Para la versin del programa en la cual la computadora ayuda al jugador J2 a buscar el nmero, se aplica el mismo
principio de la reduccin del intervalo, pero con respecto del valor propuesto por J2 y sin tomar en cuenta la sugerencia
del programa.

El programa es bastante parecido a la versin anterior, con dos modicaciones importantes:


s Hay dos variables: sugerencia, que es la mitad del intervalo posible, y propuesta, que es el valor propuesto
por el J2.
s El ajuste del intervalo se hace nicamente en el caso donde el valor de propuesta est en el intervalo [sa, sb].
El cdigo del programa es el siguiente:

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int a = 1, b = 200;
int sa, sb, sugerencia, propuesta, respuesta;

printf( Elige un numero !\n);


sa = a;
sb = b;
while(1)
{
sugerencia = (sa + sb)/2;
printf(Mi sugerencia :%d\n, sugerencia);
printf(Su propuesta :); scanf(%d, &propuesta);
printf( La respuesta :);
scanf(%d, &respuesta);
if (respuesta == 0)
break;
else if (respuesta == -1)
89
Introduccin a la programacin

sb = propuesta - 1 < sb ? propuesta -1 : sb;


else
sa = propuesta + 1 > sa ? propuesta +1 : sa;
}
exit(1);
}

En este programa se utiliza el operador ternario (con tres operandos) de comparacin ?: para el clculo del mnimo entre
propuesta -1 y sb y para el clculo del mximo entre propuesta +1 y sa. Una ejecucin del programa es la siguiente:

Elige un numero !
Mi sugerencia :100
Su propuesta :77
La respuesta :1
Mi sugerencia :139
Su propuesta :130
La respuesta :-1
Mi sugerencia :103
Su propuesta :108
La respuesta :1
Mi sugerencia :119
Su propuesta :120
La respuesta :0

Sntesis del captulo

El lenguaje C es un lenguaje de programacin de alto nivel que respeta el paradigma imperativo y ofrece la posibilidad
de escribir programas que respeten el paradigma estructurado.
Un programa se elabora basndose en un algoritmo de solucin del problema a resolver. Dicho algoritmo debe estar
muy claro en la mente del programador o expresarse con un diagrama de ujo o con un pseudocdigo.
Un programa se escribe con caracteres del cdigo ASCII (sin letras con tilde ni caracteres especiales) y respeta las
reglas de sintaxis del lenguaje utilizado en su elaboracin.
Un programa tiene una parte de directivas al preprocesador (include y define), otra parte con las declaraciones
de variables y funciones y, nalmente, la denicin de la funcin principal main.
Un bloque de programa es una proposicin compuesta o una sucesin de proposiciones. El bloque inicia con el
smbolo { y termina con }. Una proposicin simple es una expresin que termina con el smbolo ;.
Las proposiciones se escriben con variables y expresiones. Una variable tiene un nombre nico (o identicador) y
un tipo.
Una expresin se compone de constantes, variables y operadores. Para los operadores hay reglas de asociatividad y
de prioridad (o precedencia).
Otras proposiciones vlidas son:

s Estructuras alternativas: if, switch y else-if.


s Estructuras iterativas.
s break, continue y goto.

Mediante un algoritmo, el programa en lenguaje C puede escribirse de diferentes formas.


90
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

Bibliografa
s Kernighan, Brian W. y Ritchie, Dennis M. (1991) El lenguaje de programacin C, 2a edicin, Pearson Educacin:
Mxico.

Referencias de Internet
s Pgina de presentacin del lenguaje en Wikipedia:
http://es.wikipedia.org/wiki/C_(lenguaje_de_programacin)

Ejercicios y problemas

1. Hacer un programa que escriba en orden creciente los valores de tres variables a, b y c. Si por ejemplo te-
nemos los valores :
int a = 15, b = 4, c = 6; El programa debe escribir:
4 6 15

2. Establecer el signo de la suma y del producto de dos nmeros enteros m y n sin hacer las operaciones. Por
ejemplo: para n = 12 y m = 15 la suma es negativa y el producto es negativo.

3. Hacer la tabla de conversin de grados Fahrenheit a grados Celsius segn la frmula:


C = (5/9)(F 32)
Los valores para conversin sern de 0, 20, 40, ..., 360.

4. Hacer un programa de conversin de pesos mexicanos a euros y viceversa, introduciendo el valor exacto de
conversin. El programa debe terminar cuando el usuario introduzca un valor negativo. Por ejemplo:

Introduce el valor de cambio 1 peso -> euro : 0.11


Su valor para convertir : 2000 Vale 220 euros
Su valor para convertir : 3 Vale 0.33 euros
Su valor para convertir : -3
FIN
La conversin 1 euro = 9.0909 pesos
Su valor para convertir : 22 Vale 200.00 pesos
Su valor para convertir : 333 Vale 3027.27
Su valor para convertir: -4
FIN

El valor de conversin tiene mximo cuatro cifras. Los valores en euros o en pesos tienen mximo dos cifras
despus del punto decimal.

91
Introduccin a la programacin

5. Hacer un programa que calcule el nmero de das que pasan entre el da corriente y el inicio del ao (pri-
mero de enero). Por ejemplo:
int dia = 22;
int mes = 10;

Conociendo el da del primero de enero del ao, aadir a su programa el clculo del da de la semana (lu-
nes, martes, ).

6. Crear un programa capaz de calcular el nmero de das entre dos fechas:

int dia1, mes1, annio1;


int dia2, mes2, annio2;
Es importante tomar en cuenta los aos bisiestos.

7. Hacer un programa que tome en cuenta una fecha del calendario usando tres variables y calcule la fecha
despus de un nmero de das contenido en la variable intervalo, por ejemplo :

int dia = 30;


int mes = 4;
int annio = 2010;
int intervalo = 20
El programa calcular los valores (20, 5, 2010).

8. Hacer un primer programa que simule el saldo de un depsito bancario durante un nmero N de aos en
una cuenta de ahorro en la cual se entrega una suma inicial. A este depsito bancario se aplica al nal del
ao un aumento del saldo de X% y una deduccin de 100 pesos por comisin bancaria. Hacer un segundo
programa en el cual se supone que a este depsito bancario se le entrega cada mes una suma ja de M pe-
sos con M variable. El aumento del n de ao corresponde al porcentaje proporcional al nmero de meses.

9. Resolver una ecuacin de segundo grado en su forma ms general que es:

ax2 + bx + c = 0

10. Resolver un sistema de ecuaciones de primer grado de la forma:

ax + by = c
dx + ey = f
para buscar las soluciones (x, y).

11. Calcular el valor de a sin utilizar la funcin sqrt.

Sugerencia: la sucesin matemtica:

1 a
x n+1 = xn +
2 x n

con x1 = a es convergente a a por cualquier a > 0.

12. Calcular los valores 2k, con k variable desde 1 hasta 25.

92
$BQUVMPt1SPHSBNBDJOFOMFOHVBKF$DPODFQUPTCTJDPT

13. Vericar que un nmero entero n es de forma ak, con a constante entera positiva (por ejemplo a = 2).

14. Calcular cuntas cifras tiene un nmero en base 10.

15. Calcular los valores N! = 1 * 2 ** (N 1) con N variable desde 1 hasta 15.


Sugerencia: para guardar el valor de N! utilizar el tipo entero long.

16. Vericar que un nmero entero n se puede escribir de la forma: n = k! (con N! = 1 * 2 ** (K


1) * k)

17. Clculo del mximo comn divisor de dos nmeros enteros.


Aplicar el algoritmo de Euclides para calcular el ms grande comn divisor de dos nmeros. El algoritmo
tiene dos versiones: con sustracciones sucesivas o con divisiones; implementar una de las dos versiones.

18. Aadir al programa que verica si un nmero es primo la escritura de uno de sus divisores (cuando no es
primo).

19. Para un nmero entero n, escribir todas las parejas de sus divisores (d, d ) con n = d d .

20. Nmeros perfectos. Se dice que un nmero es perfecto si es igual a la suma de sus divisores (se excluye
el nmero 1). Por ejemplo, 6 y 28 son perfectos: 6 = 1 + 2 + 3 y 28 = 1 + 2+ 4 + 7 + 14.
a) Vericar que un nmero entero n es perfecto.
b) Generar todos los nmeros perfectos entre 1 y 10 000.

21. Nmeros amigos. Dos nmeros se dicen amigos si la suma de los divisores de cada nmero es igual a la
del otro nmero. Por ejemplo, 220 y 284 son nmeros amigos:
s 220 tiene como divisores 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 y 110, cuya suma es 284.
s 284 tiene como divisores 1, 2, 4, 71 y 142, cuya suma es 220.
Generar todas las parejas de nmeros amigos menores que 4 000.

22. Valores de la serie armnica. Calcular para n (nmero variable) los valores de la serie armnica que se
describe como:
1 1 1
n= + +
1 2 n
Cuidado! Los valores de la serie son reales, en el programa elegir el tipo correcto para guardar dichos
valores.

23. Clculo del valor de in(x). El valor numrico de sen(x) se puede obtener calculando la serie siguiente:

i x
2i +1
sen(x) = (1)
i =0 (2 i +1)!

equivalente a

93
Introduccin a la programacin

x x3 x5 x7
sen( x ) = - + -
1 3! 5! 7!

El clculo del seno se hace aumentando a cada paso con el valor

i x 2i +1
(1)
(2 i +1)!

hasta que el valor de

i x 2i +1
(1)
(2 i +1)!

sea :

i x 2i +1
(1)
(2 i +1)!

24. Mostrar el resultado matemtico siguiente:


Si m y n son nmeros enteros positivos de paridades diferentes (uno es par y el otro es impar) con m > n
y los nmeros son primos entre s, entonces la terna (x, y, z) con

x = m2 n2
y = 2 mn
z = m2 + n2

es una terna pitagrica primitiva.


Mostrar el resultado matemtico recproco:
Si x, y, z es una terna pitagrica primitiva, entonces existen y , > n, enteros positivos primos entre s, de
paridades diferentes, tales que:

x = m2 n2
y = 2 mn
z = m2 + n2

25. En el juego de la bsqueda del nmero, para una de las tres versiones presentadas, aadir una variable
paso que cuente el nmero de pasos realizados.

26. En el juego de la bsqueda del nmero, aadir la restriccin de hacer nicamente una cantidad mxima de
propuestas. Si el jugador J2 hace dicha cantidad mxima de propuestas sin adivinar el nmero, pierde.

27. Hacer un programa en el cual el usuario tenga la posibilidad de jugar sucesivamente como J1 o como J2 y
el otro jugador sea la computadora. El juego termina despus de 11 partidas.

28. Operaciones. Hay tres variables y un valor contenido en una variable V. Vericar para una operacin (adicin,
sustraccin, producto o divisin) si hay dos variables que se pueden operar para obtener el valor V.

Si no hay un resultado exacto escribir el resultado ms cercano.

29. Es la cuenta que se espera? Versin con tres variables y un valor objetivo. Con base en el programa rea-
lizado en el problema anterior, implementar el juego que consiste en obtener el valor objetivo operando en
cualquier orden los tres valores variables con las operaciones aritmticas +, y *.

94
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE
3 =HYPHISLZHW\U[HKVYLZ
M\UJPVULZ`YLJ\YZP]PKHK

Contenido  %JEMPLOSDEUSODEFUNCIONESRECURSIVAS


%SCRITURADEUNNMEROENTEROPOSITIVOENBASE
3.1 Introduccin
%SCRITURADEUNNMEROFRACCIONARIOENBASE
3.2 Variables y apuntadores Nmero en espejo
Variables locales y variables globales
3.8 Apuntadores de funciones
Variables dinmicas y variables estticas
Apuntadores 3.9 Funciones con nmero variable de parmetros
Tipo void Sntesis del captulo
Bibliografa
3.3 Funciones
Referencias de Internet
Denicin de una funcin
%JERCICIOSYPROBLEMAS
Llamadas de funciones
Prototipo de una funcin Objetivos
Un ejemplo completo: clculo de mximo comn s %NTENDERELFUNCIONAMIENTODELMECANISMODEASIGNACINDE
divisor y de mnimo comn mltiplo memoria para las variables y las funciones.
Transmisin de los parmetros s #OMPRENDERELFUNCIONAMIENTODELOSAPUNTADORES
3.4 Funciones estndares s -ANEJARFUNCIONESESTNDARES
Funciones matemticas <math.h> s %SCRIBIRSUSPROPIASFUNCIONES
Algunas de las funciones de <stdlib.h> s #ONOCERYUTILIZARCORRECTAMENTEELTIPODETRANSMISINDELOS
parmetros de una funcin.
3.5 Funciones de entrada/salida s %MPLEARCORRECTAMENTELASFUNCIONESDEENTRADAYDESALIDA
3.6 Recursividad s %SCRIBIRFUNCIONESRECURSIVAS
95
*OUSPEVDDJOBMBQSPHSBNBDJO

3.1 Introduccin

Para la lectura de este captulo es imperativo que el usuario conozca y sepa utilizar las bases de la programacin del
lenguaje C.
As, para su estudio, este captulo se divide en dos partes; la primera analiza las nociones de asignacin de memoria,
las variables globales y las variables locales, las variables dinmicas y las variables estticas, as como los apuntadores; la
segunda, por su parte, trata las funciones de un programa C, como las funciones del usuario y estndares; adems, hay
una parte dedicada a las funciones de entrada/salida estndar.
Sin embargo, es importante resaltar que el concepto de funcin se utiliza a lo largo de todo el libro:
s Cada vez que necesitemos un algoritmo para ilustrar la resolucin de un problema (de tamao variable), vamos a
escribir una funcin que traduzca el algoritmo.
s Para una gran variedad de nociones de la computacin, como archivos y cadenas de caracteres, y para varios as-
pectos prcticos de la programacin, como mensuracin del tiempo, interfaz con entradas y salidas no estndares,
sockets, etctera, el compilador C (en nuestro caso gcc) ofrece una gran variedad de funciones estndares.
Asimismo, en este captulo, se trata un concepto muy importante de la programacin: la recursividad; adems de que
tambin se estudia el mecanismo de asignacin de la memoria que el compilador C usa y permite.
La ltima parte de este captulo est dedicada al estudio de una nocin que es muy poco implementada en los len-
guajes de programacin: el apuntador de funcin.
Para una mayor comprensin del tema, a lo largo del captulo se presentan y comentan varios ejemplos de programas
completos.

3.2 Variables y apuntadores

En el captulo anterior se estudi la forma de las declaraciones de las variables; se hizo nfasis en que, usualmente, estas
declaraciones se encuentran/escriben al inicio del bloque de main. En realidad, las variables y las funciones se denen en
varios lugares del archivo de programa .c, como en el exterior de cualquier bloque o al interior de un bloque de funcin
(incluido el de main), o tambin en un bloque interior de otro bloque. Segn el lugar de su denicin y con base en otras
opciones, el compilador asigna de manera diferente el espacio de memoria necesario en la variable. De cualquier manera,
al momento de su uso, cada variable debe tener asignada una zona de memoria para su contenido.

Variables locales y variables globales

Para cada variable, el compilador asigna la memoria que esta necesita en trminos de tamao (nmero de bytes) y ubi-
cacin de memoria conforme al tipo de la variable que se trate.
Segn el lugar en el cual aparece la denicin de la variable, una variable se puede denir como:
s Variable global. Es aquella que se ubica al exterior de cualquier bloque de denicin de funciones o de cualquier
otro bloque.
s Variable local. Es aquella que se ubica al interior de un bloque.

96
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Por ejemplo, vase el siguiente programa C:

#include <stdlib.h>
#include <stdio.h>
int a=33;
int main(int argc,char *argv[])
{
int b=5;
printf( La variable a : %d\n, a);
printf( La variable b : %d\n, b);
}

En este caso, la variable a es una variable global y la variable b es una variable local, porque la primera variable est
denida fuera del bloque de main y la segunda variable es local a la funcin main.1 Por tanto, el programa produce la
siguiente salida:

La variable a: 33
La variable b: 5

Las variables denidas dentro de un mismo bloque se consideran variables locales a este bloque, por lo que deben tener
identicadores diferentes, es decir, nombres distintos.
As, el cdigo siguiente no es correcto y produce un error de compilacin:

#include <stdlib.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int b=5;
float b=2.55;

printf( La variable enterab: %d\n, b);


printf( La variable b: %f\n, b);
}

Error de compilacin:

variable2.c:8: error: conflicting typesfor?b? variable2.c:7: error: previous


definition of? b? was here

Si dentro de un mismo bloque se requiere que las variables tengan identicadores diferentes, es posible tener dos varia-
bles con el mismo identicador, pero denidas en bloques diferentes; en este caso, el uso de este identicador (cronol-
gicamente) se hace en su ltima denicin de variable encontrada.
En el programa siguiente se redenen dos variables con el mismo nombre, y dentro del bloque ms interior hay una
ltima denicin; es esta denicin la que ser considerada:

#include <stdlib.h>
#include <stdio.h>

int a=33; // variable global

int main(intargc, char *argv[])


{

1
En el captulo 4 se explica el funcionamiento de la funcin main y el uso de sus parmetros.

97
*OUSPEVDDJOBMBQSPHSBNBDJO

int b=5;

printf(---al incio de main \n);


printf( La variable a : %d\n, a);
printf( La variable b : %d\n, b);
{
in ta= 100; // variables locales al bloque
int b=77;
printf( >>>> al interior del bloque\n);
printf( La variable a : %d\n, a);
printf( La variable b : %d\n, b);
}
printf( >>>> al exterior del bloque\n);
printf( La variable a : %d\n, a);
printf( La variable b : %d\n, b);
}

Una vez que este bloque termina, se olvidan por completo todas las deniciones interiores del bloque y se toman en
cuenta las deniciones variables antes de abrir el bloque. Entonces, el programa produce:

--- al inicio de main


La variable a : 33
La variable b : 5
>>>> al interior del bloque
La variable a : 100
La variable b : 77
>>>> al exterior del bloque
La variable a : 33
La variable b : 5

La portada de una variable global es extendida a todo el cdigo; por ejemplo, en este cdigo la variable a es visible en
el bloque de main y en cualquier otro bloque interno del main; tambin es visible en el bloque de la funcin otro_
que_main:

#include <stdlib.h>
#include <stdio.h>

int a = 33;

void otro_que_main()
{
printf(En otro_que_main la variable a : %d\n, a);
return;
}
int main(int argc, char *argv[])
{
a=33;
printf(Al inicio la variable a : %d\n, a);
a++;
{
int a=86; //
printf( En el main el interior del bloque2 la variable a : %d\n, a);
otro_que_main();
}
}

98
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Al interior de la funcin otro_que_main, se toma en cuenta el identicador a como la variable global. Tambin se pue-
de ver que si una variable es visible, entonces su contenido se puede cambiar. El resultado del programa es el siguiente:

Al inicio la variable a : 33
En el main el interior del bloque 2 la variable a : 86
En otro_que_main la variable a : 34

Variables dinmicas y variables estticas

La asignacin de la memoria para las variables se hace de manera dinmica, excepto en el caso de las variable globales
y las variables locales estticas.
Las variables locales tienen su espacio de memoria en una zona que se asigna dinmicamente al momento de la
llamada de la funcin; por esta razn, se les conoce con el nombre de variables dinmicas. Esta zona se borra (se libe-
ra) al n del cdigo de la funcin. Mientras la funcin no est terminada, esta zona de memoria no se borra y todas sus
variables locales y dinmicas estn disponibles.
Si la funcin es recursiva,2 por cada llamada se asigna una nueva rea en la zona de memoria dedicada a las varia-
bles de funciones. De manera contraria, tambin existe la posibilidad de declarar variables estticas; en este caso, la
asignacin se hace una sola vez para cualquier llamada y el contenido no se borra al nal de la ejecucin de la funcin.
Las variables estticas pueden ser de cualquier tipo conocido (predenido o denido por el usuario). En este caso, la
declaracin se hace usando la palabra static antes del tipo:

static tipo_variable nombre_variable;


static tipo_variable nombre_variable = valor;

Opcionalmente, es posible asignar un valor a la variable. Al contrario de las variables dinmicas, esta asignacin se hace
una sola vez, al momento de la primera asignacin de memoria a la variable.
Ejemplo
Factorizacin de nmeros primos; accin que consiste en descomponer un nmero entero en divisores no triviales, los
cuales, cuando se multiplican, dan el nmero original.

En este caso, se necesita una funcin capaz de regresar lo ms pequeo del divisor de un nmero entero positivo.
Por tanto, utilizaremos esta funcin para escribir un nmero de entrada como un producto de factores de nmeros
primos.3

Una solucin con solo variables dinmicas es la que se presenta a continuacin:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

int menor_divisor(int n)
{

int k; //divisor potencial


k = 2;
while (k <= sqrt(n))

2
Vase el apartado 3.6, dedicado al estudio de la recursividad.
3
El divisor ms pequeo de un nmero tambin es un nmero primo; si no, su divisor diferente de 1 y del nmero mismo es
tambin divisor por el nmero inicial y es ms pequeo.

99
*OUSPEVDDJOBMBQSPHSBNBDJO

{
if (n % k == 0)
return k;
else
if (k == 2)
k = 3;
else
k +=2;
}
return 0;
}

int main(int argc, char *argv[])


{
int n; //el numero
int d; // un divisor
printf( Su numero a descomponer :);
scanf(%d, &n);

do
{
d = menor_divisor(n);
if (d == 0)
break;
else
{
printf( %d, d);
n = n / d;
}
}
while(n > 1);
if (n >1)
printf( %d, n);
printf(\n);
exit(0);
}

La salida de este programa es, por ejemplo:

Su numero a descomponer : 181800


2223355 101

En la funcin menor_divisor se determina el divisor ms pequeo de un nmero, vericando, primero, la divisibili-


dad entre 2, luego entre 3, entre 5 y entre otro nmero impar. En el programa principal (main), se detecta un divisor
no trivial d y luego se hace la divisin de n entre d; el proceso se repite hasta que el nmero es 1 o hasta que no se
encuentran ms divisores.
Aqu es fcil observar que cuando se detecta un divisor no trivial del nmero, en la prxima llamada es mejor iniciar
con el valor del ltimo divisor encontrado.
As, hay dos soluciones:
s Introducir un parmetro con el valor del ltimo divisor.
s Transformar la variable k en una variable esttica con la asignacin k = 2 hecha una sola vez.
La solucin que utiliza k como variable esttica es:

100
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

int menor_divisor(int n)
{
static int k = 2; //divisor potencial
// para ver el valor de la variable k al inicio de la funcion:
printf( llamada para n=%d k = %d.\n, n, k);
while (k <= sqrt(n))
{
if (n % k == 0)
return k;
else
if (k == 2)
k = 3;
else
k +=2;
}
return 0;
}

El programa principal queda idntico.

La salida es la siguiente:
Su numero a descomponer : 181800
llamada para n=181800 k = 2.
2 llamada para n=90900 k = 2.
2 llamada para n=45450 k = 2.
2 llamada para n=22725 k = 2.
3 llamada para n=7575 k = 3.
3 llamada para n=2525 k = 3.
5 llamada para n=505 k = 5.
5 llamada para n=101 k = 5.
101

La funcin de escritura printf, en el cuerpo de la funcin menor_divisor, se utiliza solo para observar el valor de
inicio de la variable k.

Apuntadores

La declaracin de cualquier variable, de cualquier tipo, se acompaa de la asignacin de una zona de memoria coherente
en tamao, para guardar su valor. Por la declaracin:
int i;

se asigna una zona de memoria de 4 bytes, con una ubicacin (direccin) de memoria mltiplo de 4. El operador unario,
&, indica la direccin de memoria de la variable. As, &i es la direccin de memoria de la variable i.
Una direccin de memoria se llama apuntador. De esta forma, la expresin &i es un apuntador. El operador unario,
&, como su nombre lo indica, se aplica a cualquier variable de cualquier tipo y nunca a una expresin compuesta (con
ms de una variable).
Por su parte, el operador unario, *, indica el contenido de memoria de un apuntador. Este se aplica nicamente a
variables y expresiones de tipo apuntador.
El siguiente programa ilustra el uso de los operadores & y *:

101
*OUSPEVDDJOBMBQSPHSBNBDJO

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])


{
int i,j;
float x;

i = 3;
printf(valor i =%d ,su direccion =%p \n, i, &i);
printf(contenido de la direccion &i = %d \n, *&i);

exit(0);
}

Este programa produce salidas diferentes, segn el sistema operativo y el tipo de mquina.4 La salida en una Apple
MacBook es:

valor i = 3, su direccion =0x7fff5fbffa2c


contenido de la direccion &i =3

En tanto, la salida del mismo cdigo desde una mquina Sun, con sistema operativo Unix, es:

valor i =3 , su direccion =ffbffaf4


contenido de la direccion &i =3

Tambin podemos declarar variables de tipo direccin de (apuntadores):

tipo *nombre_variable;
tipo * nombre_variable;

Para ilustrar, se presenta el siguiente programa:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])


{
int i, *idir;
i =3; idir=&i;
printf(valor i =%d , su direccion =%p \n, i, &i);
printf(valor idir=%p ,su direccion =%p \n, idir, &idir);
printf(el valor apuntado por idir =%d\n, *idir);
exit(0);
}

su salida es:

valor i =3, su direccion =0x7fff5fbffa2c


valor idir=0x7fff5fbffa2c ,su direccion =0x7fff5fbffa20
el valor apuntado por idir =3

4
Las diferencias son visibles sobre la direccin de memoria de la variable.

102
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Con el uso de apuntadores, es posible acceder al contenido de memoria.


Asimismo, el tipo apuntador soporta algunas operaciones aritmticas como:
s Adicin con un entero. Esta tiene por efecto calcular la ubicacin de memoria que se encuentra n posiciones
despus; donde una posicin vale un nmero de bytes equivalente al tipo de zona de memoria apuntada.
s Sustraccin (resta) - operacin inversa.
Ejemplo
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int i; int *p;

p =&i+2;
printf( Ubicacion de i :%p \n, &i);
printf( Ubicacion apuntada por p : %p \n, p);
exit(0);
}

produce (los clculos estn hechos en base 16 y la adicin se hace con 2 4):

Ubicacion de i : 0x7fff5fbffa2c
Ubicacion apuntada por p : 0x7fff5fbffa34

Los apuntadores ofrecen la posibilidad de cambiar de una manera directa el contenido de la zona de memoria apun-
tada. En el siguiente programa, el valor de la variable i se cambia usando una asignacin del valor apuntado por el
apuntador x:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int i;
int *x;

i =3;
printf(Al inicio valor i =%d\n, i);

x = &i;
*x = 15;
printf(El nuevo valor i =%d\n, i);
exit(0);
}

El programa escribe:

Al inicio valor i =3
El nuevo valor i =15

El valor mismo (y no el valor apuntado) de una variable de tipo apuntador, se puede modicar como cualquier otra va-
riable; este cambio se realiza en expresiones de tipo asignacin. No obstante, debe cuidarse que el lado derecho sea del
mismo tipo de apuntador que el lado izquierdo.

103
*OUSPEVDDJOBMBQSPHSBNBDJO

La nocin de apuntador se aplica no solamente a un tipo escalar (int, float, etc.), tambin puede aplicarse a otros
tipos. Por ejemplo:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int i= 3, j = 5;
int *api; //apuntador de entero
int **dobleap; // apuntador de apuntador de entero

api = &i;
dobleap = &api;
printf(valor i =%d,su direccion =%p \n, i, &i);
printf(valor api =%p,su direccion =%p el valorapuntado %d \n, api, &api, *api);
printf(valor dobleapp=%p , su direccion =%p valor apuntado =%p valor apuntado
por el apuntador = %d/n, dobleadp, &dobleap*(&dobleap));

exit(0);
}

En este programa, dobleap es de tipo **int, que signica un apuntador de apuntador de entero; en tanto, api es un
apuntador de entero. Entonces, el cdigo produce:

valor i =3 , su direccion =0x7fff5fbff99c


valor api =0x7fff5fbff99c , su direccion =0x7fff5fbff990 el valor apuntado 3
valor dobleapp =0x7fff5fbff990 , su direccion =0x7fff5fbff988
valor apuntado = 0x7fff5fbff99c valor apuntado por el apuntador = 3

La memoria prcticamente se llena, como se observa en la gura 3.1.

Variable Contenido Direccin

dobleap a20 a18

api a2c a20

i 3 a2c

Figura 3.1

Tipo void

En el lenguaje C, hay un tipo de dato que no contiene ningn tipo: el tipo void. No hay variables que sean de tipo void,
pero este tipo se usa para las funciones5 y para los apuntadores.

5
Vase la siguiente seccin.

104
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Un apuntador de tipo void es cualquier ubicacin/direccin de memoria sin restriccin.6 Por ejemplo, si en un pro-
grama hay la declaracin:
void a;

La compilacin produce un error:


test_void1.c:6: error: variable or field ?a? de clared void

Cualquier variable de tipo apuntador se puede convertir en tipo de apuntadorde void; sin embargo, en algunas ocasio-
nes, la conversin en el otro sentido (de void* a otro tipo) se indica, en la fase de compilacin, como una advertencia,
y puede producir resultados errneos en la ejecucin.

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
void *a;
short v, valor_entero =35;
float valor_flotante =3.5;
float *apf;

apf = &valor_flotante;
a = apf;
printf( El valor apuntado por apf %f\n, *apf);
//printf( El valor apuntado por a %f\n, *a);

a = &valor_entero;
apf = a;
printf( El valor apuntado por apf %f\n, *apf);
printf( El valor apuntado por a %f\n, *(float*)a);
exit(0);
}

La proposicin de la lnea 14 es un comentario, porque la escritura *a no es correcta; se trata de un contenido de tipo void.
Para acceder a un contenido apuntado por un apuntador de tipo void*, se necesita cambiar el tipo del apuntador
y, luego, acceder a su contenido con el operador unario *. El programa escribe:

El valor apuntado por apf 3.500000


El valor apuntado por apf 0.000000
El valor apuntado por a 0.000000

porque el valor en binario del nmero 35 representado en 2 bytes se interpreta como un valor en punto otante; enton-
ces, el resultado pierde cualquier sentido.
Ejemplo
Clculo de una potencia ak con a como valor otante y k como valor entero positivo o negativo.
Para este ejercicio la potencia no siempre es un valor positivo, puede ser cualquier valor entero; entonces tenemos
tres casos que debemos tomar en cuenta:

6
Nota del revisor: El tipo void es un tipo de dato especial del lenguaje de programacin C; se utiliza para el manejo de espacios de
memoria dinmica sin un tipo de dato en especco.

105
*OUSPEVDDJOBMBQSPHSBNBDJO

s k = 0: el valor es 1.
s k > 0: se debe calcular de manera iterativa:
axaxxa
k veces
s k < 0: esta signica k = n con n entero positivo; se debe calcular de manera iterativa:
1 1 1

a a a
n veces
Podemos hacer un programa con un anlisis y un clculo para cada caso, o poner en variables el valor que ser multipli-
cado con el operador de producto, y el valor entero de la potencia efectiva, para conocer el nmero de multiplicaciones
que se hace.
El programa es el siguiente:

#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
float a = 1.56;
int k = 5;
int i, poder;
float coeficiente, valor;

int *ap; //apuntador por la potencia positiva


float *ac; // apuntador por el coeficiente que interviene en el producto

if (k < 0)
{
poder = k;
coeficiente = 1 / a;
ap = &poder;
ac = &coeficiente;
}
else
{
ap = &k;
ac = &a;
}
valor = 1;
for ( i = 0; i < *ap; i++)
valor = valor * *ac;
printf( El valor de la potencia %f ^ %d : %f\n, a, k, valor);
exit(0);
}

La proposicin:
valor = valor * *ac;
es una expresin de asignacin, por lo cual el operador * aparece dos veces con signicaciones distintas: es el opera-
dor de multiplicacin y es el operador de contenido de apuntador, el segundo operador tiene una prioridad ms alta,
entonces no se necesitan parntesis.

106
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

El programa escribe:
El valor de la potencia 1.560000 ^ 5 : 0.108237
Ejemplo
Clculo del recuento de permutaciones. Para los conjuntos de n objetos el recuento de las permutaciones posibles tiene
el valor factorial de n (n!). Para los conjuntos de k objetos con k < n, el recuento vale:
n!
P(n, k) =
(n k)!
(Este nmero se conoce tambin como ordenaciones o arreglos de n en k).
Para calcular con una misma frmula los dos recuentos n (n!) o P(n, k), podemos usar apuntadores que apuntan
para los valores de lmite bajo y lmite alto, entre los cuales se hace la variacin de factores.
La expresin factorial de n signica:
n! = 1 2 (n 1) n
La expresin de P(n, k) signica:
n! 1 x 2 x x (n 1) x n
P(n, k) = = = (n k + 1) x x (n 1) x n
(n k)! 1 x 2 (n k)
El producto iterativo que se calcula tiene factores sucesivos de 1 hasta n o de n k + 1 hasta n.
En el programa, si tenemos que calcular el recuento de permutaciones k de n o permutaciones de n, el lmite bajo
va a apuntar sobre un valor que contiene n k + 1 o 1, y el lmite alto va a apuntar a n.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int k = 2, n = 8;
int k_de_n = 1;
int *limita_baja, * limita_alta;
int i, recuento;
int valor;

if (k_de_n == 0)
valor = 1;
else
valor = n k + 1;
limita_baja = &valor;
limita_alta = &n;
recuento = 1;
for (i = *limita_baja; i <= *limita_alta; i++)
recuento *= i;

printf(El recuento de permutaciones : %d\n, recuento);


exit(0);
}

Para los valores de n = 8 y k_de_n = 0 (se quieren las permutaciones de n) la salida del programa es la siguiente:
El recuento de permutaciones : 40320
Por los valores de n = 8, k = 2, k_de_n = 1 (permutaciones de n en k) la salida del programa es:
El recuento de permutaciones : 56

107
*OUSPEVDDJOBMBQSPHSBNBDJO

3.3 Funciones

En programacin, una funcin designa una parte de cdigo que se ejecuta al momento de las llamadas (una o varias
llamadas). En algunos lenguajes de programacin imperativos, como PASCAL, tambin se incorpora la nocin de proce-
dimiento, en el sentido de una parte de cdigo que no regresa ningn valor. El lenguaje C implementa nicamente la
nocin de funcin; el procedimiento no es ms que una funcin de tipo void.

Denicin de una funcin

Una funcin se dene por:


s Su nombre, que debe ser un identicador nico.
s Su tipo, que es el tipo de valor que la funcin regresa.
s La lista ordenada de sus parmetros, donde cada parmetro tiene un nombre y un tipo, debido a lo cual se dice
que los parmetros son formales.
s El cdigo mismo.
Con base en lo anterior, cabe destacar que la lista de los parmetros puede estar vaca.
En programacin, el tipo de la funcin puede ser cualquiera: un tipo denido por el usuario7 o un tipo estndar,
incluso el tipo void.
La declaracin de una funcin se hace con:

tipo nombre_funcion(lista parametros contipos){}

Por su parte, el cdigo se presenta como un bloque entre {} y contiene declaraciones de variables (las cuales son va-
riables locales a la funcin) y proposiciones. La ltima proposicin que se ejecuta debe ser la proposicin return, que
tiene dos formas:
return expresion;
return;
La ltima proposicin signica el n de ejecucin de la funcin y la primera se reere al valor que la funcin regresa, el
cual es el valor de la expresin.
La forma return, se usa nicamente por las funciones de tipo void.
A continuacin se presentan algunos ejemplos de funciones:

void nada()
{
}
void nada2()
{
return;
}
void linea()
{

7
Los tipos denidos por el usuario se estudian con detalle en el captulo 4.

108
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

printf(------------------------\n);
return;
}
int min(int a,int b)
{
if (a <=b)
returna;
else
return b;
}

En el ejemplo, las funciones nada y nada2 no tienen parmetros (es decir, la lista est vaca), por lo que no regresan
ningn valor; tampoco hacen ningn clculo. La diferencia entre las dos consiste en la presencia de return; para la
primera funcin, el bloque de cdigo est vaco.
La funcin linea tampoco tiene parmetros y no calcula ningn valor, pero su objetivo es escribir una lnea de - en
la salida.
La funcin min es interesante, ya que determina y regresa el mnimo entre dos nmeros enteros; sus parmetros
son dos enteros: a y b. En el cdigo se encuentran dos proposiciones return, pero solo una de estas es ejecutada.
La denicin de funciones de un programa se hace:
s Al interior de un bloque; en este caso, la funcin sera una funcin local al bloque.
s Afuera de cualquier bloque, al mismo nivel que main;8 en este caso, la funcin sera una funcin global.
En la mayora de los casos, las funciones seran denidas de manera global, al mismo nivel que la funcin main.
Es preferible concebir el programa con funciones globales solamente; de esta manera, una funcin se puede hacer
con llamadas de cualquier otra funcin.

Llamadas de funciones

Si la denicin de una funcin debe ser nica y la funcin, una vez denida, se puede llamar varias veces, cada vez que
sea necesario, la llamada de una funcin siempre aparecer en una expresin. La forma de una llamada es la siguiente:
nombre_funcion(lista valor es parametros)
y puede aparecer como parte de cualquier expresin. En la llamada se indica el nombre de la funcin y la lista de sus
parmetros dichos, actuales o reales.
La llamada se dene con los parmetros formales del cdigo de la funcin que los recibe, uno a uno, es decir, con
los valores de la lista de parmetros actuales; el cdigo de la funcin se ejecuta con estos valores y se regresa el valor
de la funcin. El valor regresado se usa como cualquier valor en el clculo de la expresin donde aparece la llamada.
Es importante resaltar que la lista de los valores de parmetros actuales debe corresponder en nmero y tipo con la
lista de los parmetros formales.
Ejemplo
Para las funciones denidas en la seccin precedente, las llamadas de estas funciones pueden ser:9

int main(int argc, char *argv[])


{

8
El cdigo principal, el bloque main, tambin es una funcin. Debido a su importancia, la funcin main se estudia con detalle en
el captulo 4.
9
Vase el programa funcions_simples.c en el CD-ROM que acompaa este libro.

109
*OUSPEVDDJOBMBQSPHSBNBDJO

int y, x=15;

nada(); nada2();
linea();
y = min(x, x-3*5);
printf( El valor que nos interesa : %d\n, y);
linea();
printf( Otro valor que nos interesa : %d\n, min (5*7, 6*6) - 3);
linea();
exit(0);
}

Aqu hay llamadas sucesivas de nada y nada2, tres llamadas de la funcin linea y dos llamadas con la funcin min .
Excepto la funcin min , las llamadas de otras funciones no tienen parmetros; sin embargo, los parntesis () deben
ser indicados.

Las llamadas de la funcin min son diferentes; los parmetros actuales son una variable simple o expresiones.
Las llamadas aparecen en expresiones complejas; ya sea, en una asignacin o en una expresin aritmtica.
El programa produce el siguiente resultado:

-------------------------
El valor que nos interesa : 0
-------------------------
Otro valor que nos interesa : 32
-------------------------

Si los parmetros actuales no corresponden en nombre con los parmetros formales, se indica un error al momento de
la compilacin.
Por otra parte, si el tipo no corresponde, se hace una conversin del tipo de parmetro actual al tipo de denicin del
parmetro. Por ejemplo, si aparecen estas llamadas:

y=min(x,x-3*5,17);
min(5);

Los mensajes del compilador resultan explcitos. As:

funcions_simples_error.c: In function ?main?:


funcions_simples_error.c:32: error:too many arguments to function ?min?
funcions_simples_error.c:33: error: too few arguments to function ?min?

Prototipo de una funcin

Para escribir correctamente la llamada de una funcin, se debe conocer:


s El tipo de la funcin.
s El nombre asignado a la funcin.
s La lista de los tipos de los parmetros.

110
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

El nombre de cada parmetro denido no es signicativo para hacer una llamada. A veces, es muy posible que en el traba-
jo de programacin se usen funciones estndares, funciones de las libreras o funciones escritas por algn otro miembro
del equipo de trabajo.
Como ya se mencion, para hacer la llamada se debe conocer la denicin completa, o al menos el prototipo de la
funcin. El prototipo de una funcin se hace como:
tipo nombre_funcion(lista de los tipos de los argumentos)
Por ejemplo, para las cuatro funciones denidas en el apartado anterior, los prototipos son:

void nada();
void nada2();
void linea();
int min(int a, int b);

Por lo que respecta a los parmetros, solo nos interesan sus tipos; no son necesarios sus nombres, pueden faltar.
Las libreras .h, que estn incluidas al inicio del cdigo, contienen deniciones de tipo, de algunas variables globales,
de algunas constantes y de una buena parte de prototipos de funcin. Por ejemplo, en la librera stdlib.h se distinguen
todos los prototipos de funciones:

void free(void *);


char *getenv(const char*);
long labs(long) --pure2;

Es necesario destacar aqu, que el lenguaje C permite la denicin y el uso de funciones con un nmero variable de pa-
rmetros, pero estas construcciones resultan muy tcnicas, por lo que sobrepasan el objetivo de este libro.

Un ejemplo completo: clculo de mximo comn divisor


y de mnimo comn mltiplo

Antes de iniciar el tratamiento de este tema, es necesario precisar algunas deniciones bsicas para su comprensin:
s El mximo comn divisor de dos nmeros enteros positivos a y b, es otro nmero entero d, que es el mayor
divisor de a y de b; a saber, las divisiones de a entre d y de b entre d, no dejan residuo.
s El mnimo comn mltiplo de dos nmeros es el menor entero m, que tiene a y b por divisores.
De esta forma, introducimos las notaciones:

d =mcd(a, b)
m =mcm(a, b)

Nos interesa obtener los valores de d y m.


Conocemos una relacin matemtica que liga los dos conceptos:
m * d = a * b
Para resolver este doble problema, es imperativo contar con un algoritmo que determine el mximo comn divisor.
Desde el punto de vista de la programacin, se impone escribir dos funciones que calculan, respectivamente, el
mximo comn divisor y el mnimo comn mltiplo. Los prototipos de las funciones seran, entonces:

int mcd(int, int);


int mcm(int, int);

111
*OUSPEVDDJOBMBQSPHSBNBDJO

Con la frmula descrita, la denicin de la funcin C mcm es:

int mcm(int x, int y)//minimo comun multiplo de 2 numero


{
return x * y / mcd(x, y);
}

Matemticamente, para calcular el mximo comn divisor se conocen dos mtodos:


s La descomposicin de los dos nmeros en factores primos. Por este mtodo, se toman los factores en comn. Por
ejemplo, en el caso de 90 = 2 32 5 y 162 = 2 34, el mximo comn divisor es 2 32 = 18.
s La aplicacin del algoritmo de Euclides de divisiones enteras sucesivas hasta que el residuo sea igual a 0. Por este
mtodo, el mximo comn divisor es, entonces, el ltimo divisor utilizado.
Ejemplo
Para buscar el mximo comn divisor de 90 y de 162, se hacen las siguientes divisiones enteras sucesivas (vase la
tabla 3.1).

Tabla 3.1

Dividendo (a) Divisor (b) Cociente Resto


90 162 0 90
162 90 1 72
90 72 1 18
72 18 4 0

El primer mtodo (descomposicin de los dos nmeros en factores primos) resulta difcil de implementar, ya que necesi-
tamos conocer los nmeros primos, un algoritmo por el clculo de la potencia que aparece en la descomposicin y otros
algoritmos, para detectar los factores comunes y la potencia mxima de cada divisor primo.
El segundo mtodo (algoritmo de Euclides) se traduce en el siguiente pseudocdigo, donde a es el dividendo, b el
divisor y r el residuo (el cociente no nos interesa):

repetir
ra%b (residuo de la divisin entera)
ab
br
hasta que r =0
Escribir a

La traduccin en el lenguaje C es:

int mcd(int x,int y) //maximo comun divisor de 2 numeros


{
int r;
r = 1;
while(r !=0)
{
r= x% y;
x= y;
y= r;
}
return x;
}

112
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

La parte principal, main, contiene la lectura de los nmeros y las llamadas para calcular el mximo comn divisor y el
mnimo comn mltiplo:

int main (int argc, char *argv[])


{
int a, b;

printf( indica los dos numeros :);


scanf(%d%d, &a, &b);

printf( los numeros son : %d %d\n, a, b);


printf( Su max comun divisor:%d\n, mcd(a,b));
printf( Su min comun multiplo:%d\n, mcm(a,b));

exit(0);
}

Al momento de la ejecucin del programa, si se introducen los valores 45 y 81, la salida es:

los numeros son :4581


Su max comun divisor:9
Su min comun multiplo:405

Transmisin de los parmetros

Si hacemos un anlisis del orden de las llamadas de las funciones en el programa, por el clculo del mximo comn
divisor, observamos que en el cdigo de la funcin mcd, los valores de entrada en los parmetros cambian durante el
clculo, mientras que al regreso de esta llamada, se hace una llamada de la funcin mcm, y el valor regresa. Entonces,
surgen algunas preguntas, como: Qu pasa con los valores introducidos en una funcin? Los valores de los parmetros
cambian o no a la salida de la funcin?
Si cambiamos la parte de main, para observar los valores de a y b al n de la llamada de la funcin mcd tenemos:

printf( los numeros son :%d%d\n,a, b);


printf(Su max comun divisor:%d\n, mcd(a,b));
printf(Los valores contenidos en a y b son: %d %d\n, a, b);

Entonces, la salida del programa sera:

Los numeros son: 56 777


Su max comun divisor:7
Los valores contenidos en a y b son:56 777

Como se puede observar, los valores de los parmetros estn transformados al interior de la funcin; sin embargo, a la
salida de la funcin (al nal de la llamada), los valores de las variables no estn modicados. En este caso, se dice que
los parmetros estn transmitidos por valor.
Los parmetros son transmitidos por valor en el caso del tipo escalar y no del tipo apuntador. Si el tipo del parmetro
es un apuntador, entonces, se dice que el parmetro es transmitido por direccin o transmitido por referencia; en este
caso, el contenido de la zona de memoria apuntada puede ser modicado.
La llamada de una funcin se realiza, prcticamente, colocando los parmetros (valores o apuntadores) en la pila
de llamada, donde se construye el ambiente del bloque de la funcin con sus variables locales y se ejecuta el cdigo. Si
en la lista de los parmetros hay un apuntador, y si se cambia su valor apuntado, este cambio ser efectivo y guardado
despus, al nal de la llamada de la funcin.

113
*OUSPEVDDJOBMBQSPHSBNBDJO

Ejemplo
Cmo podemos cambiar/intercambiar el contenido de las variables enteras (vase la gura 3.2)?

Inicial a aa b bbb

Final a bbb a aa

Figura 3.2

Este problema es equivalente al problema prctico de intercambiar el contenido de dos vasos llenos de diferentes lqui-
dos, donde la solucin sera usar un tercer vaso y hacer los cambios descritos en la gura 3.3.

2 3

a b c

Figura 3.3

La implementacin en el lenguaje C podra ser:

void cambio1(int a, int b)


{
int temp;
temp = a;
a = b;
b = temp;
return;
}

Sin embargo, en esta versin, los parmetros estn transmitidos por valor; entonces, los contenidos nunca podrn ser
cambiados. La versin correcta necesita que los parmetros estn transmitidos por direccin:

void cambio2(int *a, int *b)


{
int temp;
temp = *a;
*a = *b;
*b = temp;
return;
}

Esta es la versin que funciona correctamente.10 En esta, la parte de la funcin principal de main expresa el uso de las
dos versiones calculando:

int main (int argc, char *argv[])


{
int aa, bb;

10
El tema de intercambio de valores se estudia con profundidad en los captulos sobre el ordenamiento de los valores.

114
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

printf( indica los dos numeros :);


scanf(%d%d, &aa, &bb);
printf(los numeros son : %d %d\n, aa, bb);

cambio1(aa, bb);
printf(despues la llamada de *cambio1* : aa=%d, bb = %d\n, aa, bb);

cambio2(&aa, &bb);
printf(despues la llamada de *cambio2* : aa=%d, bb = %d\n, aa, bb);

exit(0);
}
indica los dos numeros :101 44
losnumerosson:10144
despues la llamada de *cambio1* : aa=101, bb= 44
despues la llamada de *cambio1* : aa=44, bb= 101

Ejemplo
Es posible reescribir las funciones de mximo comn divisor y mnimo comn mltiplo en una sola funcin con dos
parmetros transmitidos por valor, para los valores de entrada, y dos parmetros transmitidos por direccin, para los
valores del divisor y del mltiplo:

#include <stdlib.h>
#include <stdio.h>

void divisor_multiplo_comunes(int x, int y,int *div,int *mult)


//maximo comun divisor//y minimo multiplo comun
{
int r;
int prod;
prod = x * y;
r = 1;
while(r !=0)
{
r = x % y;
x = y;
y = r;
}
*div = x;
*mult = prod / *div;
}

int main(int argc, char *argv[])


{
int a, b;
int d, m;

printf( indica los dos numeros : );


scanf(%d%d, &a, &b);

printf( los numeros son :%d %d\n, a, b);divisor_multiplo_comunes(a,b,&d,&m);


printf(Su max comun divisor %d\n, d);
printf(Su min comun multiplo %d\n, m);

115
*OUSPEVDDJOBMBQSPHSBNBDJO

exit(0);
}

Ejemplo
La funcin min, que fue presentada antes, calcula el valor mnimo. Si queremos que una funcin regrese el mnimo de
dos valores, pero no como un valor, sino como un apuntador por la variable que contiene el valor mnimo, entonces la
funcin debe ser de tipo int*, el apuntador debe ser un apuntador de un int y los parmetros tambin deben ser
de tipo int*.

int* min_apuntado(int *a, int *b)


{
if (*a <= *b)
return a;
else
return b;
}

Para comparar el efecto de las funciones min y min_apuntado, se utiliza el siguiente cdigo:

int main(int argc, char *argv[])


{
int x = 15, y = 30;
int min_simple, *p;
min_simple = min(x, y);
printf(El minimo de %p->%d y %p->%d es : %p->%d\n, &x, x, &y, y, &min_simple, min);

p = min_apuntado(&x, &y);
printf(El minimo de %p->%d y %p->%d es : %p->%d\n, &x, x, &y, y, p, *p);
exit(1);
}

El cdigo anterior produce la siguiente salida:

El minimo de 0x7fff5fbff99c->15 y 0x7fff5fbff998->30 es : 0x7fff5fbff994->15


El minimo de 0x7fff5fbff99c->15 y 0x7fff5fbff998->30 es : 0x7fff5fbff99c->15

Como se puede observar, la variable min_simple contiene una direccin de memoria diferente que a o b; en tanto,
al contenido de p (que es de tipo apuntador) le corresponde la direccin de a.
Si queremos una funcin de tipo void con tres parmetros, el cdigo de una versin de la funcin sera:

void min_apuntado_version2(int *a, int *b, int **pp)


{
if (*a <= *b)
*pp = a;
else
*pp = b;
return;
}

El parmetro formal de salida se transmite por referencia, pero un apuntador de tipo int* no es suciente, porque el
contenido mismo del apuntador cambia. Por tanto, se requiere una direccin de apuntador para una direccin de int,
es decir, un apuntador de tipo int **. La llamada sera, entonces:

int x, y, *p;
...
min_apuntado_version2(&x, &y, &p);

116
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

3.4 Funciones estndares

El lenguaje C (el compilador gcc) ofrece al usuario una larga coleccin de funciones en sus bibliotecas de funciones, las
cuales se denominan funciones estndares.
El uso de las funciones estndares permite indicar una directiva de inclusin, haciendo mencin del nombre de la
biblioteca que las contiene:
#include<. h>

Funciones matemticas de <math.h>


Las funciones ms utilizadas son las siguientes:
s double sin(double x), double cos(double x), double tan(double x), para las funciones trigo-
nomtricas.
s double asin(double x), double acos(double x), double atan(double x), para las funciones
trigonomtricas inversas.
s double exp(double x), que signica ex.
s double log(double x), que signica el logaritmo natural ln(x),y double log10(double x), que es
el log10(x).
s double pow(x, y), que es la potencia xy.
s double sqrt(double x), que es la raz cuadrada.
s double ceil(x) regresa parte entera superior y double floor(double x) parte entera inferior.
s double fabs(double x), que es el valor absoluto.
Para la compilacin se usa la opcin -lm.

Algunas de las funciones de <stdlib.h>


A continuacin se presentan algunas de las funciones de <stdlib.h>
s int abs(int n): el valor absoluto.
s long labs(long n): el valor absoluto.
s int rand(void): un nmero entero pseudoaleatorio en el rango de 0 hasta RAND_MAX (que es una constante
predenida en la misma biblioteca).
s void srand(unsigned int n): que utiliza a la semilla n para generar una nueva secuencia de nmeros
pseudoaleatorios.
Ejemplo
Un programa que genera tres valores aleatorios: un valor entre 0 y RAND_MAX, que es una constante de la biblioteca
stdlib.h, y otros dos valores entre 0 y 30:

/*programa : uso de rand y srand */


#include <stdio.h>
#include <stdlib.h>
intmain(int argc, char *argv[])

117
*OUSPEVDDJOBMBQSPHSBNBDJO

{
int i, j;
printf( el valor RAND_MAX es :%d\n, RAND_MAX);
i = rand();
printf( un valor aleatorio es : %d\n, i);

j = rand()%31;
printf( un valor aleatorio entre 0 y 30 es : %d\n, j);

srand(1979);
j = rand()%31;
printf( otro valor aleatorio entre 0 y 30 es : %d\n, j);
exit(0);
}

Este programa genera:11

el valor RAND_MAX es :2147483647


un valor aleatorio es :16807
un valor aleatorio entre 0 y 30 es :25
otro valor aleatorio entre 0 y 30 es :6

En la actualidad, existen muchas ms bibliotecas de funciones y constantes tiles para el usuario, como para el manejo
del tipo char, para el tipo cadenas de caracteres, para el trabajo con archivos y muchos ms.12

3.5 Funciones de entrada/salida

En la biblioteca <stdio.h> hay dos funciones estndares; una para la entrada y otra para la salida estndar (que son el
teclado y la pantalla, respectivamente):

int printf(formato, argumentos);


int scanf(formato, argumentos);

Donde el formato es una cadena de caracteres y argumentos que son expresiones (para printf) o apuntadores de
variables (para scanf).
Para la funcin printf, el formato contiene caracteres ASCII. En tanto, algunos formatos de conversin
se componen del carcter % y de otro carcter (a veces, el tamao). La cadena de caracteres del formato se escribe hasta
el primer formato de conversin, donde el primer argumento de la lista se convierte, segn sea la codicacin; luego se
contina hasta el nal del formato.
Ejemplo
float promedio; int suma;
...
printf( El promedio de los valores del arreglo es : %f\n y la suma es : %d\n,
promedio, suma);

11
Segn los sistemas operativos, los nmeros generados no seran los mismos.
12
Como lo indicamos, las libreras (en el sistema Unix/Linux/Mac OS) se encuentran en el directorio /usr/include y si se
necesitan detalles sobre una funcin, el sistema operativo permite el uso del comando man. Por ejemplo, man sqrt nos indica el
prototipo, otras funciones vecinas y una explicacin.

118
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Este produce:

El promedio de los valores del arreglo es :15.629630


y la suma es :422

El primer formato de conversin, %f, se sustituye con el valor de la variable otante promedio, y el segundo formato,
%d, se sustituye con el valor entero de la variable suma.
La lista de los formatos de conversin se presenta en la tabla 3.2:

Tabla 3.2 Lista de formatos de conversin.

Formato Tipo argumento Conversin


%d %i Int decimal con signo
%o Int octal sin signo
%x %X Int hexadecimal sin signo (abc o ABC)
%u Int decimal sin signo
%c Char un carcter sencillo
%s char* los caracteres almacenados al inicio del argumento hasta que se encuentren un \0
notacin decimal con signo y punto decimal en la forma []mmm.ddd (la precisin
%f double
por defecto es 6)
%e %E double notacin decimal con escritura cientca en la forma []m.dddddE_xx
%g %G double se usa %e o %E si el exponente es menor que 4 o mayor que la precisin; si no se usa %f
%p void* impresin apuntador (depende del sistema)

Para la funcin scanf, el formato contiene:


s Blancos o tabuladores que se ignoran.
s Caracteres ordinarios (excepto %) que se espera coincidan con los caracteres buscados en la entrada.
s Formatos de conversin (casi los mismos que printf).
El mtodo de funcionamiento es el siguiente:
1. En la entrada (el teclado), se esperan cadenas de caracteres que correspondan con los caracteres ordinarios o
con los formatos de conversin hasta un separador (o varios) que puede ser: una lnea nueva, un tabulador o un
espacio.
2. Para las dos funciones (printf y scanf) es necesario indicar el mismo nmero de argumentos y de tipos
correctos (compatibles) que los formatos de conversin.
Ejemplo
En el siguiente programa hay lecturas y escrituras de valores.

/*programa ejemplo de lectura */


#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int a, b;

119
*OUSPEVDDJOBMBQSPHSBNBDJO

float d;
scanf(%d %d, &a, &b);
printf(a=%d b=%d\n, a, b);
scanf(%d %d, &a, &b);
printf(a=%d b=%d\n, a, b);

scanf(%f %d\n, &d, &a);


printf(d=%f, a=%d\n, d, a);
scanf(%f %d, &d, &a);
printf(d=%f, a=%d\n, d, a);

exit(0);
}

De este ejemplo se desprenden dos observaciones; la primera, no es fcil saber qu se debe introducir, si no hay un
mensaje explicativo antes; la segunda, scanf espera, en la entrada, algunos espacios (a veces ms de uno) para
separar los valores introducidos.
Con base en lo antes expuesto, es posible dar el siguiente consejo: en la cadena de formato de scanf usar nicamente
descriptores de formato (por ejemplo, %f o %d), sin ningn otro carcter.
En las cadenas de formatos, los caracteres especiales son:

Tabla 3.3 Caracteres especiales.

\n lnea nueva
\\ el carcter \
\t una tabulacin horizontal
\ el carcter
\ el carcter
\ooo un nmero en octal ooo
\xhh un nmero hexadecimal hh

Los formatos de conversin tambin se usan con la indicacin precisa del tamao del valor que se lee. Para los formatos
%d y %s, se debe indicar el tamao que se desea: %md, %ms, con un valor constante m . Si el valor de m es insuciente,
se usa el tamao mnimo necesario.
Ejemplo 1
Formato %d

#include <stdlib.h>
#include <stdio.h>

int main()
{
int i=786;
int j=909090;
printf( i = %d\n, i);
printf( i = %7d\n\n, i);
printf( j = %5d\n\n,j);
}

120
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Su salida es:

i = 786
i = 786
j = 909090

Se puede observar que para la variable j se toman ms de cinco caracteres.


Para el formato %f, se indican dos valores: m y n; es decir, por el nmero total de caracteres y por el nmero de
caracteres de la parte decimal, respectivamente:

%m.nf

El tamao total incluye el punto decimal y, eventualmente, el signo del valor representado.
Ejemplo
Uso de %f

#include <stdlib.h>
#include <stdio.h>

int main()
{
float f = 786.1236;
printf( f = %f\n, f);
printf( f = %7.2f\n, f);
printf( f = %10.5f\n, f);
}

El efecto de los formatos de conversin es el siguiente:

f = 786.123596
f = 786.12
f = 786.12360

Aqu se puede ver que se hace un redondeo de las cifras decimales.


Algunas funciones muy tiles para las entradas y las salidas del programa son:
s Funcin system de la librera stdlib. Esta funcin toma como parmetro una cadena constante de caracteres
que contiene un comando para el sistema operativo, el cual se ejecuta en el sistema operativo.
s Funciones fflush y fpurge. Estas funciones vacan todos los ujos de entrada y/o salida al ujo indicado. Tam-
bin permiten hacer lecturas ms claras para los valores.
Ejemplo
# include <stdio.h>
# include <stdlib.h>

int main(int argc, char *argv[])


{
int i, j;

printf(Efecto de clear : despues de la entrada de su numero la pantalla va a


borrarse.\scanf(%d, &i);
system(clear); //se borra la pantalla/ventana de trabajo

121
*OUSPEVDDJOBMBQSPHSBNBDJO

printf( introducir mas de un numero, pero unicamente el primer valor seria


leido.\n);
scanf(%d, &i);

fpurge(stdin);//si otros caracteres siguen despues del valor leido, seran


borrados
scanf(%d, &j); //se espera una nueva entrada
exit(1);
}

3.6 Recursividad

La recursividad es un paradigma de concepcin de algoritmos muy usado para la solucin de diversos problemas. Su
objetivo es describir la obtencin de algunas soluciones, como la obtencin preliminar de otra solucin.
Un problema clsico que necesita una resolucin recursiva es el concerniente al juego de las torres de Hanoi, ya que
este constituye un juego matemtico individual, en el que se dispone de un tablero con tres estacas y ocho discos (en el
caso general N), en orden creciente como se observa en la gura 3.4.

Torre Torre Torre


Figura 3.4 Torres de Hanoi.

El objetivo de este juego es mover todos los discos de una estaca a otra, respetando las reglas siguientes:
s Se mueve un solo disco a la vez.
s Se toma sistemticamente el disco que est arriba de una estaca.
s La estaca de destino debe estar vaca o tener encima un disco ms grande que el disco que se mueve.
Con estas reglas, a cada momento, las estacas que no estn vacas tienen discos en un orden creciente.
El problema de las torres de Hanoi se enuncia de la siguiente manera: Si los N discos estn todos en una estaca
fuente f, y forman una torre, se requiere la lista de los movimientos, para obtener la conguracin nal con todos los
discos sobre otra estaca d.

122
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

en p

f d
Figura 3.5

El programa que resuelve el problema de las torres de Hanoi, escribe un mensaje de tipo:

mover el disco 1 de la estaca 1 a estaca 2


mover el disco 2 de la estaca 1 a estaca 3
mover el disco 1 de la estaca 2 a estaca 3
...

Podemos suponer que los nmeros de las estacas son: 1, 2 y 3, respectivamente; entonces, 1 )f; d )3 y f &d. En este
caso, una observacin muy til es que con esta hiptesis la estaca diferente de d y de f, es siempre la estaca 6fd.
Para buscar una solucin, empezamos por analizar los casos ms simples:
s f = d, no se hace nada.
s Si N =1 (hay un solo disco para mover), se escribe nicamente mover el disco 1 de f a d
s Si hay ms de 2 discos (N * 2) y f d), una solucin sera mover los N 1 discos de f a 6 f d, mover el disco
1 de f a d, mover los N 1 discos de 6 f d a d (vase la gura 3.6).

3
1 2

6-f- f d
Figura 3.6

Aqu tenemos la solucin para N y usamos las soluciones que podemos denir, sin detallar, para N1. Entonces, se puede
decir que aqu tenemos una solucin recursiva.
Para escribir la funcin en el lenguaje C, debemos indicar los parmetros y su signicado. De esta forma, el prototipo sera:
void hanoi(int N, int d, int f)

123
*OUSPEVDDJOBMBQSPHSBNBDJO

Donde N es el nmero de discos, f la estaca inicial (fuente) y d la estaca destino.


El cuerpo de la funcin es:

void hanoi(int N, int f, int d)


{
if(f ==d )return;
if(N == 1)
{
printf( mover el disco *%d* de la estaca %d a la estaca %d\n, N, f, d);
return;
}
hanoi(N-1,f, 6-f-d);
printf( mover el disco *%d* de la estaca %d a la estaca %d\n, N, f, d);
hanoi(N-1, 6-f-d, d);
}

El cuerpo del main contiene una sola llamada de la funcin hanoi:

int main(int argc, char *argv[])


{
intf, d;
int N;

printf(Cuantos discos hay?N:);


scanf(%d,&N);

printf(La estaca fuente y la estaca destino : );


scanf(%d%d, &f, &d);

hanoi(N, f, d);
}

La solucin particular que se obtiene por N = 4 es:

Cuntos discos hay? N :4


La estaca fuente y la estaca destino : 12
mover el disco *1* de la estaca 1 a la estaca 3
mover el disco *2* de la estaca 1 a la estaca 2
mover el disco *1* de la estaca 3 a la estaca 2
mover el disco *3* de la estaca 1 a la estaca 3
mover el disco *1* de la estaca 2 a la estaca 1
mover el disco *2* de la estaca 2 a la estaca 3
mover el disco *1* de la estaca 1 a la estaca 3
mover el disco *4* de la estaca 1 a la estaca 2
mover el disco *1* de la estaca 3 a la estaca 2
mover el disco *2* de la estaca 3 a la estaca 1
mover el disco *1* de la estaca 2 a la estaca 1
mover el disco *3* de la estaca 3 a la estaca 2
mover el disco *1* de la estaca 1 a la estaca 3
mover el disco *2* de la estaca 1 a la estaca 2
mover el disco *1* de la estaca 3 a la estaca 2

La primera parte de la funcin hanoi permite recordar las especicaciones de los casos ms simples, en el caso de que
el primer if sea una condicin simple de ausencia de juego y el segundo if verique el caso de un nico disco. De lo
contrario, el cdigo contina haciendo dos llamadas de la funcin hanoi. El rbol de las primeras ocho llamadas de la
funcin recursiva es el siguiente (vase la gura 3.7):

124
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

llamada 1

hanoi (4, 1, 2)

llamada 2 llamada 8

= 8 = mover disco 4 :
hanoi (3, 1, 3) hanoi (3, 3, 2)
1 > 2

llamada 3 llamada 6

hanoi (2, = 4 = mover disco 3 : hanoi (2,


1, 2) 1 > 3 2, 3)
llamada 4 llamada 5 llamada 7 llamada 8

hanoi (1, hanoi (1, hanoi (1, hanoi (1, 1,


1, 3) 3, 2) 2, 1) 3)
mover disco 2 : mover disco 2 :
= 2 = 1 > 2 = 6 = 2 > 3

mover disco 1 mover disco 1 mover disco 1 mover disco 1


= 1 = = 3 = = 5 = = 7 =
: 1 > 3 : 3 > 2 : 2 > 1 : 1 > 3

Figura 3.7 rbol de las primeras ocho llamadas de la funcin recursiva.

En concreto, a cada llamada de la funcin (hanoi en nuestro caso, o cualquier otra funcin) se colocan los parmetros
actuales en una estructura llamada pila de llamadas y se crea el ambiente de la funcin con los valores de los parme-
tros. Si al interior de la funcin existe alguna otra llamada de la misma funcin, se pone otra llamada en la pila de llamadas.
Al nal de la funcin, dicha llamada se saca de la pila de los parmetros y se regresa a la ltima llamada, encima de la
pila. Este mecanismo de pila nos permite regresar a la funcin. Una imagen de la memoria que las funciones usan es:

Zona de variables estticas Zona de variables del main

f1 contexto

f2 contexto

Zona de variables

Pila de llamadas
Figura 3.8 Memoria de un programa.

125
*OUSPEVDDJOBMBQSPHSBNBDJO

Por ejemplo, hasta la primera escritura de un mensaje de movimiento en el problema de las torres de Hanoi, la pila de
las llamadas se compone de:
hanoi(1, 1, 3), hanoi(2, 1, 2), hanoi(3, 1, 3), hanoi(4, 1, 2)
Al nal de la llamada de hanoi (1, 1, 3), se deja la pila con solo:
hanoi(2, 1, 2), hanoi(3, 1, 3), hanoi(4, 1, 2)
Despus, la segunda escritura de mensaje de la pila aumenta con la llamada de hanoi (1, 3, 2).
El principio de una pila de llamada es que este se inserta enfrente, al igual que la extraccin, que tambin se hace
enfrente. Cuando una funcin de esta llamada se inserta en la pila, solo se extrae de la pila hasta que la funcin termina.
En cualquier lenguaje, una regla muy importante de concepcin de funciones recursivas es: En el caso o los casos
donde no se usa la recursin, sino una solucin simple, la funcin debe empezar con el tratamiento del caso de terminacin.
Los casos de recursividad se describen ms adelante, pero es importante resaltar que estos requieren que el progra-
mador est seguro de que el nmero de llamadas es nito hasta encontrar un caso simple sin recursividad.
Por otro lado, hay tcnicas de transformacin de una funcin recursiva en una funcin no recursiva, que realiza el
mismo clculo (vase el libro de Jacques Arsac, citado en la bibliografa de este captulo).
Ejemplo
El algoritmo de Euclides, expresado para la bsqueda del mximo comn divisor de dos nmeros a y b, tiene un par de
aspectos explcitos de recursividad:
s Si a%b = 0, entonces b es el mximo comn divisor.
s Si no, mcd(a,b)= mcd(b,a%b).
Por otro lado, es claro que la funcin recursiva debe empezar con el clculo de r = a%b y que la condicin de sepa-
racin del caso simple con el caso recursivo es r = 0(r = 0 en C).
Para escribir la recursin, desde un punto de vista matemtico, hay mcd(x,y) = mcd(y,x) para cualquier
pareja de (x, y); entonces, cul de las llamadas usamos: mcd(r,b) o mcd(b,r)? Para obtener una respuesta a esta
pregunta, primero debemos observar que siempre hay b > r, porque r es el resto de la divisin entera entre b, y, luego,
debemos analizar qu pasa cuando es la llamada a < b. De esta forma, si a < b, entonces r = a%b = a; esto es,
si las llamadas se hacen con mcd(r,b), estn los mismos parmetros que la llamada corriente y todas las llamadas
van a tener los mismos valores: a y b (en este orden).

mcd (a, b)
r <- a

llamada 1 llamadas 2, 3, ...

mcd (r, b)

Figura 3.9 Esquema de una llamada.

La solucin correcta es:13


int mcd(int x,int y) //maximo comun divisor de 2 numeros
{

13
Vase tambin la solucin mcd_recursiva_error.c, la cual produce un error de ejecucin porque la recursin no est correc-
tamente escrita.

126
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

int r;
r = x % y;
if (r == 0)
return y;
else
return mcd(y, r);
}

En este caso, la llamada es exactamente la misma que la de la solucin no recursiva: mcd(a,b).


En la gura 3.10 se observan dos esquemas de llamadas de esta versin correcta de la funcin para valores de en-
trada diferente:

llamada 1 llamada 1

mcd (224, 36) mcd (36, 80)

llamada 2 llamada 2

mcd (36, 8) mcd (80, 36)

llamada 3 llamada 3

mcd (8, 4) mcd (36, 8)

llamada 4

mcd (8, 4)

Figura 3.10 Esquema de llamadas para diferentes valores.

3.7 Ejemplos de uso de funciones recursivas

En esta seccin presentamos tres problemas que resolvemos con la ayuda de las funciones. Dos de estos problemas
admiten fcilmente una solucin recursiva; sin embargo, en el tercero la solucin recursiva no es tan natural al escribir; en
este caso la solucin secuencial, no recursiva, es ms clara y ms elegante.

Escritura de un nmero entero positivo en base 2


Queremos leer un nmero entero y luego escribir su representacin en base 2. Como ya se sabe, las cifras de la represen-
tacin de cualquier nmero en base 2 son una sucesin de 0 (ceros) y 1 (unos), que se obtienen en divisiones sucesivas.14

14
El algoritmo de divisiones sucesivas es presentado con detalle en el apndice 1 que se encuentra en el CD-ROM.

127
*OUSPEVDDJOBMBQSPHSBNBDJO

Por ejemplo: para n = 25 en base 10, queremos obtener el nmero en base 2:


11001
La idea de este algoritmo es hacer divisiones con 2 (la base de representacin) hasta que se obtiene el 0 (cero). En cada
divisin se guarda el residuo, que es una cifra de la representacin en base 2, y el cociente, para la prxima divisin.
Para el caso del nmero 25 se hacen las siguientes divisiones sucesivas:

Tabla 3.4

Valor Cociente Resto


25 12 1
12 6 0
6 3 0
3 1 1
1 0 1

La representacin est formada por la cifra de la ltima columna, tomada en orden inverso de obtencin, es decir, de
abajo hacia a arriba, entonces tenemos: 11001.
Si hacemos la primera divisin, dicha divisin sera la ltima si el cociente que se obtiene es 0. Por otra parte, si el
cociente obtenido no es 0, se hacen los mismos clculos para este valor del cociente. Es evidente que primero se escri-
ben la cifras obtenidas para el cociente y luego la cifra de la primera divisin.
La funcin en su forma recursiva que calcula la representacin binaria es la siguiente:

void cifras_entero_base_2(int x)
{
int y;
int r;
r = x%2;
y = x/2;
if (y != 0)
cifras_entero_base_2(y);
printf(%1d, r);
return;
}

La funcin es de tipo void, porque el clculo ms importante es la escritura de la cifra, la cual se hace despus de una even-
tual llamada (recursiva). La escritura se efecta con la funcin estndar printf y el formato %1d, ya que es una sola cifra.
El programa principal es muy sencillo:

int main()
{

int n;
printf( Cual es su numero :);
scanf(%d, &n);

printf( Su representacion en base 2 es : );


cifras_entero_base_2(n);
printf(\n);
}

128
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Este programa produce, por ejemplo:

Cual es su numero :1028


Su representacion en base 2 es : 10000000100

Por el momento, una solucin no-recursiva es ms difcil, porque se requiere almacenar la cifra de los residuos, hasta
que se obtienen todas las cifras. En el siguiente captulo introducimos la nocin de arreglo, que permite la resolucin del
problema de guardar un nmero variable de valores.
El nmero de las llamadas es igual al nmero de cifras en base 2 del nmero.

Escritura de un nmero fraccionario en base 2

Este problema es semejante al problema anterior; aqu se busca la representacin en base 2 de un nmero, pero el
nmero es fraccionario, es decir que tiene el intervalo [0.0, 1.1]. En el apndice 1 (vase el CD-ROM) se presenta un
algoritmo que trabaja con multiplicaciones sucesivas hasta obtener un nmero suciente de cifras.
La idea de este algoritmo es que el nmero se multiplique por 2, que es el valor de la base, y que el resultado de la
multiplicacin se almacene: la parte entera que resulta ser la cifra de la representacin y la parte fraccionaria que resulta
ser el operando de otra multiplicacin realizada con la base 2.
Por ejemplo, para x = 0:1356 y 8 cifras despus del punto decimal, los clculos son los siguientes:

Tabla 3.5

Valor Multiplicacin por 2 Parte entera Parte fraccionaria


0.1356 0.2712 0 0.2712
0.2712 0.5424 0 0.5424
0.5424 1.0848 1 0.0848
0.0848 0.1696 0 0.1696
0.1696 0.3392 0 0.3392
0.3392 0.6784 0 0.6784
0.6784 1.3568 1 0.3568
0.3568 0.7136 0 0.7136

La representacin se forma con las cifras de la ltima columna, tomadas en orden inverso de obtencin; es decir, de arriba
hacia abajo. As: 0.00100010.
La funcin recursiva calcula la escritura del nmero en cuestin en base 2, de acuerdo con las siguientes etapas:
s Calcular el producto con 2.
s Tomar la parte entera que es una cifra de la representacin.
s Llamar a la funcin por la parte fraccionaria restante.
Estas etapas se repiten con el nmero de cifras que se desea.

void cifras_fraccionario_base_2(float x, int n)


{

129
*OUSPEVDDJOBMBQSPHSBNBDJO

int c;
float y;
if (n == 0)
return;
else
{
c = x * 2;
y = x * 2 -c;

printf(%1d, c);
cifras_fraccionario_base_2(y, n -1);
return;
}
}

La funcin tiene dos parmetros porque tambin se requiere saber cundo terminan las llamadas. El primer parmetro
es el nmero de cifras que se desea obtener y el segundo parmetro es el nmero de cifras de la parte fraccionaria. La
parte del programa principal es la siguiente:

int main()
{

float f;
int n;

printf( Cual es su numero :);


scanf(%f, &f);
printf( Cuantas cifras despues el punto decimal :);
scanf(%d, &n);

if (f > 1 || f < 0)
{
printf( El numero no es correcto (entre 0. y 1.0)\n);
exit(1);
}
printf( Su representacion en base 2 es : );
printf( 0.);
cifras_fraccionario_base_2(f, n);
printf(\n);
}

El programa produce, por ejemplo:

Cual es su numero :0.6755


Cuantas cifras despues el punto decimal :20
Su representacion en base 2 es : 0.10101100111011011001

Nmero en espejo

En este apartado se plantea el problema de obtencin de la imagen de espejo de un nmero entero positivo. Por ejem-
plo, para la entrada 234, el programa produce 432.
Una observacin muy simple es que resulta fcil obtener las cifras del nmero, adaptando el mismo clculo aplicado
en el apartado anterior, para el clculo de las cifras en base 2:

130
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

void cifras_entero_base_10(int x)
{
int y;
int r;
r = x%10;
y = x/10;
if (y != 0)
cifras_entero_base_10(y);
printf(%1d , r);
return;
}

Una llamada de esta funcin produce, por ejemplo:

Cual es su numero :9887


Su representacion en base de 10 es : 9 8 8 7

Al interior de la funcin recursiva, cifras_entero_base_10, las cifras se obtienen de la ltima hasta el inicio, que
corresponden a la representacin del nmero en espejo. Tambin, podemos modicar la funcin de extraccin de las
cifras y aadir el clculo paso a paso del nmero en espejo. Una primera solucin usa una variable global, la cual est
modicada en el cuerpo de la funcin numero_espejo:

int aux; //la variable global


void numero_espejo(int x)
{
int y;
int r;
r = x%10;
aux=aux*10 +r;
y = x/10;
if (y != 0)
numero_espejo(y);
return;
}

int main()
{
int n; //el numero
int espejo; //el numero en espejo
printf( Cual es su numero :);
scanf(%d, &n);

aux = 0;
numero_espejo(n);
espejo = aux;
printf( El numero en espejo es : %d.\n, espejo);
}

El uso de una variable global es delicado, porque si la funcin va a ser utilizada por otro programador, el cdigo de la
funcin no resulta suciente; tambin se debe declarar la variable global y no olvidar la inicializacin de esta variable,
antes de una variable esttica, que al interior de la funcin podra ser una solucin. Sin embargo, existen dos desventajas:
s El contenido de la variable no es visible en el programa principal; entonces, hay que usar un parmetro.
s Para una primera llamada el resultado es vlido, si la variable esttica es inicializada con 0; pero, para la segunda
llamada y las siguientes el resultado es falso.

131
*OUSPEVDDJOBMBQSPHSBNBDJO

Una solucin sera el uso de un parmetro como referencia que guarde un clculo parcial del resultado esperado.
Con base en lo antes expuesto, la nueva versin de la funcin sera:

void numero_espejo_bis(int x, int *aux)


{
int y;
int r;
r = x%10;
*aux=*aux* 10 + r;
y = x/10;
if (y != 0)
numero_espejo_bis(y, aux);
return;
}

El segundo parmetro es un apuntador; entonces, en la asignacin se usa su contenido y para la llamada recursiva se
utiliza el mismo apuntador. Desde el programa principal, la primera llamada indica una direccin de memoria. En el cdigo
del programa principal main, estn presentes las dos llamadas:

int main()
{
int n; //el numero
int aux, espejo; //el numero en espejo
printf( Cual es su numero :);
scanf(%d, &n);

// primera llamada
aux = 0;
numero_espejo_bis(n, &aux);
espejo = aux;
printf( El numero en espejo es : %d.\n, espejo);

printf( Cual es su segundo numero :);


scanf(%d, &n);

// segunda llamada
espejo = 0;
numero_espejo_bis(n, &espejo);
printf( El numero en espejo es : %d.\n, espejo);
}

En este caso, la segunda llamada, numero_espejo_bis, usa directamente la direccin de la variable espejo.

3.8 Apuntadores de funciones

En las secciones anteriores de este captulo vimos que podemos considerar apuntadores para cualquier tipo de variable.
Asimismo, en el lenguaje C se permite la denicin y el uso de apuntadores de funcin.
Sabemos que int* es el tipo de un apuntador por una variable de tipo int, y que int ** es el tipo de un
apuntador para una variable de tipo int*. El tipo de una funcin se puede leer en su prototipo, que es: tipo_
regreso(tipos_argumentos), para las funciones que regresan un valor, y void(tipos_argumentos), para las fun-
ciones que no regresan nada.

132
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Naturalmente, un apuntador de funcin se declara como:

tipo_regreso (*nombre_apuntador)(tipos_argumento);
void (*nombre_apuntador)(tipos_argumento);

El operador de asignacin que se aplica es:


apuntador_funcion = nombre_funcion
Donde nombre_funcion es una funcin conocida por el programa. Despus, la asignacin del apuntador de funcin
se usa como un nombre de funcin y una llamada de tipo:
apuntador_funcion(lista_parametros_formales)
Esta es equivalente a la llamada de funcin apuntada: nombre_funcion(lista_parametros_formales).
Ejemplo
En este cdigo hay tres funciones globales: dos de tipo int(int, int) y una de tipo void(). Las dos funciones que
regresan un entero calculan, respectivamente, el mximo y el mnimo de dos enteros. La funcin de tipo void escribe
una lnea en la pantalla.

#include <stdlib.h>
#include <stdio.h>

void linea()
{
printf(----------------------------\n);
return;
}
int min (int a, int b)
{
if (a <= b)
return a;
else
return b;
}

int max (int a, int b)


{
if (a > b)
return a;
else
return b;
}

int main(int argc, char *argv[])


{
int x = 15, y = 32;
int (*p)(int, int);
int (*q)(int, int);
void (*li)();

p = min;
printf(El minimo de %d y %d es : %d\n, x, y, p(x, y));
li = linea;
li();

133
*OUSPEVDDJOBMBQSPHSBNBDJO

q = max;
printf( El maximo de %d y %d es : %d\n, x, y, q(x, y));
}

En el programa principal main, se usan tres apuntadores de funcin, uno para cada funcin.
El programa produce la salida:

El minimo de 15 y 32 es : 15
----------------------------
El maximo de 15 y 32 es : 32

Un apuntador de funcin tambin puede aparecer como parmetro actual de una funcin, solo se requiere que se espe-
cique con detalle el tipo del parmetro formal.
Ejemplo
Si se necesita el mximo o el mnimo de tres nmeros,15 el cdigo de las dos funciones es muy parecido:

int min3(int a, int b, int c)


{
return min(a,min(b,c));
}

int max3(int a, int b, int c)


{
return max(a,max(b,c));
}

Las funciones min3 y max3 usan, respectivamente, las funciones min y max anteriormente presentadas. Podemos cons-
truir una funcin genrica capaz de recibir por parmetro la funcin min o la funcin max. Su cdigo sera el siguiente:

int extremo3(int a, int b, int c, int (*compar)(int,int))


{
return compar(a, compar(b,c));
}

El parmetro formal compar, es de tipo apuntador de funcin, con dos parmetros de tipo int regresando un valor
de tipo int.
En el cdigo del programa principal se hacen dos llamadas de la funcin extremo3, indicando por un parmetro
ya sea la funcin min o la funcin max.

int main(int argc, char *argv[])


{
int x = 15, y = 32, z = 17;

//llamadas clasicas
printf(el minimo de %d, %d y %d es : %d\n, x, y, z, min3(x, y, z));
printf(el maximo de %d, %d y %d es : %d\n, x, y, z, max3(x, y, z));

//llamadas de la misma funcion con un parametro de tipo apuntador de funcion


printf( El minimo : %d\n, extremo3(x, y, z, min));
printf( El maximo : %d\n, extremo3(x, y, z, max));
}

15
El problema general de bsqueda de mnimo o mximo por un nmero N de nmeros, se tratar con detalle en el ltimo captulo
de este libro.

134
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

La salida de este programa es:

el minimo de 15, 32 y 17 es: 15


el maximo de 15, 32 y 17 es: 32
El minimo : 15
El maximo : 32

3.9. Funciones con nmero variable de parmetros

El compilador C ofrece una biblioteca estndar que permite trabajar, denir y llamar funciones con un nmero variable de
parmetros. El prototipo de una funcin con nmero variable de parmetros es el siguiente:
tipo_funcion nombre_funcion(tipo_parametro1 parametro1, ...)
Solo se indica el primer argumento con su tipo seguido de tres puntos.
La biblioteca estndar que se usa para tratar la funcin de estas caractersticas es stdarg.h. En esta biblioteca se
encuentran:
s Una denicin del tipo va_list es un apuntador que sirve para iterar los parmetros. Este imperativo indica que
en la funcin hay una variable de este tipo.
s Tres macro-directivas define16 con parmetros permiten moverse en la lista de parmetros:
s va_start que permite iniciar el recorrido de los argumentos (parmetros formales) de la funcin. Se usa con
dos parmetros: la variable local de tipo va_list apuntador y el nombre del primer argumento de la funcin. Se
produce un valor del tipo tipo_parametro1.
s va_arg se aplica al apuntador y permite detectar el siguiente valor de argumento. Se usa con dos parmetros: el
apuntador y el tipo del argumento que puede ser int, char, char* o double o apuntadores de estos tipos.
s va_end que se aplica al apuntador y permite liberar correctamente la zona de memoria ocupada por los argu-
mentos.
La cuarta directiva va_copy crea una copia de un apuntador a otro, para el caso donde se necesita ms de un recorrido
de lista de argumentos.
Existen dos losofas para trabajar con funciones con un nmero variable de parmetros:
s El primer argumento es siempre un valor entero que indica el nmero de valores que siguen en la lista
s El ltimo argumento es un valor especial llamado centinela y la deteccin de este valor indica que es el n.
Si queremos calcular la medida de un nmero variable de valores otantes (doble precisin) los prototipos de las fun-
ciones serian:

double medida(int n, ...)


double medida_centinela(double x, ...)

y las llamadas serian :

medida(5, 2.2, 1.1, 3.3, 4.4, 6.6);


medida_centinela(2.2, 1.1, 3.3, 4.4, 6.6, 1.1);

con 1.1 como valor centinela.

16
Ver el captulo 2 sobre el funcionamiento de las macro directivas con parmetros.

135
*OUSPEVDDJOBMBQSPHSBNBDJO

El programa que dene y utiliza estas funciones es:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#define CENTINELA 1.1

double medida(int n, ...)


{
int i;
float suma, x;
va_list ap_argumentos;

va_start(ap_argumentos, n);
printf( El primer parametro vale : %d\n, n);
suma = 0.0;
for (i = 0; i < n; i++)
{
x = va_arg(ap_argumentos, double);
printf( El valor del parametro es : %f\n, x);
suma += x;
}
va_end(ap_argumentos);
if (n == 0)
return 0.0;
else
return suma/n;
}

double medida_centinela(double x, ...)


{
int i;
float suma;
va_list ap_argumentos;

va_start(ap_argumentos, x);
i = 0; suma = 0.0;
while ( x != CENTINELA)
{
suma += x;
i++;
x = va_arg(ap_argumentos, double);
}
va_end(ap_argumentos);
if (i == 0)
return 0.0;
else
return suma/i;
}

int main(int argc, char *argv[])


{
printf( La medida es %lf.\n, medida(5, 2.2, 1.1, 3.3, 4.4, 6.6));
printf( La otra medida es %lf.\n,

136
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

medida_centinela(2.2, 1.1, 3.3, 4.4, 6.6, -1.1));

printf(Medidas excepcionales %lf, %lf.\n, medida(0),


medida_centinela(-1.1));

exit(0);
}

En las dos funciones hay el mismo tratamiento que en el caso donde se puede producir un error grave de clculo: la
divisin entre 0 cuando no hay valores para calcular.
En las dos funciones el recorrido inicia con la macro-directiva va_start y termina con la macro-directi-
va va_end. Entre los dos macro-directivas hay una estructura iterativa (for o while) que permite de recorrer la lista
de los argumentos.
El programa produce:

El primer parametro vale : 5


El valor del parametro es : 2.200000
El valor del parametro es : 1.100000
El valor del parametro es : 3.300000
El valor del parametro es : 4.400000
El valor del parametro es : 6.600000
La medida es 3.520000.
La otra medida es 3.520000.
El primer parametro vale : 0
Medidas excepcionales 0.000000, 0.000000.

En el siguiente ejemplo las funciones de medida regresan un solo valor y los parmetros son transmitidos por valor.
Una funcin con nmeros variables de parmetros tambin puede tener parmetros transmitidos por direccin. En
este caso se construye con la llamada de va_arg, se construye un apuntador y se usa este apuntador para poner valor
a la direccin referenciada.
Ejemplo
En este programa hay una funcin regresa1 que pone 1 en todas las variables transmitidas por referencia.

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

const int uno = 1;

void regresa1(int n, ...)


{
va_list ap;
int i;
int *x;

va_start(ap, n);
for (i = 0; i < n; i++)
{
x = va_arg(ap, int*);
*x = 1;
}
va_end(ap);
}

137
*OUSPEVDDJOBMBQSPHSBNBDJO

int main(int argc, char *argv[])


{
int a, b, c, d;
regresa1(2, &a,&b); printf( a= %d b=%d\n, a, b);
regresa1(0);
regresa1(4, &a, &b, &c, &d);
printf( a= %d b=%d c=%d d=%d\n, a, b, c, d);
exit(0);
}

Los parmetros transmitidos por referencia no tienen nombres explcitos, el acceso se hace al interior de la
estructura iterativa:

x = va_arg(ap, int*);
*x = 1;

El apuntador x toma el valor transmitido por referencia que es una direccin de variable.
El programa produce:
a = 1 b = 1
a = 1 b = 1 c = 1 d = 1

Sntesis del captulo

El lenguaje de programacin C permite el acceso directo a una celda de memoria con la implementacin de
la nocin de apuntador de memoria. De esta forma, el apuntador permite cambiar directamente el contenido
de memoria; asimismo, tambin permite a las funciones un modo de transmisin de los parmetros por valor.
El lenguaje C implementa el procedimiento o el cdigo de llamada desde el cdigo corriente, que se tra-
duce por la nocin de funcin. El programador (el usuario) tiene la posibilidad de denir y utilizar sus propias
funciones o utilizar funciones predenidas que se encuentran en las libreras estndares.
Estas funciones estndares abarcan operaciones corrientes u operaciones ms especiales, como las entra-
das y salidas, el uso de algunos valores matemticos calculados, la transformacin de tipos y otras ms.
Las dos principales funciones de entrada y salida son las siguientes: scanf y printf.
Las funciones denidas por el usuario tienen un nombre, un tipo del valor regresado y una lista ordenada
de parmetros, de los cuales, cada parmetro tiene un tipo. Al momento de la llamada de la funcin, se indica
el nombre de dicha funcin y los valores de los parmetros; estos valores deben corresponder en nombre y en
tipo con la lista de parmetros de la denicin de la funcin.
Si el tipo de los parmetros es esttico (un valor contenido en la memoria), dichos parmetros se trans-
miten por valor; en cambio, si el parmetro es un apuntador, la transmisin se hace por direccin. En el caso
de los parmetros transmitidos por valor a la salida de la funcin, los valores no cambian. Los valores de los
parmetros cambian nicamente si la transmisin de estos valores se hace por direccin.
Para utilizar una funcin estndar o una funcin denida por el usuario, primero se debe conocer el proto-
tipo de la funcin: su nombre, su tipo y la lista ordenada de los tipos de los parmetros.
Al interior de una funcin es posible llamar otras funciones, incluso se puede llamar la funcin misma. En
este caso, se habla de una funcin recursiva. La elaboracin de funciones recursivas es ms delicada porque se
necesita tomar en cuenta todos los casos a tratar y estar seguro de que la funcin termina.

138
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

Bibliografa
s Kernighan, Brian W. y Ritchie, Dennis M., El lenguaje de programacin C, 2a. edicin Pearson Educacin, Mxico, 1991.

s Arsac, Jacques, Las bases de la programacin, Ediciones Omega, Barcelona. Foundations of Programming. Studies
in Data Processing, Academic Press Inc., Estados Unidos, 1985.

Referencias de Internet
s El software gcc se puede bajar desde: http://gcc.gnu.org/

s Lista concentrada con los principales comandos de Unix/Linux: http://www.retronet.com.ar/linux.pdf

s Pgina de Wikipedia sobre la recursividad: http://es.wikipedia.org/wiki/Recursin

s Pgina de Wikipedia sobre el problema de las torres de Hanoi: http://es.wikipedia.org/wiki/Torres

Ejercicios y problemas
1. Usando variables globales, hacer un programa que lea un valor entero N y luego lea N valores otantes y,
despus de cada lectura de un otante, realice dos sumas que sirvan para calcular:

s El promedio de los N valores.

s La desviacin estndar.

2. Escribir un programa que lea dos valores de tipo float, y que luego realice la suma de los dos, pero sin
usar directamente una escritura de forma a + b, sino que use apuntadores para estos.

3. Hacer un programa que lea tres valores otantes: x, y y z, y que calcule el siguiente valor:
3
2 sen( x )cos( y )

El programa utiliza la librera math.h.

4. Escribir un programa que tome como entrada dos nmeros otantes y los interprete como la parte real y la
parte imaginaria de un nmero complejo (representacin binmica). El programa debe:

s Calcular el valor absoluto del nmero complejo y el argumento de la representacin polar.


s Vericar si el nmero no es imaginario puro.
s Calcular el conjugado del nmero.

139
*OUSPEVDDJOBMBQSPHSBNBDJO

5. Escribir una funcin de conversin de temperatura de grados Fahrenheit a grados Celsius, segn la frmula:

C = (5/9)(F32)

s Escribir una segunda funcin que realice la conversin inversa: de grados Celsius a grados Fahrenheit.
Prototipos:
float convFC(float gradosF)
float convCF(float gradosC)

s Realizar, en el programa principal, la lectura de valores para la conversin y llamadas de las dos funciones.

s Introducir en el programa principal una prueba que verique si una sucesin de dos conversiones convFC
(convCF(x)) o convCF (convFC(x)) deja su argumento constante o muy cerca de su valor inicial.

6. Escribir un programa que lea un nmero variable de valores otantes y que para cada uno de estos valores
calcule su parte entera y su parte fraccionaria. El programa debe escribir cada nmero y sus valores de parte
entera y parte fraccionaria usando una escritura de forma:

12.45 = 12 + 0.45

7. Escribir una funcin que tome como entrada un nmero otante positivo x y regrese el valor y = x 2k/
con 0 ) y < 2/.

8. Calcular, con la ayuda de una funcin, valores de tipo 2k, con k variable de 0 hasta 30. El prototipo de la
funcin es el siguiente:

int potencia2(int k);

9. Escribir una funcin de tipo void (que no regresa nada) y cambiar el contenido de dos enteros, si estos no
se encuentran en orden creciente. Escribir el programa que hace una llamada de esta funcin.

10. Escribir una funcin que verique si un nmero entero n es de forma ak, con a una constante entera positiva.
La funcin regresa 0 o 1.

11. Escribir una funcin intprimo (int n) que verique si el nmero n es primo o no.

12. Con la ayuda de la funcin del punto precedente, escribir una funcin int gemelos (int n, int m) que
verique si m y n son gemelos; donde cada uno es primo y m = n + 2.

13. Escribir una funcin int suma_divisores (int n) que regrese la suma de los divisores propios del
nmero n incluido el 1 y excluido el n. Por ejemplo, la suma de los divisores propios de 54 es: 1 + 2 + 3 +
6 + 9 + 18 + 27 = 66.

14. Nmeros amigos. Se dice que dos nmeros son amigos si la suma de los divisores propios de cada nmero
es igual al otro nmero.Por ejemplo, 220 y 284 son amigos:

1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110 = 284

1 + 2 + 4 + 71 + 142 = 220

140
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

s Usando la funcin suma_divisores escribir la funcin:

int numeros_amigos(int a, int b);

que regresa 1, si los nmeros son amigos, y 0, si no lo son.

s Vericar, por programa, si dos nmeros son amigos o no.

15. Potencias. Solucin recursiva.

s Proponer una solucin recursiva de la funcin que calcula a N, basndose en las siguientes formulas:

a0 = 1

a2K = aK aK

a2K+1 = a aK aK

s Dibujar una grca de llamadas por un valor a > 1 y N = 17.

s Introducir al cdigo una variable global:int paso, que se incrementa en cada llamada de la funcin
potencia_recursiva y que al nal se escribe el nmero total de llamadas.

s Escribir una solucin ptima desde el punto de vista del nmero de las llamadas.17

16. Matemtico. Con base en el problema de las torres de Hanoi:

s Demostrar que la solucin propuesta realiza exactamente 2N 1 movimientos.

s Demostrar que este nmero es el nmero mnimo de movimientos necesarios en la solucin.

17. De acuerdo con la solucin propuesta de las torres de Hanoi, modicar la llamada de la funcin:

printf(mover el disco *%d*de la estaca % da la estaca %d n,N, f, d);

para dibujar el disco N con N caracteres * o #.

Sugerencia: Hacer dos funciones.

s Una funcin que escriba el texto nada ms.

s Una funcin que sea capaz de imprimir un nmero variable (indicado por parmetro) de un carcter.

18. Una versin (menos conocida) del algoritmo de Euclides consiste en hacer sustracciones sucesivas del
nmero ms grande al nmero ms pequeo, hasta que se obtiene 0. Los operandos (que son iguales)
representan el mximo comn divisor. Por ejemplo, para buscar el mximo comn divisor de 90 y de 162
se realizan las sustracciones (diferencias) sucesivas (vase la tabla 3.6).

17
Se puede mostrar que este nmero es: log2N.

141
*OUSPEVDDJOBMBQSPHSBNBDJO

Tabla 3.6

a B Ms grande Ms pequeo Diferencia


90 162 162 90 72
90 72 90 72 18
72 18 72 18 54
18 54 54 18 36
18 36 36 18 18
18 18 18 18 0

En este caso, el mximo comn divisor es 18.

Se pide:

s Escribir una funcin no recursiva que implemente este algoritmo.

s Escribir una funcin recursiva para el algoritmo.

s Hacer pruebas de correccin (varias ejecuciones con diferentes valores) del programa.

19. Utilizando una llamada de la funcin que calcule el mximo comn divisor, hacer una funcin que com-
pruebe si dos nmeros enteros son primos entre ellos (considerando que nicamente tienen un comn
divisor: 1). El prototipo de la funcin es:

int primos_entre_si(int a, int b);

20. Un nmero es un palndromo si podemos hacer su lectura de izquierda a derecha y a la inversa, de dere-
cha a izquierda, obteniendo el mismo valor. La representacin decimal del palndromo tiene un centro de
simetra. Por ejemplo, 363, 10001 son palndromos y 30306 no es un palndromo. Escribir una funcin
de prototipo:

int palindromo (int n);

que regrese el valor 1 si el nmero es un palndromo y 0 si no lo es.

21. Difcil. Adaptar el programa que calcula la factorizacin de nmeros primos de un nmero entero, para
obtener al nal, en la salida, una escritura de forma:

22 33 51

por el nmero 54, por ejemplo.

22. Los nmeros de la sucesin de Fibonacci describen el crecimiento natural de varias formas de vida. La suce-
sin es: 1, 1, 2, 3, 8, 13, 21, ... y se describe con las siguientes deniciones matemticas:

142
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

F1 = 1

F2 = 1

Fn+2 = Fn+1 + Fn

s Escribir una funcin recursiva de prototipo:

intfib(intn)

para el clculo de un nmero de Fibonacci y escribir la parte de main que llame a esta funcin.

23. Escribir una nueva versin del programa del juego presentado en el captulo 2, que se reere a la bsqueda
de un nmero, implementando cada funcionalidad por una funcin. Por ejemplo:

s Para la lectura de una propuesta del nmero.

s Para la escritura de respuestas.

s Para la escritura de una sugerencia.

s Para calcular la respuesta de 1, 0 o 1.

s Por el clculo de la mitad del intervalo.

s Por el ajuste del intervalo.

24. Clculo de Cnk.

En lgebra, la notacin Cnk o (kn) corresponde a un coeciente binomial que se calcula segn la frmula:

(kn) = n!
k! (n k)!
Donde: n y k son enteros positivos con k ) n.

El tringulo de Pascal:

1
11
121
1331
14641
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
...

143
*OUSPEVDDJOBMBQSPHSBNBDJO

es la expresin de la frmula recursiva:

= 1 + 1 1

Sabemos que: 0 = = 1.

Escribir una funcin recursiva que use estas frmulas para el clculo de (kn).

25. Hacer un programa que escriba el tringulo de Pascal con una altura de N (mximo 20), usando la funcin
escrita anteriormente. La escritura debe hacerse de acuerdo con la forma presentada en el problema anterior
(difcil) o en la forma ms simple:

1
11
121
1331
14641
...

26. Dibujar la grca de las llamadas para n = 6 y k = 3. Cul es el nmero total de llamadas de la funcin
recursiva en caso general?

27. Resuelve los siguientes puntos:

a) Escribir el prototipo de una funcin de nombre producto_entero que regrese un entero y tenga dos
parmetros transmitidos por valor de tipo float.

b) Escribir la denicin de un apuntador de funcin con el nombre pfunc, a travs del cual podamos hacer
la asignacin:

pfunc = producto_entero;

c) Escribir el cdigo de la funcin producto_entero que regrese el producto de las partes enteras de los
valores de parmetros.

d) Escribir el programa principal que haya dos llamadas de la funcin producto_entero:

s Una llamada clsica producto_entero(?).

s Una llamada usando el apuntador pfunc.

e) Realizar el ejercicio anterior, pero en lugar de producto_entero, trabajar con la funcin produc-
to_entero_cambio con dos parmetros otantes transmitidos por referencia, que haya tambin el
producto de las partes enteras y, adems, cambie el contenido de cada parmetro por su parte entera.

28. Siempre vale 1 o Conjetura de Collatz. Efectuar el siguiente juego con dos jugadores. Al inicio del juego,
el primer jugador propone un nmero N, N > 1, mientras que el otro hace el clculo, con base en las siguien-
tes reglas:

144
$BQUVMPt7BSJBCMFT BQVOUBEPSFT GVODJPOFTZSFDVSTJWJEBE

s Si el nmero N es par, se calcula N/2.

s Si el nmero es impar, se calcula 3N + 1.

Si el segundo jugador obtiene 1 por su clculo, gana. De lo contrario, toca el turno al otro jugador, que debe
hacer el mismo clculo; el juego contina hasta que alguno de los jugadores obtiene 1.

a) Escribir una funcin no recursiva que calcule el siguiente valor.

b) Escribir un programa para correr el juego (escribir la lista de los nmeros sucesivos obtenidos) y el nom-
bre del jugador que gana.

c) Escribir una funcin recursiva:

int juego_siempre_1(int N, int jugador)

que corre el juego hasta su trmino (obtener 1).

d) Siempre gano yo.

De acuerdo con el juego anterior, hacer un programa que calcule y escriba todos los nmeros (hasta una
lmite L), con los cuales el jugador 1 siempre gane.

29. Funcin 91 de MacCarthy. Escribir un programa que contenga una funcin recursiva capaz de calcular la
siguiente funcin matemtica:
N 10 si N > 1
f91( N )
f91( f91( N + 11)), sino

30. Escribir una funcin con un nmero variable de parmetros (el ltimo es siempre 0, valor centinela que no
se toma en cuenta) para calcular el mximo comn divisor de varios nmeros enteros.

31. Escribir una funcin con un nmero variable de parmetros de prototipo:

int recuento_permutaciones(int i, ...)

que regresa el recuento de las permutaciones de n o el recuento de las permutaciones de n en k (k < n


nmeros enteros).

32. Escribir una funcin con nmero variable de parmetros de prototipo.

int es_permutacion(int n, ...)

que verica que los n argumentos entregados despus de n forman o no una permutacin de los nmeros
1; 2; , n, la funcin regresa 0 o 1.

33. Dados.

s Escribir una funcin de prototipo:

int dado()

145
*OUSPEVDDJOBMBQSPHSBNBDJO

que regresa un valor aleatorio entre 1 y 6 con la probabilidad 1/6. La funcin es una simulacin de la
cada de un dado en forma de cubo.

s Hacer un programa que realice un nmero importante de llamadas a su funcin dado y que verique que
los valores generados son equiprobables.

s Escribir una funcin de prototipo:

int n_dado(int n, ...)

que regresa n valores de salida entre 1 y 6 (la funcin simula la cada simultnea de n dados).

34. Juego Adivina equivalencia. El juego es el siguiente: hay dos jugadores, dos vasos que pueden contener
piedras (o granos). El jugador 1 elige en cada vaso dos nmeros de piedras. El jugador 2 intenta adivinar
proponiendo sacar k piedras de un vaso (izquierdo o derecho) y ponerlas en el otro vaso. El jugador 1 tiene
cuatro respuestas posibles:

s No hay: la operacin de transvase no es posible porque el nmero de piedras del vaso fuente no es
suciente.

s Equivalencia: la operacin de transvase se termina con xito y hay igualdad.

s Es ms: en el vaso destinacin hay ms piedras que en el vaso fuente.

s Es menos: en el vaso destinacin hay menos piedras que en el vaso fuente.

El juego termina cuando el jugador 2 adivina la equivalencia antes de 10 pasos; si despus de 10 pasos no
adivina, el jugador 2 pierde. Se toma un nmero constante (100) de piedras por total, pero en los vasos el
jugador 1 pone los nmeros de quiere.

a) Proponer y escribir funciones tiles por una interfaz del juego:

s para la lectura de una proposicin. (Cuidado! hay que proponer un valor y un sentido).

s para la lectura de una respuesta de jugador que tiene los vasos.

s para el clculo automtico de una respuesta.

b) Escribir un programa donde la computadora es el jugador 1 con las piedras (el programa genera dos
nmeros aleatorios y las respuestas).

c) Escribir un programa donde el jugador 2 es la computadora. La eleccin de la proposicin se hace de


forma aleatoria.

d) Escribir un programa donde el jugador 2 es la computadora y siempre gana en un mximo de 10 pasos.

146
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT 6OJEBEt%FMBMHPSJUNPBMQSPHSBNB
4 (YYLNSVZJHKLUHZ
KLJHYHJ[LYLZHYJOP]VZ

Contenido Sntesis del captulo


4.1 Arreglos y matrices Bibliografa
Arreglos unidimensionales Ejercicios y problemas
Asignacin dinmica de memoria para los arreglos
Matrices Objetivos
Problemas resueltos s -ANEJARARREGLOSDEVALORESESCALARES
Juego del gato s -ANEJARMATRICESDEMSDEUNADIMENSIN
4.2 Caracteres y cadenas de caracteres s #ONOCERELUSODECARACTERESYCADENASDECARACTERESEN
Tipo carcter lenguaje C.
Cadenas de caracteres s #ONOCERLASFUNCIONESESTNDARESPARAELUSOYMANEJODE
Funciones estndares para el manejo
cadenas de caracteres.
de caracteres y cadenas de caracteres
Problema resuelto s #OMPRENDERLANOCINDEARCHIVO
s 4RABAJARCONARCHIVOSDETIPOTEXTUALYDETIPOBINARIO
4.3 La funcin main
s %NTENDERELFUNCIONAMIENTODELAFUNCINmain y escribir
4.4 Archivos programas con parmetros en lnea de comandos.
s 2EALIZARPROGRAMASQUENECESITANELUSODECADENASDE
caracteres.
*OUSPEVDDJOBMBQSPHSBNBDJO

4.1 Arreglos y matrices

En el trabajo de programacin es muy probable que se requiera tratar con conjuntos de datos de manera unitaria, donde
el tamao del conjunto resulta muy importante. El arreglo es una estructura de datos que la mayora de los lenguajes
imperativos implementa. En el lenguaje C se permite la denicin y el uso de arreglos tanto unidimensionales como
multidimensionales; tambin se permite la asignacin de memoria, ya sea esttica o dinmica.
En esta seccin presentamos, de manera gradual, la nocin de arreglo esttico de tipos escalares, las nociones de la
asignacin dinmica y la asignacin esttica, el uso de arreglos de apuntadores y la denicin y el uso de las matrices (o
arreglos multidimensionales).

Arreglos unidimensionales
Declaracin y utilizacin

Un arreglo unidimensional posee un nombre (un identicador), un tipo y una dimensin. El nombre de un arreglo
es un identicador del programa, el cual debe ser el nico al interior del bloque de denicin. El tipo, por su parte, es un
tipo escalar estndar, un tipo apuntador o cualquier otro tipo denido por el programador; en este caso, se excluye el tipo
void. La dimensin es un nmero entero positivo que indica el tamao del arreglo.
Un arreglo es un conjunto de dimensin de celdas de memoria capaz de guardar valores del tipo indicado, los cuales
son guardados en un espacio contiguo de memoria. Cada elemento del arreglo corresponde a una de las celdas y tiene
un nmero de orden nico entre 0 y dimension-1.
La denicin de un arreglo se hace de la misma manera que las deniciones de otras variables del programa, es
decir, al inicio de las funciones y de los bloques de programas. La forma de la denicin de un arreglo de una dimensin
conocida es la siguiente:

tipo nombre_arreglo[dimension]

El tipo debe ser un tipo estndar o un tipo denido por el usuario y conocido por el compilador al momento de la decla-
racin. En tanto, la dimensin debe ser un valor constante de tipo entero (y positivo).
Para acceder a los valores contenidos en el arreglo se escribe:
nombre_arreglo[indice]

Donde indice debe ser una expresin entera que se evala por un valor entero de 0 hasta dimension-1. Un ele-
mento de un arreglo puede aparecer en cualquier expresin (aritmtica, de asignacin o de cualquier otro tipo), como si
fuera una variable del tipo indicado del arreglo.

Ejemplo

int i, j;
int itab[10];
float atab[10];

i = 0;
itab[i] = 5;
atab[i] = sqrt((float)itab[i]);
if (atab[i] + 2 > itab[i])
printf(...);
...

148 itab es un arreglo de enteros representados sobre 4 bytes y atab es un arreglo de


$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

flotantes de simple precisin.

Ejemplo
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int a, b, c;
int *pint[3];

a = 5; b = 55;
pint[0] = &a;
pint[1] = &b;
pint[2] = pint[0];
c = *pint[1];

printf( La direccion de a : %p\n, &a);


printf( El elemento 0 del arreglo : %p y el contenido apuntado : %d\n,
pint[0], *pint[0]);
}

El arreglo pint contiene direcciones de enteros (apuntadores) que se tratan como cualquier apuntador. El programa
produce:

La direccion de a : 0x7fff5fbff99c
El elemento 0 del arreglo : 0x7fff5fbff99c y el contenido apuntado : 5

Para acceder a la referencia de un elemento (por ejemplo, para utilizar un elemento de arreglo como parmetro transmi-
tido por referencia o para introducir un valor con la funcin estndar scanf) se utiliza el apuntador del elemento:

&nombre_arreglo[indice]

Ejemplo

En el siguiente programa existe la declaracin de un arreglo de enteros de dimensin 10, asignaciones de valores a
algunos de los elementos del arreglo y la llamada a una funcin scanf:

/* programa con una declaracion de arreglo y varios indices */


#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int i, j;
int tab[10];

i = 0;
tab[i] = 5;
tab[1] = 1;
printf( El valor de indice %d es : %d\n, 1, tab[1]);
printf( El valor de indice %d es : %d\n, i, tab[i]);

printf(Introducir el valor de indice 8 :);


scanf(%d, &tab[8]);
149
*OUSPEVDDJOBMBQSPHSBNBDJO

printf( El valor de indice %d es : %d\n,


(i + 1) * (i + 3) + 5, tab[(i + 1) * (i + 3) + 5]);
}

La salida del programa es:

El valor de indice 1 es : 1
El valor de indice 0 es : 5
Introducir el valor de indice 8 : 78
El valor de indice 8 es : 78

Como se puede observar en el ejemplo anterior, nicamente usamos los elementos por los cuales los valores fueron afec-
tados, ya sea por una operacin de asignacin o por la funcin de lectura. Si se usa un elemento sin ningn valor afectado
anteriormente, el valor que se obtiene de la celda de memoria asignada es un valor indeterminado (desconocido) que
puede inducir errores en el clculo del programa.

Ejemplo
En el programa precedente se eliminan las dos asignaciones y la lectura del elemento tab[8], y solo se almacenan
las escrituras:

...
int tab[10];
int i=0;

printf( El valor de indice %d es : %d\n, 1, tab[1]);


printf( El valor de indice %d es : %d\n, i, tab[i]);

printf( El valor de indice %d es : %d\n,


(i + 1) * (i + 3) + 5, tab[(i+ 1) * (i + 3) + 5]);
...

Por tanto, la salida del programa es:1

El valor de indice 1 es : 0
El valor de indice 0 es : 0
El valor de indice 8 es : 1606416808

La causalidad provoca que algunos valores sean cero; no obstante, tambin existen otros valores adems del cero. En este
caso, siempre se utilizan elementos del arreglo que fueron inicializados con valores (ya sea por asignacin o por lectura).
Es posible hacer una asignacin inicial de valores por todos los elementos del arreglo:

tipo nombre_arreglo[dimension] = {valor, valor, ... valor};

En este tipo de asignacin, los valores indicados se afectan uno a uno, empezando con el primer elemento, de ndice 0.

Ejemplo
Por el programa siguiente:

/* programa con una declaracion y asignacion inicial de arreglo */


#include <stdio.h>
#include <stdlib.h>

1
Esta salida es dependiente del sistema operativo y del momento de ejecucin del programa.
150
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

int main(int argc, char *argv[])


{
int i, j; int tab[10]={10, 20, 30, 40, 50, 60, 70, 80, 90, 10};

i = 0;
printf( El valor de indice %d es : %d\n, 5, tab[5]);
printf( El valor de indice %d es : %d\n, i, tab[i]);
printf( El valor de indice %d es : %d\n,
(i + 1) * (i + 3) + 5, tab[(i+ 1) * (i + 3) + 5]);
}

La salida es la siguiente:
El valor de indice 5 es : 60
El valor de indice 0 es : 10
El valor de indice 8 es : 90

La forma tab[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 10} aparece nicamente en la parte de
declaracin de variables. Si durante el programa es necesario dar valores a los elementos del arreglo, solo se utiliza el
operador de asignacin:

tab[0] = 10;
tab[1] = 20;
tab[2] = 30;
...

Si la lista de los valores para la asignacin inicial es ms corta que la dimensin, la asignacin se hace hasta el ltimo
elemento del arreglo que puede ser inicializado. Pero, si la lista de asignacin es ms larga, el compilador seala con
mensajes explcitos (warning, en ingls) y sin producir error, y la asignacin no utiliza los ltimos elementos de la lista.2
En la declaracin del arreglo con una asignacin inicial es posible no indicar la dimensin del arreglo:

tipo nombre_arreglo[] = {valor, valor, ... valor};

En este caso, se calcula el nmero de valores de la lista de asignacin y este valor (que es un nmero constante) se toma
como la dimensin del arreglo. Por ejemplo:

int itab[] = {10, 0, 10, 0, 10};

En este caso, el arreglo itab tiene cinco celdas.


El ndice que se utiliza para acceder a un elemento del arreglo, segn la semntica del lenguaje, debe ser un valor
entero entre 0 y dimension 1 (es decir, un valor constante). El compilador del lenguaje solo hace una vericacin del
tipo del valor de ndice (tipos enteros: char, short, int, long), pero no realiza las vericaciones del rango.

Ejemplo
Para un arreglo de dimensin 10, se usan varios ndices (correctos o no semnticamente), los cuales son valores cons-
tantes al momento de la compilacin o valores calculados. La compilacin se realiza sin ningn mensaje de error o de
advertencia:

/* programa con una declaracion de arreglo y varios indices correctos o no */

#include <stdio.h>

2
En el CD-ROM que acompaa este libro se incluyen dos ejemplos con listas de asignacin de tamao diferente de la dimensin
del arreglo.
151
*OUSPEVDDJOBMBQSPHSBNBDJO

#include <stdlib.h>

int main(int argc, char *argv[])


{
int i, j;
int tab[10]={3, 3, 3, 3, 3, 3, 3, 3, 3, 3};

i = 0;
printf( El valor de indice %d es : %d\n, 1, tab[1]);//indice correcto
printf( El valor de indice %d es : %d\n, i, tab[i]);//indice correcto
j = (i + 11) * (i + 3) + 5;
printf( El valor de indice %d es : %d\n, j, tab[j]);//indice incorrecto
printf( El valor de indice %d es : %d\n, -1, tab[-1]);//indice incorrecto
}

Por tanto, el programa produce:


El valor de indice 1 es : 3
El valor de indice 0 es : 3
El valor de indice 38 es : 1606417261
El valor de indice -1 es : 1

En un primer ejemplo completo de trabajo con un arreglo, se trabaja con un arreglo de cinco elementos, a n de realizar
la suma y escribir los valores de la suma y del promedio de los cinco valores.

/* programa que calcula la suma y


el promedio de 5 valores flotantes */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
float f[]={2., 6., 18., 25., 9.};
float suma;
int i;
/* el calculo de la suma */
for( suma = 0., i = 0; i<5; i++)
suma += f[i]; /* significa suma = suma + f[i] */

/* escribir el arreglo */
printf( Mi arreglo es :);
for( i = 0; i < 5; i++)
printf( %f , f[i]);
printf(\n);

/* escribir la suma */
printf( La suma de los valores es :%f\n, suma);

/* calculo y escritura del promedio */


promedio = suma / 5;
printf( El promedio de los valores es :%f\n, promedio);

exit(0);
}
152
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

En este caso, la variable suma se inicializa con 0 antes de la estructura iterativa. En la primera estructura iterativa, la varia-
ble suma contiene las sumas parciales de los elementos del arreglo. Al nal, esta variable contiene la suma de todos los
elementos. Por su parte, la segunda estructura iterativa sirve para escribir elemento por elemento. La salida del programa
es la siguiente:

Mi arreglo es : 2.000000 6.000000 18.000000 25.000000 9.000000


La suma de los valores es :60.000000
El promedio de los valores es :12.000000

Dimensin de un arreglo y su asignacin de memoria

El arreglo del ejemplo anterior se dene sin dimensin explcita, pero con una lista de asignacin inicial. La dimensin
del arreglo es de 5 y aparece explcitamente en las expresiones condicionales de las proposiciones for y el clculo del
promedio. Si deseamos trabajar con una dimensin de arreglo diferente, el programa necesita ms de una modicacin.
Por otro lado, trabajar en el programa con el valor explcito de la dimensin puede generar errores de tecleo (o sea, errores
de dedo).
El consejo es utilizar una constante para introducir la dimensin del arreglo.
Los dos tipos de constantes tiles son:

s Una constante denida con una directiva de # define.


s Una constante denida en una zona de memoria.

Nuestro programa de clculo de suma y promedio es resultado de la versin con la constante de sustitucin:

#define DIMENSION 5
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
float f[DIMENSION]={2., 6., 18., 25., 9.};
float suma, promedio;
int i;
/* el calculo de la suma */
for( suma = 0., i = 0; i < DIMENSION; i++)
suma += f[i]; /* significa suma = suma + f[i] */

/* escribir el arreglo */
printf( Mi arreglo es :);
for( i = 0; i < DIMENSION; i++)
printf( %f , f[i]);
printf(\n);

/* escribir la suma */
printf( La suma de los valores es :%f\n, suma);

/* calculo y escritura del promedio */


promedio = suma / DIMENSION;
printf( El promedio de los valores es :%f\n, promedio);
exit(0);
}

Por su parte, la versin con una constante asignada en memoria es:

153
*OUSPEVDDJOBMBQSPHSBNBDJO

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
const int dimension=5;
float f[]={2., 6., 18., 25., 9.};
float suma, promedio;
int i;
/* el calculo de la suma */
for( suma = 0., i = 0; i<dimension; i++)
suma += f[i]; /* significa suma = suma + f[i] */

/* escribir el arreglo */
printf( Mi arreglo es :);
for( i = 0; i < dimension; i++)
printf( %f , f[i]);
printf(\n);

/* escribir la suma */
printf( La suma de los valores es :%f\n, suma);

/* calculo y escritura del promedio */


promedio = suma / dimension;
printf( El promedio de los valores es :%f\n, promedio);

exit(0);
}

La constante asignada en memoria no es de utilidad en la declaracin del arreglo para indicar la dimensin.
Por tanto, la dimensin de un arreglo puede ser:
s Una constante entera explcita:

float a[10]

s Una constante entera denida con una directiva # define:

# define MAX 10
...
float a[MAX];

s Un valor implcito igual al tamao de la lista de inicializacin (asignacin inicial):

float a[]={1., 2., 2.2,0.5}; arreglo con 4 valores

La asignacin de memoria para las variables simples del programa se hace al mismo tiempo que la asignacin de me-
moria para el arreglo. En este caso, se asigna una zona de memoria contigua con un nmero de dimensin celdas; una
de celda de memoria es capaz de guardar un valor del tipo del arreglo.
As, de acuerdo con el ejemplo anterior:

int tab[10]={10, 20, 30, 40, 50, 60, 70, 80, 90, 10};

Se asignan 10 celdas de memoria para guardar enteros representados sobre 4 bytes. La imagen de la memoria es la
siguiente:
154
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

direccin valor ndice


ffbffad0 10 0
ffbffad4 20 1
ffbffad8 30 2
ffbffadc 40 3
ffbffae0 50 4
ffbffae4 60 5
ffbffae8 70 6
ffbffaec 80 7
ffbffaf0 90 8
ffbffaf4 10 9

La zona de memoria asignada por un arreglo que es contigua tiene, entonces, un tamao de dimensin tamao (tipo).
En el caso de que la dimensin real del arreglo no sea conocida al momento de la elaboracin del programa, deber
tomarse en cuenta una dimensin mxima. En este caso queda claro que si la dimensin real es mucho ms baja, esta
limita al mximo el espacio de memoria asignado, hasta que el lmite se pierde.
En el siguiente programa se guardan, en un arreglo de tipo otante, los valores de cos(i), para i de 1 hasta n;
donde n es un valor introducido por el usuario; el programa tambin calcula el promedio de estos valores; as, se supone
que n est entre 1 y 100.
/* programa que hace el promedio de los valores cos(i)/i, i = 1, N */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char *argv[])


{
float f[101];
float suma, promedio;
int i;
int N;

printf( La dimension del arreglo :);


scanf(%d, &N);
if (N<=0 || N>100)
{
printf( Dimension incorrecta\n);
exit(1);
}

/* la generacion de los valores */


for (i = 1; i <= N; i++)
f[i] = cos((float)i) / i;

/* el calculo de la suma y del promedio */


for( suma = 0., i = 1; i <= N; i++)
suma += f[i];
promedio = suma/N;
155
*OUSPEVDDJOBMBQSPHSBNBDJO

/* escribir el promedio */
printf( El promedio de los valores del arreglo es :%f\n, promedio);
exit(0);
}

La primera parte de este programa verica si la dimensin respeta los lmites impuestos.

Arreglos como parmetros de funcin


En el ejemplo anterior se trata un problema que ya fue resuelto antes: el clculo del promedio de un arreglo. Para sim-
plicar el trabajo del programador, resulta conveniente escribir una funcin capaz de calcular el promedio de cualquier
arreglo, donde lgicamente se debe transmitir la dimensin del arreglo y el arreglo. Para la transmisin de la dimensin,
es posible, sin duda alguna, transmitir un entero; en cambio, para la transmisin del arreglo, se debe determinar cmo
puede hacerse esto.
Por otro lado, al momento de la asignacin de la zona de memoria, por el arreglo, el identicador del arreglo resulta
compatible con un apuntador del mismo tipo que el del arreglo y la escritura arreglo [indice] es equivalente con
una operacin: *(arreglo + indice): el contenido apuntado por un apuntador calculado.

Ejemplo
En el siguiente programa se trata el arreglo como el apuntador al primer elemento y, luego, se trata la suma del arreglo
con un entero como un apuntador.
/* programa que trata un arreglo como un apuntador */
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
float f[4] = {1.1, 2.2, 3.3, 4.4};
int i;

printf(El primer elemento %f\n, *f);


i = 3;
printf(El elemento de indice %d es %f\n, i, *(f + i));
}

Por tanto, el programa produce:

El primer elemento 1.100000


El elemento de indice 3 es 4.400000

Es posible transmitir un arreglo como un parmetro de funcin; como el arreglo puede ser considerado un apuntador, la
transmisin se realiza por referencia, ya que los elementos del arreglo pueden ser modicados al interior de la funcin y
las modicaciones son denitivas. En el prototipo de la funcin que tiene como parmetro un arreglo, se indican el tipo
y el nombre del arreglo, pero la mencin de la dimensin no es obligatoria. Hay dos formas de indicar el tipo y el nombre
del arreglo:

tipo nombre_arreglo[dimension]
tipo nombre_arreglo[]

Ambas formas son equivalentes.


En la llamada a la funcin se indica nicamente, como parmetro actual, el nombre del arreglo u otra expresin
equivalente a un arreglo.
156
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Ejemplo
A continuacin se presenta un ejemplo donde se observa la suma de los elementos de un arreglo de valores otantes
con una funcin que recibe el arreglo por valor.

#include <stdio.h>
#include <stdlib.h>

float suma(float A[10], int N)


{
int i;
float s = 0.0;
for (i = 0; i< N; i ++)
s += A[i];
return s;
}

int main(int argc, char *argv[])


{
float f[10] = {1.1, 2.2, 3.3, 4.4};
int i;

printf( La suma de los elementos del arreglo %f\n, suma_por_valor(f, 4));


exit(0);
}

En el prototipo de la funcin aparece el tipo del arreglo y la dimensin ja; en la llamada se indica, como parmetro actual,
nicamente el nombre del arreglo.
El prototipo de la funcin puede ser escrito tambin de la siguiente forma:

float suma(float A[], int N)

Para la funcin suma, se sabe que el tamao til del arreglo que se utiliz fue nicamente de 4. Con esta misma funcin,
es posible hacer la suma de los ltimos dos elementos:

suma(&f[2], 4)

La transmisin de los parmetros se hace claramente por referencia de una funcin que lee, uno a uno, los elementos
de un arreglo:

void lectura_arreglo(float X[], int N)


{
int i;
for ( i = 0; i < N; i++)
scanf(%f, &X[i]);
}

Si se quiere que el contenido del arreglo no sea modicado a la salida de la funcin, se introduce la palabra clave const
en la parte de declaracin:

const tipo nombre_arreglo[dimension]


const tipo nombre_arreglo[]

En este caso, si al interior de la funcin los elementos del arreglo cambian por falta de un operador de asignacin o una
lectura de valor con scanf, aparecen mensajes implcitos de advertencia al momento de la compilacin.
157
*OUSPEVDDJOBMBQSPHSBNBDJO

Ejemplo
Si cambiamos la funcin de lectura del arreglo, por ejemplo:

void lectura_arreglo_const(const float X[], int N)


{
int i;
for ( i = 0; i < N; i++)
scanf(%f, &X[i]);
}

Aparece un mensaje explcito de advertencia momento de la compilacin en la lnea que contiene el scanf.
En manuales de referencia del lenguaje C, por lo general se indica que un tipo apuntador es completamente equiva-
lente al tipo arreglo sin dimensin, entonces el prototipo de la funcin de lectura tambin se puede escribir como:
lectura_arreglo(float *f, int N)

Pero, para la comprensin del cdigo por parte de cualquier otro programador, es conveniente indicar la primera forma:
lectura_arreglo(float f[], int N)

Asignacin dinmica de memoria para los arreglos

En la seccin anterior se estudi cmo los arreglos se asignan en la memoria de forma esttica. Pero, tambin es posible
aplicar otra forma de asignacin de memoria, llamada asignacin dinmica.
Si no se conoce el tamao de un arreglo y el lmite posible es muy variable o si el espacio dedicado al programa es
limitado,3 tomar un lmite superior posible no es realista. Por tanto, se debe trabajar con arreglos asignados de manera
dinmica.

Funciones para la asignacin dinmica de memoria

Para cada tipo de datos, ya sea de tipo estndar o de tipo denido por el programador o denido en bibliotecas, corres-
ponde un espacio (un lugar) de la zona de memoria con una paridad de su direccin, capaz de recibir un valor del tipo
indicado. El lenguaje C ofrece un operador unario sizeof, que puede ser aplicado a las variables (simples o arreglos) y
a los tipos y regresa el tamao necesario para almacenarlo. El valor regresado por el operador sizeof es un entero sin
signo representado sobre 4 u 8 bytes, segn el compilador; su tipo est denido como size_t. El operador se escribe
como sigue:
sizeof(tipo)
sizeof(variable)

A continuacin se presenta un ejemplo de aplicacin del operador sizeof:

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
int *p;
int a;
double f;
int x[10];

3
Por ejemplo, para los programas elaborados para telfonos mviles u otras plataformas de tipo Wi.
158
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

printf(sizeof(double) = %ld; sizeof(int) = %ld; sizeof(int*) = %ld\n,


sizeof(double), sizeof(int), sizeof(int*));
printf(sizeof(f) = %ld; sizeof(p) = %ld, sizeof(a) = %ld, sizeof(x)
= %ld\n,
sizeof(f), sizeof(p), sizeof(a), sizeof(x));
}

Este programa produce:


sizeof(double) = 8; sizeof(int) = 4; sizeof(int*) = 8
sizeof(f) = 8; sizeof(p) = 8, sizeof(a) = 4, sizeof(x) = 40

Para el compilador gcc, el tipo regresado de sizeof es un tipo equivalente al tipo entero long.
Segn los sistemas y el tipo de procesador husped, un apuntador se representa sobre 4 u 8 bytes; en nuestro caso,
el sizeof del apuntador es de 8 bytes. El sizeof de un arreglo x es dimensin sizeof (tipo).
Un apuntador puede contener una direccin de memoria para una celda, la cual es capaz de almacenar un valor del
tipo de apuntador. Esta celda est asociada a una variable; se puede tener acceso a esta celda por medio de un identi-
cador conocido que corresponde a una variable. Asimismo, la celda puede ser asociada a una zona de memoria asignada
de manera dinmica, al momento de la ejecucin del programa. Para la asignacin dinmica de una zona de memoria al
momento de la ejecucin del programa, existen varias funciones en la biblioteca estndar stdlib.h:

s malloc para una asignacin dinmica.


s calloc parar una asignacin dinmica acompaada por una inicializacin con 0.
s realloc para una re-asignacin.

En su caso, la funcin free libera zonas asignadas dinmicamente.


A continuacin se presenta un ejemplo del prototipo de la funcin malloc:

void* malloc(size_t dimension)

La funcin malloc regresa un apuntador sin tipo (tipo void*) que referencia una zona de memoria contigua de
tamao dimension bytes (dimension es un entero sin signo). El apuntador puede ser NULL, valor constante igual a
cero, denido en la biblioteca stdlib.h, en el caso de que la asignacin dinmica no haya funcionado. Antes de utilizar el
apuntador asignado dinmicamente, se debe vericar que su valor no es NULL.
Al nal del programa, para todas las zonas de memoria asignadas dinmicamente, se aconseja liberar estas zonas con
la funcin free. La funcin free tiene el siguiente prototipo:

void free(void* apuntador)

La funcin free no regresa nada, tiene como efecto liberar la zona de memoria apuntada por el apuntador que est en
el parmetro actual. Si el apuntador es NULL, no pasa nada.
Enseguida se presenta el ejemplo de un programa que trabaja con un apuntador de tipo otante:
/* programa que trabaja con malloc */
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])


{
float *ap_f;
float x=1.2;

ap_f = &x;
159
*OUSPEVDDJOBMBQSPHSBNBDJO

printf( El valor apuntado por ap_f %f\n, *ap_f);

ap_f = (float*)malloc(sizeof(float));
if (ap_f == NULL)
{
printf( Asignacion imposible.\n);
exit(0);
}
*ap_f = 25.5;
printf( El nuevo valor apuntado por ap_f %f\n, *ap_f);
free(ap_f);
exit(1);
}

En la primera parte del main se usa el apuntador de manera clsica; esto es, manejando una variable del mismo
tipo. En la segunda parte del programa se hace una asignacin dinmica acompaada por una conversin de tipo. Si la
asignacin no sale bien, el programa se interrumpe. Al nal del programa, la zona de memoria se libera con free. La salida
del programa es:

El valor apuntado por ap_f 1.200000


El nuevo valor apuntado por ap_f 25.500000

Arreglos dinmicos

Para un arreglo, es posible asignar la memoria de manera dinmica al momento de la ejecucin, principalmente con la
funcin malloc. En este caso, el arreglo se declara como un apuntador (arreglo sin dimensin):

tipo *nombre_arreglo_dinamico;

En aras de una lectura ms fcil del cdigo, resulta mejor escribir un comentario indicando qu nombre_arreglo sera
utilizado como un arreglo, no como un apuntador.
La asignacin dinmica de memoria se hace con la funcin malloc, indicando como parmetro actual la dimensin
en nombre de bytes para el arreglo; tambin se hace una conversin de tipo para el tipo del arreglo.

nombre_arreglo_dinamico= (tipo*)malloc(dimension_bytes);

Por razones de lectura y compatibilidad con varias plataformas, tambin es mejor indicar la dimensin en bytes, no solo
como una constante, sino como una expresin, es decir, un producto entre el nmero de elementos del arreglo y el
operador sizeof aplicado al tipo del arreglo:

nombre_arreglo_dinamico= (tipo*)malloc(numero_elementos * sizeof(tipo));

Un arreglo dinmico puede ser asignado ms de una vez durante la ejecucin del programa, pero antes de una nueva
asignacin se aconseja liberar con la funcin free la zona de memoria ocupada.

Ejemplo
Realizar la suma de los elementos con un arreglo de dimensin desconocido al momento de la ejecucin. Las funciones
detalladas antes, lectura_arreglo, se almacenan porque son compatibles con el arreglo dinmico.4 La parte de la
funcin main es:
int main(int argc, char *argv[])
{

4
Los parmetros de tipo arreglo son transmitidos por referencia.
160
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

float *f;
int i;
int n;

printf( La dimension del arreglo:);


scanf(%d, &n);
if ( n <= 0)
{
printf( Dimension incorrecta !\n);
exit(0);
}
f = (float*)malloc(n * sizeof(float));
if (f == NULL)
{
printf( Asignacion imposible.\n);
exit(0);
}
lectura_arreglo(f, n);
printf( La suma de los elementos del arreglo %f\n, suma_por_referencia(f, n));
free(f);
exit(1);
}

A continuacin se presenta un ejemplo de una salida del programa.

La dimension del arreglo:7


1. 2. 3. 4. 5.
6. 7.
La suma de los elementos del arreglo 28.000000

Tanto la asignacin dinmica como la asignacin esttica no tienen control alguno sobre el contenido de la zona de me-
moria asignada; dicha zona contiene valores indeterminados.5
La funcin de asignacin dinmica calloc pone por defecto, en toda la zona de memoria asignada, el valor 0; para
ser ms precisos, pone o en todos los bits. En los enteros y los otantes, los valores son equivalentes al cero numrico.
El prototipo de la funcin calloc es el siguiente:

void* calloc(size_t numero_elementos, size_t dimension_tipo)

Entonces, la asignacin dinmica de un arreglo se hace como sigue:

nombre_arreglo = (tipo*)calloc(numero_elementos, sizeof(tipo));

En el siguiente ejemplo se muestra el programa presentado antes usando calloc, en lugar de malloc:

f = (float*)calloc(n, sizeof(float));

Como se puede observar, aqu no se hace la llamada a la funcin de lectura de arreglo,6 ya que el programa produce una
salida de forma:

La dimension del arreglo:78

5
Estos valores indeterminados no son siempre 0 (vanse algunos ejemplos en el captulo 3).
6
El programa completo se puede consultar en el CD-ROM que acompaa este libro.
161
*OUSPEVDDJOBMBQSPHSBNBDJO

La suma de los elementos del arreglo despues la asignacion con calloc 0.000000
En cuanto a la funcin realloc, esta permite reasignar otra zona de memoria por un apuntador.
El contenido actual de la zona apuntada inicialmente no se pierde; primero se realiza la asignacin de la nue-
va zona, despus se hace la copia del contenido de la vieja zona a la nueva zona y, nalmente, se libera
la zona inicial. Si al inicio el apuntador es NULL, simplemente se hace la asignacin.
El prototipo de la funcin realloc es similar al prototipo de malloc:

void* realloc(size_t dimension)

Ejemplo
En este ejemplo se quiere hacer la suma de los elementos del arreglo introducidos uno a uno.
Si la nueva dimensin del arreglo es mayor que la mxima dimensin asignada, entonces se hace una llamada
a la funcin realloc. En este caso, todo el clculo iterativo se hace al interior de una estructura while. Para utilizar
directamente la primera asignacin dinmica, la funcin realloc inicializa el apuntador del arreglo con NULL y la
dimensin mxima dmax a 0. El programa principal (main) es el siguiente:

int main(int argc, char *argv[])


{
float *f;
int i;
int n, dmax;

f = NULL;
dmax = 0;
while (1)
{
printf( La dimension del arreglo:);
scanf(%d, &n);
if (n == 0) break;
if ( n < 0)
{
printf( Dimension incorrecta !\n);
continue;
}
if (n > dmax)
{
// nueva re-asignacion
f = (float*)malloc(n * sizeof(float));
dmax = n;
if (f == NULL)
{
printf( Asignacion imposible.\n);
exit(0);
}
}
lectura_arreglo(f, n);
printf( La suma de los elementos del arreglo %f\n, suma_por_referencia(f, n));
}
free(f);
exit(1);
}

Aqu usamos la convencin que arma que si no hay ms arreglos, se introduce la dimensin 0 para signicar el nal
del clculo.
162
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Hay una sola llamada explcita de liberacin de la memoria dinmica antes del nal del programa, pero cada lla-
mada de realloc tambin hace una liberacin de memoria.
La salida del programa es, por ejemplo:

La dimension del arreglo:3


1.1 1.2 1.3
La suma de los elementos del arreglo 3.600000
La dimension del arreglo:7
9. 9. 0. 0. 0. 0. 0.
La suma de los elementos del arreglo 18.000000
La dimension del arreglo:2
9.9 9.9
La suma de los elementos del arreglo 19.799999
La dimension del arreglo:0

Matrices

En el tema anterior trabajamos con arreglos de tipos bsicos (enteros o otantes) y con arreglos de apuntadores. Como
ya se dijo, un arreglo puede ser de cualquier tipo, incluso de tipo arreglo.
El lenguaje C ofrece, entonces, la posibilidad de trabajar con arreglos de dos o ms dimensiones, los cuales son tra-
tados como arreglo de arreglos (matrices o arreglos bidimensionales) o como arreglos de arreglos de arreglos y ms. La
declaracin se hace de la siguiente forma:

tipo nombre_arreglo[dimension1][dimension2]..[dimensionn];

Por lo que respecta a las dimensiones mencionadas, estas tienen las mismas restricciones que las que vimos antes: de-
ben ser constantes enteros.
Cuando se trata de dos dimensiones, la declaracin es entonces:

tipo nombre_arreglo[dimension1][dimension2];

Esta declaracin signica un arreglo con dimension1 elementos, donde cada elemento es un arreglo del tipo indicado
con dimension2 elementos.
Entonces, el primer elemento del arreglo es:

nombre_arreglo[0][0]..[0]

Para cada dimensin j, los ndices varan de 0 hasta dimensionj-1.

Ejemplo

int A[2][3];
int U[3][3]={{1,0,0},{0,1,0},{0,0,1}};
float X[3][2] = {{1., 3.}, {5., 7.}, {9., 11.}}

Donde A es una matriz con dos lneas y tres columnas, U es una matriz con tres lneas y tres columnas, que es iniciali-
zada con valores 1 o 0 (es decir, es la matriz unidad), y X es una matriz con tres lneas y dos columnas.
La matriz X tiene la siguiente estructura lgica:

163
*OUSPEVDDJOBMBQSPHSBNBDJO

columna 0 columna 1
lnea 0 1. 3.
lnea 1 5. 7.
lnea 2 9. 11.

En este caso, el elemento X[0][1] vale 3; el elemento X[2][0] vale 9. La escritura X[1] corresponde al arreglo 5., 7.
En la memoria se asigna una zona de memoria contigua de largo 3 2 sizeof (int) bytes; es decir, 6
celdas que contienen tipos otantes de simple precisin.

Tabla 4.1

Contenido 1. 3. 5. 7. 9. 11.

ndices [0][0] [0][1] [1][0] [1][1] [2][0] [2][1]

Los valores contenidos en los arreglos multidimensionales se acceden indicando los ndices en cada dimensin y se
utilizan de la misma manera que los elementos de los arreglos; esto es, como cualquier otra variable: sola en la parte
izquierda del operador de asignacin, en cualquier expresin compatible con su tipo, etctera.
Entonces, el identicador de la matriz se convierte en un apuntador de apuntador de entero. El acceso a los elementos
se realiza indicando el rango en cada dimensin. De la misma manera que para los arreglos unidimensionales, tambin es
posible usar una aritmtica de apuntadores para acceder a los elementos (esta es la aritmtica que el compilador utiliza):

int main(int argc, char *argv[])


{
float X[3][2] = {{1., 3.}, {5., 7.}, {9., 11.}};

printf( El primer elemento : %f\n, X[0][0]);


printf( Otro acceso del primer elemento %f\n, **X);

printf(X[2][0] =%f\n, X[2][0]);


printf( Escritura equivalente *(*X + 2*2) : %f\n, *(*X + 2*2));

printf(X[1][1] =%f\n, X[1][1]);


printf( Escritura equivalente *((*X + 1*2) + 1) : %f\n, *((*X + 1*2) + 1));
exit(0);
}

Este programa produce la siguiente salida:

El primer elemento : 1.000000


Otro acceso del primer elemento 1.000000
X[2][0] =9.000000
Escritura equivalente : 9.000000
X[1][1] =7.000000
Escritura equivalente : 7.000000
Ejemplo
Trabajar con una matriz de enteros 5 5, inicializar todos sus elementos a 0, leer el elemento central (que se halla en el
punto donde se cruzan la diagonal principal y la diagonal secundaria) y, nalmente, escribir la matriz en forma inteligible.
/* programa que construye una matriz inicializada a cero con un centro */
#define N 5
164
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int A[N][N];
int i, j;

for (i = 0; i < N; i++)


// recorremos la linea i
for (j = 0; j < N; j++)
// recorremos elemento por elemento
A[i][j] = 0;

// lectura del elemento central


printf( Indicar el valor del elemento central : );
scanf(%d, &A[N/2][N/2]);

// escritura de la matriz
for (i = 0; i < N; i++)
{
// escritura de la linea i
for (j = 0; j < N; j++)
printf( %2d, A[i][j]);
printf(\n); // un salto de linea
}

exit(0);
}

Una matriz u otro arreglo multidimensional puede aparecer como parmetro en las funciones; pero, si es el caso, se de-
ben indicar explcitamente casi todas las dimensiones del arreglo, con el n de poder acceder a los valores de elementos.

Ejemplo
Para el programa precedente de trabajo con una matriz con la mayora de valores a 0, podemos introducir dos funcio-
nes: una para la escritura de la matriz y otra para inicializar con 0 todos los valores.
void inicializar_0(int X[N][N])
{
int i, j;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
X[i][j] = 0;
return;
}

void escribir_matriz(int AA[][N])


{
int i, j; for (i = 0; i < N; i++)
{
// escritura de la linea i
for (j = 0; j < N; j++)
printf( %2d, AA[i][j]);
printf(\n); // un salto de linea
}

165
*OUSPEVDDJOBMBQSPHSBNBDJO

return;
}

int main(int argc, char *argv[])


{
int A[N][N];
// inicializar todo con 0
inicializar_0(A);

// lectura del elemento central


printf( Indicar el valor del elemento central : );
scanf(%d, &A[N/2][N/2]);

// escritura de la matriz
escribir_matriz(A);

exit(0);
}

Para la funcin inicializar_0, es posible indicar todas las dimensiones del arreglo; en cambio, para la funcin es-
cribir_matriz nicamente se indica la segunda dimensin. Las llamadas a las dos funciones se hacen de la misma
manera: indicando nicamente el identicador del arreglo.

Problemas resueltos

Esta seccin est dedicada a la presentacin de la solucin de dos aplicaciones usando arreglos unidimensionales o ma-
trices. El primer ejemplo se considera ms acadmico: trabajo con polinomios. El segundo ejemplo, por su parte, es la
implementacin de una interfaz del juego del gato o tres en lnea (tic-tac-toe, en ingls).

Trabajo con polinomios

En esta seccin vamos a escribir un programa con varias funciones, capaz de trabajar con polinomios. La implementacin
de algunas operaciones aritmticas como ejercicio, la dejamos para el nal de este captulo.
En lgebra, un monomio es un trmino de la siguiente forma:

a Xk

Este trmino une al producto de una constante y a una variable (matemtica) elevada a una potencia entera.
La constante a pertenece a un conjunto, por lo que se pueden realizar sumas, productos y otras operacio-
nes algbricas.7 De forma particular, nosotros trabajamos con coecientes reales, que se traducen en lenguaje C
por medio de coecientes otantes de tipo float o double. La variable matemtica X pertenece al mismo conjunto.
Un polinomio se considera una suma de monomios:

P(X) = ak xk + ak1 xk1 + + a1 x + a0

Escritura equivalente con base en las propiedades de los operadores + y :

P(X) = a0 + a1 X + + ak1 Xk1 + ak Xk


1
Por ejemplo: P(X) = 3, Q(X) = X + 3, R(X) = X3 + 5 son polinomios; sin embargo, 3x + 3 y x2 + 1 no lo son.

7
En teora, se puede concebir polinomios de coecientes complejos, enteros, fraccionarios, entre otros.
166
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Entonces, surge una primera pregunta para el programador: Cmo se representa un polinomio en el programa?
Para un polinomio son importantes sus monomios: la constante y la potencia. Por tanto, una primera idea sera guar-
dar en una matriz con dos lneas y varias columnas, las parejas (constante multiplicativa y potencia); pero al guardarlas se
debe hacer con un orden, para facilitar las operaciones.
Como las potencias son nmeros enteros positivos, solo podemos guardar las constantes (coecientes) en un arreglo
con el sentido: al ndice i lo almacenamos en el coeciente ai, de la potencia X i. Por ejemplo, para los polinomios del
ejemplo anterior:

ndices 0 1 2 3
P 3
Q 3 1
R 5 0 0 1

El grado de un polinomio es la potencia ms grande presente. Es evidente que para cada polinomio sera suciente un
arreglo de dimensin equivalente a su grado. Pero esta sera una informacin suplementaria. Entonces, podemos suponer
que el grado mximo de los polinomios sera una constante GMAX.8

#define GMAX 10
..
float P[GMAX], Q[GMAX] = {3, 1};
float R[GMAX];
..

Podemos hacer inicializaciones de los coecientes del arreglo, pero si no indicamos todos los valores hasta el ndice
GMAX-1, no se conocern los valores que ocupan la otra parte del arreglo. En este caso, es evidente que dos funciones
seran muy tiles: la lectura y la escritura de un arreglo.
Durante la lectura de un arreglo, primero se lee el grado, luego se leen sus coecientes, uno a uno, y el resto de los
coecientes se inicializan a 0.0; por ejemplo:

int lectura_polinomio(float A[])


{
int N, i;
// Leer el grado del polinomio
printf( Indicar el grado de su polinomio:);
scanf(%d, &N);
if ( N >= GMAX)
{
printf( El programa no permite trabajar con un grado tan alto.\n);
return 0;
}
// Leer los coeficientes
printf( Introducir los coeficientes !\n);
for (i = 0; i <= N; i++)
{
printf( El coeficiente del indice %d :, i);
scanf(%f, &A[i]);
}
//Poner 0 por los otros coeficientes hasta el indice GMAX - 1
for (i= N + 1; i < GMAX; i ++)

8
En el siguiente captulo utilizamos el tipo de datos compuesto struct para guardar el arreglo de los coecientes y el grado del
polinomio en una misma estructura.
167
*OUSPEVDDJOBMBQSPHSBNBDJO

A[i] = 0.0;
return 1;
}

Para la escritura de un arreglo, podemos escribir todos los GMAX coecientes; sin embargo, esta es una solucin sin ele-
gancia, ya que algunos coecientes son cero. Por otra parte, tambin podemos escribir los coecientes entre el ndice 0
y el grado del polinomio.
Una funcin muy til que calcula el grado del polinomio, se muestra a continuacin:

int grado(float X[])


{
int i;
for (i = GMAX - 1; i >= 0; i --)
if ( X[i] != 0.0) return i;
return 0;
}

Durante la escritura se corre el arreglo de coeciente i de 0, hasta el grado del polinomio, y se escribe el valor del arreglo
y el ndice, que constituye la potencia del monomio de grado i. Una primera versin es:

void printf_polinomio_v1(float X[])


{
int N, i;
N = grado(X);

for ( i = 0; i <= N; i++)


printf( %f * X^%d, X[i], i);
printf(\n);
}

La salida de esta funcin es la siguiente por el polinomio X4 + 5:

El polinomio es : 5.000000 * X^0 0.000000 * X^1 0.000000 * X^2 0.000000 * X^3


1.000000 * X^4

Si se quiere escribir el smbolo + entre los monomios e indicar solo los monomios signicativos, debemos escribir una
condicin explcita sobre el coeciente 0.0, y si no es el coeciente del grado. A continuacin se presenta una segunda
versin de escritura del arreglo:
void printf_polinomio_v2(float X[])
{
int N, i, primer;
N = grado(X);
primer = 0;

for ( i = 0; i <= N; i++)


if (X[i] != 0.0 || i == N)
{
if (primer != 0)
printf( + );
else
primer = 1;
printf(%f, X[i]);
if ( i == 1)
printf( * X);
if (i > 1)
printf( * X^%d, i);
168
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

}
printf(\n);
}

En este caso, la primera variable detecta si este es, o no, el primer monomio escrito. En el caso de que no sea el prime-
ro, se escribe el signo +. Por otra parte, si el ndice es 0 se escribe simplemente el coeciente, pero si el ndice es 1 se
escribe X nada ms.
Para el mismo polinomio X4 + 3X + 5 la escritura es:

5.000000 + 3.000000 * X + 1.000000 * X^4

Nos interesa calcular el valor de un polinomio en un punto matemtico, esto signica sustituir la variable X por un valor
concreto y realizar el clculo. En el programa esto se traduce a una funcin de prototipo:

float valor(float P[], float x);

Una primera opcin sera hacer los clculos en el orden indicado por la escritura:

P(X) = ak Xk + ak1 Xk1 + + a1 X + a0

O tambin por la escritura:

P(X) = a0 + a1 X + + ak1 Xk1 + ak Xk

El defecto de un clculo de este tipo sera el nmero total de operaciones. Tambin podemos ver que el polinomio se
puede factorizar de la siguiente manera:

P(X) = ( ((ak X + ak1) X + ak2) X + + a1) X + a0

La funcin C que traduce este clculo es:

float valor(float P[], float x)


{
int i, N;
float valor;

valor = 0.0;
N = grado(P);
for (i = N; i >= 0; i--)
valor = valor * x + P[i];
return valor;
}

En este caso, el arreglo se corre de derecha a izquierda, y la ltima operacin es la suma con el coeciente de ndice 0.

Ejemplo
A continuacin se presenta un ejemplo de llamada.

printf( El valor del polinomio en el punto %f es : %f\n, xx, valor(P,xx));

Una operacin algebraica que se utiliza con mucha frecuencia en el clculo de polinomios es la suma. As, para adicionar
dos polinomios, se hace la suma de los coecientes de los monomios del mismo grado. Por ejemplo: (X2 + 3) + (3X2 +
5X) = (1 + 3)X2 + 5X + 3. La funcin de la suma adiciona los coecientes del mismo ndice. A continuacin se presentan
dos versiones:
void suma_v1(float X[], float Y[], float S[])
{
169
*OUSPEVDDJOBMBQSPHSBNBDJO

int i;
for ( i = 0; i < GMAX; i++)
S[i] = X[i] + Y[i];
}

void suma_v2(float X[], float Y[], float S[])


{
int nx, ny, n, i;
nx = grado(X);
ny = grado(Y);
n = nx > ny ? nx :ny;

for (i = 0; i <=n; i++)


S[i] = X[i] + Y[i];
for (i = n + 1; i < GMAX; i++)
S[i] = 0.0;
}

En ambas versiones se considera que los coecientes de ndice superior al grado del polinomio son inicializados correc-
tamente a 0. Como se puede observar, en la primera versin se hace la suma de todos los ndices, mientras que en la
segunda versin se hace la suma nicamente de los ndices de 0 hasta el mximo de los grados. Despus, desde este
ndice hasta el ndice GMAX-1 se llena con 0.0.
Una operacin (transformacin) que se utiliza con mucha frecuencia en matemticas es la derivada. La derivada de
un monomio aXk, para k * 1 es el monomio kaXk1. Por tanto, la derivada de un polinomio es la suma de las derivadas
de los monomios que la componen. As, en el polinomio:

P(X) = a0 + a1 X + + ak1 Xk1 + ak Xk

su derivada es:

P(X) = a1 + 2 a2 X1 + + (k 1) ak1 Xk2 + (k 1) ak Xk1

La funcin C que traduce este clculo es:

void derivada(float X[], float D[])


{
int n, k;
n = grado(X) - 1;
for (k = 0; k <= n; k++)
D[k] = (k+1) * X[k+1];
for (k = n + 1; k < GMAX; k++)
D[k] = 0.0;
}

Juego del gato


El objetivo de este apartado es construir un programa completo que sirva de interfaz a dos jugadores. Al contrario de
lo que se hace en el captulo 3, donde se proponen tres versiones de un juego, en este caso nicamente proponemos
una versin de interfaz del juego, debido a que proponer una ayuda o una solucin automtica sobrepasa un curso de
introduccin a la programacin,9 que es el objetivo de este libro.

9
La solucin automtica de algunos juegos se realiza con la construccin y la exploracin de los rboles Alfa-Beta o
MinMax. Esto constituye un dominio de la inteligencia articial, una rama de la computacin. Vase el libro Russell, Stuart
J. y Norving, Peter (2004). Inteligencia Articial. Un Enfoque Moderno. 2a edicin. Prentice Hall, Mxico.
170
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

El juego del gato o tres en lnea (tic-tac-toe, en ingls) es un juego muy simple que se juega con papel y lpiz o
en un tablero, formado por tres lneas y tres columnas (3 3), que se entrelazan, y piedras de dos colores diferentes.
Cuando se juega con lpiz y papel, se dibuja en papel el tablero de 3 3, y cada uno de los jugadores elige un smbolo
para jugar: X u O (vase la gura 4.1).

Figura 4.1 Una conguracin del juego; en la imagen toca el turno al jugador con el smbolo X.

Desde que inicia el juego, cada jugador escribe el smbolo que eligi en una posicin libre del tablero hasta que haya
tres smbolos iguales en lnea, ya sea horizontal, vertical o en diagonal, hasta que no haya posiciones libres. Por tanto, el
ganador es quien logre colocar tres smbolos iguales antes que su contrincante. En la gura 4.2 se pueden observar tres
conguraciones ganadoras de este juego; no obstante, existen ms.

Figura 4.2 Tres distintas conguraciones ganadoras.

En algunos casos, el juego termina sin ganador; es decir, se declara un empate (en este caso se dice que se hizo el gato).

Figura 4.3 Conguracin de empate.


171
*OUSPEVDDJOBMBQSPHSBNBDJO

El objetivo de nuestro programa es hacer de la computadora un tablero de juego inteligente que entregue y guarde las
posiciones que se juegan; as, para cada movimiento y posicin colocada se vericara si alguna casilla del tablero est
libre o no y se detectara el nal de la partida, poniendo de maniesto si hay un ganador o es un empate.
Ante este reto, la primera pregunta es: Cmo podemos representar el tablero de juego? La respuesta es inmediata:
con una matriz de tipo entero (aunque short puede ser suciente). Para determinar si la posicin es libre o est ocu-
pada con uno u otro de los smbolos, X y O, podemos escribir valores constantes. Una sugerencia es escribir el siguiente
cdigo:
s 0 por la posicin libre.
s 1 por la posicin ocupada por X.
s 1 por la posicin ocupada por O.

As, esta codicacin sera traducida directamente en el programa con directivas denitorias:

#define LIBRE 0
#define X 1
#define O 1

No obstante, tambin existen otros valores constantes necesarios en el programa, que denimos como constantes de
sustitucin:

#define DIM 3
#define CIERTO 1
#define FALSO 0

El uso de estas directrices nos permite escribir un cdigo ms claro, como el del siguiente tipo:

if (tablero[i][j] == LIBRE)
..
tablero[k][j] = X;

En este caso, la matriz que representa el tablero se declara:

short tablero[3][3];

La segunda pregunta para este reto, sera: De qu manera se declara la matriz del tablero, como una variable local, al
interior del bloque de main, o como una variable global visible para cualquier funcin? La respuesta est en la manera de
crear el programa del juego. Si deseamos no usar funciones y que todo el tratamiento se realice en el bloque del main,
la matriz del tablero puede ser local; de lo contrario (lo cual resulta ms apropiado), la matriz se considera global. Si se
trabaja de forma modular, con funciones que cumplen (realizan) algunos objetivos, la ventaja de usar una matriz global es
que las listas de parmetros no se cargan con el identicador de esta matriz y el cdigo resulta ms fcil de entender. Por
ejemplo, a continuacin se presenta la declaracin de la matriz y la funcin de inicializacin con el valor de LIBRE (0):

short tablero[DIM][DIM];

void inicializacion_tablero()
{
int i, j;
for (i = 0; i < DIM; i++)
for (j = 0; j < DIM; j++)
tablero[i][j] = LIBRE;
}

Una clave importante para lograr el xito de cualquier programa, es la forma de las entradas y de las salidas. Por lo ge-
neral, en la salida debera ser posible imprimir, en forma inteligible, el tablero y algunos mensajes de los jugadores, aun
cuando la partida del juego termine o no. Por su parte, en la entrada se esperara poder ver la nueva posicin ocupada
en el tablero por cualquiera de los jugadores. Con base en lo antes expuesto, para el juego del gato podemos proponer
172
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

una codicacin de tipo juego de ajedrez (a1, b3, ...); no obstante, lo ms simple y lo ms cmodo que podemos hacer
es preguntar la posicin en la matriz tablero. Una salida clara del tablero de esta forma (aunque la forma no es la ms
bonita, s es lo bastante clara para poder comprenderla) puede ser la siguiente:
0 1 2
--------------
| | | |
0 | X | | 0 |
--------------
| | | |
1 | | | X |
--------------
| | | |
2 | 0 | 0 | X |
--------------
Las funciones que permiten este tipo de salida son las siguientes:

void print_codigo(short valor)


{
switch(valor)
{
case X:
printf( X);
break;
case 0:
printf( 0);
break;
case LIBRE:
printf( );
break;
}
}

void print_linea(int i)
{
int j;
printf( %d |, i);
for (j=0; j < DIM; j++)
{
print_codigo(tablero[i][j]);
if (j < 2)
printf( |);
else
printf( |\n);
}
}

void escritura_tablero()
{
int i;
system(clear);
printf(\n);
printf( 0 1 2\n);
printf( -------------\n);
for (i = 0; i < DIM; i++)
{
printf( | | | |\n);
print_linea(i);
173
*OUSPEVDDJOBMBQSPHSBNBDJO

printf( -------------\n);
}
printf(\n);
}

La funcin print_codigo resulta til para indicar quin es el jugador en turno (X u O). Esta salida es muy comn, pero
como ejercicio usted puede imaginar otras formas de representacin del tablero.
En el caso del juego del gato, tenemos dos jugadores (X y O), pero para el programa no hay ninguna diferencia; por
tanto, cualquier jugador puede iniciar la partida; por ejemplo, puede ser X.
Para indicar quin es el jugador en turno en el juego, introducimos en el programa una variable jugador que ni-
camente toma los valores 0 y 1. El jugador del prximo turno sera, por tanto, el valor 1. Para hacer notar el cdigo que
un jugador utiliza, introducimos un arreglo simbolo[2] que contiene los valores constantes X y O. Cuando la posicin
introducida se ocupa, se hace la siguiente asignacin:

tablero[i][j] = simbolo[jugador];

Una posicin (i, j) es correcta si i y j son los ndices correctos en el tablero (es decir, si se ubican entre 0 y DIM-1) y si el
tablero es libre. La vericacin se realiza con las siguientes funciones:
int esta_libre(int i, int j)
{
if (tablero[i][j] == LIBRE)
return CIERTO;
else
return FALSO;
}

int posicion_correcta(int i, int j)


{
if (i < 0 || i >= DIM || j < 0 ||j >= DIM)
return FALSO;
else
return esta_libre(i, j);
}

El juego en cuestin tiene una estructura iterativa; en el caso de que un jugador gane, el programa termina.
Por el contrario, si con el movimiento actual no se ha ganado, el programa contina, cambiando el jugador y aumentando
el nmero de pasos (movimientos) hechos, por ejemplo:

int main(int argc, char *argv[])


{
short paso;
short jugador;
short simbolo[2] = {X, 0};
int i, j;

// inicializacion
inicializacion_tablero();
escritura_tablero();
jugador = 0;
paso = 0;
printf ( El jugador que empieza : );
print_codigo(simbolo[jugador]);

// estructura repetitiva del juego


while (paso < DIM * DIM)
{
174
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

printf (\n El jugador que juega : );


print_codigo(simbolo[jugador]);
// lectura de la posicion de entrada
printf(\n Posicion: );
scanf(%d%d, &i, &j);
if (posicion_correcta(i, j) == CIERTO)
{
// la posicion esta correcta (indices correctos y posicion libre en tablero)
tablero[i][j] = simbolo[jugador];
escritura_tablero();
// se verifica si hay ganador
if (hay_ganador(i,j) == CIERTO)
{
printf( El ganador es:);
print_codigo(simbolo[jugador]);
break;
}
else
{
// el partido continua
jugador = 1 -jugador; // el jugador cambia
paso++; // un paso mas
escritura_tablero();
}
}
else
{
// la posicion no esta correcta
printf( La posicion no esta correcta.\n\n);
continue;
}
}
printf( \n);
if (paso == DIM * DIM)
if (paso == DIM * DIM)
// fueron DIM*DIM pasos jugados sin ganar
printf( El gato !\n);
exit(0);
}

Si el juego termina despus de nueve pasos (DIM*DIM), se deduce que el juego termina en empate (es decir, se hace
el gato).
La vericacin de un ganador no se hace recorriendo toda la matriz del tablero, sino nicamente las lneas, las co-
lumnas y las diagonales (si la posicin pertenece a las diagonales). Para recorrer una lnea, se utiliza el segundo ndice
variable (una variable k); para una columna, se usa el primer ndice que es variable y el segundo que es jo; por su parte,
las diagonales se caracterizan por lo siguiente:
s La diagonal principal tiene, para cualquier elemento, el mismo valor para sus ndices.
s La diagonal secundaria tiene la propiedad de que la suma de sus ndices es un valor constante.

int hay_ganador(int i, int j)


{
int k, suma;

// si la linea esta completa con el mismo simbolo


for (suma= 0, k = 0; k < DIM; k++)
suma += tablero[i][k];
175
*OUSPEVDDJOBMBQSPHSBNBDJO

if (suma == DIM * tablero[i][j])


{
printf( Linea completa!\n);
return CIERTO;
}
// si la columna esta completa con el mismo simbolo
for (suma= 0, k = 0; k < DIM; k++)
suma += tablero[k][j];
if (suma == DIM * tablero[i][j])
{
printf( Columna completa!\n);
return CIERTO;
}

if (i == j)
{
// (i,j) es sobre la diagonal principal
// verificar esta diagonal
for (suma= 0, k = 0; k < DIM; k++)
suma += tablero[k][k];
if (suma == DIM * tablero[i][j])
{
printf( Diagonal principal completa!\n);
return CIERTO;
}
}
if (i+j+1 == DIM)
{
// (i,j) es sobre la diagonal secundaria
// verificar esta diagonal
for (suma= 0, k = 0; k < DIM; k++)
suma += tablero[k][DIM-1-k];
if (suma == DIM * tablero[i][j])
{
printf( Diagonal secundaria completa!\n);
return CIERTO;
}
}
return FALSO;
}

Ejemplo
Ejemplo de ltima salida del programa:

0 1 2
--------------
| | | |
0 | | | X |
--------------
| | | |
1 | 0 | X | 0 |
--------------
| | | |
2 | X | | |
--------------

176
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Diagonal secundaria completa!


El ganador es: X

4.2 Caracteres y cadenas de caracteres

Hasta ahora hemos manejado nicamente datos numricos; el lenguaje C, como la mayora de los lenguajes de progra-
macin, tambin permite tratar datos textuales. En esta seccin presentamos los mecanismos para manejar caracteres,
cadenas de caracteres y las funciones estndares que el lenguaje C ofrece para trabajar con datos de este tipo.

Tipo carcter

La unidad de memoria ms pequea accesible de un programa es el byte, que en lenguaje C corresponde al tipo char
con sus dos versiones:

char
unsigned char
El tipo char almacena valores entre 127 y 127, mientras que el tipo unsigned char guarda valores entre
0 y 255.
Un carcter imprimible o de control se codica en ASCII10 y se representa en la memoria en forma de variable de
tipo char.
Una constante de tipo char se representa de dos formas:
1. Por su valor numrico en el cdigo ASCII, ya sea en base 10, 16 u 8.
2. Por el carcter (comilla) que se representa entre un par de smbolos ( ).

Ejemplo
El 99 es un valor de tipo char, el cual tambin se puede escribir como 0 o 143, en octal; 0 63, en hexadecimal, o
como c.
En la siguiente tabla se presenta una lista de algunos caracteres especiales o que no son imprimibles:

Tabla 4.2 Caracteres especiales que no son imprimibles.

Representacin en C Signicacin
\n Lnea nueva (interlnea)
\0 Carcter NULL, que tiene su cdigo 0
\t Tabulacin
\\ Carcter \
\ooo carcter cdigo ASCII equivalente a valor ooo en base 8
\ Carcter (comilla)
\ Carcter (doble comilla)

10
Para utilizar otros caracteres adems del cdigo ASCII, estos deben construirse con los tipos presentados (es decir con nuevos tipos
capaces de soportar otros cdigos sobre ms de 1 byte) y desarrollar funciones que faciliten el manejo de los datos con esta codicacin.
177
*OUSPEVDDJOBMBQSPHSBNBDJO

Para imprimir un carcter se utiliza el formato de printf es %c; para ver el carcter imprimible y ver su valor numrico
en decimal, octal o hexadecimal se utiliza o %d, %o, %x.

Ejemplo
Fragmento de programa en lenguaje C:

char abc=Z;
char zz = 99;
printf( abc = %c\n, abc);
printf( zz = %c\n, zz);
produce :

abc = Z
zz = c

A los diferentes valores (variables, constantes y expresiones) se pueden aplicar los llamados operadores de trabajo con
valores numricos, que se muestran a continuacin:

s Los operadores *, /, % no tienen sentido.


s Los operadores + y trabajan con el cdigo ASCII.
s Los operadores de comparacin < > <= >= == != se aplican tambin Cuidado!, tambin hay a > A
sobre el cdigo ASCII.

Ejemplo
A continuacin se presenta un programa que aplica operadores de valores tipo char.

/* programa que trabaja con caracteres */


/* usando algunos operadores */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char abc=f;

printf( abc = %c (el caracter) %d (su valor entero)\n, abc, abc);

if ( a <= abc && abc <= z)


printf ( El caracter es una letra minuscula\n);

printf( los 5 sucesores de %c son %c%c%c%c%c\n, abc, abc +1, abc +2, abc +3, abc+4,
abc +5);

exit(0);
}

Este programa produce la siguiente salida:

abc = f (el caracter) 102 (su valor entero)


El caracter es una letra minuscula
los 5 sucesores de f son ghijk

178
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Cadenas de caracteres

Manejar datos que corresponden, por ejemplo, a palabras, no resulta prctico si se debe trabajar carcter por carcter. El
lenguaje C trabaja con cadenas de caracteres, que constituyen sucesiones de caracteres que terminan implcitamente
con el carcter \0 o 0. Es muy importante tomar en cuenta que al inicio y al nal de cada cadena de caracteres se usa
el smbolo .
Las cadenas de caracteres se almacenan en arreglos de caracteres de tamao de al menos el largo ms 1 (por el
carcter de n de cadena).

Ejemplo

/* programa simple que trabaja con cadenas de caracteres */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char zz[7] = {c, a, d,e,n,a,\0};
char xx[7] = cadena;
int i;

// impresion caracter por caracter


printf(La primera cadena : );
for ( i = 0; i <7; i++)
printf(%c, zz[i]);
// impresion con el formato %s
printf(\n Las dos cadenas son : \n);
printf( zz = %s xx =%s\n, zz, xx);

exit(0);
}

Este programa produce la siguiente salida:

La primera cadena : cadena


Las dos cadenas son :
zz = cadena xx =cadena

El formato utilizado en la funcin printf es %s es para imprimir una cadena de caracteres.


Una constante de cadena de caracteres se escribe entre los smbolos (doble comilla) e indica los caracteres que
la componen.

Ejemplo

abc, a, \141\142\143r, abcr son cadenas de caracteres; de estas, las dos ltimas son idnticas.

En el programa del ejemplo anterior, la primera inicializacin del arreglo de caracteres se realiza elemento por ele-
mento, con cada carcter que compone la cadena, incluso hasta con el ltimo carcter de n de cadena, donde se usa
la escritura x. En la segunda inicializacin se emplea la notacin completa con toda la cadena de caracteres; de esta
forma, esta cadena es una constante que inicia y termina con el smbolo .
Una cadena de caracteres se declara con:
char nombre_cadena1[dimension]
179
*OUSPEVDDJOBMBQSPHSBNBDJO

char nombre_cadena2[]
char *nombre_cadena3

Pero, estas declaraciones no son equivalentes. En el primer caso, se asigna la zona de memoria de dimensin bytes
capaz de almacenar el contenido de la cadena de caracteres. Por su parte, en el caso de nombre_cadena2, una inicia-
lizacin con una constante de tipo cadena resulta imperativa para la asignacin de memoria.
En el ltimo caso, y debido a la ausencia de una inicializacin, no se asigna ninguna zona de memoria; nombre_
cadena3 es nicamente un apuntador de tipo carcter, el cual puede aparecer del lado izquierdo de una asignacin de tipo:

nombre_cadena3 = yy;

Con yy, otra cadena de caracteres tambin es posible asignar memoria con la funcin malloc:

nombre_cadena3 = (*char)malloc(dimension);

Las cadenas nombre_cadena1 y nombre_cadena2 jams pueden aparecer en el lado izquierdo de un operador de
asignacin.
Por su parte, la declaracin:

char *aa[10];

indica un arreglo de cadenas de caracteres o de apuntadores de caracteres.

Ejemplo

Enseguida se muestran las declaraciones de variables de tipo cadenas de caracteres con y sin inicializacin.
/* programa que trabaja con cadenas de caracteres */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char ss[]=esta es una cadena;
char *zz=cadena dos;
char *xx;
char* car[5]={lunes, martes, miercoles, jueves, viernes};

printf(ss = %s \n, ss);


printf(zz = %s \n, zz);
printf(car[4]=%s \n, car[4]);

xx = car[3];
printf(xx = %s \n, xx);

exit(0);
}
La salida del programa es la siguiente:
ss = esta es una cadena
zz = cadena dos
car[4]=viernes
xx = jueves

En el siguiente ejemplo se pueden observar dos cadenas declaradas con el tipo char*. Para la primera cadena, apca-
dena, usamos dos asignaciones, una con otra cadena y una con la funcin malloc. La segunda cadena, apsegundo,
180
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

apunta despus a la asignacin apsegundo = apcadena; la misma zona de memoria que apcadena. As, cualquier
modicacin de apcadena est visible a travs de apsegundo.

/* programa apuntadores y cadenas */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char fijo[] = Una cadena.;
char *apcadena;
char inicio_alfabeto = a, valor_corriente;
int i;
char *apsegundo;

// asignacion de una cadena de caracteres


apcadena = fijo;
printf( despues una primera asignacion apcadena contiene : %s\n, apcadena);

// uso de malloc y asignacion de valores a la cadena


apcadena = (char*) malloc(26+1);
for (valor_corriente = inicio_alfabeto, i = 0; i < 26; i++)
apcadena[i] = valor_corriente++;
apcadena[26] = \0;
printf( despues el malloc y el for apcadena contiene : %s\n, apcadena);

// apsegundo apunta a la misma direccion que apcadena


apsegundo = apcadena;
printf( apsegundo apunta por : %s\n, apsegundo);
// modificacion de apcadena
for (i = 0; i < 6; i++)
apcadena[i] = x;
apcadena[6] = \0;
printf( apsegundo apunta por : %s\n, apsegundo);

exit(0);
}

As, la salida del programa es:


despues una primera asignacion apcadena contiene : Una cadena.
despues el malloc y el for apcadena contiene : abcdefghijklmnopqrstuvwxyz
apsegundo apunta por : abcdefghijklmnopqrstuvwxyz
apsegundo apunta por : xxxxxx
La escritura de la cadena de caracteres se efecta, por el momento, con la funcin general printf y con el formato %s.
El funcionamiento es el siguiente:

1. Con base en el apuntador indicado como parmetro, se recorre la memoria, byte por byte.
2. Cada byte se interpreta con el cdigo ASCII, hasta que se encuentra con el carcter NULL \0 de n de cadena.
3. La ausencia del carcter de n de cadena provoca errores lgicos de programa o que el programa se detenga,
porque no encuentra el n de cadena y contina recorriendo la memoria.11

11
Vase el programa caracter5_errores.c en el CD-ROM que acompaa este libro.
181
*OUSPEVDDJOBMBQSPHSBNBDJO

La lectura de una cadena de caracteres permite utilizar la funcin conocida de scanf con el formato %s; sin em-
bargo, la cadena que se lee no debe tener espacios u otros separadores. En la seccin siguiente se presentan y analizan
diversas funciones de entrada y salida, con mayor adaptacin a los tipos de carcter y cadenas de caracteres.
Como una cadena de caracteres es un apuntador, existen pocos operadores estndares que trabajan con las cadenas.
En este caso, por ejemplo, la asignacin se reere al apuntador o al contenido del nivel de carcter, excepto en las decla-
raciones, donde no hay una asignacin de forma cadena = mi cadena. Entonces, se impone escribir funciones
especcas con el tratamiento de las cadenas de caracteres o utilizar funciones de las bibliotecas estndares.
El siguiente programa contiene dos funciones de trabajo con cadenas; la primera calcula el largo (tamao) de una
cadena introducida por parmetro; por su parte, la segunda funcin copia el contenido de una cadena (incluso el carcter
de n de cadena) en una cadena destinacin.

int largo (char *st)


{
int i;
i = 0;
while(1)
{
if (st[i] == \0) break;
i++;
}
return i;
}

void copia(char *f, char *d)


{
int i;
for (i = 0; ; i++)
{
d[i] = f[i];
if (d[i] == \0) break;
}
return;
}
int main(int argc, char *argv[])
{
char ss[]=esta es una cadena;
char *segunda;

printf(ss = *%s* el largo = %d \n, ss, largo(ss));


printf( el largo de mi nombre %d\n, largo(Mihaela));
segunda = (char*)malloc(largo(ss) + 1);
copia(ss, segunda);
printf( La segunda cadena es : %s\n, segunda);

exit(0);
}

En el programa de la segunda cadena realizamos la asignacin de una zona de memoria con una llamada de la funcin
malloc.
As, la salida del programa es:
ss = *esta es una cadena* el largo = 18
el largo de mi nombre 7
La segunda cadena es : esta es una cadena
182
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Funciones estndares para el manejo de caracteres y cadenas de caracteres

Varias bibliotecas estndares proponen funciones para el trabajo con caracteres y cadenas de caracteres.
En la biblioteca ctype.c hay funciones que prueban la naturaleza de un carcter ASCII, ya sea que se trate de
cifra, letra, signo de puntuacin, carcter de control, entre otros. A continuacin se presenta una lista con las principales
funciones de esta biblioteca:

Tabla 4.3

int isupper(char c) c es una letra mayscula


int islower(char c) c es una letra minscula
int isalpha(char c) isupper(c) o islower(c)
int isdigit(char c) c es una cifra
int isalnum(char c) c es letra o cifra
int isprint(char c) c es carcter imprimible
int iscontrol(char c) c es carcter de control
int ispunct(char c) c es carcter de puntuacin

En el caso de esta biblioteca, las funciones regresan el valor 0 si la condicin es falsa, o un valor diferente de 0 si la con-
dicin es cierta.
En la biblioteca stdlib.h hay tres funciones de conventir una cadena de caracteres en un valor numrico:
s double atof(char *s) convierte la cadena s en un otante.
s int atoi(char *s) convierte s en un entero.
s long atol(char *s) convierte s en un entero long.

Si la conversin es posible, la funcin regresa el valor del tipo indicado; si no las funciones regresan el valor 0.

Ejemplo

A continuacin se presenta un ejemplo de este cdigo.


char c1[ ]= 1234;
char c2[ ] = abc;
printf(atoi(c1) = %d\n, atoi(c1));
printf(atoi(c2) = %d\n, atoi(c2));

Entonces, este produce:

atoi(c1) = 1234
atoi(c2) = 0

La segunda cadena no se puede convertir en un valor numrico, ya que la funcin atoi regresa el valor 0.

La biblioteca string.h contiene funciones especializadas en el manejo de cadenas de caracteres:

s char *strcpy(char *s, char *ct) copia la cadena ct en la cadena s.


s char *strncpy(char *s,char *ct,int n) copia n caracteres de la cadena ct en s.
s char *strcat(char *s, char *ct) concatena la cadena ct al nal de la cadena s.
183
*OUSPEVDDJOBMBQSPHSBNBDJO

s char *strncat(char *s,char *ct,int n) concatena n caracteres de la ct al nal de s.


s int strcmp(char *cs, char *ct) compara ct con cs, regresa <0 , 0 o >0, segn
cs < ct (contenidos de cadenas).
s int strncmp(char *cs,char *ct,int n) compara los primeros n caracteres de cs y ct.
s int strlen(char *cs) regresa la longitud (el largo) de cs.

Las funciones anteriores suponen que todas las cadenas terminan con el carcter de n de cadena.
Las lecturas/escrituras pueden hacerse con printf y scanf (de la biblioteca stdio.h), respetando el siguiente
formato:

s %c para los caracteres.


s %s para las cadenas de caracteres.

La primera lectura de un carcter toma en cuenta el primer carcter entregado y la segunda lectura puede iniciar con el
prximo carcter del ujo. Por consiguiente, la lectura de una cadena de caracteres con scanf siempre toma en cuenta
la sucesin de caracteres hasta el primer separador (que puede ser un espacio, una lnea nueva o una tabulacin). La
funcin scanf tambin pone el carcter \0 o NULL al nal de la cadena. De esta forma, no es posible que la cadena
empiece o contenga un separador.
Para una mejor comprensin, vase con cuidado el siguiente programa simple de lectura/escritura:

/* programa 1 de lectura caracter y cadena */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char abc;
char zz[10];
printf( Lectura de un caracter y de una cadena\n);
scanf(%c%s, &abc, zz);

printf( abc = #%c#\n, abc);


printf( zz = +%s+\n, zz);

exit(0);
}

Como se puede observar en el programa anterior, las salidas son dependientes de las entradas. Entonces:

Lectura de un caracter y de una cadena


3XYZ
abc = #3#
zz = +XYZ+

Lectura de un caracter y de una cadena


4 RTRTR
abc = #4#
zz = +RTRTR+

Lectura de un caracter y de una cadena


345 67 8989898
abc = #3#
zz = +45+
184
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Las cadenas de caracteres en la funcin scanf indican el identicador de cadena, el cual constituye una direccin de
memoria, ya que con base en sus caracteres se indica su direccin de memoria.
Las funciones printf y scanf trabajan con los ujos estndares de salida y de entrada, respectivamente. En la
biblioteca stdio.h tambin hay dos funciones parecidas: sprintf y sscanf, por esta razn, la escritura o la lectura
no se hace en un ujo, sino en una cadena de caracteres.
Los prototipos son:

int sscanf(char *s, char *formato, elementos ...);


int sprintf(char *s, char *formato, elementos ...);

Por su parte, en la biblioteca stdio.h hay otras funciones de lectura/escritura de caracteres y cadenas:

s char getchar() que lee y regresa un carcter del ujo estndar de entrada.
s char putchar(char c) que escribe el carcter c ujo estndar de salida.
s char *gets(char *s, int n) que lee una cadena de caracteres s, desde el ujo de entrada hasta
que se encuentra una lnea nueva o que el largo ledo est n; se
pone al nal de la cadena el carcter \0.
s char puts(char *) que escribe la cadena s y el carcter de n de lnea \n.

Ejemplo

En este ejemplo se desarrollan las mismas funcionalidades que en el programa anterior, solo que las entradas se hacen
con otras funciones:
/* programa 2 de lectura */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
char abc;
char zz[10];
printf( Lectura de caracter y cadena\n);
abc = getchar();
gets(zz);

putchar(abc);
puts(zz);

printf( abc = #%c#\n, abc);


printf( zz = +%s+\n, zz);

exit(0);
}

As, una salida del programa es:


Lectura de caracter y cadena
345 67 8989898
abc = #3#
zz = +45 67 8989898+
185
*OUSPEVDDJOBMBQSPHSBNBDJO

Problema resuelto

Supngase que se desea leer un determinado nmero de prrafos escritos en espaol que contienen informacin acerca
de cientcos y extraer de ah los nombres propios y los aos citados; por ejemplo, el siguiente texto:12
Sir Isaac Newton (1642 -1727), fsico, lsofo, telogo, inventor, alquimista y matemtico ingls, comparte con
Leibniz el crdito por el desarrollo del clculo integral y diferencial, que ms tarde Newton utiliz para formular sus leyes
de la fsica. Entre sus principales hallazgos cientcos, destaca el descubrimiento de que el espectro de color que se ob-
serva cuando la luz blanca pasa por un prisma es inherente a esa luz, en lugar de provenir del prisma (como haba sido
postulado por Roger Bacon, en el siglo xiii).
Newton fue el primero en demostrar que las leyes naturales que gobiernan el movimiento en la Tierra y las que go-
biernan el movimiento de los cuerpos celestes son las mismas. A menudo, se le calica como el cientco ms grande de
todos los tiempos, y su obra como la culminacin de la revolucin cientca. Con base en su obra, el matemtico y fsico
matemtico Joseph Louis Lagrange (1736-1813), dijo de Newton que fue el ms grande genio que ha existido y tambin
el ms afortunado, debido a que solo se puede encontrar una vez un sistema que rija al mundo.
A partir de la informacin expuesta en los prrafos anteriores, vamos a desarrollar un programa que extraiga las dos
siguientes listas:

s Isaac Newton, Roger Bacon, Newton, Tierra, Joseph Louis Lagrange, Newton.
s 1642, 1727, 1736, 1813.

Entonces, la entrada del programa es un conjunto de cadenas de caracteres, donde cada cadena termina con una lnea
nueva. La salida est formada por las dos listas anteriores: la lista de nombres propios (que son cadenas de caracteres) y
la lista de aos, los cuales podemos considerar como valores enteros entre 1000 y 2010.
El programa debe leer cadena por cadena. Para indicar el nal del programa, al nal debemos introducir la ltima
cadena conteniendo solo un carcter especial, por ejemplo: *.
La estructura del programa es bastante simple y con una proposicin iterativa de tipo while. De esta forma, es po-
sible almacenar toda la informacin y los datos que nos interesan (en este caso, los aos y los nombres) en arreglos glo-
bales de tipo entero y cadenas de caracteres. Asimismo, como variables globales guardamos no_anos y no_nombres.
El programa es entonces:

#include <stdio.h>
#include <stdlib.h>

int anos[1000];
char nombres[1000][50];

int no_anos;
int no_nombres;

void trata_cadena(char *s)


{
return;
}
int main(int argc, char *argv[])
{
char cadena[2000];
int i;

no_anos = 0;
no_nombres = 0;

12
Extracto de Wikipedia.
186
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

printf( Introducir una a una las lineas con el texto:\n);


while(1)
{
gets(cadena);
if (cadena[0] == *)
break;
trata_cadena(cadena);
}

if (no_anos !=0)
{
printf( Los anos encontrados (%d): , no_anos);
for (i = 0; i < no_anos; i++)
printf( %d, anos[i]);
printf(\n);
}
if (no_nombres !=0)
{
printf( Los nombres encontrados (%d): , no_nombres);
for (i = 0; i < no_nombres; i++)
printf( %s, nombres[i]);
printf(\n);
}
exit(0);
}

Por el momento, la funcin trata_cadena no se ha explicado, aunque es la parte ms importante del tratamiento.
En la cadena debemos buscar nmeros que sean sucesiones de cifras, as como nombres propios que sean sucesio-
nes de letras; de esta forma, la primera letra es mayscula hasta que la cadena se termine o cuando encuentre una letra
minscula despus de un separador.
Para detectar el tipo de carcter, vamos a utilizar la funcin estndar isdigit, para vericar si este carcter es una
cifra, y las funciones estndares isupper y islower, para comprobar si son letras, y la funcin isspace para los
separadores.
Si en la funcin trata_cadena se encuentra una cifra o una letra mayscula, se realiza el siguiente tratamiento:

s En caso de una cifra, se recuperan todos los caracteres que siguen y que son cifras. Luego, se copian todos estos
caracteres en una cadena temporal; al nal de esta cadena, se pone el carcter de n de lnea. La cadena relativa
(variable numero) se convierte en nmero y si el nmero respeta el intervalo [1000, 2011] el valor de conversin
se almacena en el arreglo global nmeros.
s En caso de una mayscula, el tratamiento es parecido al del punto anterior. Primero, se toman los caracteres que
son separadores y letras; es importante destacar que para las letras minsculas no se permite que haya antes un
separador. Una vez que la cadena se detecta completamente, se copia con la funcin estndar strcpy en el
arreglo global nombres.

Entonces, el cdigo de la funcin es:

void trata_cadena(char *s)


{
int n;
int i, j, k, letra, valor;
char numero[10], nombre[100];
n = strlen(s);
i = 0;
while ( i < n)
187
*OUSPEVDDJOBMBQSPHSBNBDJO

{
if (isdigit(s[i]))
{
k = 0;
j = i;
while (j < n && isdigit(s[j]))
{
numero[k] = s[j];
k++;
j++;
}
numero[k] = \0;
valor = atoi(numero);
if (valor >= 1000 && valor <= 2011)
anos[no_anos++] = valor;
i = j;
continue;
}
if (isupper(s[i]))
{
nombre[0] = s[i];
k = 1; j = i+1; letra = 0;
while ( j < n && (isupper(s[j]) || isspace(s[j]) ||
(islower(s[j]) && letra == k-1)))
{
nombre[k] = s[j];
if (islower(s[j]) || isupper(s[j]))
letra = k;
k++;
j++;
}
nombre[letra+1] = \0;
strcpy(nombres[no_nombres++], nombre);
i = j;
continue;
}
i++;
}
return;
}

As, la salida del programa anterior es:

Los anos encontrados (4): 1642 1727 1736 1813


Los nombres encontrados (8): Isaac Newton Newton Leibniz
Roger Bacon Newton Tierra Joseph Louis Lagrange Newton

4.3 La funcin main

En el momento de la ejecucin del programa, en ocasiones sera ms til que el ejecutable corriera sin la intervencin de
un usuario (por ejemplo, para introducir datos). Pero, para que el ejecutable sea parametrable, esto es, que se puede
indicar en lnea de comando, ms que el nombre del ejecutable, se requieren algunos parmetros en el programa.
188
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

El lenguaje C ofrece esta posibilidad a travs de los parmetros de la funcin main. Hasta ahora, solo hemos dado
a conocer los parmetros formales de la funcin main, sin indicar cul de estos est en uso.
As, el prototipo de la funcin main es:

int main(int argc, char *argv[]);

Por su parte, el parmetro formal argc es un entero que seala el nmero de palabras indicadas en la lnea de
comando, incluso el nombre del ejecutable (argc vale al menos 1). El parmetro formal argv es un tipo de arreglo
de cadenas de caracteres y su caracterstica es que contiene los parmetros actuales del ejecutable.
Los parmetros actuales del ejecutable se separan entre s por espacio o tabulaciones y todo aparece sobre una
misma lnea.

Ejemplo

El siguiente programa imprime sus propios parmetros del ejecutable.

/* programa que cuenta los parametros en linea de comando */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int i;
printf( Hay %d parametros\n, argc);
printf(Los parametros son :\n);
for(i= 0; i < argc; i++) printf(*%s*\n, argv[i]);
exit(0);
}

Si la compilacin se hace con el comando, entonces:

gcc main_ejemplo1.c -o tt

El nombre del ejecutable es tt y su ejecucin se realiza con el comando Unix ./tt.

Ejemplo

Enseguida se presenta un ejemplo de ejecuciones:


maquina> ./tt 34 67 556 RRR
Hay 5 parametros
Los parametros son :*./tt*
*34*
*67*
*556*
*RRR*
maquina> ./tt
Hay 1 parametros
Los parametros son :*./tt*

En el caso de que los parmetros del ejecutable se deban tratar como nmeros, entonces se aplican las funciones de
conversin de tipo atoi y atof.
189
*OUSPEVDDJOBMBQSPHSBNBDJO

Ejemplo

En el siguiente programa se realiza la suma de los nmeros entregados como parmetros en lnea de comando. Es
importante hacer notar que no hay ms que 10.

/* programa que hace la suma de sus parametros enteros */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
int A[10];
int n, i, suma;

n = argc -1;
if (n>11)

{
printf( demasiados valores\n);
exit(0);
}

for (i = 1; i <argc; i++)


A[i-1] = atoi(argv[i]);

suma = 0;
for (i = 0; i < n; i++)
suma += A[i];

printf(La suma es : %d\n, suma);


exit(0);
}

4.4 Archivos

En computacin y en el sentido ms amplio posible, un archivo es un conjunto de bits que se encuentra en un disposi-
tivo para almacenar datos. Para un manejo adecuado, un archivo se identica con un nombre y con una ubicacin en el
dispositivo donde se encuentre, que puede ser la memoria principal o la memoria secundaria o en dispositivos externos.
En un archivo es posible leer o escribir datos (a condicin de tener los derechos).
Para el caso que nos ocupa, podemos decir que el lenguaje C es capaz de trabajar con archivos del sistema operativo.
De esta forma, existen dos formatos de archivos que este lenguaje sabe manejar en lectura y/o escritura:
s Archivos de tipo texto (formato texto). De este tipo de archivos nos interesan las sucesiones de bytes que se
forman, los datos que se introducen y que se leen en un formato de impresin.
s Archivos de tipo binario (formato binario). Gracias a este tipo de archivos, el manejo de la informacin se realiza a
nivel de bits; en este caso, los nmeros estn escritos en el formato interno de representacin.

Un archivo de texto puede ser ledo con cualquier editor de texto. Por lo general, durante la lectura/escritura de nmeros
otantes, se pierde la precisin. Un mayor espacio de representacin (con ms cifras) puede ser una solucin relativa.
190
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

Por su parte, un archivo en modo binario est dirigido nicamente a programas capaces de leerlo o escribirlo. As, un
nmero se escribe con su representacin interna (representacin binaria o punto otante), mientras que un carcter se
escribe en cdigo ASCII.
El lenguaje C trata la entrada/salida de los archivos en trminos de ujo de datos, algo parecido a los canales a travs
de los cuales uyen los datos. En este, podemos desplazarnos por el ujo, corriente arriba o corriente abajo, buscando un
bit en particular o buscando el ltimo bit o el penltimo.
Hasta el momento, solo hemos trabajado con el ujo estndar de lectura stdin (el teclado) y con el ujo estndar
de salida stdout (la pantalla).
Un archivo abierto produce un ujo en el programa, donde es posible abrir simultneamente hasta
FOPEN_MAX (valor 255) archivos a la vez.
El tipo ujo se dene como:

FILE *nombre_flujo

FILE es un tipo predenido de la biblioteca stdlib.h, en el cual es posible encontrar todas las funciones de esta
seccin.
Se puede decir que dos funciones son indispensables:

1. Para abrir el ujo: fopen.


2. Para cerrar el ujo: fclose.

La funcin fopen se escribe de la siguiente manera:

FILE* fopen(char *nombre_archivo, char *modo)

Esta funcin abre un archivo nombrado y regresa un ujo; pero, si la apertura no es correcta, la funcin regresa al valor
NULL.
El nombre_archivo es una cadena de caracteres que contiene el nombre del archivo y, si es necesario, tambin
el camino.

Ejemplos

s archivo1.dat; si este archivo est en el mismo repertorio que el ejecutable.


s ../datos/archivos2.dat; si este archivo est en el repertorio relativo ../datos.

El modo de apertura es indicado en el segundo parmetro de la funcin, como una cadena de caracteres:
s r; abre un archivo de texto para lectura.
s w; crea un archivo de texto para escritura, descarta el contenido previo.
s a; agrega (crea o abre para escribir al nal).
s r+; abre un archivo para actualizacin (lectura-escritura).
s w+; crea un archivo para actualizacin; descarta el contenido previo.
s a+; agrega, crea o abre para actualizacin y escribe al nal.

Para trabajar con archivos de tipo binario, al nal de la cadena se incluye la letra b.

Ejemplo

As, con base en lo antes expuesto, rb indica la lectura de un archivo binario, y w+b indica la actualizacin de un
archivo binario.
191
*OUSPEVDDJOBMBQSPHSBNBDJO

La funcin:

int fclose(FILE *fujo)

cierra el archivo y regresa EOF si cualquier error aparece, 0 si no.


El valor EOF es una constante de tipo carcter que indica el nal de un archivo; tambin se usa para indicar errores
posibles en el uso del archivo.

Ejemplo

Se abre un archivo de tipo texto y, si el archivo est disponible, se lee el primer carcter.

/* programa que abre un archivo y lee el primer caracter */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
FILE *f1;
char c;

f1 = fopen(cascara.c, r);
if (f1 == NULL)
{
printf(problemas de apertura del archivo %s.\n, cascara.c);
exit(1);
}
c = fgetc(f1);
printf(El primer caracter del archivo es %c\n, c);
fclose(f1);
exit(0);
}

En este caso, si el archivo cascara.c existe o no, el programa produce lo siguiente en el directorio corriente:
El primer caracter del archivo es /
problemas de apertura del archivo cascara.c.
La funcin es la siguiente:

feof(FILE *flujo)

Regresa un valor diferente de 0 si se encuentra al nal del archivo y 0 si no est al nal.


Las funciones de lectura y de escritura en los archivos son similares a las funciones que trabajan con el ujo estndar
de entrada o de salida:

1. int fprintf(FILE *flujo, char *formato, ...). Escribe, segn el formato en el ujo indicado,
los valores y regresa el nmero de caracteres escritos o un valor negativo si hay errores.
2. int fscanf(FILE *flujo, char *formato, ...). Del ujo, lee los argumentos indicados (que
deben ser apuntadores !). Regresa EOF, si se encuentra al nal del archivo durante la lectura, o un error. Regresa
el nmero de artculos de entrada ledos y convertidos.
3. int fgetc(FILE *flujo). Regresa el siguiente carcter del ujo o EOF.
192
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

4. char *fgets(char *s, int n, FILE *flujo). Lee, en la cadena s, el mximo n-1 caracteres o hasta
el carcter de la nueva lnea que lo incluye; la cadena se termina con \0. Regresa a la cadena s o el valor
NULL, si existe error o es el nal del archivo.
5. int fputc(int c, FILE *flujo). Escribe el carcter c en el ujo. Asimismo, regresa el carcter c o EOF,
si hay error.
6. int fputs(char *s, FILE *flujo). Escribe la cadena s en el ujo y el carcter \n; regresa un valor
no negativo o EOF, si existen errores.

Los ujos estndares de entrada, de salida y de error son accesibles tambin como ujo de usuario abierto. Sus nombres
estn denidos en la biblioteca stdio.h y son: stdin, stdout, stderr. La apertura y el cierre se realizan automti-
camente al inicio de la ejecucin y al nal del programa.

Ejemplo

Considrese un programa, cuyo nombre es cadena.dat, que lee un archivo, extrae y cuenta cuntas palabras hay
(una palabra se considera una sucesin de caracteres imprimibles separados por separadores, ya sea un espacio, una
tabulacin o una lnea nueva).

/* programa que abre un archivo y


lee palabras separadas por separadores o linea nueva */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])


{
FILE *f1;
char cadena[80];

int i, ff;

f1 = fopen(cadenas.dat, r);
if (f1 == NULL)
{
printf(problemas de apertura del archivo %s.\n, cadenas.dat);
exit(1);
}
i = 0;
printf(Las palabras son:\n);
while(!feof(f1))
{ ff = fscanf(f1, %s, cadena);
if (ff == EOF)
break;
puts(cadena);
i++;
}
printf(hay %d palabras\n, i);
fclose(f1);
exit(0);
}

Si en este programa no hay problemas de apertura, el archivo se recorre en una estructura iterativa while. Si se en-
cuentra EOF, en el caso de lectura errnea, se sale y tambin usa una salida.
193
*OUSPEVDDJOBMBQSPHSBNBDJO

Ejemplo
Considrese la construccin de un archivo que contiene valores otantes entre 0 y pi, y valores de seno y coseno trigo-
nomtricos:
/* programa que escribe en un archivo valores flotantes
y valores de las funciones trigonometricas */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main(int argc, char *argv[])


{
FILE *f1;
float c;

f1 = fopen(valores.dat, w);
if (f1 == NULL)
{
printf(problemas de apertura del archivo %s.\n, lista.dat);
exit(1);
}
for (c = 0.0; c <= M_PI; c += 0.01)
fprintf(f1, %f %f %f\n, c, sin(c), cos(c));
fclose(f1);
exit(0);
}

Si el archivo se puede abrir, el programa no escribe nada en el ujo stdout y crea un archivo con 315 lneas de valores.

Ejemplo
En ocasiones, la partida de un juego puede interrumpirse y reanudarse posteriormente, cierto tiempo despus. La
mayora de los juegos diseados para computadora ofrece dos funciones principales, las cuales son capaces de:
1. Guardar una conguracin corriente del juego en un archivo.
2. Cargar un archivo con una conguracin no inicial y proponer que el juego contine.

Con base en lo antes expuesto, queremos proponer estas dos funciones para el juego del gato que fue desarrollado en
una seccin anterior. Con ese n, vamos a tomar un nombre por defecto, para cargar las conguraciones; por ejemplo,
gato.dat.
El juego del gato que se desarroll antes tiene su conguracin denida con base en tres elementos: la matriz del
tablero (tablero[3][3]), el jugador que tiene que hacer un movimiento y el nmero de movimientos realizados. El
jugador que sigue en turno, se puede calcular analizando la matriz tablero; si el nmero de X es mayor que el nmero
de 0, es el jugador 0 quien juega, pero si no es el jugador X. El nmero de movimientos se detecta haciendo la suma
de los valores absolutos del tablero.
Entonces, para guardar una conguracin del juego del gato es suciente con guardar la matriz tablero. La funcin
es muy simple:
int guarda_configuracion()
{
FILE *fgato;
int i, j;

fgato = fopen(gato.dat, w);


194
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

if (fgato == NULL)
return FALSO;
for ( i = 0; i < DIM; i++)
{
for ( j = 0; j < DIM; j++)
fprintf(fgato, %d, tablero[i][j]);
fprintf(fgato, \n);
}
return CIERTO;
}

Para detectar quin es el jugador que sigue en turno, una vez que la matriz est cargada, en el programa hay dos
formas de hacerlo:
1. Se cuenta explcitamente el nmero de cada smbolo y se realiza la comparacin.
2. Se hace la suma de todos los elementos de la matriz tablero.

int carga_configuracion(short int *jugador, short int *paso)


{

FILE *fgato;
int i, j, suma;

fgato = fopen(gato.dat, r);


if (fgato == NULL)
return FALSO;
for ( i = 0; i < DIM; i++)
for ( j = 0; j < DIM; j++)
fscanf(fgato, %hd, &tablero[i][j]);
suma = 0;
*paso == 0;
for ( i = 0; i < DIM; i++)
for ( j = 0; j < DIM; j++)
{
suma += tablero[i][j];
* paso += labs(tablero[i][j]);
}
if (suma == 0)
*jugador = 0;
else
*jugador = 1;
return CIERTO;
}

Entonces, almacenamos aqu todas las declaraciones del tablero con su tipo short int, el cual no obliga a utilizar el
formato %hd. En este caso, los parmetros se transmiten por referencia.

Sntesis del captulo

Para trabajar con un volumen importante de datos, ya sea de un tipo bsico o de un tipo complejo, una buena solucin
es trabajar con arreglos unidimensionales o multidimensionales. En estos casos, los arreglos se guardan como una zona
195
*OUSPEVDDJOBMBQSPHSBNBDJO

de memoria contigua y la exploracin de estas estructuras se realiza al nivel de elemento. El acceso a un elemento del
arreglo se realiza indicando el nombre del arreglo y el ndice del elemento al interior del arreglo.
Un arreglo se considera como una variable de tipo apuntador del tipo de los elementos del arreglo; por lo general,
el arreglo puede tener una o ms dimensiones. De esta forma, para cada dimensin hay un ndice que permite acceder
al elemento; dicho ndice es un valor entero entre 0 y dimensin -1. Asimismo, un arreglo puede ser un parmetro
de una funcin.
La asignacin de la zona de memoria para los elementos de un arreglo se hace, generalmente, de manera esttica, al
momento de la declaracin del arreglo. No obstante, tambin existe la posibilidad de asignar y reasignar dinmicamente
la memoria para los arreglos con una sola dimensin.
El tratamiento de las cadenas de caracteres se hace a travs de arreglos de caracteres que contienen, en cada ele-
mento, un carcter especial al nal, como nal de cadena.
Para el manejo de los caracteres, existen varias funciones de las bibliotecas estndares.
Si se conoce el manejo de arreglos y cadenas de caracteres, el lenguaje C ofrece la posibilidad de escribir programas
con parmetros.
Los parmetros actuales del ejecutable se tratan al inicio del programa, mientras que el bloque main se maneja a
travs del tratamiento de los parmetros formales argc y argv, que aparecen en el prototipo del main.
Si los parmetros del ejecutable deben tratarse como nmeros, entonces se aplican las funciones de conversin de
tipo atoi y atof.
El lenguaje C tambin maneja la nocin de archivo texto o binario, tratndolo como un ujo en el cual se puede leer
o escribir.
Por su parte, el ujo se construye al momento de la apertura del archivo, la cual se realiza a travs de una funcin
estndar; siempre es obligatorio que el archivo se cierre.

Bibliografa
s Ritchie, Dennis y Kernighan, Brian, El lenguaje de programacin C, 2a. edicin. Pearson Educacin, Mxico, 1991.

s Tondo, Clovis L. y Gimpel, Scott E., The C Answer Book - the Second Edition, Prentice Hall, Estados Unidos, 1983.

Ejercicios y problemas

Para los ejercicios en los que se pide escribir una funcin, es indispensable escribir el programa principal
(main), en el que se debe hacer al menos una llamada a su funcin.

1. Desarrollar un programa que lea un arreglo de mximo 100 valores enteros positivos y calcule el
mximo comn divisor de todos los valores introducidos.

2. Vericar si un nmero es o no la mediana de un conjunto de valores guardados en un arreglo. Los


datos son los siguientes:
s Las entradas del programa son: el arreglo (entero o otante) y el valor posible de la mediana (del
mismo tipo que el arreglo).

196
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

s La salida es un mensaje S o No. El valor de entrada es o no el valor mediana del arreglo.


s La mediana es un valor mayor que la mitad de los valores y menor que la otra mitad.
s Si hay 2N valores, la mediana es mayor que N valores y menor que N otros valores.
s Si hay 2N+1 valores, la mediana que pertenece al arreglo y es mayor que N valores y menor que N
otros valores. Ejemplos: Para el arreglo 9 0 9 8 0, hay 8, que es el valor mediana.
s Para el arreglo 19 9 39 0, el valor de 8 puede ser el valor mediana del arreglo.

3. Crear un programa que lea un arreglo y luego:

a) Verique si el arreglo tiene sus valores en orden creciente. Es decir, que para un arreglo X de dimen-
sin N:

X[i] X[i + 1],  i = 1, N 1

b) Si el arreglo es en orden creciente, calcule el valor de su mediana.

4. Escribir una funcin que calcule la desviacin estndar de los valores de un arreglo. Su prototipo sera:
float desviacion(float A[], int N)

5. Escribir un programa C que obtenga los dos valores ms grandes de un arreglo de nmeros enteros.
Cada valor debe estar acompaado por su posicin.
Ejemplos
s Para el arreglo: 5, 8, 10, 13, 2, 5, los dos valores ms grandes son 13 y 10, con las posiciones (ndi-
ces) 3 y 2.
s Para el arreglo: 5, 5, 0, 1, 0, 1, 2, 3, 5, los dos valores ms grandes son 5 y 5, con las posiciones 0
y 1 o (La solucin no es nica!): los valores ms grandes 5 y 5, con las posiciones 1 y 8.

6. Escribir una funcin que calcule dos valores (el elemento mnimo y el elemento mximo de un arreglo).
El prototipo debe ser:
void min_max(float A[], int N, float *min, float *max)

7. Escribir una funcin que calcule el promedio olmpico de los valores de un arreglo. El promedio olmpico
de un conjunto de valores se calcula tomando todos los valores, excepto los del elemento mnimo y el
elemento mximo.

8. Escribir tres funciones para aadir un elemento a un arreglo, conforme a las siguientes posiciones:
s En la primera posicin.
s En la ltima posicin.
s En una posicin intermedia.
Los prototipos seran de la forma:

void anadir(int X[], int *N);


void anadir_posicion(int X[], int *N, int pos)

197
*OUSPEVDDJOBMBQSPHSBNBDJO

9. Trabajo con polinomios. Enriquecer la coleccin de funciones que trabaja con polinomios escribiendo
las siguientes funciones:

a) Una funcin para la diferencia:

void diferencia(float P[], float Q[], float R[]);

b) Una funcin para el producto:

void producto((float P[], float Q[], float R[]);

c) Una funcin para la operacin de divisin que regrese otros dos polinomios:

void (float P1[], float P2[], float Q[], float R[]);

Donde: el polinomio P1 es el dividendo, P2 es el divisor, Q es el cociente y R es el residuo.

d) Una funcin que verique que el polinomio es 0 (P(X) = 0)

int es_cero(float P[]);

e) Una funcin que verique si el valor x es o no una raz del polinomio:

int es_raiz(float P[], float x);

f) Una funcin que detecte si el valor de un polinomio en un punto es positivo, cero o negativo:

int signo(float P[], float x);


La funcin regresar 1, 0 o 1.

g) Una funcin que genere una primitiva del polinomio (la primitiva de una funcin matemtica tiene
la propiedad de que su derivada es la funcin inicial):

void primitiva(PI[], PR[]);

10. Desarrollar una funcin que construya, en un segundo arreglo, la imagen en espejo de un arreglo; el
prototipo sera:
void espejo(int X[], int Espejo[], int N);

11. Desarrollar una funcin que transforme el arreglo construyendo la imagen en espejo en el arreglo mis-
mo; el prototipo sera:
void espejo_transformacion(int X[], int N);

12. Escribir un programa que cuente el nmero de apariciones de un arreglo P (un patrn) al interior de un
arreglo de trabajo A. La dimensin del arreglo A es M, mientras que N es la dimensin del arreglo P, con
N mucho ms pequeo que M. Por ejemplo: el patrn1, 2 se encuentra tres veces en el arreglo 0, 1, 2,
1, 1, 1, 2, 2, 1, 2. Y el patrn 1, 2, 1 se encuentra una sola vez en este mismo arreglo.

13. Escribir dos funciones para hacer los corrimientos de un arreglo a la izquierda y un arreglo a la derecha
con un nmero de posiciones:

198
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

void corrimiento_dr(int X[], int dimension, int pos)


void corrimiento_iz(int X[], int dimension, int pos);

14. Desarrollar una funcin que permute los elementos de un arreglo:


int permutacion(int X[], int Y[], int N);

15. Justicar matemticamente que la funcin del ejercicio anterior es correcta y analizar si la funcin puede
generar cualquier permutacin, con una misma probabilidad.

16. Generacin de matrices particulares 5 7. Desarrollar un programa para cada una de las siguientes
matrices: Una matriz que contiene el valor 0 para cada elemento, excepto la primera y la ltima lneas y
la primera y la ltima columnas, las cuales contienen el valor 1.
s Una matriz que contiene los valores 0 y 1, de manera alternada, como un tablero de ajedrez.
s Una matriz formada por columnas sucesivas de 0 y 1.

17. Si el programa lee una matriz 3 3, llenar con 0, 1 y 1 en el sentido del juego del gato; vericar que
la matriz pueda ser una conguracin posible del juego; es decir, establecer si se cumplen las dos condi-
ciones siguientes:
a) Hay el mismo nmero de 1 y 1 o la diferencia entre los nmeros de 1 y de 1 vale 1.
b) Si existe un ganador, el ganador es nico (en la matriz no hay tres en lnea para X y tres en lnea para
Y).

18. Si se introduce una matriz 3 3, que es una matriz de conguracin del juego del gato (que cumple
con las dos condiciones del ejercicio anterior), hacer una funcin para cada una de las tres condiciones
siguientes:
a) Calcular qu jugador sigue o si la partida es nita.
b) Vericar si existe una posicin que un jugador pueda ocupar para ganar, por ejemplo:

Figura 4.4 Conguracin con una posicin que permite ganar al jugador a quien le toca el prximo turno.

c) Si el jugador no puede ganar inmediatamente, vericar si hay una posicin que debe ocupar para no
perder; por ejemplo:

199
*OUSPEVDDJOBMBQSPHSBNBDJO

Figura 4.5 Conguracin con una posicin que debe ser ocupada inmediatamente para que el jugador no
pierda.

19. Proponer otra forma de salida para el juego del gato, con un tablero ms visible que ocupe, por ejemplo,
una parte ms grande de la ventana de la pantalla de la computadora. Cul es la funcin que debe ser
modicada? Desarrollar completamente su funcin.

20. Desarrollar una versin menos sencilla para el juego del gato, conocida como el juego: Cinco en lnea,
donde el tablero es ms grande (por lo menos de 10 10) y se requiere que el ganador tenga cinco
puntos en lnea (ya sea por lnea, por columna o por diagonal).

21. Trabajo con una matriz dispersa. En matemticas y en computacin se considera como dispersa una
matriz de gran tamao, de la cual varios valores son 0. Por ejemplo, la siguiente matriz:
0 1 0 2 0
3 0 7 0 0
0 0 0 2 6
Con base en esta matriz dispersa:

a) Escribir una funcin que verique si una matriz es dispersa o no (por ejemplo, si tiene ms de 60% de
elementos 0, la matriz se considera dispersa).

b) Escribir una funcin que genere una matriz dispersa.

c) Escribir una funcin que tome de entrada una matriz dispersa que genere tres arreglos de la misma di-
mensin, que contengan el valor de la matriz y sus ndices.

Por ejemplo, para la matriz que se present antes se genera:


V {1 2 3 7 2 6}
I {0 0 1 1 2 2}
J {1 3 0 2 3 4}

22. Escribir una funcin que verique si una cadena de caracteres contiene todos sus caracteres distintos (por
ejemplo, (numero) y (nombre), tienen caracteres distintos, pero (programacion) no, porque la letra a
y la letra o aparecen dos veces).

200
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

23. Utilizando la funcin anterior, escribir un programa en C que lea dos cadenas de caracteres, vericar si cada
cadena de caracteres tiene caracteres distintos y si la concatenacin de las dos cadenas tambin tiene carac-
teres distintos. Ejemplos:

s Cada una de las dos cadenas numero y nombre tiene caracteres distintos, pero su concatenacin nu-
meronombre no.

s 123 y abcdefg tienen caracteres distintos y su concatenacin 123abcdefg tambin.

24. Leer una cadena de caracteres bastante grande que contenga letras, cifras, espacios y otros caracteres im-
primibles. Calcular cuntas cifras y cuntos nmeros hay. Recurdese que nmero es una sucesin mxima
de cifras.

Ejemplo: Si la cadena de entrada es Hoy es 2 de diciembre de 2010. En el saln hay 38 alumnos y 1


profesor, el programa debe indicar: 8 cifras, 4 nmeros.

25. Escribir un programa que transforme una cadena de caracteres escritos en una base b, en un nmero en
base 10. El programa debe realizar los siguientes pasos:
s Leer un valor entero b y vericar si este valor cumple la restriccin: 2 < b < 16.
s Leer una cadena de caracteres y vericar si cada carcter es una cifra o una letra de a hasta f.
s Si las dos condiciones precedentes no se cumplen, el programa debe terminar.
s Si las dos condiciones se cumplen, la cadena leda contiene bien denidas las cifras de un nmero
representado en base de b; se debe calcular el nmero en base 10 con esta representacin.
Ejemplos: si la cadena leda es: 1020 y la base b es 3, el numero representado
es
1 33 + 2 31 = 33.
Si la cadena es 1f1f y la base es 16 el nmero representado es:
163 + 15 162 + 16 + 15.

26. Escribir un programa que abra un archivo de texto y lea las lneas del archivo. En la salida el programa escribe
las lneas del archivo precedidas del nmero de lnea.

27. Escribir un programa que lea un archivo de texto que contenga lneas con informacin signicativa y lneas
de comentarios. Una lnea de comentario inicia con el carcter #. El programa debe crear un nuevo archivo
que no contienga las lneas de comentarios y parar las lneas signicativas se eliminan los espacios y las
tabulaciones de enfrente del primer carcter imprimible.
Por ejemplo, para el archivo que contiene:
# Familia Molina
Ana
Pedro
Nicolas
# Familia Perez
Olga
Rodrigo
El archivo de salida del programa debe ser:
Ana

201
*OUSPEVDDJOBMBQSPHSBNBDJO

Pedro
Nicolas
Olga
Rodrigo

28. Crear un programa de nombre que_hacer.c, cuyo ejecutable sera que_hacer que toma en la lnea de
entrada los nombres y nmeros de telfono y escribe:
s Para un nombre (sucesin de letras): escribir correo a
s Para un nmero de telfono (sucesin de cifras): llamar a
s Para otros casos (otras sucesiones de caracteres): nada.
Por ejemplo, la salida de la lnea es:
./que_hacer Liliana mama a2b3 1919 !!
sera:
escribir correo a Liliana
escribir correo a mama
llamar a 1919
29. Modicar el programa general del juego de gato para permitir que:

s Al inicio de un partido se cargue una conguarcin (si los jugadores lo desean, una conguracin cargada).

s En cualquier momento del partido, se guarde la conguracin y termine el programa.

30. Hacer un programa mi_wc.c que funcione como la lnea de comando Unix wc; es decir, que tome como
argumento en la lnea de comando un nombre de archivo y, si el archivo existe y es accesible, realizar la
cuenta de los nmeros de lneas, de palabras y de caracteres. Una salida de la lnea de comando Unix wc
es:
> wc cascara.c
9 15 105 cascara.c

31. Escribir un programa en lenguaje C que realice las siguientes operaciones:


s Lea una cadena de caracteres (de mximo 200 caracteres); esta cadena deber contener palabras
(sucesin de letras) separadas por uno o ms espacios.
s Construya una cadena de caracteres que contenga nicamente las palabras de ms de 2 caracteres y
separadas por un espacio.
s Escribir la cadena nal en la pantalla.

Por ejemplo, si la cadena inicial es:


mi gato tiene en su boca un raton
la cadena final debe ser:
gato,tiene,boca,raton

32. Escribir un programa que:


s Lea un archivo con el nombre datos.dat; el cual debe contener lneas con nmeros y cada lnea
contiene un nmero variable de nmeros enteros (entre 2 y 10 nmeros separados por un espacio).

202
$BQUVMPt"SSFHMPT DBEFOBTEFDBSBDUFSFT BSDIJWPT

s Escriba en un archivo calculos.dat, para cada lnea del archivo de entrada, una lnea con el n-
mero de valores, el valor mnimo, el valor mximo, el promedio aritmtico y el valor de la mediana
(cuidado, los ltimos dos valores son otantes).
Por ejemplo, el archivo de entrada siguiente:
2 10 12
3 3 3 3 3 3 3
0 9 8 7 6 5 4 3

Donde el archivo de salida sera:


3 2 10 8.0 10.0
7 3 3 3.0 3.0
8 0 9 5.25 5.5

203
Introduccin a la programacin 6OJEBEt%FMBMHPSJUNPBMQSPHSBNB
5 ,Z[Y\J[\YHZKLKH[VZ
;PWVZHIZ[YHJ[VZ

Contenido 0ILA COLAS DOBLESCOLAS


#ONJUNTOMATEMTICO
5.1 Introduccin Grafos
5.2 Tipos de datos denidos por el usuario 5.5 Problemas resueltos
Nombramiento de los tipos #RIBADE%RATSTENES
Tipos estructurados Problema de Josephus
Denicin de tipos estructurados Sntesis del captulo
Trabajo con variables de tipo estructurado Bibliografa
Apuntadores de los tipos compuestos Ejercicios y problemas
Tipos estructurados referenciados por otros tipos
estructurados
Tipos estructurados auto referenciados Objetivos
Tipo enumeracin y tipo unin s #ONOCERLASINTAXISYLASEMNTICADELLENGUAJE# LOQUE
Problema resuelto permite la denicin de nuevos tipos de datos.
5.3 Estructuras de datos s -ANEJARSUSPROPIOSTIPOSDEDATOS PONIENDOENPRCTICALA
Arreglos creacin y el uso de nuevos tipos de datos.
Listas ligadas s 4RABAJARCONESTRUCTURASDEDATOSAVANZADASLISTASLIGADAS
Listas circulares SIMPLESODOBLES RBOLES ETCTERA
Listas doblemente ligadas s #ONOCERYUTILIZARTIPOSABSTRACTOSDEDATOS
5.4 Tipos abstractos de datos s #ONCEBIRYDElNIR SEGNELPROBLEMA ELMEJORTIPODEDATOS
Listas para usarse.
204 204
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

5.1 Introduccin

El principal objetivo de este captulo es presentar y desarrollar las nociones de tipo denido y trabajar con cada unos de
estos tipos, con el n de manejar estructuras de datos avanzadas y tipos abstractos de datos.

5.2 Tipos de datos denidos por el usuario

El lenguaje C ofrece la posibilidad de trabajar con otros tipos de datos, adems de los tipos estndares, como escalares,
arreglos o apuntadores. Con estos nuevos tipos de datos, que estn denidos por el usuario, se procede de la misma
manera que con cualquier otra herramienta del lenguaje (variables, funciones, etiquetas, entre otras). El nuevo tipo se
dene mediante una sola declaracin y luego se utiliza tantas veces como sea necesario.
La visibilidad del tipo denido es la misma que la de la denicin de las variables. Si las declaraciones del tipo se
hacen al interior de un bloque, nicamente al interior de dicho bloque se puede usar el nuevo tipo; en caso contrario, la
solucin que se aplica, generalmente, es la siguiente: los nuevos tipos se declaran a nivel global, fuera del bloque main.
Los tipos denidos por el usuario se pueden utilizar de la misma forma que los tipos estndares: en la declaracin de
variables, como tipo de funcin (tipo del valor regresado), como tipo de parmetros en funciones, para la denicin
de nuevos tipos nombrados o implcitos, etctera.

Nombramiento de los tipos

Los tipos estndares y los tipos denidos por el usuario pueden ser nombrados con otro nombre; el nombre otorgado
constituye un identicador o un sinnimo de un tipo conocido; aunque tambin se utiliza para identicar un nuevo tipo.
La introduccin del nuevo nombre se hace con la declaracin typedef:

typedef tipo_inicial nombre_nuevo_tipo;


typedef tipo_inical nombre_nuevo_tipo[constante];
typedef tipo_inicial *nombre_nuevo_tipo;
. . .

Ya sea por un sinnimo de tipo, por un tipo arreglo o por un tipo apuntador, aqu typedef es una palabra clave, mientras
que tipo_inicial es el tipo que produce el nuevo tipo nombrado: nombre_nuevo_tipo. El uso de la declaracin
typedef es muy parecido a la declaracin de una variable, solo en el caso para el objeto que se construye con el nuevo
tipo, por lo que no se afecta ninguna zona de la memoria.
Ejemplos
typedef int entero;
typedef entero arreglo10[10];
typedef entero *apuntador_entero;
typedef entero (*funcion_bi_operandos)(entero, entero);

En esta parte:
s entero es un sinnimo del tipo estndar int.
s arreglo10 es el tipo que corresponde a un arreglo de dimensin 10 de elementos de tipo entero.

205
Introduccin a la programacin

s apuntador_entero es un tipo apuntador por una zona de memoria que contiene un valor de tipo entero.
s funcion_bi_operandos es el tipo apuntador de una funcin que regresa un tipo entero y sus dos parme-
tros tambin son de tipo entero.
Los nombres o identicadores que se dan a los nuevos tipos son seleccionados por el usuario, solo se impone que sean
identicadores nicos. No obstante, para mejorar la lectura y el trabajo con los programas se aconseja utilizar identicado-
res escritos solo con maysculas (por ejemplo, ENTERO, ARREGLO10, etc.) o identicadores de la forma Tnombretipo
o de la forma T_nombretipo (por ejemplo, T_entero, T_arreglo10, ).
En la prctica, los nuevos tipos se utilizan como cualquier otro tipo, solo se debe considerar hacer la conversin de
tipo por asignaciones o por llamadas de funciones estndares.
Ejemplo
En el siguiente programa se utilizan nuevos tipos derivados del tipo int, a los cuales se aplica la conversin de tipo
(cast); a su vez, sobre estos tipos se aplica la funcin estndar sizeof y se hacen las asignaciones.

/* programa que trabaja con nuevos tipos */

#include <stdio.h>
#include <stdlib.h>

typedef int T_entero;


typedef T_entero T_arreglo10[10];
typedef T_entero *T_apuntador_entero;

int main(int argc, char *argv[])


{
T_arreglo10 V;
T_entero a,*b;
T_apuntador_entero d;

int c = 17;
int i;

a = (T_entero)c;
b = &a;
d = (T_apuntador_entero)(&c);
d = b;

for (i =0; i < 10; i++)


V[i] = (T_entero)(i*i);
d = &V[3];
printf( a = %d\n, (int)a);
printf( dimensiones : T_entero = %ld, T_arreglo10=%ld, T_apuntador_entero=%ld\n,
sizeof(T_entero), sizeof(T_arreglo10), sizeof(T_apuntador_entero));
printf( El valor apuntado por b :%d\n, (int)(*b));
printf( El valor apuntado por d :%d\n, (int)(*d));
exit(0);
}

Este programa produce la siguiente salida:

a = 17 dimensiones : T_entero = 4, T_arreglo10 =40, T_apuntador_entero=8


El valor apuntado por b :17
El valor apuntado por d :9

206
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Ejemplo
En el siguiente programa se utilizan el tipo de apuntador de funcin y la llamada de una funcin de tipo,
ambos denidos por el usuario.

/* programa con funcion de tipos definidos por el usuario y con tipo de


apuntador de funcion */

#include <stdio.h>
#include <stdlib.h>

typedef int T_entero;


typedef T_entero (*T_funcion_bi_operandos)(T_entero, T_entero);

T_entero suma(T_entero x, T_entero y)


{
return x + y;
}

int main(int argc, char *argv[])


{
T_entero a = 12, b = 10, x;
T_funcion_bi_operandos f;

f = &suma;
x = f(a,b);
printf(La suma de a y b es : %d\n,x);
printf( El doble de a es : %d\n, suma (a, a));
exit(0);
}

Este programa produce la siguiente salida:

La suma de a y b es : 22
El doble de a es : 24

Tipos estructurados

Los tipos estructurados (o compuestos) permiten conjuntar diversos datos e informaciones que se relacionan
con un mismo concepto. De esta forma, un tipo estructurado se caracteriza por tener ms de un valor de
cualquier tipo denido, como un tipo estndar u otro tipo estructurado, e incluso otros tipos estructurados y
apuntadores de cualquier tipo; un ejemplo de esto es cuando se requiere trabajar con fechas, ya que una fecha
se dene por el valor del da, el valor del mes y el valor del ao.
Si trabajamos con tipos conocidos, es posible trabajar con tres variables de tipo entero (short o int)
para una sola fecha; no obstante, en este caso se presentan algunas desventajas evidentes, como dar nombre a
las variables y obtener un nmero grande de parmetros para las funciones que trabajaran con este concepto.
Por ejemplo, en el caso de una funcin que debe comparar dos fechas para indicar si una fecha es mayor que
otra, es decir, si la primera fecha es ms reciente que la otra, esta tendra un prototipo con seis parmetros:
int compare_fechas(int dia1, int mes1, int ano1, int dia2, int mes2,
int ano2)

207
Introduccin a la programacin

Denicin de tipos estructurados

La nocin de tipo estructurado o compuesto permite agrupar valores de varios (otros) tipos. Entonces, un tipo estruc-
turado se dene como:

struct nombre_tipo_estructurado
{
tipo1 campo1;
tipo2 campo2;
...
}

Aqu, struct es una parabra clave; nombre_tipo_estructurado es un identicador que debe ser nico; tipo1,
tipo2 son tipos conocidos, estndares o denidos por el usuario al mismo nivel o a un nivel ms alto, y campo1, cam-
po2 son los identicadores que designan las componentes del nuevo tipo estructurado. Sin embargo, estos identicado-
res son nicos solo al interior de la denicin del tipo.
Es importante resaltar que un tipo estructurado siempre tiene al menos una componente, y no un lmite para el
nmero total de sus componentes.
Esta denicin de tipo tiene efecto a nivel del compilador; permite usar el tipo para denir variables simples, apun-
tadores, arreglos y otros tipos, entre otras cosas. La construccin struct indica este nuevo tipo denido por el usuario;
sin embargo, no se hace ninguna asignacin de memoria.
La denicin de un tipo estructurado puede estar seguida de la declaracin de variables de este tipo o puede incluirse
en una declaracin typedef.
Para el ejemplo de trabajo con fechas, podemos usar la siguiente denicin:

struct fecha {
int dia;
int mes;
int ano;
};

Las variables de tipo struct fecha se declaran de la siguiente manera:

struct fecha f1, f2={12, 07, 2011};

En este caso, la variable f1 no est inicializada, al contrario de la variable f2 que s lo est; la inicializacin se realiza
indicando los valores de cada campo.
Al momento de la declaracin de estas dos variables, se hace la asignacin de memoria para cada una de estas
variables; el espacio de memoria asignado tiene un tamao de 12 bytes:
12 = 3 x sizeof(int)
La funcin sizeof se aplica tanto a las variables del tipo compuesto como al mismo tipo.
El cdigo siguiente:

printf( espacio-f1 = %ld espacio-f2 = %ld\n, sizeof(f1), size of(f2));


printf( espacio del tipo (struct fecha) = %ld\n, sizeof(struct fecha));

produce:

espacio-f1 = 12 espacio-f2 = 12
espacio del tipo (struct fecha) = 12

208
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

En este caso, tambin se pueden usar las siguientes declaraciones, las cuales son semnticamente equivalentes al ejem-
plo anterior:

struct fecha {
int dia;
int mes;
int ano;
} f1, f2={12, 07, 2011};

typedef struct fecha {


int dia;
int mes;
int ano;
} Tfecha;
Tfecha f1, f2={12, 07, 2011};

Para el primer caso, las variables f1 y f2 estn declaradas al mismo nivel que el tipo mismo. En cambio, en el segundo
ejemplo, Tfecha es un nombre de tipo que es equivalente al tipo struct fecha.
Por tanto, es posible que uno o ms campos de un tipo estructurado sean de otro tipo estructurado. En el si-
guiente ejemplo, los tipos estructurados struct fecha y struct persona permiten manejar fechas y nombres
(nombre y apellido) de personas, ya que el siguiente tipo compuesto describe informacin sobre un curso en una
universidad:

# define MAX_CHAR 50
# define MAX_HORAS_CURSO 40

struct fecha {
int dia;
int mes;
int ano;
};

struct persona {
char nombre[MAX_CHAR];
char apellido[MAX_CHAR];
};

struct curso
{
char intitulado[MAX_CHAR];
int numero_horas;
struct persona profesor;
struct persona ayudante;
char salon[MAX_CHAR];
struct fecha fecha_inicio;
struct fecha fecha_examen_global;
struct fecha fecha_curso[MAX_HORAS_CURSO];
};

En el ejemplo anterior, dos campos son de tipo estructurado struct fecha; dos son de tipo struct persona, y
uno es un arreglo de tipo struct fecha. Los campos intitulado, numero_horas y salon son de tipo estandar.

209
Introduccin a la programacin

Trabajo con variables de tipo estructurado

El acceso a los campos de una variable de tipo estructurado se efecta con el operador . (punto). Por su parte, la cons-
truccin de v.campo permite acceder al valor de campo de nombre campo, parte del tipo compuesto de la variable v.
La construccin de v.campo se trata como una variable del tipo indicado en la denicin del tipo estructurado, en
las asignaciones, en los parmetros actuales de llamada de funciones, en las expresiones, etctera.
Ejemplo
s Para las variables f1 y f2 de tipo struct fecha, las construcciones f1.ano y f2.ano indican el contenido
del tercer campo de cada variable y se tratan como variables de tipo int.
s Por una variable CC de tipo struct curso, CC.intitulado es de tipo char[] cadena de caracteres,
CC.numero_horas es de tipo int, CC.fecha_curso es un arreglo de tipo struct fecha, CC.fecha_
curso[1].mes es un valor de tipo int.
La inicializacin al momento de la declaracin de una variable de tipo estructurado s es posible; esta se realiza de la
misma manera que la inicializacin de los arreglos, siguiendo los pasos que se describen a continuacin:
1. La lista de valores de cada campo se indica entre corchetes.
2. La inicializacin puede ser parcial, indicando solo algunos valores de campos.
3. Como en el caso de los arreglos no se pone ningn valor por defecto, el valor de los campos sin inicializacin est
no determinado.

Ejemplos
struct fecha f1, f2={12, 07, 2011};

struct curso CC = {Algebra 2, 25, {Maria, Lopez}, {Mario, Rodriguez},


S.Azul, {15, 9, 20011}, {12, 12, 2011}, {{}, {18, 19, 2011}}}, CYY = {Geometria
analitica, 40, {invitado}};

En este caso, la variable f2 es completamente inicializada, mientras que la variable f1 no es inicializada.


Por su parte, la variable CC es inicializada por la mayora de los campos, excepto el campo fecha_curso, el cual
es un arreglo; en tanto, el segundo elemento s tiene una inicializacin. La variable CYY es parcialmente inicializada, por
los tres primeros campos, mientras que el campo profesor es parcialmente inicializado.
El operador de asignacin = funciona de manera global; esto signica que s es posible aplicar este operador entre di-
versas variables de tipos compuestos, a excepcin de una sola restriccin: que la parte izquierda y la parte derecha sean
del mismo tipo.
Tambin es posible poner en la parte izquierda de un operador de asignacin cualquier campo de una variable de
tipo estructurado, aunque siempre con la nica restriccin de equivalencia de tipos.
Los efectos del operador de asignacin de los tipos compuestos (estructurados) son exactamente los mismos que
para los tipos escalares:1
1. El valor obtenido se copia en la parte derecha.
2. La direccin de memoria de la variable se copia en la parte izquierda.
Ejemplo
En el cdigo:

1
Se recuerda al lector que el operador de asignacin no funciona con un arreglo en su parte izquierda.

210
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

struct fecha f1, f2={12, 07, 2011};

f1 = f2;

la variable f1 toma como valor el mismo valor que la variable f2.


Ejemplo
En el cdigo:

struct curso CC = {Algebra 2, 25, {Maria, Lopez}, {Mario, Rodriguez},


S.Azul, {15, 9, 20011}, {12, 12, 2011}, {{}, {18, 19, 2011}}}, CYY = {Geometria
analitica, 40, {invitado}};

printf(El curso %s inicia en el mes de %02d de %4d\n,


CC.intitulado, CC.fecha_inicio.mes, CC.fecha_inicio.ano);
CC.fecha_curso[0] = CC.fecha_inicio;
printf( Primera clase del profesor %s es %02d/%02d/%4d\n,
CC.profesor.apellido, CC.fecha_curso[0].dia,
CC.fecha_curso[0].mes, CC.fecha_curso[0].ano);
CYY.ayudante = CC.ayudante;
printf(El ayudante para el curso %s es %s %s\n,
CYY.intitulado, CYY.ayudante.nombre, CYY.ayudante.apellido);

// CYY.profesor = {John, Johnson}; asignacion incorrecta strcpy(CYY.profesor.


nombre, John);
strcpy(CYY.profesor.apellido, Johnson);
printf(El profesor para el curso %s es %s %s\n,
CYY.intitulado, CYY.profesor.nombre, CYY.profesor.apellido);

se hacen varias asignaciones:


s La asignacin tiene como objetivo hacer una copia del valor que se encuentra del lado derecho del signo igual (=).
s La asignacin tambin es una asignacin entre variables del mismo tipo compuesto struct persona, donde las
cadenas de los campos nombre y apellido de la parte derecha se copian en los campos del mismo nombre
en la parte izquierda.
s La asignacin como comentario no es correcta; la forma con el uso de corchetes se usa solo en el caso de la ini-
cializacin, al momento de la declaracin de variables.
s Para asignar valores a los campos componentes de CYY.profesor (a saber, CYY.profesor.nombre y CYY.
profesor.apellido) se usa la funcin strcpy, ya que estos son de tipo cadena de caracteres.
El fragmento del programa anterior produce:

El curso Algebra 2 inicia en el mes de 09 de 20011


Primera clase del profesor Lopez es 15/09/20011
El ayudante para el curso Geometria analitica es Mario Rodriguez
El profesor para el curso Geometria analitica es John Johnson

El lenguaje C permite utilizar los tipos compuestos como tipos de parmetros de funcin y tambin como tipos de re-
greso de una funcin. La transmisin de los parmetros se hace por valor o por referencia; en el caso de transmisin por
valor en la pila de llamadas, se copia el contenido completo de la variable de tipo estructurado.
Para el tipo struct fecha que fue denido antes, podemos decir, por ejemplo, que posee las siguientes funciones:

void imprima_fecha(struct fecha f)


{

211
Introduccin a la programacin

printf( %02d/%02d/%4d , f.dia, f.mes, f.ano);


}
int equivalencia_fechas(struct fecha x1, struct fecha x2)
{
if (x1.ano == x2.ano && x1.mes == x2.mes && x2.dia == x2.dia)
return 1;
else
return 2;
}

int compar_fechas(struct fecha f1, struct fecha f2)


{
if (f1.ano < f2.ano)
return 1;
if (f1.ano > f2.ano)
return -1;
if (f1.mes < f2.mes)
return 1;
if (f1.mes > f2.mes)
return -1;
if (f1.dia < f2.dia)
return 1;
if (f1.dia > f2.dia)
return -1;
else
return 0;
}
struct fecha inicio_ano(struct fecha f)
{
struct fecha aux = {1,1};
aux.ano = f.ano;
return aux;
}

En este caso, la funcin imprima_fecha realiza la impresin en un formato conveniente con solo una llamada a
printf. Las funciones equivalencia_fechas y compar_fechas sirven para comparar valores de tipo estructu-
rado. Por su parte, la funcin inicio_ano es una funcin que construye la fecha del 1 de enero del mismo ao que
el valor transmitido por el parmetro. A continuacin se presenta el ejemplo de un programa que hace llamadas a estas
funciones:

struct fecha f1, f2={12, 07, 2011}, f3;


int valor;

f1 = f2;
printf( La primera fecha :);
imprima_fecha(f1);
printf(\n);

if (equivalencia_fechas(f1, f2))
printf( Mismas fechas\n);
else
printf( No\n);
imprima_fecha(f1);
f3 = inicio_ano(f1);
valor = compar_fechas(f1,f3);

212
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

switch(valor)
{
case 1 : printf( es antes de );
break;
case -1 : printf( es despues de );
break;
case 0 : printf( es misma que );
break;
}
imprima_fecha(f3);
printf(\n);

La salida de este programa es:

La primera fecha : 12/07/2011


Mismas fechas
12/07/2011 es despues de 01/01/2011

Apuntadores de los tipos compuestos

El trabajo con apuntadores de tipo estructurado es similar al trabajo que se realiza con cualquier otro tipo apuntador.
Por ejemplo, en el siguiente programa apf es un apuntador que, una vez que se realiza la asignacin apf = &f2,
permite modicar directamente el contenido de la variable f2.

struct fecha f1, f2={12, 12, 2011};


struct fecha *apf;

f1 = f2;
printf( La primera fecha :);
imprima_fecha(f1);
printf(\n);

apf = &f2;
(*apf).mes++;
if ((*apf).mes == 13)
{
(*apf).mes = 1;
(*apf).ano++;
}
printf( La segunda fecha transformada:);
imprima_fecha(f2);
printf(\n);

La salida del programa es:

La primera fecha :12/12/2011


La segunda fecha transformada:12/01/2012

La escritura (*apf).dia indica que primero se accede al contenido global de la zona de tipo estructurado y luego se
extrae el campo dia.
En este caso, la novedad es que hay un operador de acceso -> (guin y mayor) que compone las dos operaciones:
la de acceso al valor compuesto y la de extraccin de un campo.

213
Introduccin a la programacin

Asimismo, el cdigo anterior puede ser reescrito de la siguiente manera:

apf->mes++;
if (apf->mes == 13)
{
apf->mes = 1;
apf->ano++;
}

La construccin (*apf).mes es correcta; sin embargo, se acostumbra usar la construccin ms leble: apf->mes.
El operador -> tiene la ms alta prioridad; por tanto, en la expresin apf->mes++, se evala primero el operador ->
de acceso y luego el operador ++ de incremento. El operador de acceso ., tambin tiene una prioridad alta con respecto
a los operadores aritmticos, lgicos y relacionales.2
En los apuntadores de tipos estructurados tambin es posible utilizar la funcin asignacin dinmica de memoria
malloc, la cual permite asignar una zona en la memoria en donde se puede guardar un valor del tipo compuesto.Se
aconseja liberar la zona de memoria con la funcin free al nal del programa o al nal del tratamiento de la zona de
memoria asignada dinmicamente.
Ejemplo
apf = (struct fecha*)malloc(sizeof(struct fecha));
apf->dia = 6;
apf->mes = 6;
apf->ano = 2011;
printf( La fecha generada es :);
imprima_fecha(*apf);
printf(\n);
free(apf);

En el caso de las funciones con parmetros de tipo estructurado transmitidos por referencia, el acceso a los campos
se hace con el operador de acceso ->.
Ejemplo
void transforma_manana(struct fecha *f)
{
f->dia++;
if (f->mes ==1 || f->mes == 3 || f->mes == 5 || f->mes == 7
|| f->mes == 8 || f->mes == 10) // mes de 31 dias excepto diciembre
{
if (f->dia == 32)
{
f->dia = 1; f->mes++;
}
return;
}
else if (f->mes == 2) // mes de febrero
{
if ((f->dia == 29 && f->ano%4 ! = 0)|| (f->dia =30 && f->ano %4 ==0))
{
f->dia = 1; f->mes = 3;
}
return;

2
Vase la tabla del captulo 2 que contiene las prioridades y las reglas de asociatividad para todos los operadores del lenguaje.

214
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

}
else
if (f->mes == 4 || f->mes == 6 || f->mes == 9 || f->mes ==11)
// mes de 30 dia
{
if (f->dia == 31)
{
f->dia = 1; f->mes++;
}
return;
}
else if (f->dia == 32) // mes de diciembre
{
f->dia = 1; f->mes = 1;
f->ano++;
return;
}
return;
}

Este cdigo es una funcin que transforma la fecha transmitida por parmetros incrementndola. Una llamada a esta
funcin es la siguiente:

apf = (struct fecha*)malloc(sizeof(struct fecha));


apf->dia = 31;
apf->mes = 7;
apf->ano = 2011;
printf( La fecha generada es :);
imprima_fecha(*apf);
printf(\n);
printf( La fecha siguiente es :);
transforma_manana(apf);
imprima_fecha(*apf);
printf(\n);

Este cdigo produce la siguiente salida:

La fecha generada es :31/07/2011


La fecha siguiente es :01/08/2011

Tipos estructurados referenciados por otros tipos estructurados

Un tipo estructurado puede contener un campo de tipo apuntador con cualquier tipo estructurado, incluso puede tener
un apuntador con el tipo estructurado mismo.
Ejemplo
El tipo struct curso_ref posee como campos apuntadores a los tipos estructurados: struct persona y
struct fecha.

struct fecha {
int dia;
int mes;
int ano;

215
Introduccin a la programacin

};

struct persona {
char nombre[MAX_CHAR];
char apellido[MAX_CHAR];
};

struct curso_ref
{
char intitulado[MAX_CHAR];
int numero_horas;
char salon[MAX_CHAR];
struct persona *profesor;
struct persona *ayudante;
struct fecha *fecha_inicio;
struct fecha *fecha_examen_global;
struct fecha *fecha_curso[MAX_HORAS_CURSO];
};

El tipo estructurado contiene campos de tipos clsicos: entero o cadenas de caracteres, pero tambin tiene cuatro apun-
tadores de otros tipos estructurados y un arreglo de apuntadores del tipo struct fecha. La gura 5.1 constituye un
esquema de la estructura del tipo struct curso_ref:

fecha_examen_global fecha_curso
intituladonumero_horas salon profesor ayudante fecha_inicio

struct curso_ref

dia mes ano


dia mes ano dia mes ano
nombre apellidos
struct fecha
struct persona struct fecha struct fecha
nombre apellidos
dia mes ano
struct persona
struct fecha

Figura 5.1

En este caso, lo ms difcil no es la declaracin del tipo sino el trabajo con estos campos de tipo apuntador. Inicialmente,
el apuntador tiene un valor indenido, excepto si se inicializa a NULL; luego, el apuntador apunta sobre una zona de
memoria que ya existe y que fue creada de otra forma, es decir con una llamada a la funcin malloc. En este ltimo
caso, se maneja el operador ->.
Ejemplo
Si queremos llenar una estructura de tipo struct curso_ref, por lo comn se utiliza una funcin de la siguiente
forma:

void lectura_fecha(char mensaje[], struct fecha *f)


{
printf( %s en la forma dia/mes/ano :,mensaje);
scanf(%d/%d, &f->dia, &f->mes, &f->ano);
return;
}

216
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

En el cdigo del programa principal, para cada apuntador de struct fecha, primero se hace una asignacin dinmica
de memoria con malloc y luego se llama a la funcin creacion_lectura_fecha, la cual llena los campos de la
estructura apuntada desde la entrada estndar; enseguida, se cambia el apuntador a esta nueva zona de memoria.

int main(int argc, char *argv[])


{
//struct curso_ref CC;
struct curso_ref CYY = {Geometria analitica, 4,Sala Z23};
//struct fecha *aux;
char cadena[MAX_CHAR];
int i;

printf( Para el curso %s favor de indicar las fechas \n,


CYY.intitulado);
creacion_lectura_fecha(inicio del curso, &CYY.fecha_inicio);
//mismas operaciones por una otra fecha
creacion_lectura_fecha(examen global, &CYY.fecha_examen_global);
// generacion de elementos
for ( i = 0; i < CYY.numero_horas; i++)
{
sprintf(cadena, curso no %d , i +1);
creacion_lectura_fecha(cadena, &CYY.fecha_curso[i]);
}
// imprima los valores guardados
printf(El curso imprima_fecha(*CYY.fecha_inicio);
printf( y termina con el examen global );
imprima_fecha(*CYY.fecha_examen_global);
printf(\n);
printf( curso no 3 : );
imprima_fecha(*CYY.fecha_curso[3 - 1]);
printf(\n);

//libera la memoria por las zonas apuntadas


free(CYY.fecha_inicio);
free(CYY.fecha_examen_global);
for ( i = 0; i < CYY.numero_horas; i++)
free(CYY.fecha_curso[i]);
exit(0);
}

Al nal del programa se liberan todas las zonas de memoria apuntadas asignadas dinmicamente.
As, el programa produce, por ejemplo:

Para el curso Geometria analitica favor de indicar las fechas !


inicio del curso en la forma dia/mes/ano :12/09/2011
examen global en la forma dia/mes/ano :12/12/2011
curso no 1 en la forma dia/mes/ano :12/09/2011
curso no 2 en la forma dia/mes/ano :16/09/2011
curso no 3 en la forma dia/mes/ano :12/10/2011
curso no 4 en la forma dia/mes/ano :26/10/2011
El curso Geometria analitica inicia 12/09/2011 y termina con el examen global
12/12/2011
curso no 3 : 12/10/2011

217
Introduccin a la programacin

Como para todos los apuntadores del mismo tipo (struct curso_ref) siempre se hacen las mismas tres ope-
raciones (malloc, llamada a una funcin de lectura y asignacin de apuntador), se puede pensar que sera mejor
escribir una sola funcin que realice todas estas operaciones. Entonces, el parmetro de entrada sera un campo de
tipo apuntador al tipo estructurado, por lo cual se hara la asignacin dinmica y la lectura de los valores contenidos en
la zona apuntada. En este caso, como el apuntador cambiara, el tipo de parmetro sera un apuntador de apuntador:
struct fecha **. Por tanto, la funcin sera la siguiente:

void creacion_lectura_fecha(char mensaje[], struct fecha **f)


{
*f = (struct fecha *)malloc(sizeof(struct fecha));
printf( %s en la forma dia/mes/ano :, mensaje);
scanf(%d/%d/%d, &(*f)->dia, &(*f)->mes, &(*f)->ano);
return;
}

Enseguida, se aade un parmetro ms que se coloca antes de las lecturas con la funcin scanf, adems de un men-
saje indicando qu fecha debe entregarse. El contenido del apuntador de apuntador que cambia, recibe la direccin de
la zona de memoria asignada dinmicamente con malloc. Para acceder a los valores que se leen, se aplica el operador
-> al apuntador simple *f. Entonces, la funcin scanf se encuentra en espera de una direccin de memoria; por con-
siguiente, el acceso a un campo para lectura se hace con una expresin de tipo &(*f)->dia, la cual es equivalente a:
&((*f)->dia).
De esta forma, el programa principal en su parte media cambia y queda de la siguiente manera:

printf( Para el curso %s favor de indicar las fechas /n, CYY.intitulado);


creacion_lectura_fecha(inicio del curso, &CYY.fecha_inicio);
//mismas operaciones por una otra fecha
creacion_lectura_fecha(examen global, &CYY.fecha_examen_global);
// generacion de elementos
for ( i = 0; i < CYY.numero_horas; i++)
{
sprintf(cadena, curso no %d , i + 1);
creacion_lectura_fecha(cadena, &CYY.fecha_curso[i]);
}

La salida es estrictamente idntica a la del programa anterior.

Tipos estructurados auto-referenciados

Es importante destacar que no hay ninguna restriccin sobre el tipo posible de un apuntador que aparece en un tipo
estructurado; entonces, es posible utilizar apuntadores del mismo tipo. Estos tipos estructurados reciben el nombre de
auto-referenciados.
Ejemplo
Si tenemos una estructura para modelar la informacin sobre una persona, podemos aadir apuntadores por estructu-
ras del mismo tipo para indicar los padres.

struct persona {
char nombre[MAX_CHAR];
char apellido[MAX_CHAR];
struct persona *madre;

218
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

struct persona *padre;


};

En este caso, el esquema de la estructura del tipo struct persona es el siguiente:

nombre apellido madre padre

struct persona
nombre apellido madre padre

nombre apellido madre padre struct persona

struct persona

Figura 5.2

Un ejemplo de cdigo que trabaja con este tipo auto-referenciado es el siguiente:

struct persona ANA = {Ana, Garcia, NULL, NULL};


struct persona *aux;

aux = (struct persona *)malloc(sizeof(struct persona));


strcpy(aux->nombre, Marisol);
strcpy(aux->apellido, Garcia);
aux->madre = NULL;
aux->padre = NULL;
ANA.madre = aux;

ANA.padre = (struct persona *)malloc(sizeof(struct persona)); strcpy(ANA.padre-


>nombre, Pedro);
strcpy(ANA.padre->apellido, Garcia);
ANA.padre->madre = NULL;
ANA.padre->padre = NULL;

printf( Los padres de %s %s son : %s %s, su madre, y %s %s, su padre.\n


ANA.nombre, ANA.apellido, ANA.madre->nombre, ANA.madre->apellido,
ANA.padre->nombre, ANA.padre->apellido);

En este programa se inicializa una variable ANA con valores en los campos que no son apuntadores; en tanto, en los
apuntadores se coloca NULL y luego se intenta colocar referencias en los apuntadores ANA.madre y ANA.padre. En el
caso del apuntador ANA.madre, primero se asigna dinmicamente una zona de memoria por un apuntador aux, luego
se llama a los campos de esta variable y, por ltimo, se asigna a ANA.madre la variable aux. Para el caso del apuntador
ANA.padre, primero se trata directamente con este apuntador la asignacin dinmica de la memoria. Para llenar con
valores concretos la estructura apuntada por ANA.padre se usan expresiones con dos operaciones de acceso. As pues,
este cdigo produce la siguiente salida:
Los padres de Ana Garcia son : Marisol Garcia, su madre, y Pedro Garcia, su padre.

219
Introduccin a la programacin

Tipo enumeracin y tipo unin

Los tipos enumeracin y unin son tipos de datos mucho ms simples de manejar que el tipo struct, adems de que
se tiene un control sobre los valores permitidos; no obstante, se usan poco.
El tipo enumeracin permite dar un nombre a un conjunto de identicadores que son constantes de valor entero.
La denicin de este tipo se hace con la palabra clave enum, como en el siguiente ejemplo:

enum nombre_tipo_enumeracion {identificador1, identificador2, ...};

El nombre nombre_tipo_enumeracion es un identicador que indica este tipo de enumeracin. Enseguida, se usa
enum nombre_tipo_enumeracion como nombre del tipo.
Los identicadores entre corchetes tambin son nicos y se tratan como valores enteros. De esta forma, estos iden-
ticadores se pueden indicar tambin con un valor identificador = valor_entero.
Ejemplo
enum calificacion {A, B, C, D, E};
enum escala_dolor {AA = 20, BB = 15, CC = 10, DD = 5, EE = 0};

Aqu A vale 0, B vale 1, E vale 4. La llamada siguiente a printf es:

printf( A = %d C = %d 20*B + 3*E = %d\n, A, C, 20 * B + 3 * E);

Este programa produce la siguiente salida:

A = 0 C = 2 20*B + 3*E = 32

Los identicadores A hasta EE tienen los valores indicados.


Se pueden declarar variables simples o arreglos del tipo de enumeracin. Dichas variables son compatibles con el tipo
entero int y la declaracin con este tipo de enumeracin y con con el tipo int es ms una razn de visibilidad de tipos.
Ejemplo
Con base en el juego del gato3, es posible denir un tipo de enumeracin para los valores posibles del tablero de juego
y el tablero de este tipo. As:

enum codigo_gato {X = 1, LIBRE = 0, O = -1};

int main(int argc, char *argv[])


{
enum codigo_gato tablero[3][3];
int i, j;

for (i = 0; i < 3; i++)


for (j = 0; j < 3; j++)
tablero[i][j] = LIBRE;
tablero[0][0] = X;
tablero[2][2] = O;

for (i = 0; i < 3; i++)


{
for (j = 0; j < 3; j++)

3
Vase el captulo 2

220
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

printf( %2d, tablero [i][j]);


printf(\n\n);
}
exit(0);
}

Este programa inicializa el tablero y coloca dos valores en las esquinas. La salida del programa es:

1 0 0
0 0 0
0 0 -1

Este tipo de unin es muy similar sintcticamente al tipo struct, ya que se indica una lista de campos con tipo y
nombre, pero semnticamente los dos tipos son muy diferentes. El tipo de unin no indica un conjunto de campos sino
una interpretacin posible del campo, ya que solo guarda un valor que puede ser interpretado segn los tipos indicados.
Entonces, la gran diferencia radica en que los campos del tipo de unin son superpuestos en una misma zona de
memoria. El tamao de la zona de memoria ocupada por n valor de este tipo de unin es el mximo de los tamaos de
los campos (al contrario de lo que sucede con el tipo estructurado, donde el tamao es la suma).
El tipo de unin se introduce con la palabra clave union; por tanto, la sintaxis es la siguiente:

union nombre_tipo_union
{
tipo1 campo1;
tipo2 campo2;
...
};

El acceso a los campos se hace con el operador . (punto). En este caso, el nombre del tipo es union nombre_tipo_
union. Este tipo se usa como cualquier otro tipo denido por el usuario.
Ejemplo
En el siguiente programa se introduce el tipo union calificacion, el cual puede contener un otante de nombre
valor20, a n de guardar los valores numricos con punto decimal, o un solo carcter, para guardar la calicacin
de tipo A - F.

union calificacion {
float valor20;
char literal;
};

int main(int argc, char *argv[])


{
union calificacion mi_calificacion, ayer;

mi_calificacion.valor20 = 18.8;
printf( Mi calificacion de hoy es %f\n, mi calificacion.valor20);

ayer.literal = B;
printf( La calificacion de ayer es : %c\n, ayer.literal);

printf( El valor de mi_calificacion.literal = %c\n, mi_calificacion.literal);


exit(0);
}

221
Introduccin a la programacin

En este ejemplo, las dos variables de tipo unin se tratan de formas diferentes: una como un valor numrico y la otra
como una literal, que son las dos primeras llamadas a la funcin printf, las cuales son coherentes con las asignaciones
que las preceden. La tercera llamada es la ms extraa, por lo que el valor contenido en la zona de la variable mi_li-
teral se interpreta sobre el primer byte como un cdigo ASCII.
Este programa produce la siguiente salida:

Mi calificacion de hoy es 18.799999


La calificacion de ayer es : B
El valor de mi_calificacion.literal = f

Es responsabilidad del programador ofrecer una interpretacin correcta del campo que se elige para el tratamiento.

Problema resuelto

En lgebra, el espacio vectorial n se dene como el producto cartesiano iterado de consigo mismo. Los elementos de
A
n son vectores; un vector v D tiene una representacin geomtrica (norma y ngulo) y una representacin algbrica
equivalente:

v = (v 1, v 2 , , v n )

Entonces, vi D , para todos i = 1, n.


En este caso, las operaciones algbricas posibles son: suma, diferencia, producto con un escalar, producto con un
escalar de dos vectores y producto vectorial.
La norma euclidea (o mdulo) de un vector en un operario unario. La aplicacin deseada es una biblioteca de fun-
ciones para manejar vectores de los espacios vectoriales n.
Entonces, queremos trabajar con vectores para:
s E
ntregar vectores y escribir vectores.
s R
ealizar operaciones algbricas si los vectores pertenecen a un mismo espacio vectorial.
s C
alcular el mdulo y el vector normalizado asociado.
s P
oder comparar dos vectores; es decir, si son equivalentes (igualdad de todos los componentes) o paralelos.

Por denicin, n es el espacio vectorial cartesiano que est dotado con la norma euclidiana. La norma de un vector v
(o el mdulo) se dene como:

 n
v = v i2
El vector normalizado se dene con respecto a la norma:
 1 
v NORM =  v
v

A excepcin del vector 0 = ( 0, 0, , 0 ) . El vector normalizado tiene siempre su norma equivalente a 1.
 
Con dos vectores v = (v1, v2, . . ., vn) y u = (u1, u2, . . ., un) y un escalar k D , las operaciones algbricas se denen
como:
s S
uma y diferencia:
 
v + u = (v1 + u1, v2 + u2, . . . , vn + un)
 
v u = (v1 u1, v2 u2, . . . , vn un)

222
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

s Producto de un vector con un escalar:



k . v = (kv1, kv2, ..., kvn)
s P
roducto escalar:
n
v u = v i ui
s Coseno de dos vectores:
 
  v u
cos(v , u ) =  
v u
 
Para comparar dos vectores v y u , existe la condicin de equivalencia (identidad):
 
v u

Esta se dene como vi = ui, para cualquier i = 1, n.


Tambin se denen dos relaciones de posicin:
 
1. Vectores paralelos; v  u si y solo si:
 
cos(v , u ) = 1

2. Condiciones equivalentes:
 
s k D que satisface v = K u .
 
s 6ECTORESNORMALIZADOSIDNTICOS v NORM = u NORM.
     
s 6ECTORESPERPENDICULARES v u si y solamente si cos( v u ) = 0, codicin equivalente a: v u = 0 (producto
escalar equivalente a cero).
Los valores de las componentes de un vector se guardan, naturalmente, en un arreglo; por tanto, la dimensin de un vec-
tor es un entero. Si trabajramos con la hiptesis general de que el programa trabaja con vectores de varias dimensiones,
entonces el uso de un tipo estructurado para guardar juntos estos valores sera una evidencia. As, una primera propuesta
de tipo puede ser:

struct vector_basico {
int n;
double c[MAX_DIMENSION];
};

En este caso, se toma una constante MAX_DIMENSION como el nmero mximo autorizado para la componente del
vector.
Estos datos son sucientes para resolver las preguntas planteadas. As, se puede decir que la primera parte de esta
seccin usa este tipo para desarrollar las funciones.
Por otro lado, si se realiza una vez el clculo de la norma, tambin se puede calcular rpidamente el vector normali-
zado. La vericacin del paralelismo de dos vectores es equivalente a la vericacin de la equivalencia (igualdad) de sus
vectores normalizados. Lo ideal y ms conveniente sera guardar la dimensin y las componentes en la misma estructura,
as como el valor de la norma y un apuntador por su vector normalizado. De esta forma, un nuevo tipo estructurado y
auto-referenciado sera:

struct vector {
int n; double c[MAX_DIMENSION];
double norma;
struct vector *normalizado;
};

223
Introduccin a la programacin

La gura 5.3 muestra la estructura de los dos tipos:

n c norma normalizado

struct vector
n c norma normalizado

struct vector
n c

struct vector_basico

Figura 5.3

Solucion bsica
Con el tipo estructurado, la funcin ms simple es struct vector_basico; as, con la ayuda de las deniciones
matemticas, es fcil desarrollar las funciones en lenguaje C de las operaciones algbricas:

int suma(struct vector_basico X, struct vector_basico Y, struct vector_basico *Z)


{
int i;
if (X.n != Y.n)
return FALSO;
Z->n = X.n;
for(i = 0; i < Z->n; i++)
Z->c[i] = X.c[i] + Y.c[i];
return CORRECTO;
}
int diferencia(struct vector_basico X, struct vector_basico Y, struct vector_ba-
sico *Z)
{
int i;
if (X.n != Y.n)
return FALSO;
Z->n = X.n;
for(i = 0; i < Z->n; i++)
Z->c[i] = X.c[i] - Y.c[i];
return CORRECTO;
}
void producto_con_valor(double k, struct vector_basico X, struct vector_basico *Y)
{
int i;
Y->n = X.n;
for(i = 0; i < Y->n; i++)
Y->c[i] = k * X.c[i];
return;
}
int producto_escalar(struct vector_basico X, struct vector_basico Y, double *p)
{
double s;
int i;

224
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

if (X.n != Y.n)
return FALSO;
s = 0.0;
for(i = 0; i < X.n; i++)
s += X.c[i] * Y.c[i];
*p = s;
return CORRECTO;
}

El resultado de una operacin algebraica se traduce en un parmetro transmitido por referencia de la funcin C que co-
rresponde. Algunas funciones, como suma, regresan un valor de tipo de enumeracin:

enum verdad {CIERTO = 1, CORRECTO = 1, FALSO = 0};

Un valor FALSO corresponde al caso de las dimensiones de vectores incompatibles.


De igual modo, la lectura y la escritura de un vector tambin son funciones fciles de entender:

int lectura_vector(struct vector_basico *X)


{
int n, i;

printf( Lectura de un vector.\n Introducir la dimension del vector:);


scanf(%d, &n);
if (n >= MAX_DIMENSION || n <= 0)
return FALSO;
X->n = n;
printf( Lectura de los elementos:);
for(i = 0; i < X->n; i++)
scanf(%f, &X->c[i]);
return CORRECTO;
}
void imprima_vector(struct vector_basico X)
{
int i;
printf( dimension = %d. componentes : , X.n);
for ( i = 0; i < X.n; i++)
printf( %lf, X.c[i]);
printf(\n);
return;
}

En la funcin lectura, la primera lectura de la dimensin no se hace directamente en la variable con, sino en una
variable temporal, con el n de preveer un eventual caso de error (dimensin negativa o fuera de la lmite).
La funcin de clculo del vector normalizado se basa en la funcin del clculo de norma y la funcin producto_con_
valor:

float norma(struct vector_basico X)


{
double s;
int i;

s = 0.0;
for(i = 0; i < X.n; i++)
s += X.c[i] * X.c[i];

225
Introduccin a la programacin

return sqrt(s);
}

int vector_normalizado(struct vector_basico X, struct vector_basico *Y)


{
double nx;
nx= norma(X);
if (nx == 0.0)
return FALSO; // el vector normalizado no esiste
producto_con_valor(1/nx, X, Y); return CIERTO;
}

Las funciones que verican el paralelismo o la perpendicularidad de los vectores, tambin se realizan usando las funcio-
nes de clculo algbrico:

int son_paralelos(struct vector_basico X, struct vector_basico Y)


{
int i;
double nx, ny;
struct vector_basico NX, NY;

nx = norma(X);
ny = norma(Y);
if (nx ==0.0 || ny ==0.0)
return FALSO; // un vector nul no esta paralelo con nadie
vector_normalizado(X, &NX);
vector_normalizado(Y, &NY);
printf(NX:);imprima_vector(NX);
printf(NY:);imprima_vector(NY);
for(i = 0; i < X.n; i++)
if (NX.c[i] != NY.c[i] )
return FALSO;
return CIERTO;
}
int son_perpendiculares(struct vector_basico X, struct vector_basico Y)
{
double p;
int rep;
rep = producto_escalar(X,Y, &p);
if (rep == CIERTO && p == 0.0)
return CIERTO;
else
return FALSO;
}

Un ejemplo de programa que llama a estas funciones es el que se presenta a continuacin:

int main(int argc, char* argv[])


{
struct vector_basico X, Y, Z, U, V;
double k = 3.0;

lectura_vector(&X);

226
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

printf( Vector X: );
imprima_vector(X);

suma(X, X, &Y);
printf( Vector Y=X+X: );
imprima_vector(Y);

producto_con_valor(k, X, &Z);
printf( Vector Z = 3 * X: );
imprima_vector(Z);

if (son_paralelos(X,X) == CIERTO)
printf( X y X son paralelos.\n);
else
printf( No, X y X no son paralelos.\n);

lectura_vector(&U);
if (vector_normalizado(U, &V) == FALSO)
exit(0);
printf( Vector U: );
imprima_vector(U);
printf( Vector normalizado de U: );
imprima_vector(V);
if (son_perpendiculares(X,V) == CIERTO)
printf( X y V son perpendiculares.\n);
else
printf( No, X y V no son perpendiculares.\n);
exit(1);
}

La salida que produce este programa es la siguiente:

Lectura de un vector.
Introducir la dimension del vector:4
Lectura de los elementos:1 2 3 4
Vector X: dimension = 4. componentes : 1.000000 2.000000 3.000000 4.000000
Vector Y=X+X: dimension = 4. componentes : 2.000000 4.000000 6.000000 8.000000
Vector Z = 3 * X: dimension = 4. componentes : 3.000000 6.000000 9.000000 12.000000
NX: dimension = 4. componentes : 0.182574 0.365148 0.547723 0.730297
NY: dimension = 4. componentes : 0.182574 0.365148 0.547723 0.730297
X y X son paralelos.
Lectura de un vector.
Introducir la dimension del vector:4
Lectura de los elementos:4 -3 2 -1
Vector U: dimension = 4. componentes : 4.000000 -3.000000 2.000000 -1.000000
Vector normalizado de U: dimension = 4. componentes : 0.730297 -0.547723 0.365148
-0.182574
X y V son perpendiculares.

Sin embargo, una desventaja menor del tipo estructurado elegido, es que el clculo de la norma se puede realizar varias
veces. Por su parte, una desventaja de la construccin de las funciones y del programa general es que se deben declarar
todos los vectores de manera esttica y no dinmica.

227
Introduccin a la programacin

Solucin elaborada

En esta solucin queremos cumplir dos puntos:


1. Si se hace el clculo por la norma del vector que se calcula, se pretende que el vector normalizado y el resultado
de estos clculos sean guardados de manera permanente.
2. Al manejar vectores de manera dinmica, la asignacin no se hace en el bloque main, en el momento en el cual
se necesita.
La declaracin del tipo de estructura y de sinnimos por el tipo mismo y por el tipo apuntador de estructura se ejemplica
en el siguiente programa:

// definicion de tipo
struct vector {
int n;
double c[MAX_DIMENSION];
double norma;
struct vector *normalizado;
};

typedef struct vector Tvector;


typedef Tvector *PTvector;

Un vector puede ser creado de una copia de otro vector (en este caso tambin se hace una copia del vector normalizado),
si es que existe o para lectura. En ambos casos, los tipos de creacin se realizan de manera dinmica:

PTvector construccion_copia(PTvector X)
{
Tvector *aux;
if (X == NULL)
return NULL;
aux = (Tvector *)malloc(sizeof(Tvector));
// se el contenido de X en aux
*aux = *X;
if (aux->normalizado != NULL)
{
if ( aux->norma == 1)
aux->normalizado = aux;
else
aux->normalizado = construccion_copia(X->normalizado);
}
return aux;
}

void creation_lectura_vector(PTvector *X)


{
int i, n;
Tvector *aux;
// lectura de la dimencion del vector;
printf( La dimension del espacio:);
scanf(%d, &n);
if (n > MAX_DIMENSION)
{
printf( Dimension muy grande !);
*X = NULL;

228
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

return;
}
//asignacion dinamica para una zona
aux = (Tvector *)malloc(sizeof(Tvector));
aux->n = n;
aux->normalizado = NULL;
aux->norma = -1; // un valor indicando que la nprma no esta calculada
//lectura de valores
printf( Introducir los %d valores: , n);
for ( i = 0; i < n; i++)
{
scanf(
}
// X toma la direccion de la nueva zona de memoria
*X = aux;
}

La funcin de impresin de un vector tambin toma en cuenta el valor de la norma si est calculado:

void imprima_vector(PTvector X)
{
int i;
if (X == NULL)
{
printf( vector vacio.\n);
return;
}
printf( dimension = %d. componentes : , X->n);
for ( i = 0; i < X->n; i++)
printf(%lf, X->c[i]);
printf(\n);
if (X->normalizado != NULL)
printf( norma = %lf, X->norma);
return;
}

Las funciones algbricas inician como en la primera solucin, es decir, con una de las dimensiones de los vectores, y luego
se realiza la asignacin de memoria:

PTvector suma(PTvector X, PTvector Y)


{
Tvector *aux;
int i;

if (X->n != Y->n)
return NULL;
aux = (Tvector *)malloc(sizeof(Tvector));
aux->n = X->n;
aux->norma = -1;
aux->normalizado = NULL;
for (i = 0; i < aux->n; i++)
aux->c[i] = X->c[i] + Y->c[i];
return aux;
}

229
Introduccin a la programacin

Gracias a los clculos de la norma y del vector normalizado, las dos operaciones se realizan mediante una sola funcin:

void construccion_normalizado(PTvector X)
{
int i;
double s = 0.0;
PTvector aux;

for (i = 0; i < X->n; i ++)


s += X->c[i]*X->c[i];
X->norma = sqrt(s);
if (X->norma == 0)
{
X->normalizado = NULL;
return;
}
aux = (PTvector)malloc(sizeof(Tvector));
aux->n = X->n;
aux->norma = 1.0;
aux->normalizado = NULL;
for (i = 0; i < X->n; i++)
aux->c[i] = X->c[i]/X->norma;
X->normalizado = aux;
}

La vericacin del paralelismo de vectores inicia con la comprobacin de la compatibilidad de dimensiones; enseguida,
se verica si los vectores normalizados estn calculados y, por ltimo, se comprueba explcitamente la equivalencia de
los vectores normalizados asociados:

int vectores_paralelos(PTvector X, PTvector Y)


{
int i, rep;
if (X->n != Y->n)
return FALSO;
if (X->normalizado == NULL)
construccion_normalizado(X);
if (Y->normalizado == NULL)
construccion_normalizado(Y);
if (X->norma == 0.0 || Y->norma == 0.0)
return FALSO;
rep = CIERTO;
for ( i = 0; i < X->n; i++)
if (X->normalizado->c[i] != Y->normalizado->c[i]) { rep = FALSO; break; }
return rep; }

Un ejemplo de programa principal4 es el siguiente:

int main(int argc, char *argv[])


{
Tvector *X;
PTvector Y, Z;

4
Se deja como ejercicio al nal de la unidad la realizacin de las otras funciones de diferencia, productos y vericacin de la perpen-
dicularidad.

230
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

creation_lectura_vector(&X);
printf( ---Vector X :);
imprima_vector(X);

Y = construccion_copia(X);
printf( ---Vector Y :);
imprima_vector(Y);

Z = suma(X, Y);
printf( --- Vector Z = X + Y);
imprima_vector(Z);

if(vectores_paralelos(X,Z) == CIERTO)
printf( X y Z son paralelos\n);
else
printf( X y Z no son paralelos\n);
if(vectores_paralelos(Y,Z) == CIERTO)
printf( Y y Z son paralelos\n);
else
printf( Y y Z no son paralelos\n);

exit(0);
}

La salida de este programa es:

La dimension del espacio:3


Introducir los 3 valores:2 3 4
---Vector X : dimension = 3. componentes : 2.000000 3.000000 4.000000
---Vector Y : dimension = 3. componentes : 2.000000 3.000000 4.000000
--- Vector Z = X + Y dimension = 3. componentes : 4.000000 6.000000 8.000000
X y Z son paralelos
Y y Z son paralelos

Como se puede observar, esta solucin parece ms difcil que la lectura; no obstante, en este caso solo se utilizan varia-
bles asignadas dinmicamente y los datos de un vector algbrico se almacenan en una misma estructura de un vector
algbraico.

5.3 Estructuras de datos

Una estructura de datos permite el almacenamiento y el acceso a un conjunto de datos de un cierto tipo.
Este tipo de datos puede ser estndar (enteros, otantes, cadenas de caracteres) o de tipo denido por el usuario,
que en la mayora de los casos es de tipo estructurado. La caracterstica comn del tipo elegido es que el tamao es jo
y suciente para guardar cualquier elemento del conjunto de datos inicial.
No obstante, hay un problema, que es almacenar datos de este tipo permitiendo el acceso para lectura y escritura.
En este caso, la escritura tiene el objetivo de insertar nuevos elementos, con el n de cambiar el contenido o eliminar
algn elemento.
En ciertos casos, tambin se imponen algunas restricciones relacionadas con el orden de los elementos en la estruc-
tura de los datos; por ejemplo, un orden creciente o decreciente.

231
Introduccin a la programacin

En esta seccin se estudia el almacenamiento y el acceso a las estructuras de datos, sin la imposicin de ninguna
otra restriccin. Para simplicar la escritura de los algoritmos y de las explicaciones sobre el funcionamiento de estas es-
tructuras, debe quedar claro que se supone que nicamente se guardan valores enteros.
Las estructuras de datos presentadas se analizan en trminos de acceso a un elemento, por lo que debe conocerse
su posicin o su valor, en funcin del costo de insercin de un elemento y de la operacin inversa de eliminacin.

Arreglos

Un arreglo unidimensional es una forma de almacenar datos del mismo tipo, que se implementa en la mayora de los
lenguajes de programacin. En el captulo 2 se presenta y se estudia la manera de declarar, asignar en la memoria, leer
y escribir el contenido de un arreglo.
Un arreglo de enteros se declara de manera esttica como:

int nombre_arreglo[dimension_maximal];

Un arreglo se puede declarar como un apuntador; luego, se hace la asignacin dinmica de la zona de memoria que va
a contener sus elementos con la llamada a la funcin malloc:

int nombre_arreglo[];
. . .
nom_arreglo = (int*)malloc(dimension_maximal*sizeof(int));

Un arreglo se caracteriza por el nombre que se le asigna, por su dimensin declarada, por el nmero de sus elementos,
el cual est limitado por su dimensin, y por su contenido mismo. Los elementos contenidos en un arreglo se caracte-
rizan por poseer un ndice (en lenguaje C, el ndice tiene valores entre 0 y dimension_maximal -1), el cual indica
la posicin al interior del arreglo. El acceso a un elemento del arreglo, cuando se conoce su ndice, se hace en tiempo
constante, escribiendo simplemente:

nombre_arreglo[indice]

Dos funciones de entrada/salida son, por ejemplo: 5

int lectura_arreglo(int X[], int *N, int dimension_maxima)


{
int i;
printf( Dimension del arreglo (inferior al %d) :, DIM_MAX+1);
scanf(%d, N);
if (*N >= DIM_MAX)
return FALSO;
printf( Elementos del arreglo :);
for (i = 0; i < *N; i++)
scanf( %d, &X[i]);
return CORRECTO;
}
void escritura_arreglo(int X[], int N)
{
int i;
printf( Dimension del arreglo : %d\n, N);

5
Cada vez que se necesite, se llamaran estas funciones o algunas muy parecidas.

232
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

printf( Elementos :);


for (i = 0; i < N; i++)
printf( %d, X[i]);
printf(\n);
}

La insercin en un arreglo se puede hacer en tiempo constante, escribiendo el elemento al nal del arreglo; antes de la
llamada, se debe vericar que la nueva dimensin:

void insercion_arreglo(int v[], int *dim, int valor)


{
v[*dim] = valor;
++*dim;
return;
}

La gura 5.4 muestra el funcionamiento de este tratamiento:

0 1 2 N-1 N dimension_maximal

Figura 5.4

Cuando se elimina un elemento, es indispensable que la posicin del elemento eliminado se ocupe, porque en el arreglo
los elementos forman una zona contigua. Para ocupar esta posicin vacante, lo ms simple es tomar el ltimo elemento
del arreglo y ponerlo en esta posicin, como se observa a continuacin.

void eliminacion_arreglo(int v[], int *dim, int posicion)


{
v[posicion] = v[*dim-1];
--*dim;
return;
}

La gura 5.5 muestra el inicio de una eliminacin, acompaada por la mudanza del ltimo elemento del arreglo.

0 1 2 i N-2 N-1 dimension_maximal

Figura 5.5

Gracias a estas dos funciones es posible transmitir, por referencia, el tamao del arreglo, debido a que este valor cambia,
incrementndose o decrementndose, con el n de indicar el nuevo tamao.
En un arreglo, la bsqueda de un valor se realiza recorriendo los elementos de este mismo arreglo, ya sea empezan-
do por el inicio o por el nal del arreglo, hasta que se encuentra el valor, se logre el xito de la bsqueda o hasta que se
termine el arreglo; en este ltimo caso la bsqueda se considera sin xito.

233
Introduccin a la programacin

La complejidad de estas dos operaciones (insercin y eliminacin) es 0(1) (tiempo constante).

int busqueda_arreglo(int X[], int N, int valor, int *pos)


{
int i;
for (i = 0; i < N; i++)
if (X[i] == valor)
{
*pos = i;
return CIERTO;
}
return FALSO;
}

En el mejor de los casos, el elemento que se busca se encuentra en la primera posicin, mientras que en el peor de los
casos, el elemento no se encuentra y se hacen N+1 operaciones, por un arreglo con N elementos. Si en el arreglo
los elementos que se buscan frecuentemente estn al inicio del mismo, la bsqueda resultara ms ecaz.
Para medir un nmero promedio de comparaciones, se deben tomar en cuenta las siguientes probabilidades:
pi: el elemento de ndice i es el elemento que se busca.
q: el elemento no aparece en el arreglo.
Ya que pi y q son probabilidades, se tiene que pi, q D [0, 1] y q + n 1 p = 1 .
i =0 i
Para llegar hasta un elemento de ndice i, se hacen i + 1 operaciones; en tanto, para ir hasta el n del arreglo sin xito
se hacen N + 1 operaciones. Entonces, el nmero medio de comparaciones que se hace es:

M = 1 p0 + 2 p1 + . . . + N pn1 + (N + 1) q

Se puede suponer, sin restriccin de generalidad, que p0 = p1 = . . . = pN 1; de esta forma, el nmero medio de operacio-
nes deriva en las siguientes frmulas:
N
M = p0 x + q( N + 1)
N (N + 1)
M = p0 + q (N + 1)
2
n 1 1q
De la ecuacin pi = 1 y de la hiptesis p0 = p1 = . . . = PN1, se obtiene que p0 = N
. As, el valor de M se obtiene de
las siguientes frmulas:
i 0

1 q (N + 1)
M= + q (N + 1)
N 2
1+ q
M= (N + 1)
2
Si se realiza la hiptesis analizada antes, a saber, q = 0, es seguro que el elemento que se busca se halla en el arreglo;
mientras que el nmero medio de operaciones es: N +1 .
2
En el caso de que se haya realizado la hiptesis: q = 12 , tenemos la misma probabilidad de que el elemento se en-
cuentre o no en el arreglo; entonces, se obtiene M = 3(N +1) .
N
El nmero medio de operaciones que se hacen para una bsqueda de valor en un arreglo es 0(N).
El trabajo con arreglos es muy intuitivo y el desarrollo de las funciones es bastante fcil. No obstante, presenta ven-
tajas y desventajas; la principal ventaja, la ms importante de esta estructura de datos, es que el acceso a un elemento
(cuando se conoce su posicin) se hace en tiempo constante. Por su parte, la principal desventaja del uso de los arreglos
es que la asignacin de la memoria se hace, esencialmente, de manera esttica, al momento de la compilacin.

234
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Listas ligadas

Una lista ligada o lista enlazada se construye a base de celdas de un tipo estructurado auto-referenciado que permite
ir de una celda a la siguiente.
En la gura 5.6 se observa la representacin de una celda de memoria que constituye la base de una lista ligada.

clave next

struct elemento

Figura 5.6

Este tipo estructurado se dene como:

struct elemento
{int clave;
struct elemento *next;
};

Entonces, la lista ligada se traduce por un apuntador a este tipo estructurado:

typedef struct elemento* Tlista;

Por defecto, una lista vacia tiene el valor NULL. Por convencin, y en la mayora de los casos, la lista termina con un ele-
mento, por lo cual el ltimo elemento tiene el apuntador next a NULL. El apuntador next de una celda indica cul es
la celda que sigue en la lista.

Ejemplo de lista ligada

En la siguiente 5.7 es posible observar la representacin de una lista que contiene los valores 1, 3, 5 y 7.

L1357 clave next clave next clave next clave next


1 3 5 7 NULL

Figura 5.7

Por su parte, en el programa siguiente se construye esta lista ligada particular:

/* programa que construye una lista ligada */


#include <stdlib.h>
#include <stdio.h>

struct elemento
{int clave;
struct elemento *next;
};

typedef struct elemento* Tlista;

235
Introduccin a la programacin

int main(int argc, char *argv[])


{
struct elemento e1, e3, e5, e7;
Tlista L1357;
Tlista aux;

e1.clave = 1;
e3.clave = 3;
e5.clave = 5;
e7.clave = 7;

e1.next = &e3;
e3.next = &e5;
e5.next = &e7;
e7.next = NULL;

L1357 = &e1;
printf( Los elementos de la lista son : );
for (aux = L1357; aux != NULL; aux = aux->next)
printf( %d, aux->clave);
printf(\n);

exit(0);
}

En el programa anterior, las primeras ocho asignaciones permiten escribir valores en las celdas (campo clave) y ligar los
cuatro elementos que componen la lista. Por su parte, la asignacin L1357 = &e1 indica el inicio de la lista; el apuntador
L1357 toma como valor la direccin de e1.
En la ltima parte del programa se efecta un recorrido de la lista para escribir sus valores. La variable de tipo apunta-
dor aux permite lograr este propsito, ya que se trata de una variable auxiliar que indica, a cada momento, la direccin de
una celda de la lista. Para dirigirse a la siguiente celda se toma en consideracin el campo next; entonces, la asignacin
aux = aux->next permite ir de una celda a otra. El recorrido se termina en la ltima celda que tiene el campo next
a NULL.
En esta versin de programa, las celdas que componen la lista estn asignadas de manera esttica:

L1357
clave next clave next clave next clave next
1 3 5 7 NULL
e1 e2 e3 e4

Figura 5.8
Esta solucin no es viable para trabajar con listas de gran tamao, ya que resulta imposible declarar a priori una variable
del tipo struct elemento para cada celda y usar esta cuando se necesita.

Si se quiere trabajar con la asignacin dinmica de las celdas, resulta indispensable elegir variables de tipo Tlista
(equivalente a struct elemento*):

int main(int argc, char *argv[])


{
struct elemento *pe1, *pe3, *pe5, *pe7;
Tlista L1357;
Tlista aux;
pe1 = (Tlista)malloc(sizeof(struct elemento));

236
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

pe3 = (Tlista)malloc(sizeof(struct elemento));


pe5 = (Tlista)malloc(sizeof(struct elemento));
pe7 = (Tlista)malloc(sizeof(struct elemento));

pe1->clave = 1;
pe3->clave = 3;
pe5->clave = 5;
pe7->clave = 7;

pe1->next = pe3;
pe3->next = pe5;
pe5->next = pe7;
pe7->next = NULL;

L1357 = pe1;

printf( Los elementos de la lista son : );


for (aux = L1357; aux !=NULL; aux = aux->next)
printf( %d, aux->clave);
printf(\n);}

exit(0);
}

El principio del funcionamiento de este programa se basa en los siguientes puntos:


Primero, se asignan dinmicamente celdas de memoria que estn referenciadas por las variables de tipo apuntador:
pe1, pe3, pe5 y pe7. La gura 5.9 muestra las variables que usa este programa:

clave next clave next clave next clave next


1 3 5 7 NULL

malloc malloc malloc malloc


L1357

pe1 pe3 pe5 pe7

Figura 5.9

En otra versin del programa, los valores de la lista L1357 se almacenan en un arreglo y, mediante una estructura itera-
tiva, creamos celdas con contenido y ligas.

int main(int argc, char *argv[])


{

struct elemento *pe;


Tlista L1357;
Tlista aux;

int valores[] = {1, 3, 5, 7};


int i;

aux = NULL;

237
Introduccin a la programacin

for (i = sizeof(valores)/sizeof(int) - 1; i >= 0; i--)


{
pe = (Tlista)malloc(sizeof(struct elemento));
pe->clave = valores[i];
pe->next = aux;
aux = pe;
}
L1357 = aux;

printf( Los elementos de la lista son : );


for (aux = L1357; aux !=NULL; aux = aux->next)
printf( %d, aux->clave);
printf(\n);

while (L1357 !=NULL)


{
aux = L1357;
L1357 = L1357->next;
free(aux);
}
exit(0);
}

El trabajo que corresponde a cada iteracin es asignar una nueva celda, llenar los campos clave y next, y preparar el
siguiente paso o el n de creacin de la lista:

clave next clave next clave next


3 5 7 NULL

malloc

pe aux

Figura 5.10

Aqu, la variable auxiliar aux tiene la funcin de indicar el fragmento de lista ligada que es correcto. Por tanto, se inicia
con NULL y durante una iteracin se indica el ltimo elemento de la lista. La creacin de la lista inicia con la ltima celda.
El funcionamiento de esta ltima versin del programa es mucho ms exible que el de las anteriores; nicamente
se requieren dos variables para la creacin de cualquier tamao de lista. La nica precaucin que debe considerarse es
liberar el contenido de la lista (sus celdas) al nal del programa.

Creacin, copia, insercin y otras funciones para listas ligadas

Una lista ligada se puede explorar conociendo nicamente el primer elemento del apuntador, el cual recibe el nombre
de apuntador de lista.
En este caso, solo se realizan dos funciones muy sencillas: la impresin de una lista y el clculo del largo de una lista:

void imprima_lista(Tlista L)
{
Tlista aux;

238
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

for (aux = L; aux !=NULL; aux = aux->next)


printf( %d, aux->clave);
printf(\n);
}

int largo_lista(Tlista L)
{
int i;
i = 0;
while (L != NULL)
{
i++;
L = L->next;
}
return i;
}

Estas dos funciones trabajan segn un mismo principio; ambas realizan un recorrido a lo largo de la lista, desde el inicio
hasta el nal (por lo cual el campo next es equivalente a NULL). Existen dos formas de utilizar este principio, las cuales
son diferentes entre s: en un caso se usa una variable auxiliar aux y una estructura iterativa for, mientras que en el otro
caso se usa el cambio del valor de apuntador usando la asignacin L = L->next, el interior de una estructura iterativa
while.
En el caso ms general, la creacin de una lista ligada se realiza por etapas:
Creacin de una lista vaca.
Inse rciones sucesivas (cada uno de los elementos se inserta al inicio de la lista).
El cdigo de estos dos tratamientos es el siguiente:

void creacion_lista_vacia(Tlista *L)


{
*L = NULL;
}
void insercion_lista(Tlista *L, int valor)
{
Tlista aux;

aux = (Tlista)malloc(sizeof(struct elemento));


aux->clave = valor;
aux->next = *L;
*L = aux;
}

En ambas funciones, la lista se transmite por referencia, transmitiendo el apuntador del apuntador de la primera celda,
debido a que este cambia.
Si la funcin creacion_lista_vacia es muy evidente, la funcin de insercin merece una explicacin; se tra-
baja por etapas:
s Se asigna dinmicamente una celda de lista, la cual se llena con el valor transmitido en parmetro y con el apun-
tador actual de la lista.
s Se actualiza el nuevo apuntador de lista.
Este principio est representado en la gura 5.11.

239
Introduccin a la programacin

clave next

(0)

L (2)

(3)
clave next
valor

(1)
malloc
aux

Figura 5.11

Con la ayuda de estas dos funciones, creacion_lista_vacia e insercion_lista, se puede desarrollar una
funcin que llena una lista desde elementos introducidos por la lectura:

void creacion_lista_lectura(Tlista *L)


{
int i, n, valor;
printf( Introducir cuanto elemento se desea en la lista: );
scanf(%d, &n);

creacion_lista_vacia(L);
printf( Indicar los elementos de la lista en orden inverso :);
for( i= 0; i < n; i++)
{
scanf(%d, &valor);
insercion_lista(L, valor);
}
return;
}

Una funcin que es bastante sencilla y que es esencial para un adecuado funcionamiento de los programas que manejan
las estructuras dinmicas de tipo listas ligadas, es la funcin que libera la memoria ocupada por los elementos de una
lista ligada:

void liberacion_lista(Tlista L)
{
Tlista aux;
while (L != NULL)
{
aux = L;
L = L->next;
free(aux);

}
}

Para hacer una copia de una lista ligada, se crea una segunda lista ligada que contiene exactamente el mismo nmero de
elementos que la primera, cada uno de los cuales es una copia del elemento que le corresponde de la lista de origen;

240
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

para su realizacin, se copia elemento por elemento hasta que se encuentra el n de la lista. Una funcin recursiva debe
comenzar tratando el caso ms simple de la lista vaca. En el caso contrario, existe al menos un elemento, por lo que se
hace una asignacin dinmica de memoria para hacer la copia del primer elemento de la lista; entonces, se copia la clave
y el apuntador contenido en la celda. Este sera el resultado de la llamada recursiva para una lista que contiene menos
elementos. El cdigo de esta funcin es el siguiente:

void copia_lista(Tlista L, Tlista *LD)


{
Tlista aux;
if (L == NULL)
{
creacion_lista_vacia(LD);
return;

}
copia_lista(L->next, &aux);
*LD = (Tlista)malloc(sizeof(struct elemento));
(*LD)->clave = L->clave;
(*LD)->next = aux;
return;

Para calcular la complejidad de esta funcin se necesita el lmite de una funcin matemtica T(n), el cual expresa el n-
mero de operaciones necesarias a travs de una lista con n elementos. De esta forma, tenemos las siguientes relaciones
matemticas:

T(0) = C0

T(n) = Cp + T(n 1)

Aqu se puede ver que T(n) = nCp + C0, con C0, Cp constantes. Entonces, la complejidad de la funcin que hace la copia
de una lista ligada es O(n).
La complejidad de las otras funciones presentadas en esta seccin tambin es O(n) en nmero de operaciones, por
lista ligada, conteniendo n elementos.

Acceso, bsqueda y eliminacin de elementos en listas ligadas

En un arreglo, el acceso a un elemento, cuando se conoce su ndice (o su posicin en el arreglo), se realiza en un tiempo
constante. Por otra parte, en una lista ligada, el acceso a los elementos, a excepcin del primer elemento, necesita reco-
rrer la lista. Una funcin de acceso al elemento de posicin entregada por parmetro es la siguiente:

int acceso_elemento(Tlista L, int posicion, Tlista *E)


{
int i;
for (i = 1; i < posicion && L != NULL; i++)
L = L->next;
if (L == NULL)
return FALSO;
else
{
*E = L;
return CORRECTO;
}
}

241
Introduccin a la programacin

En esta funcin, el elemento que est al frente de la lista tiene la posicin 1. Existen dos posibilidades recorriendo la lista:

s La lista tiene menos posicin de elementos; en este caso, la funcin regresa FALSO.
s La lista tiene ms posicin de elementos; en este caso, la funcin regresa el valor CORRECTO y el parmetro E
regresa el apuntador por la celda de la posicin del elemento.

La complejidad de esta funcin es de 0(k), donde k es el valor de la posicin que se busca, en caso de xito y 0(n); en
caso contrario, con n el tamao de la lista. Por lo comn, la complejidad de cualquier posicin de la lista es 0(n).
La bsqueda de un valor en una lista ligada, tambin se realiza recorriendo la lista desde el primer elemento hasta
que se encuentra el elemento buscado o hasta el nal de lista. Entonces, el cdigo de la funcin puede ser:

int busqueda_elemento(Tlista L, int valor, Tlista *E)


{
Tlista aux;
for (aux = L; aux != NULL; aux = aux->next)
if (aux->clave == valor)
{
*E = aux;
return CIERTO;
}
return FALSO;
}

Como en el caso de la funcin precedente, esta funcin tambin regresa un apuntador de la primera celda, la cual se
encuentra conteniendo el valor.
En esta funcin, la complejidad de la funcin de bsqueda tambin es 0(n); asimismo, los clculos de la complejidad
son los mismos que para la bsqueda en arreglo.
La eliminacin de un elemento de una lista se trata de dos maneras diferentes:
Segn la posicin del elemento que se borra (elimina):
s Si es el primer elemento, nicamente se libera la zona de memoria correspondiente y el apuntador de la lista
cambia.
s Si no se trata del primer elemento, se impone la reconstitucin de la liga del elemento que precede el elemento
eliminado, antes de la liberacin de la zona de memoria.
La gura 5.12 representa el tratamiento de los elementos en el primer caso, cuando solo se cambia el apuntador de lista:

clave next clave next

(0)
(2)
L
(1)

aux L

Figura 5.12

Si se elimina un elemento que no es el primero, es necesario detectar cul es el elemento que precede a este en la
lista, ya que la liga de este elemento debe actualizarse antes de borrar la celda que nos interesa.

242
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

La gura 5.13 presenta el tratamiento que se realiza cuando se encuentra el elemento que se desea eliminar:

clave next clave next

(0) (2)
(1)
prec aux
L

Figura 5.13

El cdigo de la funcin que realiza la eliminacin de un elemento indicado por su apuntador es el siguiente:

int eliminacion_elemento(Tlista *L, Tlista ap)


{
Tlista aux, prec;
if (*L == ap)
{
//eliminacion del primer elemento de la lista
*L = (*L)->next;
free(aux);
return CIERTO;

}
// el elemento no es el primero de la lista
aux = (*L)->next;
prec = *L;
while(aux != ap && aux != NULL)
{
prec = aux;
aux = aux->next;

}
if (ap == aux)
{
// Se prepara la liga para poder eliminar la celda apuntada por ap
prec->next = prec->next->next;
free(aux);
return CIERTO;

}
else
{
// El elemento no se encuentra en la lista
return FALSO;
}
}

La llamada de esta funcin debe hacerse despus de una bsqueda de valor o despus del acceso a un elemento, cuan-
do se conoce su posicin.
La parte ms complicada de esta funcin es la deteccin del elemento y de su predecesor en la lista. Este clculo
corresponde a la estructura while y se hace en 0(k) operaciones, con k en la posicin del apuntador transmitido por

243
Introduccin a la programacin

el parmetro. Las operaciones de actualizacin de liga y la llamada a free siempre se hacen en tiempo constante. La
complejidad de la funcin es en la media de O(n), con n el nmero de elementos de la lista.

Centinelas

Las listas ligadas utilizadas antes respetan la convencin de la ltima celda, que tiene el apuntador next a NULL; sin
embargo, en ocasiones es posible poner otro valor constante como indicador de n de lista, el cual recibe el nombre de
centinela.
Por ejemplo, para una operacin de fusin de dos listas,6 es mejor usar una centinela Z para todas las listas;
en este caso, el apuntador next apunta al elemento mismo y tiene como clave el mximo valor entero que se puede
almacenar.
La gura 5.14 muestra esta centinela:

clave next

Figura 5.14

El siguiente cdigo contiene la declaracin de Z como una variable global y la funcin que la crea:

#include <limits.h>
#define MAXINT INT_MAX

//definicion de la variable centinela


Tlista Z;

//llenar la variable Z
void creation_Z()
{
Z = (Tlista)malloc(sizeof(struct elemento));
Z->clave = MAXINT;
Z->next = Z;
}

La introduccin de una centinela y su uso en lugar del valor NULL no modican en nada la complejidad de las funciones
presentadas, en ocasiones solo se simplica la escritura de algunos clculos.

Listas circulares

Una lista circular ligada es una lista por medio de la cual el ltimo elemento de esta apunta al primer elemento.
En la gura 5.15 se representa una lista circular que contiene los elementos 1, 3, 5, 7 y 9.

5
Esta nocin se explica en el siguiente captulo

244
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

L13579
clave next clave next
1 3
clave next
5

clave next clave next


9 7

Figura 5.15

Los tipos de datos que se pueden manejar con esta estructura de lista circular son estrictamente del mismo tipo los que
se manejan en las listas simples.

struct elemento
{int clave;
struct elemento *next;
};
typedef struct elemento* Tlista;
typedef Tlista Tlista_circular;

El acceso a los elementos de una lista circular se realiza mediante un apuntador; dicho acceso sucede en tiempo cons-
tante por el elemento de esta primera celda, pero el acceso a otras celdas se hace en tiempo variable. Para trabajar con
una lista circular, utilizar un apuntador por una celda es suciente.
Es importante destacar aqu que el trabajo con listas circulares debe respetar la estructura de estas mismas y tomar en
cuenta que no hay prcticamente un n de lista; el n se debe detectar vericando la equivalencia con el inicio de la lista.
Las dos siguientes funciones realizan operaciones bsicas con listas circulares:
s Imprimir la clave (el valor contenido) de cada elemento.
s acceder por posicion; esto consiste en determinar el elemento de posicin transmitido por parmetro. Si el
tamao de la lista es ms pequeo que este valor, se contina el recorrido de la lista.

void imprima_lista(Tlista_circular L)
{
Tlista_circular aux;
if (L == NULL)
return;
aux = L;
do
{
printf( %d, aux->clave);
aux = aux->next;
}
while (aux != L);
printf(\n);

245
Introduccin a la programacin

void acceso_elemento(Tlista_circular L, int posicion, Tlista_circular *E)


{
int i;
for (i = 1; i < posicion; i++)
L = L->next;
*E = L;
}

La insercin de un elemento en una lista circular se puede hacer en cualquier lugar, pero para realizarlo en tiempo
constante, independiente del nmero de celdas, lo mejor sera hacer la insercin despus del primer elemento, como se
muestra en la gura 5.16:

aux (1)
clave next
malloc
(2)
(3)
L clave next
clave next

(0)

Figura 5.16

El cdigo de la funcin de insercin que funciona de esta manera es el siguiente:

void insercion_lista(Tlista_circular *L, int valor)


{
Tlista_circular aux;

aux = (Tlista_circular)malloc(sizeof(struct elemento));


aux->clave = valor;
if (*L == NULL)
{
// lista fue vacia
aux->next = aux;
*L = aux;
}
else
{
// lista no fue vacia, insercion en la segunda posicion
aux->next = (*L)->next;
(*L)->next = aux;
}
}

246
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Una funcin muy til es la liberacin de la memoria ocupada por las celdas de la lista circular; aplicamos el principio
de la deteccin del n de lista comparando cada uno de los valores desde el inicio hasta el valor nal. El cdigo es el
siguiente:

void liberacion_lista(Tlista_circular L)
{
Tlista_circular inicio, aux;
inicio = L;
do
{
aux = L;
L = L->next;
free(aux);
}
while (L != inicio);
}

Para la eliminacin de un elemento indicado por su apuntador, primero se debe buscar el elemento que precede al ele-
mento borrado. No obstante, existen excepciones; la primera la constituye el caso de la lista vaca que regresa FALSO, y
el segundo caso es la lista con un solo elemento que resulta vaca y regresa CIERTO.
La deteccin del elemento que precede al elemento que se desea eliminar se hace recorriendo los elementos de
la lista hasta que se encuentra el apuntador transmitido por referencia. La liga de este elemento se actualiza y se libera
fsicamente de la memoria ocupada por su vecino.

clave next clave next

(0) (2)

(1)
prec aux

ap

Figura 5.17

Es importante destacar aqu el caso del elemento borrado que se encuentra al inicio de la lista, para lo cual se modica
el apuntador de lista circular. El cdigo de la funcin de eliminacin de un elemento es el siguiente:

int eliminacion_elemento(Tlista_circular *L, Tlista ap)


{
Tlista inicio, aux, prec;
if (*L == ap && (*L)->next == *L)
{
// la lista circular tiene un solo elemento
aux = ap;
free(aux);
*L = NULL;
return CIERTO;
}

247
Introduccin a la programacin

inicio = *L;
aux = (*L)-> next;
prec = *L;
do
{

if
(ap == aux)
break;
prec = aux;
aux = aux->next;
}

while(aux != inicio->next);

if (ap != aux)
{
//El apuntador no se encuentra en la lista circular
return FALSO;
}
// el apuntador encontrado
prec->next = aux->next;
free(aux);

if (*L == ap)
// se ha eliminado el primer elemento de la lista
*L = prec->next;

return CIERTO;
}

Un programa que construye la lista circular conteniendo los elementos 1, 3, 5, 7 y 9, y que borra algunos elementos, es
el siguiente:

Tlista_circular L13579, aux;

creacion_lista_vacia(&L13579);
insercion_lista(&L13579, 1);
insercion_lista(&L13579, 9);
insercion_lista(&L13579, 7);
insercion_lista(&L13579, 5);
insercion_lista(&L13579, 3);

printf( La lista L13579 es : );


imprima_lista(L13579);

acceso_elemento(L13579, 11, &aux);


printf( El elemento de posicion 11 de la lista es %d \n,aux->clave);

eliminacion_elemento(&L13579, aux);
printf( Despues borrar este elemento - la lista es :);
imprima_lista(L13579);

acceso_elemento(L13579, 3, &aux);

248
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

printf( El elemento de posicion 3 de la lista es %d \n, aux->clave);

eliminacion_elemento(&L13579, aux);
printf( Despues borrar este elemento - la lista es :);
imprima_lista(L13579);

liberacion_lista(L13579);
exit(0);

El orden de las inserciones es 1, 9, 7, 5, 3 porque las inserciones se hacen en segunda posicin. La llamada de la funcin
acceso_elemento(L13579, 11, &aux) induce un doble recorrido de la lista circular.
La salida de este programa es:

La lista L13579 es : 1 3 5 7 9
El elemento de posicion 11 de la lista es 1
Despues borrar este elemento - la lista es : 3 5 7 9
El elemento de posicion 3 de la lista es 7
Despues borrar este elemento - la lista es : 3 5 9

Listas doblemente ligadas

Una lista doblemente ligada es una estructura lineal por la cual cada elemento de dicha lista est ligado a sus dos vecinos:
el antecesor y el sucesor. En la gura 5.18 se representa una lista doblemente ligada que contiene los elementos 1, 3,
5, 7:
LD1357

clave prec next clave prec next clave prec next clave prec next
1 NULL 3 5 7 NULL

Figura 5.18

Este tipo de lista se dene por el apuntador que se encuentra al inicio de esta. El apuntador al nal de la lista se puede
obtener recorrindola toda. Los clculos son ms simples si se almacenan los dos apuntadores. Las deniciones necesa-
rias en lenguaje C para el manejo de listas doblemente ligadas son:

struct delemento
{int clave;
struct delemento *prec;
struct delemento *next;
};

249
Introduccin a la programacin

typedef struct delemento* Tlista;


typedef struct dlista {Tlista inicio; Tlista fin;} Tlista_doble;

El tipo estructurado por una celda contiene dos apuntadores que lo auto-referencian; en este, se indica un sinnimo para el
apuntador de la celda y un tipo estructurado con dos apuntadores para una lista doblemente ligada, los cuales indican
el inicio y el nal de la lista.
Para insertar un elemento en una lista doblemente ligada se debe indicar si este se insertar al inicio o al nal de la
lista, ya que estas son las posiciones en las cuales la insercin se hace en un tiempo constante. Una lista doble vaca tiene
los dos apuntadores (el de inicio y el del nal) con NULL. El cdigo de la creacin de una lista vaca y de la insercin al
inicio de esta lista es el siguiente:

void creacion_lista_vacia(Tlista_doble *L)


{
L->inicio = NULL;
L->fin = NULL;
}

void insercion_lista_doble_inicio(Tlista_doble *L, int valor)


{
Tlista aux;

aux = (Tlista)malloc(sizeof(struct delemento));


aux->clave = valor;
if (L->inicio == NULL)
{
// lista fue vacia
aux->next = NULL;
aux->prec = NULL;
L->inicio = aux;
L->fin = aux;
}
else
{
// lista no fue vacia
aux->next = L->inicio;
L->inicio->prec = aux;
L->inicio = aux;
}
}

Para detectar una posicin, el cdigo que se utiliza es similar al cdigo que se usa para una lista simplemente ligada; lo
mismo sucede para la liberacin de la memoria y la impresin de contenido clave de las celdas.

int acceso_elemento(Tlista_doble LD, int posicion, Tlista *E)


{
Tlista L;
int i;

L = LD.inicio;
for (i = 1; i < posicion && L != NULL; i++)
L = L->next;
if (L == NULL)
return FALSO;
else
{

250
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

*E = L;
return CORRECTO;
}

void liberacion_lista(Tlista_doble LD)


{
Tlista aux, L;
L = LD.inicio;
while (L != NULL)
{
aux = L;
L = L->next;
free(aux);
}
}

void imprima_lista_doble(Tlista_doble LD)


{
Tlista aux;
if (LD.inicio == NULL)
{
printf( Lista vacia\n);
return;
}
aux = LD.inicio;
do
{
printf( %d, aux->clave);
aux = aux->next;
}
while (aux != NULL);
printf(\n);
}

Para la eliminacin de un elemento de una lista doblemente ligada no es necesario recorrer toda la lista para detectar
el elemento que lo precede, porque esta informacin se encuentra en la misma celda. Para guardar la coherencia de la
informacin acerca del inicio y el nal de la lista doblemente ligada, hay ms casos para analizar. El cdigo es el siguiente:

int eliminacion_elemento(Tlista_doble *L, Tlista ap)


{

if (L->inicio == ap && L->fin == ap)


{
// lista con un solo elemento que se borra
free(ap);
L->inicio = NULL;
L->fin = NULL;
return CIERTO;
}
if (L->inicio == ap)
{
// se borra el primer elemento
L->inicio->next->prec = NULL;

251
Introduccin a la programacin

L->inicio = L->inicio->next;
free(ap);
return CIERTO;
}
if (L->fin == ap)
{
// se borra el ultimo elemento
L->fin->prec->next = NULL;
L->fin = L->fin->prec;
free(ap);
return CIERTO;
}
// ap apunta a un elemento en el interior de la lista
ap->next->prec = ap->prec;
ap->prec->next = ap->next;
free(ap);
return CIERTO;
}

La complejidad de la operacin de eliminacin es 0(1), ya que siempre se realiza un nmero nito y conocido de ope-
raciones. La complejidad de las otras operaciones son del mismo orden 0(n), como en el caso de los otros tipos de lista
(simple o circular), o 0(1) para la insercin en el lugar ms conveniente.

5.4 Tipos abstractos de datos

Un tipo abstracto de datos o un tipo de datos abstracto es la descripcin de una forma de almacenar y acceder a los
datos imponiendo una lista de operaciones que se deben realizar sobre los datos de este tipo. Un tipo abstracto de datos
aparece en la descripcin de los algoritmos y no tiene detalles de implementacin. Por tanto, la implementacin de un
algoritmo en un lenguaje de programacin impone la eleccin de las estructuras de datos ms conveniente para el tipo
abstracto de datos. Los tipos de datos ms utilizados en la descripcin de algoritmos y en la programacin, en general, son:
s Pila, cola, doble cola.
s Lista.
s Conjunto matemtico.
s Grafos.
s Listas ordenadas.
s rboles binarios de bsqueda.
s Cola de prioridades.
En esta seccin presentamos la implementacin de los primeros tipos, ya que en el siguiente captulo estudiamos los
tipos abstractos de datos que son construidos sobre una relacin de orden entre los valores.

Listas
El concepto de lista impone poder insertar y borrar elementos, indicar la posicin de un elemento en la lista, unir dos lis-
tas, hacer copias, entre otras cosas. La lista es un concepto de la programacin que aparece en los lenguajes declarativos
de programacin para la inteligencia articial LISP y Prolog.

252
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Las estructuras de arreglo, lista ligada, lista ligada circular y lista doblemente ligada permiten la implementacin de las
operaciones listadas antes.
El tiempo para realizar operaciones depende de la estructura de datos elegida 0(1), para la insercin, y de 0(n), para
la bsqueda de elementos. Otras operaciones tienen varios tiempos de ejecucin; por esta razn, segn la frecuencia de
estas operaciones, se aconseja seleccionar la estructura de datos que ofrece el mejor desempeo para las operaciones
ms frecuentes.

Pilas, colas, dobles colas

La cola (queue, en ingls) es una estructura mediante la cual es posible realizar la extraccin de los elementos en el
mismo orden de la insercin: orden FIFO (First In First Output).
Por su parte, una pila (stack, en ingls) es una estructura a travs de la cual se realiza la extraccin de los elementos
en orden inverso a la insercin: orden LIFO (Last In First Output).
La doble cola (deque, en ingls) permite la insercin y la extraccin de elementos desde cada extremo.
Las siguientes guras representan un ferrocarril de estos tres conceptos:

Salida Entrada

Pila

Una pila representada como una red de


conmutacin de ferrocarril.

A veces ayuda a entender el mecanismo de una pila en trminos de una analoga


con el cambio de vagones de ferrocarril, segn lo propone E. W. Dijkstra.

Salida Doble cola Entrada

Figura 5.19

Fuente: Knuth, Donald, El arte de programar ordenadores, Vol. 1: Algoritmos fundamentales, Ed. Revert, 1986.

La implementacin de una pila se puede hacer con una lista ligada o con un arreglo. En el caso del arreglo, las entra-
das/salidas se realizan al nal, mientras que para el caso de la lista ligada las entradas/salidas se hacen al inicio. De esta
manera, las operaciones se realizan en tiempo constante.

253
Introduccin a la programacin

Con el objetivo en mente de realizar las operaciones en tiempo constante, lo mejor es utilizar una lista doblemente
ligada para implementar la cola y la doble cola, si no el acceso al lado opuesto de la estructura se hace en un tiempo
proporcional al nmero de elementos.

Conjunto matemtico

En su denicin, un conjunto matemtico establece la unicidad de los valores contenidos y la ausencia de orden entre
los elementos. Tambin se destaca la relacin de pertenencia y las operaciones de unin, interseccin y diferencia.
Un conjunto matemtico se puede representar con un arreglo o con una lista ligada, lo ms difcil resulta vericar la
unicidad de un valor al momento de la insercin. La implementacin de la equivalencia tambin es una operacin com-
plicada, ya que es necesario tomar los elementos de un conjunto y vericar la pertenencia en el otro conjunto.

Grafos

Un grafo se dene como un conjunto de nodos y un conjunto de aristas entre los nodos, ya que la funcin de una arista
es unir dos nodos del grafo. En este caso, se utiliza la notacin G = (V, E), con V conjunto de nodos y E conjunto de aristas.
Donde V es un conjunto nito, por esta razn a los nodos de un grafo se les asignan valores entre 1 y N; en este caso, N
es el nmero de nodos. Un nodo es vecino de otro si tienen en comn (comparten) una arista.
En la gura 5.20 se presenta un ejemplo de grafo:

1
5
4

3
Figura 5.20

Un grafo se puede representar por:


s Una matriz de adyacencia, que es una matriz N N con 0 y 1, donde el valor 1 indica la presencia de la arista entre
los nodos y el 0 la ausencia de esta.
s Una lista de adyacencia; por cada nodo se guarda una lista con los nodos vecinos.
La matriz de adyacencia del grafo del ejemplo anterior se muestra a continuacin:

0 1 1 0 0
1 0 1 1 0

1 1 0 1 0

0 1 1 0 1
0 0 0 1 0

254
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Por su parte, las listas de adyacencia para este grafo son:


nodo 1: 2, 3
nodo 2: 1, 3, 4
nodo 3: 1, 2, 4
nodo 4: 2, 3, 5
nodo 5: 4

5.5 Problemas resueltos

El objetivo de esta seccin es el planteamiento de dos problemas concretos, en los cuales el algoritmo es bastante claro,
y poner en evidencia las ventajas o las desventajas de elegir una u otra estructura de datos. As, presentaremos solucio-
nes completas con dos estructuras de datos e indicaremos, para cada programa, la complejidad en trminos de tiempo.

Criba de Eratstenes

Es un procedimiento (algoritmo) que permite generar todos los nmeros primos hasta alcanzar un lmite N. Este algoritmo
funciona de la siguiente manera:
s Genera todos los nmeros del 2 hasta N.
s Toma los nmeros uno a uno hasta el lmite de N.
s Cuando se toma un nmero k se eliminan (jalan) todos los nmeros que siguen y que son divisibles entre k.
s Cuando se termina el tratamiento de un nmero k, se toma el siguiente elemento j que no est eliminado.
s Al nal, los nmeros que no se han borrado son los nmeros primos entre 2 y N.

Ejemplo
Para los valores hasta 25:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Los nmeros primos son: 2 3 5 7 11 13 17 19 23.

Borrar un nmero puedo ser efectivo, pues se elimina el valor de la estructura de datos o solo se coloca una marca de
borrado, indicando que se trata de un nmero con divisores. La estructura de datos debe permitir el acceso al elemento
siguiente o a un elemento con un ndice conocido. Hay varias formas para almacenar los nmeros de 2 hasta N, en tr-
minos de estructuras de datos: arreglos o listas ligadas.
Las otras operaciones que se deben hacer con la estructura de datos que guardan los nmeros son:
s Inicializar la estructura con los nmeros de 2 hasta N.
s Poder recorrer toda la estructura hasta el nal para extraer los nmeros primos

255
Introduccin a la programacin

Para buscar los nmeros que se borran en una etapa y que constituyen nmeros divisibles entre un valor k o que recorren
la lista, la cual se prueba con cada nmero presente si es o no divisible entre k, se detectan automticamente, ya que
se trata de los nmeros mltiplos de k.
Otra observacin importante es que el orden de generacin de los nmeros del 2 hasta N se respeta durante todo
el algoritmo.
Si se toma como estructura de datos una lista ligada, esta estructura permite acceder a los elementos de manera
secuencial y borrar un elemento se hace en tiempo constante, sin cambiar el orden de los elementos; en este caso, la
nica precaucin que se debe considerar es manejar juntos el apuntador de un elemento y el apuntador del elemento
que lo antecede.
Si se toma como estructura de datos un arreglo, el acceso a cualquier elemento se hace en tiempo constante; sin
embargo, borrar realmente un elemento y almacenar el orden de sus elementos parece una operacin complicada, por-
que hay que desplazar los elementos que siguen en el arreglo. La solucin ms simple es poner un valor fuera del rango
del nmero 2 hasta N, para indicar el estado de borrado de un elemento; por ejemplo, un valor negativo.
Pero el desarrollo del programa utilizando un arreglo resulta ms simple; asimismo, la complejidad en trminos de
nmero de operaciones parece ms ventajosa con esta estructura.
El programa principal que se propone tiene el cdigo siguiente:

int main(int argc, char *argv[])


{
int N;
int sqr, posicion, tamano, val;
int *CE;

lectura_limite(&N);
// asignacion dinamica de memoria
CE = (int*)malloc((N-2)*sizeof(int));
// llenar el arreglo con los valores de 2 hasta N
tamano = generar_valores(CE,N);
// preparacion de las variables
sqr = (int)sqrt(N);
posicion = 0;
while(CE[posicion] <=sqr)
{
if (CE[posicion] == BORRADO)
{
posicion++;
continue;
}
val = CE[posicion];
eliminar(CE, val, posicion+1, tamano);
posicion++;
}
extraer_primos(CE, 0, tamano);
exit(0);
}

Para ser lo ms exible posible en lo que respecta al arreglo, la memoria se asigna dinmicamente. Por la constante BO-
RRADO tomamos el valor negativo -1, denido con una directiva # define.
Las funciones de lectura de la dimensin, de inicializacin con valores entre 2 y N y de recorrido nal del arreglo para
extraer los nmeros que no estn borrados son muy simples:

void lectura_limite(int *N)


{

256
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

printf( Indicar la limite para la generacion de numeros primos :);


scanf(%d, N);
return;
}

int generar_valores(int C[], int N)


{
int i,j;
for(j = 2, i = 0; j <=N; j++, i++)
C[i] = j;
return i;
}

void extraer_primos(int C[], int inicio, int fin)


{

int i;
printf( Los numeros primos son :);
for (i = inicio; i < fin; i++)
if (C[i] != BORRADO)
printf( %d, C[i]);
printf(\n);
}

La complejidad en un nmero de operaciones para las ltimas dos funciones es 0(N).


Para eliminar los mltiplos del valor corriente indicado en una primera versin del main por CE[posicion] se
hace de la siguiente manera:

void eliminar(int C[], int valor, int inicio, int fin)


{
int i;
for (i = inicio; i < fin; i++)
if (C[i] % valor == 0)
C[i] = BORRADO;
}

Esta solucin consiste en recorrer los elementos mayores del valor corriente hasta el valor nal del arreglo y vericar la
divisibilidad con valor para cada elemento. Entre la posicin del elemento corriente y el nal del arreglo hay N 2 valor
elementos; entonces, se hacen N 2 p operaciones por cada nmero primo p entre 2 y N .
En el programa main, la estructura repetitiva se ejecuta N 2 veces y cada vez que se ejecuta al mximo un n-
mero constante de operaciones ms una llamada a la funcin de eliminacin, si el valor corriente del arreglo es primo. La
complejidad de esta parte del programa es:

N
c ( N 2 p) + C2 ( N 2)
con C y C2 constantes.
Esta frmula est limitada abajo por 0(NlogN) y arriba por 0(N2).
Otra versin de la funcin de eliminacin consiste en detectar los mltiplos sin pasar por otros valores, tomando en
cuenta que si CE[posicion] contiene un valor p entonces los elementos de forma CE[posicion + i *p] con-
tienen un mltiplo de p. El cdigo de esta versin es el siguiente:

257
Introduccin a la programacin

void eliminar(int C[], int valor, int inicio, int fin)


{
int i;
for (i = inicio + valor; i < fin; i = i + valor)
C[i] = BORRADO;
}

Por tanto, la lla,mada en la estructura main es:

eliminar(CE, val, posicion, tamano);

para poder detectar correctamente los mltiplos.


inicio
Para esta funcin, el nmero de operaciones que se hacen es finvalor ; a saber, a travs de un nmero primo p entre
N 2 p
2 y N se hacen p . El nmero total de las operaciones de la parte iterativa de while es:
N
N 2 p
c p
+ c2 ( N 2)
pprimo , p= 2

Este valor es menor al valor de la primera versin; el orden del valor es O(NloglogN), y se calcula como suma de una
serie armnica.
Ejecutando las dos versiones sobre una misma mquina y por N bastante grande, se obtiene una diferencia signi-
cativa del tiempo de ejecucin de las dos versiones de programa. 7
Si queremos implementar la criba de Eratstenes con lista ligada, la eliminacin resulta efectiva con los valores que
no son primos, pero s son divisibles entre p, donde p < N .
La estructura del programa sigue los mismos principios:
s Inicializacin de la estructura inicial y de las variables.
s Una estructura iterativa que permite extraer valores, que son nmeros primos, de la lista ligada hasta llegar al lmite < N .
En este caso, el bloque de main es el siguiente:

int main(int argc, char *argv[])


{
Tlista LE; //la lista con los numeros de 2 hasta N
int N;
int sqr; //raiz cuadrada de N
int p; // el numero primo corriente
Tlista aux;

lectura_limite(&N);
generar_lista(&LE, N);
sqr = (int)sqrt(N);

aux = LE;
while (aux->clave <= sqr)
{
p = aux->clave;
eliminar(aux, p);
aux = aux->next;
}
printf( La lista de numeros primos hasta %d es : , N);

7
En un equipo MacBook, con un procesador de 2.53 GHz, se necesitan 8.53 segundos para la primera versin y 4.47 segundos
para la segunda versin, para la generacin de los nmeros primos hasta 3 x 106.

258
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

imprima_lista(LE);
exit(0);
}

Para la inicializacin y la eliminacin de mltiplos, se utilizan dos funciones. No es necesario desarrollar una funcin que
escriba los nmeros primos; una funcin normal de escritura de elementos de la lista es suciente. La complejidad de esta
escritura es 0(PN), donde PN es un nmero de nmeros primos menores que N. La funcin de inicializacin es simple;
esta genera la lista ligada con los valores de 2 hasta N:

void generar_lista(Tlista *L, int N)


{
Tlista primer, prec, aux;
int i;

primer = (Tlista)malloc(sizeof(struct elemento));


primer->clave = 2;
prec = primer;
for (i = 3; i <=N; i++)
{

aux = (Tlista)malloc(sizeof(struct elemento));


aux->clave = i;
prec->next = aux;
prec = aux;
}
aux->next = NULL;
*L = primer;
}

La complejidad de esta funcin es 0(N).


La funcin de eliminacin recibe nicamente dos parmetros:
1. Un apuntador por una parte de lista.
2. Un valor por el cual se eliminan todos los mltiplos encontrados en la lista.
La eliminacin de un elemento en una lista ligada impone la reconstruccin del apuntador next de la celda precedente
a la celda borrada. Por esta razn, se usan dos apuntadores, uno con la celda corriente y otro con la celda que lo precede.
El cdigo de la funcin es el siguiente:

void eliminar(Tlista L, int valor)


{
Tlista prec, corr;
prec = L;
corr = L->next;
while (corr != NULL)
{
if (corr->clave % valor == 0)
{
// se debe eliminar la celda apuntada por corr
prec->next = corr->next;
free(corr);
corr = prec->next;
}
else
{
prec = corr;
259
Introduccin a la programacin

corr = corr->next;
}
}
}

La complejidad de esta funcin es comparable a la complejidad de la funcin de eliminacin por un arreglo, versin 1.
La complejidad de todo el programa resulta del mismo orden que la complejidad de la primera versin usando un
arreglo. El tiempo efectivo de ejecucin siempre resulta mayor, pero proporcional al tiempo de ejecucin de la primera
versin.
En el caso de este problema de criba de Eratstenes, la implementacin usando un arreglo es mejor que la imple-
mentacin usando lista ligada.

Problema de Josephus
El problema de Josephus8 por k y n parmetros de entrada consiste en detectar el ltimo elemento y eliminarlo. La din-
mica del problema (juego) es la siguiente:
s n personas estn ubicadas alrededor de un crculo imaginario.
s Se mata (elimina) a cada k persona, se inicia con la persona nmero 1.
s Se concluye cuando queda una sola persona.
Para n = 8 y k = 3, las personas se enumeran como se muestra en la gura 5.21, el orden de salida es: 3, 6, 1, 5, 2, 8,
4; como se puede observar, el ltimo elemento que queda es el 7.

2
8

3
7

6 4

Figura 5.21

Si queremos implementar una solucin que calcule el resultado del problema, se debe utilizar una estructura de datos
que permita la eliminacin (real o simblica de un elemento) y que tambin permita recorrer los elementos que repre-
sentan a los jugadores; esta operacin debe ser hecha respetando un orden preciso.
Aunque es posible utilizar un arreglo o una lista ligada, la mejor opcin es usar una lista circular, ya que esta permite

8
Una versin conocida del problema de Josephus es el juego infantil que dice: De tin marn de don pinge / Cucara, macara, titere
fue / Yo no fui, fue Tet / Pgale, pgale que ella fue. A quien le toca el sealamiento ella fue, sale del juego.

260
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

una traduccin ms precisa del funcionamiento real del procedimiento.


Para cualquier estructura de datos que se elija, la estructura del programa es:

inicializar los datos, estructuras y variables simples


P@0 // P es el numero del paso
mientras P < Q hacer
contar hasta N
eliminar elemento
P@P+1
fin mientras

Si se trabaja con un arreglo, para eliminar un elemento (persona) se coloca un valor de BORRADO y no se elimina fsi-
camente el elemento de la estructura. La versin del programa que trabaja con un arreglo es la siguiente:

int main(int argc, char *argv[])


{
int k, n; //parametros del problema
int *C; //arreglo que guardan las personas
int m;//numero de persona que salieron
int posicion, indice;
lectura_parametros_josephus(&n, &k);
C = (int*)malloc(n*sizeof(int));

inicializar_arreglo(C, n);

m = 0;
posicion = 0;
while (m < n-1)
{
cuenta(C, posicion, n, k, &indice);
//printf( Paso %d : La persona que sale es =*%d=\n, m, C[indice]);
C[indice] = BORRADO;
m++;
posicion = (indice + 1)%n;
}

buscar_el_ganador(C, n);
exit(0);
}

La funcin de lectura tiene una forma muy simple y realiza la lectura de los parmetros del problema de Josephus; por su
parte, la funcin de inicializacin coloca los valores de 1 hasta n en el arreglo y, por ltimo, la funcin de salida tiene como
objetivo buscar en el arreglo el nico elemento que no est borrado. La funcin de inicializacin tiene una complejidad
de 0(n), lo mismo que la funcin de escritura.

void lectura_parametros_josephus(int *n, int *k)


{
printf( Indicar cuantas personas(n) hay alrededor del circulo: );
scanf(%d, n);
( printf( Indicar el valor del contador (k): );
scanf(%d, k);
}

void inicializar_arreglo(int C[], int n)

261
Introduccin a la programacin

{
int i;
for (i = 0; i < n; i++)
C[i] = i + 1;
}

void buscar_el_ganador(int C[], int n)


{
int i;
for (i = 0; i < n; i++)
if (C[i] != BORRADO)
{
printf( El ganador es la persona -%d-\n, C[i]);
return;
}
printf( No se encontro ningun ganador\n);
}

Lo ms interesante es la funcin cuenta, la cual toma como parmetros el arreglo, su tamao n y el nmero k, regre-
sando el ndice con un valor que no se haya borrado y que se site a una distancia de k con respecto a la posicin inicial.

void cuenta(int C[], int pos, int n, int k, int *indice)


{
int j;
j = 0;
while(j < k)
{
if (C[pos] != BORRADO)
{
j++;
if (j == k)
break;
}
pos = (pos +1)
}
*indice = pos;
}

El nmero de operaciones que se realiza en una llamada a la funcin cuenta es variable: desde k, para las primeras
veces, hasta n, cuando el arreglo tiene muchos valores eliminados.
El nmero de pasos de iteracin de la estructura while del programa principal es n 1; entonces, la complejidad
de todo el programa es: 0(kn) y 0(n2).
Si se usa una lista ligada circular como estructura de datos, la eliminacin de un elemento resulta ser la operacin
ms delicada, ya que se necesita el apuntador del elemento que precede el elemento borrado, para guardar una lista
circular coherente. La funcin cuenta regresa, entonces, dos parmetros: el apuntador por el elemento que se cuenta a
k y el apuntador por el elemento que lo precede.
El programa principal es el siguiente:

int main(int argc, char *argv[])


{
int n, k; //parametros del problema
Tlista C; // lista circular que contiene a las personas
Tlista prec; //un apuntador util para borrar elementos
Tlista aux; //el apuntador por la persona que sale

262
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

int m;

lectura_parametros_josephus(&n, &k);
inicializar_lista_circular(&C, n);
m = 0;
while (m < n - 1)
{

cuenta(C, k, &aux, &prec);


printf( Paso %d : La persona que sale es =*%d=\n, m, aux->clave);
prec->next = aux->next;
free(aux);
C = prec->next;
m++;
}
printf( La persona que gana :);
imprima_lista(C);
printf(\n);

exit(0);
}

Las funciones de inicializacin y de cuenta del elemento k son las siguientes:


void inicializar_lista_circular(Tlista *L, int n)
{
Tlista primer, aux;
int i;

*L = NULL;
primer = (Tlista)malloc(sizeof(struct elemento));
primer->clave = 1;
*L = primer;
for (i = 2; i <= n; i++)
{
aux = (Tlista)malloc(sizeof(struct elemento));
aux->clave = i;
(*L)->next = aux;
*L = aux;
}
(*L)->next = primer; // se cierre la lista
*L = primer; //se regresa el apuntador por el elemento 1
}

void cuenta(Tlista C, int k, Tlista *aux, Tlista *prec)


{
int j;

*prec = C;
*aux = C->next;
j = 2;
while (j < k)
{
*prec = *aux;
*aux = (*aux)->next;
j++;

263
Introduccin a la programacin

}
}

La complejidad de la funcin de inicializacin es 0(n), en trminos de nmero de operaciones, y la complejidad de la fun-


cin cuenta es (k) (exactamente ck operaciones con c constante). Entonces, la complejidad global del programa es .
Si comparamos las complejidades tericas de las dos versiones de programa, resulta evidente que la versin que
utiliza una lista ligada circular es mejor. Pero si se compara el tiempo de ejecucin de los programas, por pequeos valores
de n, la diferencia no es signicativa; sin embargo, para n = 4000000 = 4 x 106 y k = 17, los tiempos9 son sensiblemente
diferente: 10 segundos para la versin en la que se utiliza la lista circular y 19 segundos para la versin en la que se usa
un arreglo.

Sntesis del captulo

En este captulo estudiamos la posibilidad de denir sinnimos a travs de tipos conocidos, estndares o propios del
usuario; tipos escalares o derivados (apuntador, arreglo, funcin, etc.). Tambin aprendimos que la palabra clave type-
def permite elegir un identicador, para que sea el nombre del nuevo tipo.
Si los arreglos agrupan mltiples valores del mismo tipo y con la misma semntica, el tipo estructurado o compuesto
denido con struct permite conjuntar valores de tipos diferentes, con semnticas diferentes. Los datos que forman
parte del tipo compuesto tratan, generalmente, de un mismo concepto.
Los tipos estructurados pueden contener apuntadores para cualquier otro tipo del lenguaje, incluso del mismo tipo.
As, en este caso se habla de tipos estructurados auto-referenciados.
Los tipos auto-referenciados permiten la denicin de estructura de datos de tipo lista ligada: simple, doble o circular.
El tiempo de las operaciones que manejan las estructuras de datos de tipo listas ligadas, en ocasiones es diferente
del tiempo de acceso a un arreglo, el cual constituye la estructura de datos ms simple.
Los dos tipos de estructuras de datos, arreglos y listas ligadas, permiten la implementacin de una parte de los tipos
abstractos de datos ms utilizados: pilas, colas y colas dobles.
Durante la resolucin de problemas, despus de la fase de anlisis, la decisin de utilizar arreglos o listas ligadas en
el programa depende de la naturaleza de las operaciones necesarias de los datos; la eleccin inuye decisivamente en el
desempeo del programa.

8
Los programas fueron ejecutados en un MacBook con un procesador de 2.53 Ghz.

264
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Bibliografa
s Knuth, Donald, El arte de programar ordenadores, vol I: Algoritmos fundamentales, Editorial Revert, 1986. (The
Art of Computer Programming, Volume 1: Fundamental Algorithms, 3rd edition, 1997, Addison-Wesley Professio-
nal.)

s , El arte de programar ordenadores, vol III, Editorial Revert, 1986. (The Art of Computer Programming,
Volume 3: Sorting and Searching, 3rd edition, 1998, Addison-Wesley Professional.)

s Ritchie, Dennis y Kernighan Brian, El lenguaje de programacin C, 2a. edicin, Pearson. Educacin, Mxico, 1991.

s Sedgewick, Robert, Algorithms in C, Third edition, Addison-Wesley, 1998.

Ejercicios y problemas
1. Segn el modelo de trabajo con la funcin struct fecha, escribir una funcin de prototipo:
void lectura_persona(struct persona *P)

que lea dos cadenas de caracteres y llene la variable apuntada de tipo struct persona.

2. Escribir la funcin de asignacin dinmica de la memoria con base en una estructura de tipo struct
curso, haciendo llamadas a las funciones que llenan estructuras compuestas por fechas y personas.

3. Escribir una funcin que tome en la entrada dos variables de tipo struct curso y regrese 0 si las fechas
se superponen (un curso inicia antes que el otro haya terminado) y 1 si las fechas no se sobreponen. Utilizar
llamadas para la funcin comparar_fechas.

4. Denir un tipo Tmatriz que sea capaz de trabajar con matrices (arreglos bidimensionales) de dimensin
variable en trminos del nmero de lneas y de columnas, pero sin que este nmero exceda al 10 (una
constante).

Escribir funciones para:


s Lectura: las dimensiones y los elementos se leen uno a uno.
s Escritura.
s Inicializacin de una matriz unidad o una matriz 0n.
s Suma y producto, probando antes si las dimensiones son compatibles con estas operaciones (nmero de
lneas o columnas).

5. Denir primero un tipo estructurado struct info_producto y luego un sinnimo Tproducto, capaz
de guardar datos relacionados con los productos que un vendedor de verduras tiene en su tienda: nombre
de la fruta/verdura, cantidad existente, precio medio por kilo/pieza. El programa dispone de un archivo
lista.dat que contiene el detalle de su mercanca en la forma:

Producto1 cantidad1 precio1


Producto2 cantidad2 precio2

265
Introduccin a la programacin

Los datos seran almacenados en un arreglo de tipo Tproducto; el tamao mximo del archivo es de 400
lneas. Escribir un programa que lea el archivo lista.dat y que llame un arreglo de elementos del tipo
Tproducto y detecte el producto con la cantidad mxima y el precio mnimo.

6. Retomando los mismos datos del problema anterior, se supone que el tamao del archivo lista.dat
es variable y desconocido, por lo que se pide hacer un programa que:

s Lea una primera vez el archivo, para asignar de manera dinmica el espacio de memoria a travs del arreglo
de tipo Tproducto, el cual va a contener los datos sobre los productos.

s Lea una segunda vez este archivo y guarde los datos en el arreglo.

s Escriba en otro archivo, llamado lista_final.dat, todos los productos que tienen una cantidad
mayor a 10.

7. Con el objetivo de ayudar al profesor en el clculo de las calicaciones de un curso:

s Proponer un tipo estructurado conveniente que almacene la calicacin nal y eventuales notas de cada alumno.
s Declarar un arreglo de este tipo (u otra estructura) capaz de guardar todas las calicaciones de un grupo de
alumnos.
s Escribir un programa capaz de realizar varias lecturas de resultados parciales y que al nal de cada ejecucin
del programa se almacenen en un archivo todos los datos disponibles.

8. En el programa que se encuentra al nal de la primera seccin, que trabaja con vectores representados me-
diante el tipo estructurado struct vector, an faltan algunas funciones. Escribir las siguientes funciones
y las llamadas necesarias en el programa principal main:

s El producto con un valor escalar.


s El producto escalar de dos vectores.
s La diferencia de dos vectores.
s La vericacin de que dos vectores sean perpendiculares.

9. En el mismo programa que trata el tema de los vectores por los cuales se almacena el vector normalizado,
an falta por escribir una funcin que libera la zona de memoria ocupada para las variables con asignacin
dinmica de memoria. Escribir una funcin de prototipo:

_#UIDADO PRIMEROSEDEBELIBERAR SIEXISTE ELESPA


CIODEMEMORIAOCUPADOPORELVECTORNORMALIZADO void libera_vector(PTvector V);

10. Escribir una funcin de prototipo:

void copia_arreglo(int T[], int nt, int S[], int &ns)

la cual hace una copia del arreglo T en el arreglo S. Se supone que el arreglo S tiene la memoria asignada.

11. En la funcin busqueda_arreglo de la seccin 2 se busca una aparicin del valor en arreglo (la primera,
ya que el arreglo se recorre de izquierda a derecha). Nos interesa obtener todas las posiciones del arreglo
que contienen este valor. Por tanto, el prototipo de la funcin que realiza esta bsqueda completa sera el
siguiente:

void copia_arreglo(int T[], int nt, int valor, int posicion[], int *np)

266
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Los parmetros transmitidos por valor, nt y valor, son los parmetros de entrada; es decir, el tamao del ar-
reglo T por el cual se efecta la bsqueda y el valor. Los parmetros de salida transmitidos por referencia son:

s *np indica el nmero de apariciones del valor en el arreglo T.

s posicion[] es el arreglo que guardan las posiciones que contienen el valor.

Por ejemplo, para el arreglo T:1 2 3 1 1 3 y el valor = 1, la funcin debe regresar *np: 3 y posi-
cion[]: 0 3 4.

Cul es la complejidad en trminos de tiempo de ejecucin de la funcin?

12. Modicar la funcin busqueda_arreglo con el n de cambiar la posicin del elemento buscado. Cuando se
encuentra este elemento y su ndice i no es 0 (es decir, no es el primer elemento del arreglo), se intercambia el
elemento encontrado con su vecino de ndice i 1. Crear un programa que verique el funcionamiento correcto
de esta funcin.

13. Usando el tipo presentado en este captulo para trabajar con listas ligadas, escribir una funcin no recursiva
para hacer la copia de una lista en otra lista. El prototipo de la funcion sera:

void copia_lista_sin_recursion(Tlista Inicial, Tlista *Final)

Cul es la complejidad de su funcin?

14. Para una lista ligada, escribir una funcin que obtenga la copia en espejo de una lista inicial. El prototipo de la
funcin sera:

void copia_lista_en_espejo (Tlista Inicial, Tlista *Espejo)

Por ejemplo, si la lista inicial contiene los valores 1 3 5 7 9 (en ese orden), su copia en espejo tendra que
contener 9 7 5 3 1.

15. Para una lista simplemente ligada, escribir una funcin que verique que la lista contiene un palndromo. El
prototipo de la funcin sera:

int es_palindromo(Tlista L)

Un palndromo es una sucesin de valores que es idntica a su imagen en espejo. La sucesin 1 2 3 4 3 2


1 es un palndromo, mientras que 1 1 2 2 3 no es un palndromo.

16. Telfono descompuesto

El juego del telfono descompuesto consiste en transmitir una palabra o una frase bastante larga entre varias
personas; el juego inicia cuando la primera persona dice la palabra en la oreja del vecino; es muy probable
que el vecino no entienda bien y transmita la palabra o frase a su otro vecino de manera errnea o alterada. El
nuevo jugador transmite la palabra oda al siguiente vecino y as sucesivamente hasta que la palabra (alterada)
regresa al primer jugador. Si la palabra regresa bien o con muy pocas modicaciones, el jugador pierde. Por
ejemplo, las modicaciones posibles de algunas palabras son las siguientes:

s Se pierden las ltimas dos o tres letras.


s Se duplican letras como c, l, n, r.
s Al interior de la palabra, una letra puede ser cambiada por otra.

267
Introduccin a la programacin

El objetivo de este programa sera simular el juego construyendo una lista ligada que contenga la palabra o la
frase inicial y que con una determinada probabilidad se modique la palabra, modicando as la lista que la
contiene.

Primero, se necesita denir un tipo estructurado capaz de contener letras. Luego, se requiere establecer
las siguientes funciones:

a) Lectura de una cadena de caracteres y la construccin de la lista ligada que contiene sus palabras.
b) Decisin de hacer o no modicaciones a la lista, indicando tambin el tipo de modicacin.
c) Para cada tipo de modicacin, crear la funcin que realiza la modicacin.

d) Una funcin capaz de extraer la palabra desde la lista ligada y guardarla en un arreglo como una cadena
de caracteres.

e) Una funcin capaz de comparar dos cadenas de caracteres, si tienen o no ms de la mitad de letras en
comn.

Escribir un programa que utilice el tipo estructurado propuesto y las funciones que simulan las transformacio-
nes de la palabra inicial durante N pasos, donde el valor de N se lee en la entrada del programa.

17. La funcin presentada, eliminacion_elemento, borra un elemento indicado por un apuntador. Escribir
una funcin de prototipo:

int eliminacion_elemento_valor(Tlista &L, int valor)

que borre el primer elemento que contiene el valor transmitido por parmetro.

18. (Difcil) Escribir una funcin de prototipo:

int eliminacion_todos_elementos_valor(Tlista &L, int valor)

que elimine de la lista todos los elementos conteniendo el /tt valor. Sugerencia: utilizar una funcin
recursiva.

19. Trabajo con listas circulares. En la seccin que aborda el tema de las listas circulares faltan algunas fun-
ciones.

Escribir las funciones siguientes y poner en la parte de main las llamadas a las funciones propuestas:

s El tamao (nmero de celdas) de una lista.


s La insercin al inicio de la lista (y no).
s La creacin de una lista circular de tamao variable mediante una sola funcin que lea valores y haga lla-
madas a funciones de insercin.
s La bsqueda de un elemento que guarde en su clave un valor transmitido como parmetro.

20. Cortar una lista. Escribir una funcin que corte una lista L en dos listas; el corte se hace en una posicin
transmitida por parmetro, como en la gura 5.22 para la lista 1, 3, 5, 7 y el corte en la posicin 2.

268
$BQUVMPt&TUSVDUVSBTEFEBUPT5JQPTBCTUSBDUPT

Linicial clave next clave next clave next clave next


1 1 1 1 NULL

Linicial Lsegunda
clave next clave next clave next clave next
1 1 NULL 1 1 NULL

Figura 5.22

La funcin tiene como prototipo:

int corte_lista(Tlista *Linicial, Tlista *Lsegunda, int posicion);

21. Separacin de elementos de lista. En una lista ligada hay varios valores enteros; hacer una funcin que
destruya la lista inicial y, con los elementos que la componen, crear dos listas ligadas: una con los nmeros
pares y otra con los nmeros impares (vese la gura 5.23).

Linicial clave next clave next clave next clave next clave next
1 2 5 7 8 NULL

Limpares Lpares
clave next clave next clave next clave next clave next
1 2 5 7 8 NULL

Figura 5.23

El prototipo de la funcin sera:

void separa_lista(Tlista *Linicial, Tlista *Lpares, Tlista *Limpares);

22. Para una lista doblemente ligada, escribir una funcin que inserte un elemento al nal de lista y otra funcin
que inserte un elemento en una posicin intermedia k. Los prototipos de estas funciones seran:

void insercion_lista_doble_fin(Tlista_doble *LD, int clave)


void insercion_lista_doble_intermedia(Tlista_doble *LD, int clave, int posi-
cion)

23. Conjunto matemtico implementado con arreglos

a) Implementar el tipo abstracto del conjunto matemtico como un arreglo con su dimensin.

269
Introduccin a la programacin

b) Escribir funciones de insercin y de eliminacin de valor. Para la insercin, primero se debe vericar si el
elemento existe o no en el conjunto.

c) Escribir funciones para las tres operaciones de conjuntos conocidas.

24. Listas ordenadas. Los elementos de una lista son ordenados, por lo que se dice que la lista es ordenada si
un elemento es seguido de un elemento mayor. Por ejemplo: la lista 1 2 3 6 8 9 es ordenada, pero la lista 9
8 12 14 no lo es.

Para una lista ligada simple, escribir una funcin que verique si la lista es ordenada o no. El prototipo de la
funcin sera:

int es_ordenada(Tlista L);

Una lista ligada circular es ordenada si cuenta con un elemento a partir del cual la lista est ordenada.

Ejemplos: la lista 3 4 5 6 1 2 es ordenada, porque desde el elemento 2 el orden se respeta; por el contrario,
la lista 3 4 5 6 1 2 0 no est ordenada.

Escribir la funcin que verica si la lista circular es ordenada y que tambin regresa el elemento a partir de
cual el orden se respeta. Su prototipo sera:

int es_ordenada_circular(Tlista_circular L, Tlista_circular *E);

25. Versin del problema de Josephus. En el momento en el cual una persona sale, se considera que esta
persona elige a otra que tambin sale. El clculo se contina con el vecino de la segunda persona. Con la
estructura de datos que le parezca mejor, escribir el programa que implemente esta versin.

270
6OJEBEt%FMBMHPSJUNPBMQSPHSBNB
6 )ZX\LKH:LSLJJP}U
6YKLUHTPLU[V

Contenido
6.1 Introduccin Sntesis del captulo
6.2 Fundamentos tericos y descripcin de problemas Bibliografa
Relacin de orden Ejercicios y problemas
Marco general de estudio y estructuras de datos
Bsqueda
Objetivos
Seleccin
Ordenamiento s #ONOCERLASDElNICIONESDELOSTRESPROBLEMAS
6.3 Arreglos y listas ligadas fundamentales de la computacin: bsqueda, seleccin
Bsqueda y ordenamiento.
Seleccin s #ONOCERLASCOMPLEJIDADESTERICASDELOSPROBLEMAS
Mantenimiento: insercin y eliminacin
s )MPLEMENTARALGORITMOSPTIMOSPARAPROBLEMASCON
Ordenamiento
estucturas de datos de tipo arreglo o lista ligada.
6.4 Montculos
Denicin y propiedades s #ONOCERLAESTRUCTURADEMONTCULO SUFUNCIONAMIENTO
Implementacin y su implementacin.
Insercin y eliminacin de elementos
Ordenamiento por montculo

271
*OUSPEVDDJOBMBQSPHSBNBDJO

6.1 Introducccin

En este captulo se abordan y desarrollan tres problemas tpicos de programacin:

1. La bsqueda de un valor.
2. La seleccin de los valores extremos (mnimo, mximo y cuartiles).
3. El ordenamiento de valores.

Para cada uno de estos tres casos se inicia con un estudio terico sobre el desempeo de la mejor solucin que existe y
se concluye con la exposicin de las soluciones que existen en trminos de algoritmos y de estructuras de datos adapta-
das; todo esto implementado en el lenguaje C.
De igual manera, para los tres problemas de programacin se trabaja con las mismas hiptesis:

1. Los conjuntos de trabajo tienen una relacin de orden y se pueden representar por completo en la memoria
interna de la computadora.
2. Los problemas se pueden tratar cuando estn almacenados en la memoria externa.

Existen soluciones que funcionan para las dos memorias: interna y externa; ese es el caso de las tablas de dispersin y de
los rboles-B. Por tanto, los problemas que se tratan en este captulo no estn restringidos a ser trabajados nicamente
con la memoria externa.

6.2 Fundamentos tericos y descripcin de problemas

En esta seccin se describe y explica cules son los problemas principales que se manejan en el rea de la programacin. En
este sentido, los fundamentos tericos resultan de gran importancia, ya que nos permiten tener una idea ms clara de los
problemas, adems de que tambin podemos analizar cul es la cualidad de una solucin con respecto a la solucin ideal.

Relacin de orden

Una de las principales hiptesis de trabajo para el caso de los problemas que se estudian y la proposicin de sus solucio-
nes, es que los elementos de los conjuntos que se tratan pertenecen al universo de posibles valores para los elementos
de X. Esto es, los elementos de X tienen una relacin de orden completa: a saber, por cualesquiera dos elementos, x,
y X se puede responder en un tiempo de clculo (de decisin) nito con una y solo una de las siguientes relaciones:

a) x = y: equivalencia.
b) x < y o y > x: x menor que y.
c) x > y o y < x: x mayor que y.

Esto signica que cualesquiera dos valores del universo X son comparables.1 Esta relacin tambin deriva en una relacin
< o >; esto es, x > y, con la signicacin x = y o x > y.

1
Tambin hay casos de universos en los cuales dos elementos no son siempre comparables; la relacin es de orden parcial.

272
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

La relacin < es una relacin de orden estricta; por su parte, la relacin < se conoce como relacin de orden larga,
la cual presenta las siguientes condiciones:

s Reexividad: x < x para cualquier x del universo.

s Antisimetra: si x < y y y < x, entonces x = y.

s Transitividad: si x < y y y < z, entonces x < z.

Tambin se considera que el punto de vista de la implementacin en nuestra relacin de orden se traduce en una com-
paracin, a travs del uso de un operador estndar, como: <, >, <=, >=, ==, !=, o de una funcin estndar strcmp o
strncmp (por el tipo cadena de caracteres) o, si el tipo de los valores es de un tipo compuesto, con las llamadas a las
funciones denidas por el usuario. En el captulo anterior usamos una funcin struct fecha para el tipo compuesto:2

struct fecha {
int dia;
int mes;
int ano;
};

int compar_fechas(struct fecha f1, struct fecha f2)


{

if (f1.ano < f2.ano)


return 1;
if (f1.ano > f2.ano)
return -1;
if (f1.mes < f2.mes)
return 1;
if (f1.mes > f2.mes)
return -1;
if (f1.dia < f2.dia)
return 1;
if (f1.dia > f2.dia)
return -1;
else
return 0;
}

El tiempo para obtener una respuesta a una pregunta: x < y o x = y o x < y (en este caso, la respuesta es S o NO), es
considerado como signicativo y constante (para cualquier pareja de valores), cualquiera que sea el modo de resolucin
de la comparacin: operador relacional o llamada a una funcin.
En la mayor parte de este captulo trabajamos con nmeros enteros (tipo int); el conjunto de estos enteros se
considera del universo X. En el caso de algunas estructuras de datos vamos a considerar cadenas de caracteres (tipo
char[]), como el caso de la discusin sobre tablas de dispersin. En muchos casos se trata de elementos de cierto
tipo compuesto, pero las operaciones de bsqueda, seleccin u ordenamiento se hacen con respecto a un compo-
nente de este tipo, al que se llama clave. El universo X es, entonces, el conjunto de claves posibles de los elementos.
En resumen, en la resolucin de un problema se trabaja con un subconjunto U de X; naturalmente, U X y U es un
conjunto nito que se representa en la memoria interna, en una estructura de datos conveniente. Por tanto, podemos

2
El ejemplo completo se encuentra en el captulo 5, seccin 1.

273
*OUSPEVDDJOBMBQSPHSBNBDJO

considerar, por una parte, que el conjunto U = {x1, x2 xN}, con como cardinal del conjunto U (||U|| = N), y por otra parte
tambin el tamao del problema a resolver.

Marco general de estudio y estructuras de datos

Como se mencion antes, los tres problemas principales que se tratan en este captulo son los siguientes:
1. La bsqueda de un valor.
2. La seleccin del elemento extremo (mnimo o mximo).
3. El ordenamiento de un conjunto nito de valores.
Para cada uno de los problemas anteriores se describe la pregunta de una manera formal, con el n de poder indicar,
despus, de manera formal, la complejidad terica de su resolucin, a saber, el lmite de baja complejidad para cualquier
solucin concreta, en el caso ms general.
El desempeo de las soluciones propuestas tambin depende de la estructura de datos que se selecciona para
almacenar los datos que se tratan.
A lo largo de este captulo se trata cada uno de los siguientes puntos para cada problema:
s Presentar las soluciones que existen, usando las estructuras de datos conocidas: arreglo o lista ligada.
s Proponer nuevas estructuras de datos y las soluciones que se van a utilizar.
s Poner en evidencia el desempeo de estas soluciones.
s Abordar la dinmica de las estructuras de datos (generales o especcos) desde el punto de vista de la creacin, la
insercin (adicin) de nuevos elementos y la supresin (eliminacin) de elementos existentes.

Bsqueda

El problema de bsqueda es un problema de vericacin de la existencia (pertenencia) de un elemento o una clave en


el conjunto de trabajo. La respuesta es de tipo S o NO.
En caso de que la respuesta sea positiva, tambin se espera la ubicacin del elemento encontrado, como un ndice,
un apuntador o cualquier otro mecanismo para acceder al elemento en tiempo constante o razonable.
En el caso de que la respuesta sea negativa, en ocasiones, es posible regresar el elemento ms cercano o el mayor
de todos los ms pequeos del nmero buscado.
De manera formal, si hay un elemento x del mismo universo que el conjunto de trabajo U (x X y U X), la respuesta
a la incgnita sera:

xU

Si U = { x1, x2 xN}, se pregunta si regresa un ndice i (i entre 1 y N), tal que x = xi.
Hace ms de 40 aos, el gran computlogo Donald Knuth arm que una gran parte del tiempo que se dedicaba
al cmputo, se utilizaba para resolver problemas de bsqueda; hoy en da, el segundo uso ms intenso de Internet es la
bsqueda mediante motores de bsqueda. Asimismo, en el uso de bases de datos, el problema de bsqueda tambin
se debe resolver varias veces para cada interrogacin compleja de la base.
Si el conjunto de trabajo U contiene claves mltiples (esto es, los elementos se guardan con repeticin), el problema de
bsqueda exige indicar: un solo elemento, el ndice ms pequeo o el ndice ms grande, o todas las apariciones.

274
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

La estructura de datos que permite dar una respuesta rpida a los problemas de bsqueda se conoce con el nom-
bre de diccionario. Algunos ejemplos de estructuras de tipo diccionario son: arreglos ordenados, listas ligadas ordena-
das o no, rboles de bsqueda, rboles equilibrados, rboles rojo-negro, rboles H-equilibrados (o de Adelson-Velskii),
rboles-B y tablas de dispersin, entre otros.
Para analizar la complejidad del problema, es indispensable tomar en cuenta el nmero de operaciones de compara-
cin de elementos. Se considera que esta operacin es la ms complicada de todos los clculos. Una comparacin entre
dos elementos, a, b X es de la forma a : b y el resultado es: = o < o >.
Un rbol de comparaciones para un conjunto con dos elementos U = { x1, x2} y un elemento x1 que se busca es el
siguiente:

x1 : x

< >
=
S
x2 : x x2 : x

< = > < = >

NO S NO NO S NO

Figura 6.1

Otro ejemplo de rbol es el siguiente:

x2 : x

< >
=
S
x1 : x x1 : x

< = > < = >

NO S NO NO S NO

Figura 6.2

En el ejemplo de la gura 6.2 se inicia con la comparacin del segundo elemento.


En los dos rboles anteriores, el nmero de comparaciones, en el peor de los casos es de 2, que corresponde al
nmero mximo de nodos interiores de un camino entre la raz y una hoja. Un determinado camino contiene siempre
nodos interiores y una sola hoja con la respuesta. En el caso de una respuesta positiva, la clave que se busca se encuentra
en el ltimo nodo interior del camino.

275
*OUSPEVDDJOBMBQSPHSBNBDJO

Un clculo de bsqueda se traduce en un rbol de comparaciones. As, el grado de un nodo es de:

s 3 por la raz.
s 1 por un nodo que es una hoja (sin hijo).
s 4 por los otros nodos, los cuales son nodos interiores (ni raz, ni hojas).

Por cada nodo interior, hay como mximo otros dos nodos descendentes, los cuales son nodos interiores. Si se construye
un rbol de comparaciones A con un conjunto con N elementos, entonces A tiene al menos N nodos interiores.
Con base en estas observaciones, es posible mostrar el siguiente resultado matemtico:

s Teorema: La bsqueda de un elemento en un conjunto de tamao N, siempre se realiza haciendo al menos log2
N comparaciones.
s Demostracin: Si A es un rbol de comparaciones para la bsqueda en el conjunto con N elementos, el nmero
de comparaciones es la profundidad del rbol excepto 1; esto es, el nmero de nodos interiores del camino mxi-
mo entre la raz y una hoja.

El nmero mnimo de comparaciones necesario se encuentra en la profundidad mnima de cualquier rbol de compara-
ciones para los N elementos iniciales.
As, sea k la profundidad mnima del rbol de comparaciones que contiene al menos N nodos interiores. A cada nivel
se encuentran 2i nodos. As, entonces hay:

1 + 2 + + 2k1 < N < 1 + 2 + + 2k1 + 2k


2k1 < N < 2k+1 1
k < log2 N + 1 < k +1

De aqu, se deduce que k es la parte entera superior de log2 (N + 1). Entonces:

k = log2 N.

Este resultado terico muestra que cualquier algoritmo en 0(log2N) es considerado como ptimo.

Seleccin

La denicin formal del problema de seleccin es la siguiente. Del conjunto U = { x1, x2 xN} se desea obtener el si-
guiente valor:

mn (U) = min tal cual min < xi , i = 1, N

Y el valor:
mx (U) = max tal cual max > xi , i = 1, N

Lo anterior signica que se trata de dos problemas separados; por un lado, el clculo del mnimo y, por el otro, el clculo
del mximo, los cuales son completamente equivalentes (si se conoce una solucin para cada problema, esta misma
solucin se puede utilizar para el otro problema). Como la relacin de orden < es total y el conjunto inicial es nito, es
posible determinar la solucin del problema de seleccin en tiempo nito.
Segn la denicin misma del problema, se deben hacer al menos N 1 comparaciones para cada conjunto que no
est organizado en ninguna estructura de datos conveniente, para buscar el mnimo o el mximo. Si se pretende buscar
los dos extremos, es posible demostrar que el tiempo de seleccin sera 3 N2 1 .

276
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

Proposicin: Si los valores del conjunto U se tratan de manera secuencial (es decir, uno despus de otro, sin poder
regresar), solo el clculo del mnimo se hace con N 1 comparaciones. El clculo del mnimo y del mximo se hace con
al menos 32N 2 comparaciones, en el peor de los casos.
Demostracin: Para la primera parte se requiere demostrar que se necesitan al menos N 1 comparaciones. En el
caso de que se realicen menos de N 1 comparaciones, a saber N 2 o menos, signica que uno de los elementos de
U no ha sido tratado; que es absurdo. Para la segunda parte se requiere demostrar que se necesitan al menos 32N 2
comparaciones. Se supone que trabajamos por induccin, segn el nmero N de los elementos del conjunto.
s En el caso de N = 2, una sola comparacin es suciente para detectar el mnimo y el mximo:

32
2
2=32=1

s En el caso de N = 3, se puede proponer el siguiente algoritmo, el cual calcula el mnimo y el mximo de los dos
primeros elementos y luego se comparan sucesivamente con x3:

if x1 < x2 then
min x1; max x2
else
min x2; max x1
endif
if x3 < min then
min x3
else
if x3 > max then
max x3
endif
endif

s Si se supone cierta la proposicin para todos los M, M < N, hacemos la demostracin para N + 1.
(3 N 1)
Segn la hiptesis inductiva por N 1 se necesita 2
.

As, sean mn y mx el mnimo y el mximo de los primeros N 1 elementos. Para calcular el mnimo mn y el
mximo mx de todos los N + 1 elementos, podemos proponer el clculo siguiente:

calculo de min y max


if xN-1 < xN then
minxN-1; maxxN
else
minx2; maxxN-1
endif
if min < min then
minmin
endif
if max > max then
maxmax
endif

En la ltima parte del algoritmo se realizan tres comparaciones; entonces el nmero total de comparaciones es:
(3 N 1) 3N
+3=
2 2
Si las operaciones de seleccin del mnimo o del mximo se realizan con frecuencia, los elementos se pueden insertar
y borrar, lo ms conveniente es organizar el conjunto de datos U en estructuras de datos convenientes. Una estructura

277
*OUSPEVDDJOBMBQSPHSBNBDJO

de datos es aquella que permite la realizacin de las siguientes operaciones: insercin, eliminacin, clculo del extremo
(mnimo o mximo) y extraccin (eliminacin) del extremo; esta ltima recibe el nombre de cola de prioridades.

Ordenamiento

En el tercer volumen de su amplia monografa, El arte de programar ordenadores, Donald Knuth estudi a profundidad
el problema de bsqueda y el problema de ordenamiento. Al inicio de la parte dedicada al ordenamiento, Knuth indica
que 25% del tiempo de cmputo (en aquel tiempo) estaba dedicado a las operaciones de ordenamiento. Hoy en da,
las cosas no han cambiado radicalmente, ya que las operaciones de ordenamiento tambin son muy frecuentes de una
manera explcita. Por ejemplo, destacan las operaciones bancarias ordenadas de forma decreciente, de acuerdo con la
fecha, o los resultados de un motor de bsqueda de Internet, donde los resultados aparecen ordenados segn un criterio
de pertinencia que no es explcito.
De manera formal, ordenar un conjunto de elementos U = { x1, x2 xN} es equivalente a determinar una permutacin
 de los ndices {1, 2 N}, de tal manera que hay:

i < j : x(i) < x( j )

Esto signica que:


x(1) < x(2) < < x(N)

Entonces, en la mayora de los casos se obtiene por ordenamiento el conjunto U = { x1, x2 xN} = { y1, y2 yN} con los
elementos yi ordenados.
En el anlisis de las soluciones de ordenamiento intervienen varios criterios, entre los que destacan:
s La representacin del conjunto inicial; a saber, la estructura de datos que contiene el conjunto U.
s El nmero de operaciones que se hacen. En este caso, las operaciones ms signicativas son: las comparaciones
y el nmero de asignaciones entre elementos, para cambiar la zona de memoria ocupada; en algunos algoritmos
tambin se realizan intercambios entre elementos.
s Si el resultado del ordenamiento se encuentra en la misma parte de la memoria que el conjunto inicial, entonces se habla
de ordenamiento in situ en un caso y de ordenamiento con memoria suplementaria en el otro caso.
s Estabilidad. Se dice que si el orden inicial de los elementos equivalentes (de claves equivalentes) se guarda en el
resultado del ordenamiento, entonces el mtodo (algoritmo) de ordenamiento es estable. La propiedad de estabilidad
es importante en el caso donde se requiere obtener un resultado ordenado segn una clave principal; en cambio, en
el caso donde las claves principales son equivalentes, el ordenamiento se hace segn otra clave.
Por ejemplo, inicialmente tenemos un conjunto de nombres de alumnos ordenado alfabticamente donde a cada alum-
no se le asigna un valor de calicacin nal; entonces, si el ordenamiento, segn esta calicacin, es estable, el orden nal
presenta una lista ordenada con el nombre de los alumnos acompaado de las calicaciones, tambin ordenadas por el
mismo valor de calicaciones, es decir en orden alfabtico. Entonces, se puede decir que se hace un solo ordenamiento
segn un solo criterio, para que el ordenamiento sea estable.

Ejemplo

Para el conjunto 3, 2, 1, 3, 1, el orden 1, 1, 2, 3, 3, no es estable, debido a que no se respeta el orden inicial de los

valores; en este caso, el orden 1, 1, 2, 3, 3, s es estable.

Con el objetivo de analizar mtodos de ordenamiento, se recomienda construir rboles de ordenamiento, donde en los
nodos internos (que no son hojas) hay comparaciones y en las hojas hay permutaciones del conjunto inicial.
Por ejemplo, para N = 2 existe el siguiente rbol:

278
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

x1 : x2

< >

x1, x2 x2, x1

Figura 6.3

Para N = 3 existe el rbol siguiente:

x1 : x2
< >

x2 : x3 x1 : x3

< > < >

x1, x2, x3 x1 : x3 x2, x1, x3 x2 : x3


< > < >

x1, x3, x2 x3, x1, x2 x2, x3, x1 x3, x2, x1

Figura 6.4

Estos rboles de comparaciones tienen como hojas las permutaciones del conjunto inicial U, pero para que estos sean
correctos es necesario (aunque no suciente) encontrar, al menos una vez, todas la permutaciones. As, el nmero de
hojas es por lo menos de N!.3
El nmero mximo de operaciones que se hacen en un mtodo de ordenamiento descrito por un rbol de compara-
ciones es indicado por el tamao del camino entre la raz y una hoja (el nmero de nodos de comparacin incluso la raz).
A continuacin se describen algunas propiedades.
s Proposicin es la profundidad (el camino ms largo entre la raz y una hoja) de un rbol de comparaciones, la cual
es de al menos log2 N!.
s Demostracin. Esta propiedad se basa en la observacin de que un rbol de comparaciones tiene sus nodos in-
ternos de grado 3, con dos descendentes, y que la raz tiene grado 2, tambin con dos descendentes.
Basndonos en este resultado, es posible enunciar el siguiente teorema:
Cualquier algoritmo de ordenamiento necesita al menos 0(N log2 N) comparaciones, en el caso ms desfavorable.
Demostracin del teorema: Si se considera el rbol de comparaciones que traduce el algoritmo, en el caso ms
desfavorable, se encuentra en el camino ms largo entre la raz (el inicio del algoritmo) y una hoja (el resultado nal).
Es importante destacar que el largo de este camino es lo mismo que la profundidad del rbol.
Entonces, el nmero mnimo de comparaciones de un algoritmo, en el caso ms desfavorable, es de log2 N!. Si aqu
aplicamos la frmula de Stirling:
N
N
N! 2 N
e
3
Se recuerda que N! = 1 x 2 x 3 . . . x N es el nmero de permutaciones posibles de N objetos.

279
*OUSPEVDDJOBMBQSPHSBNBDJO

se obtiene log2 N! N log2 N.


Los algoritmos de ordenamiento que se presentan y estudian en este captulo son:

s Cuadrticos: 0(N2).
s Subcuadrticos: 0(N log N) en media, 0(N2) en el peor de los casos.
s Optimales: 0(N log N).
s Lineales: 0(N); estos funcionan nicamente para conjuntos particulares.

6.3 Arreglos y listas ligadas

En el captulo 5 se estudian los arreglos y las listas ligadas, estructuras de datos que tienen en comn una visin lineal
de los elementos que se almacenan. En su caso, el arreglo tiene la ventaja del acceso directo a cualquier elemento, pero
necesita una zona de memoria contigua para guardar sus elementos. Por su parte, la lista ligada tiene la ventaja de poder
contener un nmero importante de elementos, sin ninguna restriccin sobre la memoria utilizada; su desventaja proviene
del acceso a los elementos, ya que falta el acceso directo a los mismos; se puede acceder a un elemento recorriendo la
lista desde su inicio hasta que se encuentra el elemento.
Asimismo, en el captulo 5 se explica que estas dos estructuras, de arreglo y de lista ligada, no tienen ninguna restric-
cin entre los elementos. La insercin y la supresin de elementos se realizan fcilmente al n de arreglo o al inicio de
lista. Por lo general, la bsqueda se realiza con 0(N) operaciones.
En esta seccin seguimos trabajando con arreglos y listas ligadas, pero analizando los siguientes problemas: bsque-
da, seleccin y ordenamiento. As, para cada una de las estructuras de datos vamos a tratar dos casos:
a) Los elementos estn ordenados.
b) Los elementos no estn ordenados.

Bsqueda

En estructuras lineales de tipo arreglo y lista ligada es posible aplicar el mtodo de bsqueda secuencial. En tanto, para
un arreglo ordenado, se puede aplicar el mtodo de bsqueda binaria. Un caso particular de bsqueda binaria que
siempre se aplica por arreglos ordenados, cuando se conoce la distribucin inicial de los elementos, es la bsqueda por
interpolacin. La bsqueda binaria no se puede aplicar fcilmente a listas ligadas porque se necesita un acceso directo a
los elementos.

Bsqueda secuencial
El principio de la bsqueda secuencial es muy simple. Primero, se recorre el conjunto U = { x1, x2 xN}, desde el inicio
(o desde el n), en bsqueda de un elemento despus de otro, que es su vecino, hasta que se encuentra el valor que
se busca.
As, para cada i, de 1 hasta N, comparar x con xi. Si x1 = x, EXITO para por el ndice i.
Para un arreglo (no ordenado), la funcin que traduce este algoritmo es la siguiente:

int busqueda_secuencial(int X[], int N, int valor, int *pos)


{
int i;
for (i = 0; i < N; i++)

280
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

if (X[i] == valor)
{
*pos = i;
return CIERTO;
}
return FALSO;
}

La funcin toma como parmetros de entrada el arreglo, su dimensin y el valor que se busca, y regresa uno de los dos
valores, CIERTO o FALSO. Como parmetro de salida (transmitido por referencia), la funcin toma el primer ndice que es
equivalente al valor. Una llamada de esta funcin tiene la forma:

busqueda_secuencial(A, na, valor, &pos)

Para el caso de una lista ligada elaborada con elementos del tipo:

struct elemento
{int clave;
struct elemento *next;
};

typedef struct elemento* Tlista;

la funcin de bsqueda secuencial es la siguiente:

int busqueda_secuencial(Tlista L, int valor, Tlista *E)


{
Tlista aux;
for (aux = L; aux != NULL; aux = aux->next)
if (aux->clave == valor)
{
*E = aux;
return CIERTO;
}
return FALSO;
}

La funcin anterior tiene tres parmetros: el apuntador de la lista, el valor que se busca y un apuntador al elemento, que
es equivalente al valor buscado. Una llamada a esta funcin es de la forma:

busqueda_secuencial(L1, val, &aux)

Para analizar la complejidad de la funcin de bsqueda,4 se toman las probabilidades pi donde el valor buscado sea equi-
valente al elemento xi y la probabilidad q del valor no est en el arreglo; entonces, el nmero promedio de comparaciones
que se hacen es:

M = 1 x p0 + 2 x p1 + + N x pn1 + (N + 1) q

Hay que q + Ni= 1 pi = 1 y pi, q [0,1]. Si se supone que p1 = p2 = pN, el nmero medio de comparaciones de la
funcin deriva en:

M = p1 N (N + 1) + q(N + 1)
2
As, en la hiptesis q = 0 (donde se busca un valor que seguramente pertenece a U), el nmero promedio de operacio-
nes es N + 1 .
2

4
Se recuerda que los clculos se hicieron en el captulo 5.
281
*OUSPEVDDJOBMBQSPHSBNBDJO

1
Por su parte, en la hiptesis q = 2
(donde existe la misma probabilidad de que el elemento se encuentre o no en el
arreglo), se obtiene M = 3 ( NN+ 1)
As, el nmero promedio de operaciones que se hace para una bsqueda secuencial de valor en un arreglo o una
lista es 0(N).
Para la bsqueda secuencial en estructuras ordenadas, el principio es el mismo, nicamente que cuando se en-
cuentra un elemento mayor que el valor, se puede terminar la bsqueda sin xito, debido a que todos los elementos que
siguen tambin son mayores que el valor buscado.
Para cada i de 1 hasta N comparar x con xi.

s Si xi = x, salida con XITO para el ndice i.


s Si x < xi, salida SIN XITO.

La funcin que traduce este algoritmo para un arreglo ordenado es la siguiente:

int busqueda_secuencial_ordenada(int X[], int N, int valor, int *pos)


{
int i;
for (i = 0; i < N; i++)
{
if (X[i] == valor)
{
*pos = i;
return CIERTO;
}
if (X[i] > valor)
{
*pos = i;
return FALSO;
}
}
return FALSO;
}

De esta forma, en el caso FALSO (es decir, SIN XITO), el ndice que se regresa indica el primer elemento del arreglo
mayor que el valor buscado.
La funcin de bsqueda secuencial en una lista ligada con elementos en orden creciente es la siguiente:

int busqueda_secuencial_ordenada(Tlista L, int valor, Tlista *E)


{
Tlista aux;
for (aux = L; aux != NULL; aux = aux->next)
{
if (aux->clave == valor)
{
*E = aux;
return CIERTO;
}
if (aux->clave > valor)
{
*E = aux;
return FALSO;

282
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

}
}
return FALSO;
}

Para calcular el nmero promedio de comparaciones, debemos considerar las siguientes probabilidades:

s pi: probabilidad x = xi, i = 1, N.


s q0: probabilidad x < x1.
s qN: probabilidad x > xN.
s qi: probabilidad xi < x < xi + 1, i = 1, N 1.
N N
Las probabilidades pi y qj verican: pi + qi = 1
i =1 i =1

Si hacemos la hiptesis:

p1 = p2 = = pN y q0 = q1 = = qN

Entonces, el nmero promedio de comparaciones se obtiene de:

M = p1 N (N + 1) + q0 (N + 1) (N + 2)
2 2
Si el valor buscado se encuentra, en efecto, en el conjunto U, esto signica:
qi = 0, i = 0, N
El nmero de comparaciones proviene de: M N+1
2
1
Si el valor buscado se encuentra en el arreglo con la probabilidad 2 , entonces:

2N + 3
M= 4

Este resultado indica que el mtodo de bsqueda secuencial con estructuras ordenadas es ms rpido que en el caso de
una estructura desordenada, mediante un factor de proporcionalidad.
La complejidad del algoritmo de bsqueda secuencial en todos los casos es 0(N).

Bsqueda binaria
Este mtodo de bsqueda de valor funciona de manera ptima con arreglos ordenados. La idea principal es la reduc-
cin del espacio en el cual se busca el valor. Al inicio, el espacio es el arreglo completo. Entonces, se toma un ndice
al interior del intervalo (la mitad, por ejemplo) y este elemento (el pivote) se compara con el valor buscado. Si la
comparacin pone en evidencia la equivalencia, la bsqueda termina con xito; de lo contrario, si el valor buscado es
menor que el elemento pivote, entonces se reduce el intervalo de bsqueda hacia la izquierda. En el caso opuesto,
el intervalo se reduce a la derecha.
Sean i y d los ndices del intervalo en el cual se busca el valor x. Entonces, el inicio es i 1 y d N. El siguiente es
un paso iterativo de la bsqueda con un ndice j al interior del intervalo [i, d], i < j < d:
if x = xj then
XITO
endif
if x > xj then
ij+1
else
dj-1
endif

283
*OUSPEVDDJOBMBQSPHSBNBDJO

La bsqueda termina ya sea en un caso de xito o cuando el intervalo [i, d] no existe, porque i resulta mayor que d.
Para elegir el ndice j al interior del intervalo, existen varias opciones posibles:

s De manera aleatoria.
s A la mitad del intervalo.
s Con otro procedimiento.

El cdigo de la funcin recursiva de bsqueda binaria, tomando la mitad del intervalo, es el siguiente:

int busqueda_binaria_rec(int T[], int g, int d, int valor, int *pos)


{
int m;
if (g > d) return FALSO;
m = (g + d) / 2;
if (T[m] < valor)
return busqueda_binaria_rec(T, m+1, d, valor, pos);
if (T[m] == valor)
{
*pos = m;
return CIERTO;
}
else
return busqueda_binaria_rec(T, g, m-1, valor, pos); }

La llamada incial de esta funcin es la siguiente:

busqueda_binaria_rec(A, 0, na-1, valor, &pos)

La versin no recursiva de esta funcin es la siguiente:

int busqueda_binaria(int T[], int g, int d, int valor, int *pos)


{
int m;
while (g <= d)
{
m = (g + d) / 2;
if (T[m] == valor)
{
*pos = m;
return CIERTO;
}
if (T[m] < valor)
g = m + 1;
else
d = m - 1;
}
return FALSO;
}

En comparacin con el algoritmo de bsqueda secuencial que encontraba el primer valor del arreglo equivalente al valor
buscado, este algoritmo encuentra cualquier elemento equivalente.5

5
Para encontrar el primer elemento equivalente se debe modicar la condicin de terminacin del algoritmo.

284
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

Teorema: La bsqueda binaria (dicotmica), tomando la mitad del intervalo, necesita el mximo [log2N] + 1 compa-
raciones, tanto en caso de xito como en caso contrario.
Demostracin: Si despus de una comparacin entre el valor y la clave del arreglo, no hay equivalencia, el espacio
de bsqueda se reduce de la mitad, es decir, el tamao se divide entre dos.
N
Entonces, despus de k comparaciones, el espacio de bsqueda es de tamao 2k
.
N
En este caso, la bsqueda se termina, lo mismo ocurre cuando hay xito en el caso o cuando hay 2k < 1.
Entonces, se realizan al mximo k + 1 comparaciones, donde k = [log2N].
La complejidad de la bsqueda binaria es 0(log2N), porque las operaciones que se realizan a cada paso, incluso el clculo
de la mitad del intervalo (tambin la constitucin de la pila de llamadas para la versin recursiva) se hacen en tiempo
constante.
Asimismo, el algoritmo de bsqueda binaria tambin est implementado como una funcin en la biblioteca de fun-
ciones estndares stdlib. El prototipo de esta funcin stdlib.h es el siguiente:
void *bsearch(const void *key,
const void *base, size_t nel, size_t size,
int (*compar)(const void *, const void *));
De esta forma, la funcin recibe como parmetros:
s Un arreglo key de un tipo (puede ser cualquiera) de dimensin size.
s La dimensin del arreglo (su nmero de elementos): nel.
s Un apuntador para el valor que se busca: base.
s Un apuntador para la funcin que realiza la comparacin de los elementos del arreglo.
Esta funcin regresa un apuntador para un elemento del arreglo equivalente al valor apuntado para base o bien regresa
el valor NULL, si el valor no se encontr; en este caso, se supone que el arreglo key est ordenado de forma creciente.
Esta funcin tiene una complejidad en 0(logN).
Un ejemplo de uso de la funcin bsearch es:

static int comp(void *e1, void *e2)


{
int *p1, *p2;
p1= e1;
p2 = e2;
return (p1 <= p2);
}

int T[300];
int N;
int val;

int main()
{
int *p;
.....
p = bsearch(&val, T, N, sizeof(int), comp);
if (p== NULL)
printf( No se encontro \n);
else
printf( El valor %d se encontre.\n , *p);
....
}

285
*OUSPEVDDJOBMBQSPHSBNBDJO

El programador solo debe implementar la funcin de comparacin. La funcin regresa 1, 0 o 1, segn el resultado de
la comparacin <, = o >.

Bsqueda por interpolacin


Este mtodo es una adaptacin del algoritmo de bsqueda binara (dicotmica), a travs del cual se intenta seleccionar
un mejor ndice j al interior del intervalo [i, d]. Esta bsqueda tiene similitud con la bsqueda de una palabra que empieza
con la letra a en un diccionario; en este caso, el diccionario no se abre a la mitad, sino muy cerca de su inicio.
La bsqueda por interpolacin es un caso particular de bsqueda binaria, solo que el ndice interior m i, d que
se elige entre el lmite izquierdo i y el lmite derecho d se toma imaginando que los elementos del arreglo son unifor-
memente distribuidos. Esta hiptesis es muy fuerte, pero si se conoce la distribucin de los elementos, mediante una
funcin de distribucin, se puede adaptar el mtodo de bsqueda por interpolacin a esta funcin.
Si los elementos del arreglo estn uniformemente distribuidos, entonces los binomios de valores (k, xk) se pueden
representar sobre una misma lnea. Si el valor que se busca, x, jams se encuentra en el arreglo del ndice m, el punto
(m, x) pertenece a la misma lnea.
En la gura 6.5 se representa el caso ideal con los puntos (ndice, elemento del arreglo) sobre un mismo segmento
de lnea:

T[d]

T[i]

i m d

Figura 6.5

Si se supone que los puntos (i, xi), (d, xd) y (m, x) son co-lineales, el ndice m se puede calcular segn la frmula que
traduce la condicin de co-linealidad:
x xi x xi
= d
mi di

Como el ndice m es un valor entero, entonces su valor ser:

x xi
m=  (d i) + i
x d x i
Entonces, se impone que xi xd y xi x xd .
286
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

La funcin que traduce esta bsqueda por interpolacin es la siguiente:


int busqueda_interpolacion(int T[], int g, int d, int valor, int *pos)
{
int m;

while (g <= d)
{
if (valor < T[g])
return FALSO;
if (valor > T[d])
return FALSO;}
if (T[g] == T[d])
{
*pos = g;
return CIERTO;
}

m = 1.0 * (valor - T[g]) / (T[d] - T[g]) * (d - g) + g;


if (T[m] == valor)
{
*pos = m;
return CIERTO;
}
if (T[m] < valor)
g = m + 1;
else
d = m - 1;
}
return FALSO;
}

En la parte derecha de la asignacin:

m = 1.0 * (valor - T[g]) / (T[d] - T[g]) * (d - g) + g;

se fuerza el clculo exacto del ndice m con una primera multiplicacin con el valor otante 1.0; de esta manera, todos
los clculos se hacen en punto otante. Si no se fuerza el clculo al punto otante, la divisin es la divisin entera y se
pierde el sentido correcto del clculo.
Esta funcin tiene una complejidad de 0(loglogN),6 siempre con base en la hiptesis de la distribucin uniforme de
los valores del arreglo. Este resultado es mejor que el de la complejidad terica ptima de cualquier algoritmo de bs-
queda. Esta no es una contradiccin, porque el algoritmo de bsqueda por interpolacin tiene este desempeo en un
caso particular.
Esta hiptesis tan fuerte es primordial para el funcionamiento rpido de la funcin. Por ejemplo, para los valores: 1, 2,
3, 4, 5, 6, 1000, 1001, 1002, 1003, 1004, 1005, la bsqueda del valor 12, aplicando el algoritmo de bsqueda por in-
terpolacin, necesita seis iteraciones, mientras que el algoritmo clsico de bsqueda binaria necesita solo tres iteraciones.
Se puede proponer una bsqueda hbrida con dos ndices: uno, el primero, estara a la mitad del intervalo, y el otro
sera calculado por interpolacin, por lo que se elige el mejor.

6
La demostracin de este resultado es muy tcnica. Vase el libro de G. N. Gonnet (1984), Handbook of Algorithms and Data
Structures, Addison-Wesley, p. 34.

287
*OUSPEVDDJOBMBQSPHSBNBDJO

int busqueda_interp_dicotomica(int T[], int g, int d, int valor, int *pos)


{

int m;

while (g <= d)
{
if (valor < T[g])
return FALSO;
if (valor > T[d])
return FALSO;
if (T[g] == T[d])
{

*pos = g;
return CIERTO;
}

m = 1.0 * (valor - T[g]) / (T[d] - T[g]) * (d - g) + g;


if (T[m] == valor)
{
*pos = m;
return CIERTO;
}
if (T[m] < valor)
g = m + 1;
else
d = m - 1;
}
return FALSO;
}

Entre las dos posiciones del ndice pivote, se toma el valor que reduce al mximo posible el intervalo.

i m me d

Figura 6.6

La complejidad de esta funcin deja 0(logN).

Seleccin

Gracias a una estructura de tipo lista ligada o a un arreglo ordenado creciente, el valor mnimo se encuentra en la primera
posicin y el valor mximo en la ltima. Para obtener estos valores, se necesitan 0(1) operaciones, excepto para determi-
nar el mximo en una lista ligada que impone recorrer por completo la lista para determinar este elemento.
En el caso del arreglo y las listas ligadas, que no son ordenadas, el clculo del mnimo o del mximo se hace, en 0(N)
operaciones, ms precisamente en N 1 comparaciones de claves.
La funcin que determina el ndice del elemento mnimo de un arreglo de entero es:

void determina_min(int T[], int n, int *pos)


{

288
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

int i;
int min;

min = T[0];
*pos = 0;
for (i = 1; i < n; i++)
if (T[i] < min)
{

min = T[i];
*pos = i;
}
return;
}

Un ejemplo de llamada a esta funcin es:

determina_min(A, na, &pos);


printf( El minimo es %d y su posicion es %d/n, A[pos], pos);

Si se trabaja con listas ligadas, es posible aplicar, sin ningn problema, el mismo principio para recorrer los elementos de
la lista; tambin se hacen exactamente N 1 comparaciones para una lista de N elementos. El cdigo de esta funcin es:

void min_lista(Tlista L, Tlista *M)


{
Tlista aux;

if (L == NULL)
{
*M = NULL;
return;
}
*M = L;
aux = L->next;

while (aux != NULL)


{
if (aux->clave < (*M)->clave)
*M = aux;
aux = aux->next;
}
return;
}

Un ejemplo de llamada a esta funcin es:

Tlista L1, aux, Min;


...
min_lista(L1, &Min);
printf( El elemento minimo de la lista: %d\n, Min->clave);

En estas dos funciones, la variable min contiene el mnimo relativo del inicio hasta el punto corriente. Para obtener
el mximo, el algoritmo es exactamente el mismo: se inicia una variable max con la clave del primer elemento y desde
el segundo elemento hasta el n se ajusta la variable max si se encuentra una clave mayor.

289
*OUSPEVDDJOBMBQSPHSBNBDJO

Si se desea determinar en un mismo recorrido el mnimo y el mximo, lo ptimo es que en una primera versin de
un algoritmo se recorran los elementos, desde el segundo elemento hasta el nal del arreglo, comparando sistemti-
camente la clave primero con el mnimo y luego con el mximo. Es importante resaltar aqu una observacin simple: si
la clave proviene de un mnimo, no vale la pena compararla con el mximo relativo. El cdigo de la funcin que trabaja
segn este principio es el siguiente:

void determina_min_max_simple(int T[], int n, int *posmin, int *posmax)


{
int i;
int min, max;

min = T[0];
*posmin = 0;
max = T[0];
*posmax = 0;
for (i = 1; i < n; i++)
{
if (T[i] < min)
{
min = T[i];
*posmin = i;
}
else
if (T[i] > max)
{
max = T[i];
*posmax = i;
}
}
return;
}

A cada iteracin se hacen una o dos comparaciones. El nmero total de comparaciones es entre N 1 (el arreglo est
en orden decreciente) y 2(N 1) (el arreglo est en orden decreciente).
En el estudio terico del problema de seleccin conjunta de mnimo y mximo, se demuestra que el nmero mxi-
mo necesario es 3N 2
2. En esta demostracin del resultado fue puesto en evidencia un trabajo para parejas de valores
sucesivos. Inspirndonos en este principio, una funcin que calcula conjuntamente el mnimo y el mximo es la siguiente:

void determina_min_max(int T[], int n, int *posmin, int *posmax)


{
int i;
int j, k;
int min, max;

// caso del arreglo con un solo elemento


if (n == 1)
{

*posmin = 0;
*posmax = 0;
return;
}
// inicializacion para min y max
if (T[0] < T[1])

290
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

{
min = T[0];
*posmin = 0;
max = T[1];
*posmax = 1;
}
else
{
min = T[1];
*posmin = 1;
max = T[0];
*posmax = 0;
}
// recorrer 2 por 2 los elementos del arreglo
for (i = 2; i < n-1; i +=2)
{
if (T[i] < T[i+1])
{
j = i;
k = i+1;
}
else
{
j = i+1;
k = i;
}
if (T[j] < min)
{
min = T[j];
*posmin = j;
}
if (T[k] > max)
{
max = T[k];
*posmax = k;
}
}
// si n es impar se trata el ultimo elemento del arreglo
if (n % 2 == 1) //n impar
{

if (T[n-1] < min)


{
min = T[n - 1];
*posmin = n - 1;
}
else
if (T[n-1] > max)
{
max = T[n - 1];
*posmax = n - 1;
}
}
return;
}

291
*OUSPEVDDJOBMBQSPHSBNBDJO

Esta funcin tiene tres pasos:


1. Inicializar las variables min y max con los dos primeros valores del arreglo.
2. Recorrer el arreglo con un paso de dos, comparando un elemento con su vecino sucesor, con el n de determinar
cul de los dos se compara con el min y cul con el max.
3. Si el arreglo tiene un nmero impar de elementos, se debe tratar el ltimo elemento.

Al paso 1 se le realiza una sola comparacin, durante la estructura iterativa; a diferencia de los pasos 2 y 3, a los cuales
se les hacen tres comparaciones. Si es necesario, al paso 3 se le hacen una o dos comparaciones. Por tanto, el nmero
total de comparaciones es 3N2
2.
En conclusin, podemos decir que en un arreglo o en una lista ligada ordenada, el mnimo se determina en 0(1)
operaciones. En cambio, para un arreglo o una lista sin ningn orden entre sus elementos, determinar el mnimo o el
mximo se hace con N 1 comparaciones y una complejidad de 0(N). Para determinar conjuntamente el mnimo y
el mximo se necesitan 0(N) operaciones.

Mantenimiento: insercin y eliminacin

La insercin y la eliminacin de un elemento de una estructura sin orden se hacen en tiempo constante 0(1), excepto
en el caso de la eliminacin de un elemento de una lista ligada, cuando este tiempo constante interviene despus de
encontrar la referencia del elemento que lo precede en la lista. En el caso de la falta de orden en la estructura, la insercin
se hace en el lugar ms conveniente para el programador; ya sea al inicio de la lista o al nal del arreglo.
Para el caso de las estructuras ordenadas, el orden de los elementos siempre se debe respetar. Cuando se trata de
la insercin, primero se busca el lugar de la insercin y luego se realiza dicha insercin. La bsqueda de valor se hace en
0(N), para las listas o para los arreglos, si se aplica la bsqueda secuencial; tambin en 0(logN), para el caso de los arreglos,
si se aplica la bsqueda binaria. En el caso de la lista ligada, la insercin se hace en tiempo constante. Por otro lado, para
el caso del arreglo, se deben mudar todos los elementos que siguen a un elemento insertado. Dicha mudanza necesita
entre 0 y N operaciones (la insercin se realiza al nal del arreglo o al inicio). En promedio, la complejidad de la mudanza
es de 0(N). Por su parte, la complejidad global de la operacin de insercin es, entonces, en todos los casos, de 0(N).
La eliminacin de un elemento tambin se acompaa de una mudanza de los elementos; sin embargo, en el caso de
los arreglos solo se hace una reconstitucin de las ligas (apuntadores) para las listas ligadas. La complejidad de esta opera-
cin es de 0(N), tanto en el caso de la lista ligada, para lo cual la bsqueda es en 0(N) y la eliminacin de la celda en 0(1),
como para el arreglo, ya que la complejidad de la mudanza de elementos es 0(N) y una bsqueda es 0(logN) o 0(N).
Para el caso de los arreglos, la operacin de eliminacin de un elemento, cuando se conoce su posicin, se traduce
en la siguiente funcin:

void eliminacion(int T[], int *N, int pos)


{
int j;
for (j = pos; j < *N ; j++)
T[j] = T[j+1];
*N = *N - 1;
return;
}

Los elementos del arreglo se deben mudar de una posicin hacia la izquierda, empezando por el elemento de ndice pos
hasta el n del arreglo. La dimensin del arreglo debe cambiar al nal de la funcin de eliminacin.
La llamada de esta funcin se hace despus de una bsqueda de valor en el arreglo, pero solo en el caso de bs-
queda con xito:

if (busqueda_binaria(A, 0, na-1, valor, &pos) == CIERTO)


eliminacion(A, &na, pos);

292
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

Una funcin de insercin que, al mismo tiempo, hace la bsqueda del lugar y la mudanza de los elementos que siguen
al valor insertado es la siguiente:
void insercion_busqueda_secuencial(int T[], int *N, int valor)
{
int j;
for (j = *N-1; j >= 0; j--)
{
if (T[j] <= valor)
break;
T[j+1] = T[j];
}
T[j+1] = valor;
*N = *N + 1;
return;
}

Los elementos que requieren cambiar de posicin son los que se encuentran al nal del arreglo, hasta que se encuentre
la posicin de insercin ms adecuada. La complejidad de esta funcin es de 0(N). En el mejor de los casos, se hace
una sola comparacin, y el elemento insertado es mayor que todos los elementos del arreglo; en el peor de los casos, se
hacen N comparaciones y N mudanzas para el caso de la insercin al inicio del arreglo.
Si primero se quiere utilizar la bsqueda binaria para determinar la posicin del elemento que se inserta, es necesa-
rio cambiar la funcin de bsqueda binaria para regresar tambin un ndice en el caso de bsqueda sin xito. Esta sera
la posicin de insercin del nuevo valor. La funcin de bsqueda binaria cambiada y de la insercin, despus de una
bsqueda binaria, es:

int busqueda_binaria(int T[], int g, int d, int valor, int *pos)


{
int m;
while (g <= d)
{
m = (g + d) / 2;
if (T[m] == valor)
{
*pos = m; return CIERTO;
}
if (T[m] < valor)
g = m + 1;
else
d = m - 1;
}
*pos = g;
return FALSO;
}

void insercion_busqueda_binaria(int T[], int *N, int valor)


{
int j;
int pos;
if (busqueda_binaria(T, 0, *N-1, valor, &pos) == CIERTO)
pos = pos + 1;
for (j = *N-1; j >= pos; j--)
T[j+1] = T[j];
T[j+1] = valor;
*N = *N + 1;
return;
}

293
*OUSPEVDDJOBMBQSPHSBNBDJO

Ordenamiento

Existen varios mtodos de ordenamiento, algunos ptimos, pero difciles de implementar; otros, en cambio, cuadrticos
o subcuadrticos, que resultan fciles de entender o desarrollar. En esta seccin se presentan varios mtodos de ordena-
miento; no obstante, algunos de estos solo son aplicables para los arreglos y no para las listas ligadas.

Ordenamiento de burbuja
El ordenamiento de burbuja es un mtodo bien conocido por los profesores de deportes, quienes lo aplican en el mo-
mento de ordenar a los alumnos por su altura (del ms bajo al ms alto), indicando, cuando es necesario, si dos alumnos
que se siguen deben intercambiar de posicin.
La idea central del algoritmo (en ingls, bubblesort) es intercambiar, iniciando por un lado, los elementos vecinos que
no estn en orden adecuado. Al nal de un recorrido de la estructura (arreglo o lista ligada), se verica si los intercambios
fueron hechos. Si un recorrido se encuentra sin intercambios, entonces ahora se dice que los datos son ordenados, si no
se hacen nuevos recorridos.

Ejemplo
Al inicio los datos son:

09873

Un primer recorrido, de izquierda a derecha, implica los intercambios siguientes: 9 con 8, 9 con 7 y 9 con 3. As, al nal
de este recorrido los elementos son:
08739

Otro recorrido impone los intercambios: 8 con 7 y 8 con 3. En este paso se puede ver que el ltimo elemento se en-
cuentra bien colocado. Al nal de este recorrido los elementos son:

07389
Otro recorrido que inicia con el primer elemento y termina con el tercero impone el intercambio: 7 con 3. Al nal se tiene:

03789

Un nuevo recorrido se hace sin intercambios y el algoritmo termina.


Una funcin importante que es necesario implementar aqu, es el intercambio de contenido entre dos elementos. En
este caso, para simplicar el discurso, en lugar del trmino intercambio usamos la palabra cambio entre dos elementos.
En el caso de dos elementos de un tipo bsico (por ejemplo, un entero), el cambio de contenido se hace con una
funcin con parmetros transmitidos por referencia:

void cambio_contenido(int *a, int *b)


{
int aux;
aux = *a;
*a = *b;
*b = aux;
return;
}

En las funciones de ordenamiento que se presentan, se va a utilizar una funcin que cambia dos elementos de un arreglo
y que recibe como parmetros el arreglo y los ndices:

294
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

void cambio(int T[], int i, int j)


{
int tmp;
tmp = T[i];
T[i]= T[j];
T[j] = tmp;
}

Para el caso de una lista ligada, es posible que se enven los apuntadores de celdas que se deben cambiar. Una posible
funcin de cambio de contenido es la siguiente:

void cambio_contenido(Tlista *E, Tlista *F)


{
int aux;
aux = (*E)->clave;
(*E)->clave = (*F)->clave;
(*F)->clave = aux;
}

Para el caso de cambio de dos elementos que se siguen en una lista ligada, otra funcin de cambio que modica las ligas
(apuntadores) entre celdas es la siguiente:
void cambio(Tlista *E, Tlista prec)
{
Tlista aux, baux;
if (*E == NULL || (*E)->next == NULL)
return;
printf( Inicio de la funcion de cambio\n);
aux = *E;
baux = (*E)->next;
aux->next = baux->next;
baux->next = aux;
if (prec != NULL)
prec->next = baux;
*E = baux;
}

La funcin anterior recibe como parmetros el apuntador de la celda, la cual cambia con su vecino sucesor, y el apuntador
de la celda precedente. En la gura 6.7 se representa el cambio de ligas que se efecta.

clave next clave next


prec -> next:

*E aux *E baux

Figura 6.7

295
*OUSPEVDDJOBMBQSPHSBNBDJO

La funcin de ordenamiento de burbuja que trabaja con esta versin de funcin de cambio es la siguiente:

void orden_burbuja(Tlista *L)


{
Tlista aux, prec, inicio;
int i, j, N, es_cambio;

N = tamano(*L);
inicio = *L;
for ( i = N; i > 0; i--)
{
aux = inicio;
prec = NULL;
es_cambio = FALSO;
for ( j = 1; j < i; j++)
{
if (aux->clave > aux->next->clave)
{
cambio(&aux, prec);
if (prec == NULL)
inicio = aux;
es_cambio = CIERTO;
}
prec = aux;
aux = aux->next;
}
if ( es_cambio == FALSO) break;
}
*L = inicio;
return;
}

Es posible que la primera celda de la lista inicial cambie de lugar; entonces, guardamos en una variable local inicio
un apuntador del inicio de lista. La variable es_cambio indica si durante un recorrido se hacen o no intercambios entre
elementos.

Otra versin del mismo ordenamiento que usa la otra funcin de cambio es la siguiente:

void orden_burbuja_version2(Tlista *L)


{
Tlista aux, sig, inicio;
int i, j, N, es_cambio;

N = tamano(*L);
inicio = *L;
for ( i = N; i > 0; i--)
{
aux = inicio;
sig = aux->next; es_cambio = FALSO;
for ( j = 1; j < i; j++)
{
if (aux->clave > sig->clave)
{
cambio_contenido(&aux, &sig);
es_cambio = CIERTO;

296
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

}
aux = sig;
sig = aux->next;
}
if ( es_cambio == FALSO) break;
}
*L = inicio;
return;
}

Si trabajamos con arreglos, el recorrido durante el algoritmo de ordenamiento por burbuja se puede realizar de izquierda
a derecha o de derecha a izquierda. En el primer caso (de izquierda a derecha), los elementos que estn ordenados se
agrupan al nal del arreglo; en el segundo caso (de derecha a izquierda), los elementos se agrupan al inicio del arreglo.
Si no se toma en cuenta el hecho de hacer o no cambios para detener el algoritmo lo ms rpido posible, el cdigo del
algoritmo de bsqueda de burbuja es el siguiente:

void ordenamiento_burbuja(int T[], int N)


{
int k, j;

for(k = 0; k < N-1; k++)


{
for (j = N-1; j > k ; j--)
if (T[j] < T[j-1])
cambio(T,j, j-1);
}
return;
}

void ordenamiento_burbuja_v2(int T[], int N)


{
int k, j;
for(k = 0; k < N-1; k++)
{
for (j = 0; j < N-1-i ; j--)
if (T[j] < T[j-1])
cambio(T,j, j-1);
}
return;
}

En este caso, la primera funcin recorre el arreglo de derecha a izquierda y la segunda versin en el sentido opuesto. Una
llamada a una de las dos funciones se hace simplemente con:

ordenamiento_burbuja(A, na);

Este algoritmo es uno de los ms simples en trminos de comprensin y de tiempo de desarrollo. No obstante, tambin
se le pueden realizar mejoras; la primera mejora que se puede imaginar es introducir una variable que indique, durante
un paso (una iteracin, segn la estructura ms externa), si se hacen o no cambios entre elementos vecinos; si no fue
hecho ningn cambio, el algoritmo termina porque el arreglo es ordenado. Entonces, el cdigo es el siguiente:

void ordenamiento_burbuja_mejora(int T[], int N)


{
int k, j;

297
*OUSPEVDDJOBMBQSPHSBNBDJO

int es_cambio;

for(k = 0; k < N-1; k++)


{
es_cambio = FALSO;
for (j = 0; j < N-1-k ; j++)
if (T[j+1] < T[j])
{
cambio(T,j + 1, j);
es_cambio = CIERTO;
}
if (es_cambio == FALSO)
break;
}
return;
}

Si se analizan las primeras versiones (ordenamiento_burbuja y ordenamiento_burbuja_v2) es posible obser-


var que el algoritmo trabaja sin memoria excedente, solo utiliza un nmero nito de variables locales.
Por otro lado, si se realizan las comparaciones, siempre en un sentido adecuado para hacer el cambio, es decir solo
si T[ j ] > T[ j + 1], evitando cambiar valores equivalentes, el ordenamiento por burbuja obtenido es estable.
De esta forma, si se analiza el nmero total de operaciones, se puede ver que hay dos tipos de operaciones: com-
paraciones de elementos vecinos y cambios. El nmero total de comparaciones es:

N 1 N 1 N( N 1)
CN = ( N + 1 k 1) = ( N k ) =
K =1 K =1 2

En el peor de los casos, corresponde a un arreglo ordenado decreciente; a cada comparacin se hace un cambio, en total:
N(N 1)
EN = C N = 2

En el mejor de los casos, el arreglo es ordenado, por tanto se hacen 0 cambios:

EN = 0

La complejidad de todo el algoritmo deja en 0(N2); se trata de un algoritmo cuadrtico en el peor de los casos.
Sin embargo, nos interesa la complejidad media, indicada por el nmero medio de cambios entre elementos que
se hacen. Entonces, si nos interesa el nmero medio de cambios de posiciones, por una permutacin de valores T se
puede ver que el nmero de cambios es equivalente al nmero de parejas de elementos que no estn en orden.7 Por
ejemplo, para la sucesin:

S = (0 9 8 7 3)

se deben hacer: 0 + 3 + 2 + 1 = 6 intercambios.


En tanto, para la permutacin en espejo:

S = (3 7 8 9 0)

se deben hacer 1 + 1 + 1 + 1 = 4 intercambios.

7
En la teora algebraica de las permutaciones, una pareja de elementos de una permutacin que no est en orden se llama inversin.

298
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

En este caso, es fcil mostrar que para cualquier permutacin S de N elementos, la suma del nmero de inversiones
en S y S es N (N2+ 1) :

EN (S) + EN (S) = N (N + 1)
2
Como ya se mencion antes, nos interesa el nmero medio de intercambios, el cual se calcula segn la siguiente frmula:
1
EN (S)
N! Spermutacion

Cada permutacin es la imagen en espejo de otra permutacin. Entonces hay que:


EN (S) = E N (T )
Spermutacion Tpermutacion

Esta es equivalente a:
EN (S) = E N (T )
Spermutacion Tpermutacion

Por tanto, se deduce que:


Spermutacion
EN (S) =
1

2 Spermutacion
(E N
(S) + EN (S) )
Si aplicamos la frmula anterior, obtenemos:


Spermutacion
EN (S) =
1
2

Spermutacion
(E N
(S) + EN (S) = ) 1
2
N!
N(N +1)
2
N(N +1)
= N!
SpermutacionE N ( S ) 2

As, el nmero medio de cambios entre elementos es:

N (N + 1) .
4

En este caso, si se introduce una variable para detectar el nal del algoritmo lo ms pronto posible (como en la versin
ordenamiento_burbuja_mejora con la variable es_cambio), el nmero de comparaciones se puede reducir; sin
embargo, no se reduce el nmero de cambios efectuados.
La complejidad general del algoritmo de ordenamiento de burbuja es 0(N2).

Ordenamiento por seleccin


La idea de ordenamiento por seleccin es muy natural; el mnimo se detecta a cada paso y se coloca en la posicin de
la estructura. Los pasos varan desde el inicio (o para los arreglos) hasta la penltima posicin.
Por ejemplo, en la conguracin inicial:

09873

Al nal del paso 0:

09873

299
*OUSPEVDDJOBMBQSPHSBNBDJO

Al nal del paso 1:

03879

Al nal del paso 2:

03789

Al nal del paso 3:

03789

Segn este algoritmo, la funcin que ordena un arreglo es la siguiente:


void ordenamiento_seleccion(int T[], int N)
{
int i, j;
int posmin, valmin;

for(i = 0; i < N-1; i++)


{
posmin = i; valmin = T[i];
for (j = i+1; j < N; j++)
if (T[j] < valmin)
{
posmin = j;
valmin = T[j];
}
if (posmin != i)
cambio(T, i, posmin);
}
return;
}

A cada paso se efectan N 1 i comparaciones para detectar el mnimo y el mximo de un cambio entre la posicin
del mnimo y la posicin i. Entonces, para cualquier conguracin inicial, el nmero de comparaciones y el nmero de
cambios son:

s CN = N (N 1) .
2
s 0 < EN < N 1

En este caso no hay diferencias signicativas entre el mejor de los casos, donde se hacen 0 cambios, y el peor de los
casos, donde se hacen N 1 cambios. A la mitad se puede mostrar que el algoritmo necesita 0(N) cambios entre ele-
mentos.8
La complejidad del algoritmo es 0(N2). Se trata de un algoritmo cuadrtico.

Ordenamiento por insercin


La idea de este algoritmo es que es creciente; es decir, las estructuras ordenadas se construyen sucesivamente con 1, 2,
... hasta N elementos. En el paso i se considera que los primeros elementos estn ordenados, por lo que el elemento de
rango i + 1 se inserta en la estructura ordenada. La bsqueda de la posicin de insercin se puede hacer con cualquier
mtodo que se conozca y se puede aplicar a la estructura de datos.

8
Vase el libro de D. Knuth referido en la bibliografa y en el captulo 5.

300
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

En un arreglo, se puede iniciar por el nal del arreglo T; en este caso, se tratan los elementos T[N 2], T[N 3] ...
T[1], T[0]. Tambin se puede empezar por el inicio del arreglo, tratando los elementos T[1] hasta T[N 1]. Entonces, se
deduce que se puede utilizar cualquier mtodo de bsqueda del lugar de insercin.
La siguiente funcin implementa un ordenamiento por insercin, por lo cual la bsqueda de la posicin para insertar
se hace secuencialmente, al mismo tiempo que la transferencia de elementos:

void ordenamiento_insercion_sec(int T[], int N)


{
int i, j;
int aux;

for(i = 1; i < N; i++)


{
aux = T[i];
for (j = i-1; j >= 0 ; j--)
if (T[j] > aux)
T[j+1] = T[j];
else
break;
j = j + 1;
T[j] = aux;
}
return;
}

Para un arreglo, la funcin de ordenamiento siguiente tambin utiliza una bsqueda binaria de posicin; luego, si la posi-
cin es interna, en el segmento de arreglo ordenado se hacen transferencias para insertar el valor. En este caso, la funcin
de bsqueda binaria (dicotmica) fue adaptada para regresar la posicin de la posible insercin:

int busqueda_dicotomica(int T[], int g, int d, int valor)


{
int m;
if (g >= d) return g;
m = (g+d) / 2;
if (valor < T[m])
return busqueda_dicotomica(T, g, m, valor);
else
return busqueda_dicotomica(T, m+1, d, valor);
}

void ordenamiento_insercion_dicotomica(int T[], int N)


{
int i, j;
int aux, pos;
for (i = 1; i < N; i++)
{
if (T[i] < T[i-1])
{
pos = busqueda_dicotomica(T, 0, i-1, T[i]);
aux = T[i];
for (j = i-1; j >= pos; j--)
T[j+1] = T[j];
T[pos] = aux;

301
*OUSPEVDDJOBMBQSPHSBNBDJO

}
}
}

Para ordenar una lista ligada, se debe tener una funcin que inserte una celda (un elemento de tipo struct ele-
mento) en una lista ligada ordenada. Por otra parte, para ordenar cualquier lista, al inicio se toma una lista vaca, que se
considera como ordenada, en la cual se insertan las celdas que se toman de la lista inicial. El cdigo de las dos funciones
es el siguiente:

void insercion_celda_lista_orden(Tlista *L, Tlista *Elemento)


{
Tlista aux;
Tlista temp, prev;
int valor;

if (*L == NULL)
{
(*Elemento)->next = NULL;
*L = *Elemento;
return;
}

aux = *Elemento;
valor = aux->clave;

if (valor <= (*L)->clave)


{

aux->next = *L;
*L = aux;
return;
}
else
{
prev = *L;
temp = (*L)->next;
while ((temp != NULL) && (valor > temp->clave))
{
prev = temp;
temp = temp->next;
}
prev->next = aux;
aux->next = temp;
}
return;
}
void orden_lista_insercion(Tlista *L)
{
Tlista LI, LF;
Tlista aux;

LI = *L;
LF = NULL;
while (LI != NULL)
{

302
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

aux = LI;
LI = aux->next;
aux->next = NULL;
insercion_celda_lista_orden(&LF, &aux);
}
*L = LF;
}

La complejidad del algoritmo de ordenamiento por insercin se evala con base en el nmero de operaciones: compa-
raciones y transferencias; en este caso, las comparaciones se utilizan para buscar el lugar de insercin, luego se hacen las
transferencias de elementos de una celda vecina a otra.
Si se usa un ordenamiento por insercin con una bsqueda secuencial, a cada paso i se realizan entre 1 e i + 1
comparaciones.
s En el mejor de los casos, se trata de un arreglo ordenado, donde solo se verica que el elemento se halle correc-
tamente ubicado; aqu tampoco se realizan transferencias.
CN = N 1
TN = 0
s En el peor de los casos se realizan i + 1 comparaciones a cada paso; el arreglo es inversamente ordenado.

N2 N2 N( N 1)
C N = i +1 = i =
i = 0 i =1
2

El nmero de transferencias es TN = CN (N 1), porque a cada paso se realiza una transferencia menos que el nmero
de comparaciones.

N (N 1) (N 1)(N 2)
TN = (N 1) =
2 2
s En un caso medio, es posible imaginar que el arreglo se obtiene de manera aleatoria, es decir a cada paso i hay
i + 1 . Entonces, se obtiene:
2
N (N 1)
CN =
4
(N 1)(N 4 )
TN = C N N 1 =
4
Este algoritmo se efecta, entonces, en 0(N2) comparaciones y 0(N2) transferencias. Se trata de un algoritmo cuadrtico.
Si se usa un ordenamiento por insercin con una bsqueda binaria a cada paso i, se realiza el mismo nmero de
transferencias que en el caso de la bsqueda por insercin. As, hay entre 0 y ( N 1)(2N 2) transferencias, en un caso medio
0(N2) transferencias. Como el mtodo de bsqueda cambia, se puede decir que el nmero de comparaciones depende
de esto.
A cada paso i se realizan entre 1 y [log2i + 1] comparaciones.
s En el mejor de los casos se hacen CN = N 1 comparaciones.
s En el peor de los casos se hacen:
N 1 N
CN  log 2 i +1) = log 2 i = log 2 N
i=0 i=0
A saber, CN es 0(NlogN).
Entonces, este algoritmo es el ms adecuado para el nmero de comparaciones y por ser cuadrtico debido al n-
mero de transferencias.

303
*OUSPEVDDJOBMBQSPHSBNBDJO

Ordenamiento de Shell
Es un mtodo de ordenamiento que aplica de una manera original el ordenamiento por insercin, a excepcin de que
no trabaja con el arreglo entero, sino con subconjuntos del arreglo. En este caso, cada subconjunto contiene elementos
a distancia h variable. La variacin del valor de h se realiza sucesivamente:
N > ht > ht1 > h2 > h1 = 1
En este caso, prcticamente h se toma de la sucesin matemtica:
1, 4, 13, 40, ...,
ht = 3ht1 + 1
La idea es que estos ordenamientos sucesivos induzcan segmentos ordenados uno con respecto del otro y que el orde-
namiento del paso ms pequeo trabaje nicamente con estos segmentos.9
La complejidad de esta funcin para sucesiones de pasos de clculo generales ht sera de y (son conjecturas). Aqu
se puede mostrar que el nmero de comparaciones tiene un lmite alto para la sucesin de h:
La funcin que realiza el ordenamiento de Shell tiene dos etapas:
1. Se calcula el primer paso ht.
2. Se itera el paso h reducindolo y a cada iteracin se hace el ordenamiento por insercin de los elementos a
distancia j. El cdigo de la funcin es el siguiente:
void ordenamiento_Shell(int T[], int N)
{
int h, i, j, aux;
// calculo del primer h : lo mayor que 3h+1 <= N
for (h = 1; h <= N; h = 3*h +1);
h = h / 3;
// ordenamientos a paso h
for (; h > 0; h = h/3)
for (i = h; i < N; i++)
{
aux = T[i]; j = i;
while ((j >= h) && (T[j-h] > aux))
{
T[j] = T[j-h];
j -= h;
}
T[j] = aux;
}
}

Este algoritmo es sub-cuadrtico, adems de que resulta bastante ecaz para N y es fcil de programar. Se trata de un
ordenamiento inestable, la inestabilidad aparece al inicio cuando los elementos no se tratan juntos.

Ordenamiento rpido
Se trata de un mtodo propuesto por C. A. Hoare, en 1962 (quicksort, en ingls). Su principio se basa en la tcnica: dividir
e imperar. La idea central de este mtodo es separar el conjunto de valores y ordenarlo en dos conjuntos disjuntos, de tal
manera que los valores del primer conjunto sean menores que los elementos del segundo conjunto; en este caso, cada
conjunto se ordena de manera recursiva.
El pseudocdigo esquemtico de este mtodo es:

9
Vanse las animaciones del CD-ROM.

304
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

fonction ordenamiento_rapido(T[],g,d)
if g < d then
division_arreglo(T,g,d,j);
ordenamiento_rapido(T,g, j-1);
ordenamiento_rapido(T, j+1,d);
endif
endfonction

En este pseudocdigo, la llamada inicial es: ordenamiento_rapido (T, 1, N) u ordenamiento_rapido (T, 0,


N 1). La parte ms sensible es la funcin division. Esta divisin se realiza con respecto a un valor pivote, con el n
de tener los elementos menores que el pivote, entre las posiciones g, i, j 1, y los elementos mayores, entre las posicio-
nes j + 1 y d; entonces, el pivote estara en la posicin j.
Idealmente, lo adecuado sera tomar como pivote el valor medio del arreglo, entre las posiciones g y d; sin embargo,
esto resulta imposible. Entonces, lo que se puede hacer es tomar cualquier elemento como pivote; por ejemplo, el primer
elemento T[ g ]. En este caso, se recorre el arreglo de derecha a izquierda, con un ndice j, y de izquierda a derecha, con
un ndice l, hasta que se encuentren dos elementos que no estn en un orden adecuado con respecto al pivote: T [ l ] >
pivote > T [ j ]. Es este caso, y si l < j, se hace un intercambio entre las posiciones l y j. La divisin se termina cuando los
dos ndices se cruzan: l > j.
El cdigo de esta funcion es, por tanto:

void division(int T[], int g, int d, int *j)


{
int k, l;
l = g + 1;
k = d;
while (l <= k)
{
while (T[k] > T[g]) k = k-1;
while ((l <= k) && (T[l] <=T[g])) l = l+1;
if (l < k)
{
cambio(T, l, k);
l = l+1;
k = k-1;
}
}
cambio(T, g, k);
*j = k;
}

La funcin de ordenamiento es la siguiente:

void ordenamiento_rapido(int T[], int g, int d)


{
int j;
if (g < d)
{
division(T, g, d, &j);
ordenamiento_rapido(T, g, j-1);
ordenamiento_rapido(T, j+1, d);
}
}

305
*OUSPEVDDJOBMBQSPHSBNBDJO

La complejidad de la funcin de divisin es de N 1 o N + 1 comparaciones y al mximo N2 cambios. Si se notan CN, el


nmero de operaciones del ordenamiento rpido por N elementos, entonces este valor depende de la posicin nal del pivote:

CN = Cp1 + CNp + N 1

s En el mejor de los casos, el pivote es el valor medio:


C N = 2C N + N 1
2

Entonces:

CN = 0(NlogN)

s En el peor de los casos, el pivote es el mnimo de los valores; en otras palabras, el arreglo est ordenado:

CN = CN1 + C1 + N 1

Entonces:
CN = 0(N2)

s En un trmino medio, se puede mostrar por induccin que:


Propiedad
El nmero medio de operaciones que se hacen por el ordenamiento de N valores verica:

CN < 2NlogN

Demostracin
Es claro que C0 = 1 y que C1 = 0.
1
Si los valores son aleatoriamente generados, la probabilidad de que el pivote sea la posicin p es de N
.
Segn estas probabilidades hay:
1 N
C N = N 1+ C p 1 + C N p )
N p=1

o
2 N 1
C N = N 1+ Ci
N p=1

cuando se almacenan los valores de Ci signicativos.


Entonces, se deduce:
2 N 1
C N = N 1+ Ci
N p=1

NC N = ( N + 1) C N 1 + 2N
CN C -1 2
= N = =
N +1 N N +1
C 2 2
 N 2 + + =
N 1 N N + 1
C2 N
2
 =
3
+
k = 3 +1
k
CN
El valor N +1
puede aproximarse por:

21N dx = 2 ln N
1
x

306
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

As, se obtiene: CN 2(N + 1)lnN < 2Nlog2N.


Entonces, el algoritmo de ordenamiento rpido es sub-ptimo en caso medio y es cuadrtico en el peor de los casos.
Con respecto a los otros algoritmos de ordenamiento, para arreglos de tamao reducido (5 < N < 15), el desempeo
del ordenamiento rpido es ms lento que los algoritmos cuadrticos de tipo burbuja, mientras que el ordenamiento por
insercin es an mejor, donde una causa seran las llamadas recursivas.
El algoritmo no usa memoria suplementaria, solo algunas variables para la funcin de divisin. La funcin de divisin
que se usa induce la inestabilidad del algoritmo con respecto al orden entre valores equivalentes.
Algunas otras versiones y adaptaciones existen o pueden ser propuestas por este algoritmo:
s Elegir el pivote de manera aleatoria en el intervalo [g, d].
s Cortar el conjunto en tres partes (y no en dos): a la izquierda los elementos estrictamente menores con respecto
al pivote; primero, los valores equivalentes al pivote y luego los elementos estrictamente mayores al pivote.
s Eliminar una de las dos recursiones; realizar la recursividad por medio del conjunto que resulta menos largo.
s Para los valores pequeos de N (N < 15) se requiere utilizar otro algoritmo de ordenamiento.
El algoritmo de ordenamiento rpido est implementado a nivel de la biblioteca estndar de funciones stdlib.h. El
prototipo de la funcin es:

void qsort(void *base, size_t nel, size_t width,


int (*compar) (const void *, const void *));

Los parmetros de esta funcin son:


1. base, el arreglo que se ordena.
2. nel, nmero de elementos del arreglo.
3. width, el largo de un elemento (el largo del tipo de los elementos del arreglo).
4. compar, el apuntador de la funcin de comparacin que se aplica a los elementos del arreglo.

Ordenamiento por mezcla


El ordenamiento por mezcla (merge sort, en ingls) consiste (tambin) en dividir el conjunto inicial en dos conjuntos. En
este caso, ambos conjuntos son del mismo tamao; sin embargo, cada conjunto se ordena y luego se hace una mezcla
para obtener el conjunto nal ordenado.
El pseudocdigo de la funcin es el siguiente:

fonction ordenamiento_mezcla(T)
partcionar(T;S1;S2) // T = S1 U S2
ordenamiento_mezcla(S1);
ordenamiento_mezcla(S2);
mezclar(S1;S2;S);
TS;
endfonction
La parte ms interesante no son las llamadas recursivas, sino la funcin de mezcla. Si dos conjuntos estn ordenados, la
operacin de mezcla solo permite obtener el conjunto nal ordenado.

Ejemplo
S1 = {1, 4, 5, 12, 17}

S2 = {2, 3, 10, 25, 30}

Por tanto, el conjunto obtenido por la operacin de mezcla es:

S = {1, 2, 3, 4, 5, 10, 12, 17, 25, 30}

307
*OUSPEVDDJOBMBQSPHSBNBDJO

A continuacin se presenta el cdigo de una funcin de mezcla para dos arreglos:

void mezcla(int T[], int S[], int U[], int N, int M)


{
int i, j, k;
T[N] = MAXINT;
S[M] = MAXINT;
for (i = 0, j = 0, k = 0; k < M+N; k++)
if (T[i] < S[j])
U[k] = T[i++];
else
U[k] = S[j++];
}

La funcin recibe como parmetros los arreglos: dos de entrada, el arreglo resultado y los tamaos de los arreglos.
Las asignaciones con el valor MAXINT sirven para poner una centinela al nal de los arreglos, lo que permite colocar
un cdigo unitario para esta operacin de mezcla.
La complejidad de esta funcin de 0(M + N), con N y M, es el tamao de cada arreglo.
La funcin de ordenamiento por mezcla utiliza este procedimiento en la ltima etapa. Es importante destacar que las
etapas de esta funcin respetan las etapas generales del pseudocdigo, aunque los arreglos estn organizados de otra
manera. As:
s Primero se constituyen las dos mitades, las cuales aplica recursivamente el algoritmo.
s La segunda etapa consiste en copiar los arreglos ordenados que estn ubicados en la zona de memoria del arreglo
inicial en otro arreglo, poniendo los arreglos al nal; uno junto al otro.
s Al nal se aplica la operacin de mezcla.
Por ejemplo, si se entrega el siguiente arreglo con 15 elementos resulta:
T: 15 14 13 1 20 9 8 7 6 5 4 3 1 33 0
Las llamadas recursivas de ordenamiento por mezcla producen:
T: 0 7 8 9 12 13 14 15|0 1 3 4 5 6 33
Cuando se copian los elementos en el arreglo, se obtiene:
S: 0 7 8 9 12 13 14 15|33 6 5 4 3 1 0
Al nal del ordenamiento por mezcla se tiene el arreglo inicial T:
T: 0 0 1 3 4 5 6 7 8 9 12 13 14 15 33
El cdigo de esta funcin de ordenamiento por mezcla que trabaja con arreglos es el siguiente:

void ordenamiento_mezcla(int T[], int g, int d)


{
int S[DIM_MAX];
int i, j, m, k;
if (g == d) return;

m = (g + d) / 2;
ordenamiento_mezcla(T, g, m);
ordenamiento_mezcla(T, m+1, d);
for (i = m; i >= g; i--)
S[i] = T[i];
for (j = m+1; j <= d; j++)

308
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

S[(d + m + 1) - j] = T[j];
for (k = g, i = g, j = d; k <= d; k++)
if (S[i] <= S[j])
{
T[k] = S[i];
i++;
}
else
{
T[k] = S[j];
j--;
}
}
El algoritmo de ordenamiento de esta forma necesita un espacio de memoria suplementario del mismo largo que el
arreglo inicial. Se trata de un ordenamiento estable, ya que la operacin de mezcla respeta la estabilidad. Para calcular la
complejidad de la funcin, sea CN el nmero de operaciones necesarias para un arreglo de N elementos. Entonces, hay:
CN = CN/2 + CN/2 + N/2 + N/2
Relacin equivalente a:
CN = 2CN/2 + N
Entonces, se obtiene:
CN = 0(NlogN).
Por tanto, el algoritmo de ordenamiento por mezcla es ptimo.
La operacin de mezcla se realiza, por ejemplo, con el siguiente cdigo:

Tlista mezcla(Tlista l1, Tlista l2)


{
Tlista res;
struct elemento u;
u.next = NULL;
res = &u;
while((l1 != NULL) && (l2 != NULL))
{
if (l1->clave <= l2->clave)
{
res->next = l1;
l1 = l1->next;
res = res->next;
}
else
{
res->next = l2;
l2 = l2->next;
res = res->next;
}
}
if (l1 != NULL)
res->next = l1;
if (l2 != NULL)
res->next = l2;
return u.next;
}

309
*OUSPEVDDJOBMBQSPHSBNBDJO

Al inicio de esta funcin se declara un elemento de tipo lista, que es el primer elemento de la lista que se forma durante
la mezcla. Al principio de la mezcla se cortan las ligas entre elementos, para constituir la lista resultado. La operacin de
mezcla se realiza hasta que se encuentra el nal de la lista para cada una de las dos listas. Despus, la lista que siempre
contiene elementos se liga a la ltima celda de la lista resultado. Esta funcin destruye las listas de parmetro, debido a
que la lista resultado se construye con las celdas de las dos listas iniciales.
La complejidad de esta funcin tambin es 0(M + N), donde M y N son los tamaos de cada lista de parmetro.
La funcin de ordenamiento por mezcla llama explcitamente a esta funcin de mezcla. Al inicio se calcula el tamao
de la lista; si la lista contiene un solo elemento, la recursividad termina. En caso contrario, se construyen dos listas cortando
la liga que se encuentra a la mitad de la lista inicial y las llamadas recursivas de la funcin de ordenamiento se hacen a
travs de estas listas. As, el cdigo de la funcin es el siguiente:

void ordenamiento_mezcla(Tlista *L)


{
Tlista L1, L2, tmp;
int i, N;

N = tamano(*L);
if (N <= 1) return;
L1 = *L; L2 = *L;
tmp = NULL;
for (i = 0; i < N/2; i++)
{ tmp = L2; L2 = L2->next; }
tmp->next = NULL;
ordenamiento_mezcla(&L1);
ordenamiento_mezcla(&L2);
*L = mezcla(L1, L2);
}

La funcin de ordenamiento por mezcla, aplicada a las listas, produce una complejidad ptima 0(NlogN) y tiene la ventaja
de que en esta no se utiliza la memoria suplementaria.

Ordenamiento por enumeracin


Es un algoritmo que se aplica a un arreglo particular, que contiene un nmero nito de valores entre 1 y k (k es un entero
positivo). El principio de este algoritmo es enumerar primero cuntos elementos del arreglo son equivalentes a un valor
i con 1 < i < k. Luego se calculan las posiciones de los valores equivalentes a un valor i que van a ocupar un lugar en el
arreglo resultado. Luego se recorre el arreglo de entrada y se construye el arreglo resultado.
El cdigo de esta funcin es el siguiente:

void ordenamiento_enumeracion(int T[], int V[], int N)


{
int C[K_MAX + 1];
int i, j;
// etapa 1 : se enumera cuantos valores hay
for (j = 1; j <= K_MAX; j++)
C[j] = 0;
for (i = 1; i <= N; i++)
C[T[i]]++;
// se calculan las posiciones sumando los numeros de apariciones
for (j = 1; j < K_MAX; j++)
C[j] += C[j-1];
// se llena el arreglo resultado
for (i = N ; i > 0; i--)
{

310
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

V[C[T[i]]] = T[i]; _#UIDADO%STAFUNCINTOMALOSNDICESDEL


C[T[i]]--; arreglo desde 1 hasta N. La constante
} K_MAX constituye el lmite de los valores
return; que el arreglo puede contener.
}

Para analizar el nmero de operaciones de la funcin, los clculos son bastante simples; esto es, para cada estructura
iterativa for al interior se ejecuta siempre un nmero nito de operaciones. Entones, el nmero total de operaciones es:
aK_MAX + bN + CN
Sabemos que K_MAX es una constante, entonces la complejidad de este mtodo de ordenamiento es de 0(N), el cual
es mejor que la complejidad terica. Pero no hay ninguna contradiccin, este resultado de complejidad es mejor que el
lmite terico que tiene como justicacin la distribucin de los valores iniciales que se ordenan.
Este algoritmo de ordenamiento tambin tiene la propiedad de que es estable (la estabilidad se almacena durante la
ltima etapa, cuando se recorre el arreglo desde el nal hasta el inicio).
Sin embargo, la nica desventaja de este arreglo es que se necesitan dos arreglos ms:
1. Un arreglo del mismo tamao, para el arreglo resultado.
2. Un arreglo de tamao K_MAX, para enumerar los valores encontrados y calcular, despus, las posiciones en el
arreglo.

Ordenamiento por casilleros


El ordenamiento por casilleros (bucket sort, en ingls) es un mtodo muy intuitivo que consiste en ordenar los N elemen-
tos en N listas disjuntas; luego, cada lista se ordena y al nal se concatena; esto es, cada lista es un casillero. Por ejemplo,
si se supone que los elementos del conjunto inicial son [0, 1] al interior del intervalo, cada lista Li , con i de 0 hasta N 1,
contiene los elementos entre Ni e i N+ 1 .
El pseudocdigo del algoritmo es el siguiente:

for i = 1 to N
anadir T[i] a la lista L[ nT[i]]
endfor
for j = 0 to N - 1
ordenar la lista L[j]
endfor
concatenacion L[j], j = 0;N -1.

La complejidad de la primera etapa de construccin de las listas y de la ltima concatenacin de las listas ordenadas, para
obtener la lista nal, se produce con n comparaciones y N otras operaciones, para cualquier implementacin concreta de
estas listas L [ i ] (con arreglos o con listas).
Para ordenar las listas que se forman, se utiliza cualquier algoritmo de ordenamiento; debido a que, intuitivamente,
las listas tienen pocos elementos, lo mejor es utilizar un algoritmo cuadrtico. Entonces, la complejidad del algoritmo es
la suma de estas complejidades.
El algoritmo de ordenamiento por casilleros necesita memoria suplementaria y es estable si los algoritmos que se
aplican para cada lista son estables.
Este algoritmo resulta lineal en cuanto al nmero de operaciones, incluso cuando se aplica un mtodo de ordena-
miento cuadrtico a las listas obtenidas, como en el caso de los valores iniciales que estn uniformemente distribuidos.
Proposicin: Si los valores iniciales estn uniformemente distribuidos en el intervalo [0,1], la complejidad del algorit-
mo de ordenamiento por casilleros es 0(N).
Demostracin: Sea nj el nmero de elementos de la lista L [ j ], el tiempo que se espera para odenar estos elementos
es:
E[ 0( n2j )] = 0( E[ n2j ])

311
*OUSPEVDDJOBMBQSPHSBNBDJO

Segn las observaciones hechas antes, el nmero medio de operaciones realizadas por el algorimo de ordenamiento por
casilleros es:
N 1 N 1
0( E[ n2j ]) = 0( E[ n 2j ])
j =0 j =0

Entonces, si T [ i ], i = 0, N 1 son uniformemente distribuidos. As, k = nj tiene una distribucin discreta binomial B(k; N,
p), con p = N1 .
Entonces, para este caso se utilizan las siguientes frmulas:
E[ nj ] = Np = 1
1
Var[ nj ] = Np(1 p) = 1
N
1
E[ n2j ] = Var[ nj ] + E[ nj ]2 = 2
N
As pues, E[ n2j ] = 0(1) y la complejidad del algoritmo es 0(N).

6.4 Montculos

Un montculo es una estructura de datos que se puede usar como una pila de prioridades, para obtener o extraer el ele-
mento extremo (mnimo o mximo). El montculo se puede implementar en un arreglo o en un rbol con ligas. En esta sec-
cin lo implementaremos en arreglos con el objetivo de utilizar la representacin por montculo para ordenar elementos.

Denicin y propiedades

Un montculo (heap, en ingls) es una estructura de datos de forma arborescente, la cual se distingue por las siguientes
caractersticas:
s Es binaria (esto signica que cada nodo que no est en una hoja tiene dos descendientes).
s Es completa, excepto el ltimo nivel, que puede contener cuando mucho un solo nodo con una sola hoja.
s Contiene en cada nodo, incluso en las hojas, una clave (un valor).
s Cada clave contenida en un nodo es mayor que todas las claves ubicadas en su descendencia.
Ejemplos
La arborescencia que se representa en la gura 6.8 es un montculo:

5 3

7 9 6 8

11 10 15 13 9

Figura 6.8

312
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

La siguiente arborescencia no se considera un montculo, porque no est completa:

5 3

7 6 8

11 10 9

Figura 6.9

Esta denicin de montculo corresponde a un montculo por mnimo, tambin se puede introducir una denicin de
montculo por mximo, en el cual una clave es mayor que todas las claves que se encuentran y se consideran como
descendientes.
Una primera observacin que se puede hacer es que el mnimo se encuentra en el nodo raz. Conocer el mnimo del
conjunto clave es equivalente a poder acceder al nodo raz.
Cualquier sub-arborescencia de un montculo tambin es un montculo.
Para un nivel i (en donde la raz tiene el nivel 0), que est completo (lo que signica que no es el ltimo nivel), se
encuentran 2i nodos.
Un montculo que contiene N nodos y una altura h (es decir, que tiene h + 1 niveles, incluso hasta la raz) verica la
siguiente relacin:

log2(N + 1) 1 ) h < log2(N + 1)

Esta relacin se demuestra adicionando los nodos en cada nivel y porque en el ltimo nivel hay entre 1 y 2h nodos.

Implementacin

Un montculo se puede implementar como un rbol con ligas, donde cada celda tendr el tipo siguiente:

struct elemento_monticulo
{int clave;
struct elemento *izq;
struct elemento *dr;
};

El acceso a la raz se realiza en tiempo constante; en cambio, resulta difcil acceder directamente a una hoja y, lo ms im-
portante, resulta an ms difcil acceder a la ltima hoja para poder insertar un nuevo elemento o eliminar un elemento.
Entonces, lo ms recomendable es representar el montculo mediante un arreglo. La siguiente propiedad nos indica
cmo se puede realizar esta representacin de la manera ms conveniente:

Propiedad de representacin
Un montculo de N elementos se puede representar con un arreglo del mismo tamao, donde cada nodo de rango i
representa el ndice i en el arreglo; el rango de un nodo se obtiene recorriendo el montculo por niveles, empezando en
la raz y siguiendo de derecha a izquierda.

313
*OUSPEVDDJOBMBQSPHSBNBDJO

En el caso del montculo del ejemplo anterior, su representacin con arreglo sera como se observa en la gura 6.10:

1
1

2 5 3 3

4 7 4 9 6 6 7 8

8 11 9 10 10 15 11 13 12 9

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

Figura 6.10
_#UIDADO ,OS NDICES DEL ARREGLO SE USAN
desde 1 hasta N.

Como se puede observar en la gura 6.10:


s Las hojas tienen los ndices de N
2
+ 1 hasta N.
s Si T [ i ] es un elemento del montculo, sus descendientes directos (sus hijos) son T [ 2i ] y T [ 2i + 1].
s Si T [ i ] no es una hoja (i > 1), entonces el nodo padre es T [ i/2].
La siguiente funcin verica con el mximo N 1 comparaciones si los elementos de un arreglo entre los ndices 1 y N
forman un montculo:

int es_monticulo(int T[], int N)


{
int i;

for ( i = 2; i <= N; i ++)


if (T[i] <= T[i / 2])
return FALSO;
return CIERTO;
}

Insercin y eliminacin de elementos

Por lo general, el montculo es una estructura dinmica; entonces, lo que ms nos debe interesar es hacer las operaciones
de insercin y de eliminacin para el mnimo y para cualquier elemento.

Eliminacin del mnimo


La eliminacin del mnimo de un montculo se realiza en tres pasos:
1. Se elimina la raz.
2. Se sustituye la raz con el ltimo elemento del montculo.

314
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

3. Se reconstituye el montculo recorriendo desde la raz hasta las hojas (de arriba hacia abajo), cambiando valores
presentes en el camino.
En este caso, el primer paso es natural, el segundo tiene como objetivo almacenar la estructura del montculo, con su
propiedad binaria y completa, y, por ltimo, el tercer paso reconstituye la condicin que debe cumplirse entre las claves.
La siguiente imagen muestra de manera esquemtica este procedimiento:

Figura 6.11

Lo ms difcil es recorrer el montculo desde la raz. Esto signica que en una posicin se debe vericar que la clave de
un nodo j es menor que las claves de sus hijos.

hijo hijo + 1

Figura 6.12

En esta gura (vase gura 6.12), primero se detecta al hijo con la clave ms baja (su ndice es hijo). Aqu aparecen dos
casos:
s T [ j ] < = T [ hijo ], entonces la relacin se respeta para el otro hijo y la arborescencia con la raz j es un montculo.
s T [ j ] > T [ hijo ], entonces se intercambian las claves de los nodos j e hijo y se contina el recorrido con el nodo hijo.
La funcin que se traduce de este procedimiento es la siguiente:

void eliminacion_min_monticulo(int T[], int *N)


{
int j, hijo;

T[1] = T[*N];
(*N)--;
// recorrido de la raiz hasta (el maximo) una hoja
j = 1;
while(j <= *N / 2)

315
*OUSPEVDDJOBMBQSPHSBNBDJO

{
hijo = j*2;
if ((hijo+1 <= (*N)) && (T[hijo+1] < T[hijo]))
hijo = hijo+1;
if (T[j] < T[hijo])
break;
else
{
cambio(T, j, hijo);
j = hijo;
}
}
return;
}

La funcin cambio intercambia el contenido de dos elementos que pertenecen al montculo:

void cambio(int T[], int i, int j)


{
int tmp;
tmp = T[i];
T[i]= T[j];
T[j] = tmp;
}

Insercin de un elemento
Esquemticamente, la insercin de un valor en un montculo se puede representar como se muestra en la gura 6.13:

Figura 6.13

En la gura 6.13 se ponen en evidencia los dos pasos que se ejecutan en este caso:
1. Aadir el nuevo elemento a la ltima posicin.
2. Reconstruir el montculo de abajo hacia arriba, es decir, de la nueva hoja hasta la raz, haciendo los cambios ne-
cesarios si la relacin entre las claves no se respeta.
El recorrido se hace subiendo por cada nodo j; este recorrido se compara con el nodo padre (padre = j/2).

316
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

padre

j j+1

Figura 6.14

Aqu se presentan dos casos:


1. La condicin del montculo se respeta: T [ padre ] < = T [ j ]. En este caso, el recorrido termina porque en la parte
de arriba las condiciones entre claves se respetan y abajo las condiciones son correctas.
2. Si T [ padre ] > T [ j ], las claves entre estos dos ndices se cambian; la condicin de montculo se respeta con este
y con el otro hijo, pero se debe vericar que corresponda con el padre del padre.
La funcin que realiza este tratamiento es la siguiente:

void insercion_monticulo(int T[], int *N, int valor)


{
int j, padre;
*N = *N + 1;
T[*N] = valor;
j= *N;
padre = j/2;
while ((padre >= 1) && (T[j] < T[padre]))
{
cambio(T,j, padre);
j = padre;
padre = j/2;
}
}

Un ejemplo de programa principal que llama a las dos funciones presentadas es el siguiente:

imprima_simple_monticulo(MT, N);
eliminacion_min_monticulo(MT, &N);
printf( Despues la eliminacion del = min = :);
imprima_simple_monticulo(MT, N);

printf( Introducir un elemento por insertar : );


scanf(%d, &val);
insercion_monticulo(MT, &N, val);
imprima_simple_monticulo(MT, N);

Un ejemplo de salida es:

Dimension del monticulo : 9


Elementos : 1 2 3 4 5 6 7 8 9

317
*OUSPEVDDJOBMBQSPHSBNBDJO

Despues la eliminiacion del = min = : Dimension del monticulo : 8


Elementos : 2 4 3 8 5 6 7 9
Introducir un elemento por insertar : -1
Dimension del monticulo : 9
Elementos : -1 2 3 4 5 6 7 9 8

Eliminacin de un elemento
Para eliminar un elemento, es necesario seguir los pasos que se muestran a continuacin:
1. Sustituir el elemento eliminado por el ltimo elemento y actualizar la dimensin del montculo.
2. Reconstituir el montculo recorriendo por arriba o por abajo, a partir de este punto y cambiando, si necesario,
contenidos. Si en una etapa no se necesita un cambio, el algoritmo termina.
Despus de la sustitucin del nodo eliminado por el ltimo nodo, puede aparecer una contradiccin con la propiedad
del montculo o con los hijos; en este caso, el recorrido se realiza hasta las hojas o con el nodo padre, hacindolo sobre
el camino hasta la raz. Una vez que se detecta una contradiccin nodo-hijo o nodo-padre, las otras contradicciones son
de la misma naturaleza.10
La complejidad de las operaciones de insercin y de eliminacin se genera por el costo del recorrido. El largo mxi-
mo de este recorrido es la altura del montculo h = log2N. En cada iteracin de este recorrido, se realiza un nmero
constante de operaciones: comparaciones o cambios. Entonces, la complejidad de las tres funciones es 0(log2N) para
un montculo de tamao N.

Ordenamiento por montculo

Un montculo es una estructura interesante que permite la extraccin del valor mnimo en un tiempo razonable. La idea
bsica del algoritmo de ordenamiento por montculo (heap sort, en ingls) es la construccin inicial de un montculo
que contenga todos los elementos del arreglo y luego, de manera iterativa, extraer el mnimo y colocarlo en la ltima
posicin. Al nal, los elementos estaran en orden decreciente.
La gura 6.15 indica el esquema general de la segunda etapa de trabajo del algoritmo:

elementos
montculo ordenados

1 j j + 1 N

Figura 6.15

Se recurre a este caso si se necesita el orden creciente o se hace una imagen en espejo del resultado, o bien se trabaja
con un montculo por mximo.
La primera etapa (construccin inicial del montculo) puede realizarse por inserciones sucesivas.
La segunda etapa consiste en considerar, de manera iterativa, a cada paso i, el montculo de largo N i; intercambiar
el mnimo con la ltima posicin, y trabajar para obtener nuevamente un montculo, ya que el primer elemento no respeta
las condiciones del montculo. Entonces, se equilibra la estructura, desde la raz hasta las hojas, haciendo comparaciones
del nodo corriente con sus hijos, hasta que se respeta la condicin.

10
Dejamos como ejercicio la escritura de esta funcin.

318
$BQUVMPt#TRVFEB4FMFDDJO0SEFOBNJFOUP

El cdigo de la funcin de ordenamiento y de la funcin que equilibra el montculo es el siguiente:

void equilibrar_monticulo_abajo(int T[], int N, int j)


{
int hijo;
while(j <= N/2)
{
hijo = j*2;
if ((hijo+1 <= N) && (T[hijo+1] < T[hijo]))
hijo = hijo+1;
if (T[j] < T[hijo])
break;
else
{
cambio(T, j, hijo);
j = hijo;
}
}
return;

void ordenamiento_por_monticulo(int T[], int N)


{
int i, j;
j = 1;
for (i = 2; i <= N; i++)
insercion_monticulo(T, &j, T[i]);
for (i = 1; i < N; i++)
{
printf( Paso i = %d \n, i);
imprima_simple_monticulo(T, j);
cambio(T, 1, j--);
equilibrar_monticulo_abajo(T, j, 1);
}
}

La primera etapa tiene N pasos; a cada paso, la insercin necesita entre 1 y log2h comparaciones, donde h es la profun-
didad variable del montculo. La complejidad de esta etapa del algoritmo es (NlogN).
En la segunda etapa se hace al menos un cambio y una comparacin en cada paso. Como mximo se realizan h + 1
cambios y 0(h) comparaciones, donde h es la profundidad variable del montculo. La complejidad de esta etapa tambin
es (NlogN).
Se puede decir que este algoritmo es ptimo en trminos del nmero de operaciones, por lo que no necesita me-
moria suplementaria. El hecho de que no sea estable es solo un defecto, ya que al momento de la construccin del
montculo, el orden entre elementos equivalentes puede cambiar.
Una posible mejora sera construir ms rpido el montculo inicial con los N elementos. La idea es la siguiente: si
existen dos montculos, de la misma profundidad, y el primero est completo en su ltimo nivel y si se toma un valor de
la raz, esto es suciente para equilibrar este nodo por abajo, con cambios sucesivos con sus hijos, hasta que la condicin
entre claves se cumpla o se trate una hoja. La gura 6.16 muestra este principio.

319
*OUSPEVDDJOBMBQSPHSBNBDJO

Figura 6.16

Otra observacin importante que debe considerarse en la construccin de un montculo de tamao N, es que solo tenga
una hoja en el montculo. Entonces, para construir un montculo es necesario recorrer los nodos que no tengan hojas
desde el penltimo nivel hasta la raz, con el n de equilibrarlo de arriba hacia abajo. Entonces, esto se hace para cada
nodo i de rango N/2 hasta 1; el equilibrio tiene como mximo un nmero de operacin de h', donde h' es la profundidad
del nodo i. Esta profundidad es, por tanto, log2N log2i. Por ende, la complejidad de esta etapa es 0(N), debido a que
existen las siguientes equivalencias:

N /1 1
N
(log 2 N log 2 i ) = 2
log 2 N log 2 i
i =1 i = N1/ 2
N N N N N N
= log 2 N log 2 ( N / 2)!  + log 2 log 2
2 2 2 2 2 2

El cdigo de la nueva funcin de ordenam