Algoritmos y su Codificación enC++
Volumen 2
César Liza A vila
Grupo. Creadores lV/nTllJnn.r1n
tu naturaleza Creativa
w
•
•• .... . ....
.
.. .
.
.:
1 /
DEDICATORIA
Datos de catalogación bibliogrÚ¡:;
Liza Avila, Cesar Algoritmos y su Codificación en C++. Volumen 2
A mis padres: Dr. César Liza Ortíz y Nery Avila Acosta por el esfuerzo constante en la formación de sus hijos.
Editorial Creadores . Trujillo, Perú, 2002
Sin su ejemplo y apoyo nunca hubiera llegado a ser un profesional de éxito y mucho menos haber publicado algún • libro.
Tema: Programación de computad'0ras Formato: 14.5 x 20.5
Páginas: 234
A mis amigos y colaboradores: LoudesDíaz, limar Goicochea, Kevin Llontop y Alfredo del Castillo, por su importante crítica y aporte en el contenido del libro. A mi querido amigo, que ya no está con nosotros, Rogelio Briceño.
Algoritmos y su Codificación en CH. Vol 2. Primera Edición, Agosto 2002
El contenido de esta obra esta protegido por ley. Queda prohibida la Jeproducción total o parcial de este libro, por cualquier medio, sin permiso escrito del autor.
A mi modelo, amiga y futura colega: Ana Luisa Doig Ascate, que adorna nuestra portada con sus lindos ojos y bella sonrisa.
© 2002, Derechos Reservados por César liza Avila.
Al fotógrafo y amigo: Víctor Torres García, por captar la belleza de nuestra modelo.
Distribución y Ventas Editorial Creadores Av. América Norte 401 - Trujillo. Teléfono (044) 221363
Al diseñador de la portada: Javier Asmat, mi gratitud por su amistad y el buen gusto en el diseño de la portada.
e-mail:
[email protected] Web Site: ww~ocities.cQj;lCesar li?-ª
j
DILE NO A LA PIRATERÍA Escribir un libro cuesta muchas horas de esfuerzo intelectual. Publicarlo y distribuirlo . constituye un esfuerzo económico muy grande donde no se espera obtener grandes ganancias económicas. Si copias este libro cometes un delito y además matas la difusión del conocimiento técnico contribuyendo a que nuestro Perú siga siendo subdesarrollado.
NO COPIES MIS LIBROS Hemos detectado que debido al gran éxito alcanzado por nuestros libros, éstos se encuentran fotocopiados y anillados listos para su VENTA ILEGAL en muchos "centros de fotocopiado" frente a Universidades e Institutos. Cuando menos lo esperen, los visitaremos cayéndoles con todo el peso de la ley
PRESENTACION
f:sta obra es la continuación de "Algoritmos y su Codificación en C++", y viene a completar el vacío que existía respecto a temas que no tratamos en ella. Manejo de caracteres, arreglos bidimensionales, estructuras, uniones, enumeraciones, manipulación de bits, generación de números aleatorios, simulación, punteros, asignación. dinámica de memoria, y archivos, son tratados mediante más de 81 ejemplos completos de' programación. Asimismo, encontrará más de 140 ejercicios propuestos que le servirían para practicar lo aprendido, y que serán de valiosa ayuda para los docentes en la elaboración de prácticas y exámenes. Para la compilación de los programas' presentados en este libro, utilizamos el Microsoft Visual C++, en aplicaciones en modo consola, pero puede utilizar otros compiladores haciendo pequeñas modificacionés al código. En este segundo volumen, no utilizamos los diagramas N/S, pues consideramos que el programador tiene la suficiente madurez lógica y puede estructurar la solución al problema para luego pasar a su codificación. El primer capítulo, trata sobre las cadenas de caracteres, que son implementadas en C/C++ como arreglos, donde cada uno de sus elementos es un carácter. Es importante su estudio puesto que el hombre se comunica con palabras más que con números, y la computadora solo utiliza números para todo, inclusive para representar cada una de las letras, ya sea mediante el código ASCII o su sucesor el UNICODE. Los arreglos bidim'Emsionales o matrices, constituyen un elemento importante en matemáticas pues permiten resolver un amplio conjunto de problemas con muchísimas aplicaciones reales, así como almacenar datos que necesitan 2 dimensiones para ser identificados. El segundo capítulo, aborda este tema mediante numerosos ejemplos.
I
/1
Las estructures, uniones '1 enumeraciones son tratadas en el tercer capítulo. Las estructuras son construcciones en C/C++, que permiten agrupar un conjunto de atributos de diferentes tipos de datos, en una única variable; las estructuras constituyen los predecesores de las clases. Las uniones definen en una misma zona de memoria uno o más elementos, permitiendo su ahorro; mientras que las enumeraciones hacen mas claro nuestro programa, ya que podemos usar nombres en vez de números.
CONTENIDO
Presentación"."., ... ,.................................. ' .... ,.. ,." .. , ............ ,............. ', .• ,"" 7
CAPITULO 1: Cadenas de Caracteres ..................................... " ..... 15 Muestre la tabla de códigos ASCii i .1 1.2 Programa que muestre el uso de las secuencias de escape 1.3 Convierta una palabra en mayúsculas a minúsculas 1.4 Muestre la suma de verificación de un mensaje 1.5 Dada una cadena numérica obtenga el número que representa 1.6 lea una frase y muéstrela palabra por palabra 1.7 Cree su propia función para ingresar caracteres desde teclado 1.8 Encripte una cadena sumando sus ASCII con los dela clave 1.9 Encripte una cadena con una tabla de equivalencia 1.10 Diga como es alfabéticamente, una palabra respecto a otra 1.11 Programa que utiliza las macros y funciones provistas en ctype.h 1.12 Uso de las funciones estándar provistas en el archivo string.h 1.13 Diga si una palabra es palíndroma
En el cuarto iCaJ~ítuio, mostramos cómo g,enerar filúmems ~Iieator¡os con distribuciones tales como ?oisson, V Normal; abordando problemas mediante la técnica conocida como Simulación Montecarlo.
la manipulación de bits, la tratamos en el quinto capítulo, y es uno de mis favoritos, pues en él, mostramos cómo la computadora almacena y procesa internamente los datos operando sobre bits; además existen muy pocos libros que traten adecuadamente este tema, y lo tratan con escasos ejemplos. Este libro es la excepción pues mostramos diversas e interesantes aplicaciones. Los datos que usamos y los programas que ejecutamos en una computadora, ocupan memoria. Mediante los punteros podemos acceder a cualquier zona de memoria asignada a nuestro programa. Asimismo, mostramos como usar la asignación dinámica, para reservar la memoria necesaria según como la vayamos necesitando. Estos temas son tratados en el sexto capítulo.
CAPITULO 2: Arreglos Bidimensionáles ......................................... 41 Lea una matriz con f filas y c columnas y iuego la imprímala 2.2 Encuentre la transpuesta de una matriz 2.3 Genere una matriz identidad de orden n 2.4 Dada una matriz diga si ésta es una matriz triangular superior 2.5 Dada dos matrices obtenga la matriz ampliada 2.6 Inserte un vector en una columna determinada de una matriz 2.7 ¿Cuál es la producción mensual de un grupo de fábricas? 2.8 Posiciones a las que se mueve la reina en un tablero de ajedrez 2.9 Suma de dos matrices 2.10 Producto de dos matrices 2.11 Determinante de una matriz 2.12 Resuelva un sistema de ecuaciones simultáneas 2.13 Inversa de una matriz 2.14 Programa que juega "tres en raya" 2.15 Lea un conjunto de palabras en una matriz y ordénelas
2.1
En el sétimo y último capítulo, mostramos cómo hacer persistentes nuestros datos en archivos; así como, analizar e interpretar ¡os diversos formatos de almacenamiento, abriéndonos la puerta a un mundo de aplicaciones. Espero que la presente, supere la acogida que tuvo el primer volumen. Le rogamos que cualquier crítica, sugerencia, o inquietud, la . dirija a la siguiente dirección electrónica:
[email protected];.QI!L que gustosos la responderemos con el ánimo de mejorar cada día. Asimismo, le invitamos a visitar nuestra página web www.geocities.com/cesarJi;¡;:a.
César Liza Avila
"-'-, I
,~.
l CAPITULO 3: Estructuras Uniones y Enumeraciones ........ 89 3.1 Suma de dos números complejos ¿A cuánto debe venderse cada kilo para no ganar ni perder? 3.2 3.3 Encuentre el valor numérico de un polinomio f(x) 3.4 Busque una fecha y diga cuál fue su temperatura 3.5 Gestione los datos de un conjunto de alumnos ;3.6 Programa que permita al usuario resolver un crucigrama 3.7 Defina, en una misma zona de memoria, un entero y un flotante 3.8 Pida un número de día de la semana, y muestre el nombre del día 3.9 Lea una moneda y diga a cuántos céntimos equivale 00 • • •
oo • • • • • • • •
CAPITULO 4: Manipulación de 8Its ........................................ 115 4.1 Muestra el uso de operadores a nivel de bits: -, &, I , ", « y » 4.2 Lea un número, muestre sus bits tal como se ven en memoria 4.3 Encripte un mensaje por manipulación de bits con XOR 4.4 Programa que genera una baraja de cartas. Use campos de bits Programa que lee una hora y obtiene el número que la representa 4.5 4.6 Lea un número que represente una fecha válida y obtenga la fecha Lee una hora y da el número que la representa. Campos de Bits 4.7 4.8 Muestre cada bit de un punto flotante de simple precisión 4.9 Estándar IEEE 754 de un flotante de simple precisión 00 • • • •
CAPITULO 5: Generación de Números Aleatorios y Simulación ...... 143 5.1. Genera números aleatorios por el Método Congruencial Lineal 5.2. Genera aleatorio discreto en [O, RAND_MAX], [O, a-1], y [a, b] 5.3. Genera aleatorio en el intervalo continuo entre [0,1] Y [a, b] 5.4. Caminata aleatoria de borrachito, a igual distancia de casa-cantina 5.5. Ley Fuerte de los Grandes Números Calcule la integral definida de una función f(x), por simulación 5.6. 5.7. Calcule el valor de PI, por simulación 5.8. Obtenga los números ganadores del siguiente sorteo de la Tinka 5.9. Genera aleatorios con Distribución Poisson y media lambda 5.10. Genera aleatorios con Distribución Exponencial y media 1/1-1 5.11. Genera aleatorios con Distribución Normal 5.12. Genera una permutación aleatoria (mezcla) de n elementos 5.13. Genera combinación aleatoria de n elementos, en grupos de r
t
CAPITULO 6: Punteros y Asignación Dinámica de Memoria ........... 173 6.1 Declare variables y muestre sus direcciones de memoria 6.2 Función que intercambia dos valores 6.3 Lea coordenadas cartesianas, y conviértalas a polares 6.4 Direcciones de memoria que ocupan los elementos de un arreglo 6.5 Usando arreglos y punteros, lea n elementos y ordénelos 6.6 Lea una palabra y reemplace una letra por otra 6.7 Determine si una palabra es palíndroma 6.8 Concatene 2 palabras Ordene los elementos de arreglo, usando un arreglo de punteros 6.9 6.10 Función que lee n empleados, usando un puntero a una estructura 6.11 Arreglo dinámico de n flotantes 6.12 Puntero a función 6.13 Función que intercambia dos valores. Use referencias
CAPITULO 7: Archivos ...............................................................201 7.1 Lea una serie de caracteres y cree un archivo con ellos 7.2 Lea el nombre de un archivo y muestre su contenido por pantalla 7.3 Lea un archivo y cópielo hacia otro 7.4 Divida un archivo en otros, similar al Hacha 7.5 Lea una serie de registros y guárdelos en un archivo 7.6 Construya un Visor Hexadecimal 7.7 Comprima un archivo por reducción de caracteres repetidos 7.8 Lista los archivos comprimidos en *.ZIP 7.9 Dada una carpeta, muestre los directorios y archivos que contiene
Acerca del Autor Otras Obras del Autor. Distribución y Ventas
Cadenas de Caracteres
Cadena de Caracteres 15
Solución: Es conocido que las computadoras son capaces de almacenar solo CEROS y UNOS, es decir números en binario. Pero el humano además de números utiliza símbolos, por lo que fue necesario codificarlos, para que sean almacenados en las computadoras. Por ejemplo, podríamos decir que si almacenamos el número en binario 0100 0001 (equivalente a 65 en decimal) estaremos almacenando la letra 'A'. Otro grupo de programadores pudo haber definido como código de 'A' el número 0000 0000, esto trajo, allá por la década del 60, la necesidad de definir un estándar. Este estándar se llama Tabla de Códigos ASCII (American Standar Code for /Ilformation /nterchallge, Estándar Americano de Codificación para el Intercambio de Información), la cual nos proporciona una lista de códigos a los que se les ha asociado un símbolo, que permite que los equipos y programas puedan intercambiar información con otros equipos y programas. La Tabla de Códigos ASCII original contiene 128 códigos (del O al 127), puesto que este es la máxima combinación de valores que se puede obtener con un byte (8 bits) sin ocupar el bit del signo (2 7). Posteriormente, se amplió esta tabla para más símbolos como los caracteres con tilde, a esta tabla se le conoce como Tabla de Códigos ASCII Extendida y consta de 256 símbolos (del O al 255), pues esta es la máxima cantidad de valores que se pueden conseguir con un byte (28) usando inclusive el bit del signo. Como es de esperarse los 128 primeros caracteres de la Tabla ASCII Extendida son los mismos que la Tabla ASCII original. En la Tabla ASCII los 32 primeros caracteres (del O al 31) son caracteres de control, esto es; realizan una acción, en vez de mostrar un símbolo.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
m
16 Algoritmos y su Codificación en C++. Volumen 2.
Cadena de Caracteres f7
César Liza Avila
--_._-~~-_._-
IDee Hex
Tabla de Códigos ASCn
128 129 130 131
:l~
132 133 134 135 136 137 138 139 1"10 141 1"12 1"13 1"14 145 1"16 147 148 149 150 151 152 153 154 155 156 157 158 159
80
IChal:
Ji)'ee lBIex
r;
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 . 180
81
Ü
82 83 8"1 85 $6 87 88 89
é
SA 8B BC 8D .8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
a a a
a C;
é
e
e i
i i
ii.
A É
lE Ó
ti
6 ti
u Y
o Ü
e f. Y E.
:f
181 182 183 184 185 186 187 183 169 190 191
AO Al A2 A3 A4
AS A6 A7
Clluil:
á i Ó
Ú
ñ
.
Ñ o
AS
¿
A9 AA AB AC AD .II.E AF BO B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
r
.., 11 1,.,;
i
« » .:.;.:
...
~~~:~~~:
11 I i
IDee lBIex 192 193 194 195
196 197 198 199 200 201 202 203 20"1 205 206 207 208 209 210 211 212
~
213
il
.u
214 215 216 217 218 219 220 221
d
222
1
223
11
'1
{I 11
il
JI
CO Cl C2 C3 C4 C5 C6 C7 CS C9 CA CB CC CD CE CF DO Di . D2 D3
D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
I¡;hal:
,-
IDee
1..
22"1 225
T
226
¡..
227 228 229 230 231 232 233 234 235 236 237 23S 239 240 241 242 243 244 245 246 247
+ ~ I~
I!.
IF ,!!:
'ir
Ir JL
lr
,b
lL
T "Ir IL
b
F Ir
t
+-J
248
I
249 250 251
lW
252
~ ~
253 254
r
l1li
¡¡¡¡ex I:haír
--------_._~-"
EO
ex
.1\1
e,
E2 E3 E4
r n
E
ES
CJ
E6
11
E7
E8 E9 EA EB EC ED EE EF FO
~
n
±
F2
¿
F3
~
(')
Q
!J
"" 0
n -
F4
r
F5
J
F6 F7 F8 F9 FA FB FC FD FE
'Í
~
o ------_.2.5.5
Ff
Podemos generar el símbolo a partir de su código ASCII, usando el teclado numérico. Esto lo conseguimos pulsando simultáneamente la tecla Ají + Códi.go ASen ingresado con el teclado numérico.
© César Li(:a Aví!a. Mis libros son económicos.
IZO
ha.\" razón para (·o¡liarlos.
18 Algoritmos y su Codificación en C++. Volumen 2.
Cadena de Caracteres 19
César Liza Avila
Para mostrar cada código ASCII, nuestro programa genera números del O al 255, imprimiendo el código i, y el símbolo (char) i. Note el uso de la conversión tipo casi que usamos para forzar a la computadora a mostrar el símbolo en vez del número. Una conversión casi, consiste en colocar el tipo de dato deseado, entre paréntesis, delante de la variable a la cual queremos cambiarle de tipo solo para la operación actual.
Solución: Ya sabemos que los primeros 32 caracteres de la Tabla ASCII son caracteres de control. Con el fin de poder represet1tarlos en el código fuente, se recurrió al artificio de utilizar la barra invertida ( \ ) seguida de una serie de caracteres; estas son las llamadas Secuencias de Escape o caracteres de barra invertida, las cuales representan un carácter que realiza una acción y están precedidos siempre por \ (back slash). Adicionalmente, como para su representación se utilizan los caracteres " \ Y ", se hizo necesario crear secuencias de escape que permitan mostrar estos caracteres. Secuencias de Escape
Hay gente que practica el "ASCn Art" (Arte ASCII), que consiste en diseñar figuras utilizando únicamente caracteres ASCII, se realizan reñidos torneos internacionales para premiar la mejor creación ASCII. Puede buscar en www.google.com "Arte ASCII", inclusive encontrará programas en donde se ingresa una imagen y ésta sale en caracteres ASCII. Adicionalmente, el lector debe saber que, debido a la necesidad de incorporar al juego de caracteres otros símbolos y alfabe"tos de otros idiomas, y que éstos rebasan la capacidad que puede almacenarse en variables de un byte (tipo char en DOS), se amplió este juego de caracteres, denominándose a este nuevo estándar UNICODE en donde cada código ocupa 2 bytes por lo que puede tener un máximo de 65,536 códigos (esto es i 6), es por ello que en los sistemas que lo utilizan, como Windows, cada variable de tipo char ocupa 2 bytes. © César Liza A vila. No mates la producción intelectual, no copies éste libro.
© Cdsar Liza A vita. Mis libros son económicos, no hay razón para copiarlos.
20 Algoritmos y su Codificación en e + +. Volumen 2.
Cadena de Caracteres 21
César Liza A vila
Nuestro programa imprime cada una de las secuencias de escape deseada, mostrando su efecto. Note como podemos utilizar directamente el código ASCII en vez de la secuencia de escape, y como usamos las comillas simples', en vez de las dobles ", pues las secuencias de escape ·representan un único carácter.
Solución:
,
Observando la Tabla de Códigos ASen notamos que las mayúsculas van desde el 65 (carácter A) hasta el 90 (carácter Z) y que las minúsculas van desde el 97 (carácter a) hasta el 122 (carácter z) Es decir, que para convertir un carácter de mayúscula a minúscula se le suma 32 (97 -65), mientras que para convertirlo de minúsculas a mayúsculas se le resta 32. La cadena de caracteres o el arreglo de caracteres cad[ ] sirve para almacenar cada carácter de la frase ingresada.
~~T"""'""..-.-~~==.~~~~-.....,-,~-:-o-:¡
.....
,'. :COD1FIGAdóNiÍtNC++ . . .. ~denl}e:i
#iti¿htde<íóstreám.:h~,"
·.2).l~(~ad [LÍM]i . . ',,{.,-
'. oJit<>'tltlgfe~~;pá(]e.i~a:)~;,;:'· -'
,;.i~~~~~(a~g;F:,;··':.:;; . ,. . :
;' :cout«}'la qil.denaeq' lililiússulases
¡~~~~!t~~f,~¿~~> ·;.w:l}lle(9ad[tll~:N.I:JLL;) .<.... ,yv¿
....
¡~~!f.i~i~~¡~¡, .Ql~~f: it~ir~".· <-->-~~,~ .<
','.;4," .:. e
.•
>
"
La función cadToMin( ) aprovecha el hecho que las cadenas de caracteres siempre terminan en NULL. Esta función toma uno a uno los caracteres y si están en mayúsculas, esto es su código está entre 65 y 90, les suma 32 para convertirlas a minúsculas (cad[i] = cad[i] + 32). Recuerde que, todo cambio que haga una función sobre un arreglo, será conocido por el resto del programa, por ello no es necesaria la sentencia return para devolver la nueva cadena. Esto es, debido a que los arreglos se pasan a las funciones por referencia. Comprenderá más sobre ello en el capítulo sobre punteros.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos,
110
hay razón para copiarlos.
\1
22 Algoritl1lo.! y su Codi/icllt"ión en C++. Volumen 2.
César Liza A vi/a
Solución: .Las sumas de verificación, son técnicas no criptográficas que permiten detectar errores en la transmisión de mensajes. Consiste en calcular un valor (checksum) a partir de una serie de caracteres. Cuando enviamos un mensaje, también enviamos su checksum, el destinatario puede entonces calcular el cbecksum al mensaje y compararlo con el checksum recibido, si fueran distintos habrá un error en la transmisión del mensaje. Un ejemplo de sumas de verificación son los CRC's o Códigos de Redundancia Cíclica, como el CRC-32, o el Protocolo de Datagrama de usuario UDP, entre otros. Un algoritmo sencillo para calcular el checksum es sumar los códigos ASCII (o UNICODE) y obtener la suma como residuo de 232, es decir para el caso que la suma sobrepase el límite de los enteros (4 bytes ó 32 bits) la suma de verificación contendrá el residuo respecto a 232 •
Cadena de Caracteres 23
Los algoritmos de suma de verificación están per¡sados para la transmisión de mensajes por canales no maliciosos, pues es fácil alterar el mensaje sin que varíe el checksum. Los llamados CCV (cryptographic check values) evitan esto, y son simílares a las sumas de verificación con la diferencia que usa una función hjlsh para que dos mensajes diferentes no ·~~¡í~~:t··~~~·~~~'¡#~IP .•,.,. " ..... , ·'·illÍmplt~~tPl'~I~"IJtr~ci.I· tengan el mismo checksum. .. Estos algoritmos son de dominio público, no hay claves y cualquiera puede cal,?ularlas. Entre ellos podemos resaltar: SHA-l (Secure Hash Algorithm), RIPEMD-160, MDS (Message Digest A 19orithms) , de ellos el
desde el teclado y los alm~cena en frase hasta un máximo de LIM. El ingreso termina cuando se presiona enter y, a diferencia de cin, éste último además termina la lectura, con el primer espacio en blanco o con la tecla tab, por lo que cin no puede usarse para ingresar una frase. La función checkSum( ), toma cada carácter ( for (i=O; frase[i]; i++) ) calculando la suma de sus códigos ASCII, (s+=frase[i] ) esto es a pesar que frase[i] es de tipo charo En C/C++/C# no debemos preocupamos por obtener del residuo respecto a 2 32, puesto que para tipos unsigned, cuando rebasamos este límite, automáticamente se regresa la cuenta a cero.
más usado es el MD5 implementado, por ejemplo, en el MD5Summer (www.md5summer.org). en el fileAlyzer (http://www.safcr-nclwOrking.org/es/ downJoadlindex.html, de paso le sugiero descargar en la misma página el Spybot Search & Destroy excelente software detector de spywares o software espía). Muchas páginas en Internet muestran los MD5 qe los archivos, con el fin que el usuario al descargarlos compruebe que no han sufrido alteraciones (por ejemplo, la página de descarga del Spybot - Search & Destroy usa los MD5). Programas como los antivirus, usan un CCV para actualizarse, otros 10 crean para archivos ejecutables y notar si han sido alterados. El uso común hace que tanto los checksum como los CCV sean llamados solo checksum, pero usted ya conoce las diferencias. Para autenticar el emisario, se usan los llamados MAC (Códigos de Autenticación de Mensajes). En ellos tanto el emisor como el receptor comparten una clave, lo que permite al recept9rverificar si el documento es auténtico y que fue enviado por quien dice enviarlo. Es similar a una función hash, es decir se aplica a un mensaje y genera un valor MAC, pero las hash no tienen claves, mientras que las MAC si. Entre ellos destaca CBC-DES, HMAC, UMAC, PMAC. Una técnica aún más poderosa son las firmas digitales, que permiten al receptor verificar la autenticidad e integridad del mensaje, es decir el emisor no puede negar la autoría del mensaje, pues es el único poseedor de la clave, a diferencia de las MAC donde la clave es compartida por varias entidades. Ejemplos de algoritmos para firmas digitales tenemos: RSA, ESIGN (Efficient digital SIGNAture), MacEliece, entre otros.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vilo. Mis libros son económicos, no hay razón para copiarlos.
.
. La
sentencIa ,>' ;;i~;!f:j!~i;boDiFiCA¿í.{J~~( . .
cm.gethne(cad, LlM) lee caracteres
1 24 Algoritmos)! su Codificación en C+ +. Volumen 2.
Cadena de Cara(:teres 25
César LiGa A ]lila
Solución: Cada carácter de la cadena ingresada, por ejemplo "581", es un símbolo que representa un número, pero no es el valor del número. Así '8' es el carácter ocho, y no el valor 8. Nuestro programa debe lograr que a partir de la cadena "581" de tipo mar I ], obtengamos el valor de 581 de tipo Ini.
rt!~f!~~~ '~~Im!t~~~ 48
'O'
49 50 51 52 53 54 55 56 57
'1 ' '2' '3'
'4'
'5' '6' '7' '8'
'9'
Para ello nos apoyamos en el subconjunto mostrado de la Tabla ASCII, del cual deducimos, que si deseamos el valor numérico correspondiente al "carácter número", necesitamos restar 48 a cada ASCn que represente el número. 1'5' I'8' 1'1' INULL I La cadena "581" se representaría como: Pero internamente se almacenará como: Si restamos 48 a cada código y lo multiplicamos por su valor posicional correspondiente tendremos: (5348)xl0x10 + (56-48)xl0 + (49-48) 581
=
\
".' ...•..... CODIFIcÁCtoNEN'Ó++ " ' .
ltlnClude
·\; ; . . ' .,.,.~;.
Esto es lo que hacemos en nuestro programa. La· función cadTolnt( ), procesa cada carácter ( while (cad[i) != NULL), recuerde que toda cadena termina en NULL )
restándole 48 (cad[i]-48), y va acumulando el resultado no sin antes aumentar un valor posicional al resultado anterior (nro*10).
~
'".' {,J.:, ;:'
-:},>' ,t(:;} "'';'. ";".,,".;
" ,~>,
" . ; ~',
. >"~
•
irttcá(lTol*Ccr
.
»
'
n
.{ intn~ó::=Q,i7Ó;'/¡ . \yhi1 e(cad[i] !;'NULt)'
{oro;= nm,*l (J + (;a(ml~·48;
:.pal(kj'=3 NUL,L; ';·"~21.Ú;«pal«endI; :l/~}
"
iniciamos una nueva'·'c,";~-",~."';-" palabra haciendo que la siguiente palabra tenga cero caracteres (k=O). <9 César Liza A vila. No mates la producción intelectual,
/lO
copies éste libro.
© César Liza Avila. Mis libros son económicos,
110
IllIr razón para copiar/os.
Cadena de Caracteres 27 César Liza Avila
26 Algoritmos y su Codificación en C++. Volumen 2.
·Solución: La función leerFrase( ), hace lo pedido. En ella el método cout.flush( ), obliga a mostrar por pantalla todas las salidas que aún no han sido impresas. El bucle do{ }while (fraseri-l]!=l3); se repite mientras el carácter ingresado no sea ENTER (ASCII 13). Dicho carácter es leído en frase[i]= (unsigned char) getch( ); la' función getch( ), devuelve el entero correspondiente al ASCII de la tecla pulsada y su prototipo se encuentra en conio.h, razón por la cual lo incluimos al inicio del programa. S i pulsamos cualquier tecla diferente de BackSpace (BS, ASCII 8), entonces lo imprnmmos haciéndolo parte de la frase (i++), De haber sido BS, eliminamos el último carácter (i--), siempre y cuando exista (i>O). ~odemos modificar nuestra función leerFrase( ), para permitir sólo el mgreso de números, sólo letras, sólo mayúsculas, o minúsculas, es decir un ~ayor control de los caracteres cuyo ingreso aceptamos, inclusive cuando mgresemos un password podemos mostrar el carácter que deseemos. @
César Liza Avila. No males la producción intelectual,
-,
w-
110
copies éste libro.
SoluCión: Este tipo de encriptamiento consiste en cambiar un carácter por otro, utilizando la tabla ASCII. Para ello reemplazamos cada carácter por otro ubicado a una distancia igual al ASCII de la clave utilizada. Nuestra
función
encriptar( ), toma cada carácter mientras no sea CERO o NULL, esto se escribe de manera simplificada en el for, como frase[i], que equivale a escribir
frase[i]!=NULL.
A
cada carácter le sumamos el código· ASCII de la clave, transformándolo en otro código y por ende en otro símbolo. El proceso de desencriptado es similar, pero ahora desencriptar( ) resta a cada carácter el código ASCII de la clave, retornándola a su código original. © César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
Cadella de Carac,tere.\ 29
César Uza A vila
28 Algoritmos y su Codificación en C++. Volumen 2.
i.
Solución: Dado que la tabla ASCII es estándar, el algoritmo de encriptamiento del problema anterior (2.8) tiene algunas desventajas. Sin embargo, podemos mejorarlo definiendo nuestra propia tabla de caracteres a reniplazar. La cadena (arreglo de caracteres) tablal contiene los caracteres que deseamos sean remplazados por otros, aqueUos que no estén en esta tabla no serán remplazados. La cadena tabla2, contiene los caracteres de reemplazo, observe que la tabla2 debe tener la misma cantidad de caracteres que la tablal, no debe tener caracteres repetidos y es posible que tenga cualquiera de los 256 caracteres ASCII, a excepción del NULL, que es usado como terminador de cadena.
Note como inicializamos tablal, tabla2 y cad, en lugar de ingresarlas desde teclado.
La función encripta( ), toma cada carácter de la cadena a encriptar cad[i] y lo busca en la cadena tl ( ir (cad[i] == HU] ), si lo encuentra lo reemplaza con el carácter asociado (cad[i] = t2[j]) Y sale del bucle más intemo (break), para tomar el siguiente carácter a encriptar. La función desencripta( ) es similar, nada mas que ahora busca el carácter en la t2, y lo remplaza por su carácter correspondiente en tl. Aunque es valido inicializar la cadena tal como se muestra, es recomendable encriptar las tablal y tabla2 y mejor aun, inicializarlas usando sus códigos ASCII en vez de los caracteres. Esto es así, puesto que con un editor hexadecimal es posible ver las cadenas definidas como constantes na. de dentro 3A2:000GO ('DOOOOOO 43i,164GE. .... f e nuestro 6E6.s";2¿9 ,07461':4 61'202021) .3.t.2 )OÜÜO ejecutable; tal ~~~~~~~~ :~:~:~~~ !~~~~!! :~~:.~~~~ I~ k~j{J_ como se puede ,)(!OCOOOOO OClOODOOO r-07S420Ú 8('1 ':4C:I)Ct -.- ... .:;-r,j: oonOO(Jf)O 20;f,4?OQ -;OUA!)(:O observar al 000:)(:000 f:rSOGI)(!úO 696F7:37<\ f<.t?IS.E.6? ... J.:.·sttini ...:J utilizar el .textOOO . .. " ,,' FileAlyzer. d~gen-::ript""da
(;E¡:'12(q·;~· 1
a.c!"'n~
nc!."ip!;ed~
__
~~~~~~r~ E~E~~~ ~;~:~~~: ~:~;:i:~ :; ':~~:§~~~~~~: ~ _L..:.l¡'
l~fiOO(lOÓO
}
(í.)
("psar Liza Avila. No
Illllfes
'i;;
--
:<:~:·'+)A¡
.~?<
'
"I .... '.}'""-"'-'-'-,.;......~
la producción intelectual.
110
copies t5ste libro.
(i;)
Char Liza Avila. Mis libros son económicos,
110
ha,- ra;::ón para
("Ol/illrlo\'.
30 Algoritmos y su Codificación en C+ +. Volumen 2.
César Liza Avila
Solución: La función mayorPalabrn( ), toma cada carácter de cada cadena y los compara.
Cadena de Caracteres 3 J
Solución: La librería estándar ctype.h, contiene un conjunto de macros y funciones, que nos permiten saber si un carácter es un dígito, una letra, convertirla a mayúsculas o minúsculas, si és un signo de puntuación, entre otras. El programa adjunto hace uso de esta librería.
Si son diferentes retorna la diferencia de los códigos ASCII del carácter de la primera cadena, con el carácter de la segunda.
, . "." .
. Si esta diferencia es mayor que O, la primera palabra será alfabéticamente mayor que la segunda.
~
,:.~'
"«
1.' ~
. ",,,
~>'~ ,,:;5, :" thf;::;j~:':~"' ; ,
~.
!,.,
'
, 1"¿'
1
,1
:1
Si retorna menor que O, la· primera palabra será alfabéticamente menor. En caso de no ser ni mayor ni menor pasamos a probar el siguiente carácter (i++).
Recuerde, si una función devuelve un valor siempre podrá usarlo en una expresión condicional
De haber' analizado todos los caracteres, salimos del buclewhile, y retornamos O indicando que son iguales. ID César Liza Avila. No mutes la producción intelectual,
ir ( isdigitCl ') ) cout«"Es dígito"; else cout«"No es dígito";
110
copies éste libro.
© César Liza Avila. Mis libros son ecof/órni('us, no hay razón pam copiar/os.
:1
32 Algoritmos y su Codificación en C+ +. \lo/limen 2.
César Liza A vila
Cadena de Caracteres 33
Solución: Debido a que el e, no cuenta con el tipo base string, lo implementa como un arreglo de tipo char, en donde el fin de cadena es indicado por el terminador NULL y, siendo tan frecuente su uso, es que los compiladores del lenguaje e, proveen el archivo string.h, el cual contiene las declaraciones prototipo de funciones para manejo de cadenas, algunas de las cuales mostramos en el siguiente cuadro.
if(stremp(el, (2)==0)
strcmp( )
{
,"
strupr(ead)
strupr( )
strlwr(ead)
)¡,~t;rit>i ;.:~Ollt«"
strncat( )
stmeat(el, e2, n)
strcpy( )
strepy(e 1, (2) >~
strncpy( )
rmrnpr,,<
caracteres de una
strnepy(el, e2, n)
strstr( )
strstr(el, c2)
~f~k,
"."
strchr( )
(i':l
un carácter (car) dentro de la ). Si lo encuentra devuelve UIl puntero a sino devuelve NULL.
César Liza A vila. No
lI1ate~
la pruducúón intelectual.
/lO
strchr( el, car)
.. "'
'~ú~;%:,~gúlicit~ni~ideh~'j;~~2~di¿~if'b~~':
strcat( (;adJ~'cad2)¡":;", coút« "Ahora lil,c;adJ, ~s:i~:~:
copie.\
éste
libro.
© César Li::.a A vila. Mis libros son económicos,
.,; /lO
ha" razón para CflJliarlos.
l, 34 Algoritmos y su Codificación en
e + +.
Cadena de Caracteres 35 .
.César Liza A vila
Volumen 2.
Solución: Nuestro algoritmo consiste en: Tomar el prímer carácter y compararlo con el último, Tomar el segundo carácter y compararlo con el penúltimo, Tomar el tercer carácter y compararlo con el antepenúltimo, y así sucesivamente. Si alguna vez los caracteres han sido diferentes retomará CERO, o sea falso, no es palíndroma, pero en caso de haber evaluado todos los caracteres retomaremos 1, indicando que es palíndroma. Para obtener la cantidad de caracteres que tiene una palabra usamos la función strkn( ), cuyo prototipo se encuentra en string.h, razón por la cual la incluimos al inicio del programa.
Le sugiero probar el uso d~ las funciones strncmp( ), strstr( ), strchr( ), strrchr( ), entre otras, que no han sido utilizas en este programa. Otras funciones interesantes para el manejo de cadenas se encuentran en stdlib.h, y mostramos su uso en un pequeño ejemplo adicional. # inclllde # inclllde void main(void)
{
int entero =atoi ("123"); double doble =atof.("567.45"); long largo =atol ("1234567890"); char cad[IOO]; int base = 2; itoa (722, cad. base); cout«"cadena a entero :" «entero cout«"cadena a doble :"« doble cout«"cadena a long :"« largo cout«"Entero a cadena en base dada:
'\ L
«endl; «endl; «endl; " « cad « endl;
1\
(0 Cé.l(lr Li;:a Avila. No mate~ la producción intelectual,
/'lO
© César Liza A vi/a. Mis libros son económicos. no hay razón para copiarlos.
copies éste libro.
¿fe,.
i. Escriba una función que devuelva la posición de la primera ocurr~ncia de un carácter dentro de una cadena. Por ejemplo, la przmera ocurrencia de 'a' en "ulala" esta en la posición 2. Si no existe que devuelva -l. No use strchr( ). 2. Escriba una función que reemplace todas las ocurrencias de un carácter por otro. , . , ., 3. Dados dos palabras muestre las letras que estan en la mls.ma'p0slcl~n. 4. Escriba un pequeño corrector ortográfico que haga lo slgUlente: SI la palabra termina en "don" agréguele el tilde correspondie~~e. 5. A una cadena se le ha cambiado los caracteres de las pOSICIOnes pares por las impares. Obtenga la cadena original. Por ejemplo, si .ingresa usepirro debe obtener superior. Sugerencia: use un algorztmo de intercambio. 6. Convierta una frase de minúsculas a mayúsculas. 7. Escriba una función que permita ingresar un password, mostrando asteriscos *, para cada carácter. El password ~eberá ser eT:cript~do dentro del programa, para que 110 pueda ser VIsto por algun edaor hexadecimal. 8. Diga si una frase es palíndroma, esto es leída de izquierda a derecha es lo mismo que de derecha a izquierda. Sugerencia: Lea la frase con gets( ), cill.getline( ) o con nuestra función leerF~ase() del p:oblema 1.7, luego elimine los espacios en blanco y determme Si es palmdroma. Por ejemplo, "Abajo me mojaba" y "Anita la gorda lagartona no traga la droga latina" son frases palindromas. 9. Lea un número entero y escriba una función que obtenga una cadena COIl el número. Por ejemplo, para el entero 384 debe obtener la cadena "384". No use itoa( ). i O. Lea una cadena que representa un número con punto decimal y transfórmela a número Por ejemplo, la cadena "183.59" será el número 183.59. No use ato/( ). i 1. Lea un número con punto decimal y obtenga una cadena. Por ejemplo, si el número es 38.72, la cadena será "38.72". 12, Escriba un programa que verifique si un mensaje ha sido alterado. Sugerencia: use un checksum. @
César Liza Avila. No mates la producción intelectual,
Cadena de Caracteres 37
César Liza A vila
36 Algoritmos y su Codificación en C++. Volumen 2.
110
copies éste libro.
13. Lea una frase y convierta cada letra inicial a mayúsculas. Por ejemplo, "juan pérez mamani" será "Juan Pérez Mamani".· 14. Escriba un programa que diga cuál es la palabra más Larga de una frase indicando el número de letras. 15. Obtenga una subcadena empezando desde una posición inicial hasta otra final. Por ejemplo, dada la cadena "La belleza está en los ojos del que ama", la subcadena desde la posición 3 hasta la 10 es "belleza". Usted debe realmente obtener la subcadena no imprimir desde esos subindices 16. Escriba ufJa función que concatene dos cadenas. Por ejemplo: las cadenas "ojitos" y "lindos", concatenadas será "ojitoslindos': No use strcat( ). Usted realmente debe concatenar las cadenas y no sólo imprimirlas juntas.
17. Lea tres enteros que representen las horas, minutos y segundos y forme una cadena. Por ejemplo: h= 10, m=20 y s=45, la cadena será "10:20:45". 18. Escriba su propia función que busque una cadena dentro de otra. No use strstr( ). . 19. ¿ Cuántas letras diferentes tiene una palabra ingresada por teclado? 20. Una forma de encriptar un mensaje con clave de varios caracteres es repetir la clave hasta tener el tamaño del mensaje a encriptar y luego sumar cada ASCII del mensaje con cada ASCII de la clave. Por ejemplo: Mensaje "Arriba Perú" Clave = "abc."· Sería:
=
1r b a I pie l. r ú NULL I a I b I c I a I b I c I a lb' UiJILNULL I lA', r
Luego debemos sumar los códigos ASCII de 'A' con 'a', 'r' con 'b', 'r' con 'e' y así sucesivamente, obteniendo el mensaje enáiptado. Para desencriptalo tendríamos que ingresar nuevamente la clave, repetir la clave hasta tener el tamaíiO del mensaje y luego restarlos del ASClI de cada carácter del mensaje encriptado.
@
César Liza Avila. Mis lihros son económicos,
/10
hay razón para copiarlos.
J
Arreglos Bidimensionales .
;1'
~":
I ,
Arreglos Bidimensionales 41
Solucion: Una matriz o arreglo bidimensional es O'O aO,1 a 0,2 . . . a O,c-I un conjunto de números dispuestos en al,O al,1 31.2 .. · a l,<:1 f filas (líneas horizontales) y c A= az.o a2,1 a2,2.1 .. 32,e_1 columnas (líneas verticales). Se dice que la matriz es de orden fxc, mientras a¡CIJ) al'_I,1 ar-I,2'" ar-l,c_1 , que si tuviera igual cantidad de filas y fxc columnas sería una matriz cuadrada. Cada elemento aij, queda identificado mediante su fila i, y colunma j en la que se encuentra. En CIC++ y C# los subíndices empiezan desde cero y se indican encerrados entre corchetes. Así por ejemplo, el elemento que se encuentra ubicado en la tercera fila, segunda columna es el a[2](l]. Definimos dos constantes ',' é6ÚIFléACÍONEN c++ ' simbólicas FIL y COL, que #Újcíltqe nuestro arreglo, en este caso 50. void 'léerMatdz(Írit[ J[COL1,iht; ¡nt); Una matriz en CIC++ se declara YOi(i,ir6pMatríz'(iit[][CoJ:Jtillt, lnt); ,'," ;"Jj '.~' indicando el tipo de dato, seguido de las dimensiones máximas para v614ffiáiWCyptd),t'F .:; ",' las filas y colunmas encerradas rAhta[l~lLJ[~()q·;'" entre corchetes ( ¡nt m[FIL][COL] ), ,:-iriJhc; '" "o~"f,;.' como todo arreglo estático, éstas dimensiones deben' ser constantes, ya sea que usemos constantes simbólicas (# define FIL SO), simplemente constantes ( const int Ím¡iMatdz(a f;,ey':'" ',o," FIL=50; ) o colocando } I,lfinm,
~
,
" ' " " ", . . "
.."
0.'", •. -",>;;'."'
_',
-
" " " " "..
'
,¡"o":.
'i.'
f~j~!j~i~J1~!f
© César Liza A vilcl, Mis libros son económicos, no hay razón pam copiarlos.
... 42 Algoritmos y su Codificación en C+ +. Volumen 2.
César Liza A víla
La función leerMatriz( ) acepta 3 argumentos. El primero, de ellos (m[ )[COL]), es la matriz en donde deseamos ingresar los datos (en realidad es la dirección de . memoria a partir de la cual se almacenan los elementos). El segundo, la cantidad de filas que deseamos leer; mientras que el tercer argumento indica la cantidad de columnas de la matriz. Esta función utiliza dos bucles para poder leer todos los elementos de la matriz. La variable j representa las columnas y controla el bucle más interno poi lo que Ilumenta más r~pido que las filas; esto significa que el ingreso se hará fila por lila y dentro de cada fila se leerán cada una de sus columnas. De manera similar, la' función impMatriz( ) toma cada elemento imprimiéndolo. Cuando· pasamos como parámetro un arreglo (m), lo hacemos indicándole su nombre ( leerMatriz(m, f, c) o impMatriz(m, f, c) ), pero al declarar el argumento es necesario indicade a la función la cantidad . máxima de columnas que'tendrá (void leerMatriz(int m[ ][COLJ. int f, int· c) o void impMatriz(int m[ ][COLJ, int f, int c) ), pues el computador LOma esta cantidad (COL), para calcular la dirección de memoria donde se encuentra ~l primer elemento de cada fila; y es que, un arreglo bidimensional se almacena en memoria de manera lineal y consecutiva, esto es se almacena como si fuera un ¡arreglo unidimensional!, pero sabe cuántos elementos' debe avanzar (COL) para obtener la siguiente fila.
Arreglos Bidimensionales 43
Solución:
S~a A una'~atriz de f filas y e columnas, la tra~spuesta de A se denota por A, Y se obttene colocando las filas (o columnas) de A como columnas (o filas)en Al. I
Por ejemplo, matriz
A=
n
G
3x2
(
su transpuesta será
Es decir tomamos cada fila y la convertimos en columna, o lo que es lo mismo cada 'elemento de la matriz original mij ahora será mji'
Al pasar arreglos de cualquier dimensión a una función, lo que realmente se pasa es la dirección de inicio del arreglo y no sus valores. Es por ello que el paso de arreglos es un paso de parámetros por referencia y no por valor. Así los cambios que se hagan al arreglo dentro de la función serán reconocidos por maine ), sin n~cesidad de una sentencia return, siendo las funciones leerMatriz( ) e impMatriz( ) de tipo void.
La nueva matriz tendrá e filas y f columnas.
(O César Liza Avila. No mates la producciói'i intelectual. no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay raz.ón para copiarlos.
44 Algoritmos y su Codificación en C++. Volumen 2.
Arreglos Bidimensionales 45
César Liza A vila
Solución: Una matriz identidad también llamada unidad o unitaria, es aquella cuyos elementos son todos ceros excepto aquellos que. pertenecen a la diagonal principal que son unos. La diagonal principal de una matriz esta formado por los elementos en los que i=j (su contraparte es la diagonal secundaria en la cual i+j=n1), esto es:
nxn
La función transpuesta( ), necesita conocer la matriz original (m), y la matriz donde se almacenará su transpuesta (t), además de la cantidad de filas (f) y colurrmas (e). En ella usamos los bucles para tomar cada uno de los elementos de la matriz m, cambiando sus filas en columnas (t[j][i] = m[i][j]).
~;) César Liza Avila. No mates le producción intelectual, no copies éste libro.
Por ejemplo la matriz identidad de orden 4 será:
En una matriz identidad se cumple: 1 + 1 + 1 + ... (p veces) = p.l A.I = LA =A ¡n = 1 © César Liza Avila. Mis libros son económicos, no Izav razón para copiarlos.
1
Arreglos Bidimensionales 47
César Liza Avila
46 Algoritmos y su Codificación en C++. Vvlwnen 2.
Solución: Una matriz triangular superior, es aquella matriz cuadrada, cuyos elementos debajo de la diagonal principal son todos ceros, es decir aij =O, para todo i>j.
A=
a 0.0 aO.1 aO.2'·· aO.n-1 O a 1.1 a 1.2' .. a I.nl O a 2.2 •.. a 2. n· r O
O
O
O ... an-I.n-I
nxn
I
© César Liza A vila. No mates la producción intelectual, no copies éste libro.
En la función esTriangular( ), recorremos todos los elementos preguntando por la condición i>j, y si alguno de éstos elementos es diferente de cero, entonces no es matriz triangular superior (return O). De recorrer todos los elementos y no haber encontrado algún valor igual a cero entonces será triangular superior (return 1).
Mafr=:i.i· Ingresada: 1 O
O
52
U
36 8
~
41 7 9
O O O 10 Es tr~anc~
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
César Liza A vila
48 Algoritmos y su Codificación en C++. Volumen 2.
Arreglos Bidimensionales 49
Nuestro programa obtendrá la matriz aumentada solo si ambas matrices tienen la misma cantidad de filas (f1==f2).
Solución: Dadas dos matrices A de orden mxn y B de orden mxp, o sea con el mismo número de filas (m), se denomina matriz ampliada o aumentada a la matriz resultante de unir ambas, en una única matriz de orden mx(n+p). Es decir:
A
=
a 0,0
ao,1
a 0,2
a o, n·1
a 1,0
a 1,1
al, 2
al, n·1
a 2,0
a 2, 1
a 2,2
a 2, n·1
am.I,O
am.I,1
a m·I,2· ,. am-!,n-I
mXn
B
[,,"
b o, 1
b 0,2
b 1,0
b 1,1
bl,2
b 2,0
b 2, I
b 2,2
bm-I,O ' bm.I,1
b m-I,2"
b."] b I,p-I b 2, p-I
. bm_l,p.1
mxp
I
a o, 1 a n. 2
a o, n·1
al,1
: bo,o a 1. 2 . . . a 1. n-I : bl,o
a 2, I
a 2,2 . . . a 2, n-I : b 2, o
•••
I
am_I,1 a m.I,2·· .am-I,n-I
I I I
I
b o. 1 b O,2'" bO,P_I] bl,1 bI,2 . . . b l,p-1 b 2, I
b 2, 2 . . . b 2, p·1
bm-I,o b m-l.1 b m- I,2"
.bm-I,p-I
mx(n+p)
C9 César Liza Avda. No mates la producción intelectual, no copies éste libro.
C9 César Liza Avila. Mis libros son económicos, no hav razón para copiar/os.
50 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
Arreglos Bidimensionales 51
Solución: Sea A una matriz de fxc y V un vector, o más propiamente dicho un vector columna pues tiene f filas y 1 columna (también existen los vectores fila compuesto por 1 fila y e columna).
A=
col raO.2" .aO.n-1
["
a 0.0
a 0.1
a 1.0
a 1.1
a 1.2"
a 2.0
a 2.1
a2.2··· a2,n.1
v~ '.'
an-I.O an-I.I
a n-I.2· •• an-l.n_1
Vn_1
VI
• a I.n-I
fxc
fxl
Si deseamos insertar el vector columna V en la columna col de la matriz A, tendremos:
A=
aO.1
~
al.O
al,l
VI
al.2 .. ·al. n_1
a2.0
a2.1
V2
a2.2· .. a2.n_1
, an-I.O an_I,1
La función matAumentada( ) toma cada elemento bij y lo añade a partir de la primera columna libre (el) de la matriz a ( a[i][c1+j] b [i]U] ).
=
© César Liza A vi/a. No mates la produccion intelectual, no copies éste libro.
col
'ao.o
aO.2 ... aO.n_1
Vn_1 a n_I.2_··
fx(c+l)
Es evidente que para lograr esto debemos "hacer esp~cio" en la matriz, para poder colocar el vector columna. Esto lo conseguimos desplazando cada columna de la matriz, empezando desde la última (n-1) y hasta lá columna col, luego podemos colocar en dicha columna el vector dado.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos_
52 Algoritmos y su Codificación en C+ +. Volumen 2.
César Liza A vila
Arreglos Bidimensionales 53
En agregaColumna( ), tomamos la última columna (c-l) y copiamos todos sus elementos hacia la columna siguiente ( m[iJU+l] = m[i][j] ), continuamos con la columna anterior (j--) hasta la columna en la cual se insertará el vector (j>=col). En este punto la columna col y col+ 1 tendrán los mismos elementos. Finalmente, el último bucle reemplaza los elementos de la columna col por los elementos del vector. © César Liza A vila. No mates la producción intelectual, no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
54 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vi/a
Solución: Si cada fila representa una fábrica y cada columna un mes, podremos almacenar lo que produce cada fábrica por mes en una celda de prod[ilu], donde i indica la fábrica y j el mes de producción.
La producción de todas las fábricas para un mes determinado esta dado por la suma de todos los elementos de cada columna. A diferencia de los programas anteriores, la matriz prod[ ][ l, es definida como tipo float, por lo que las funciones deben contemplar este tipo. Luego de leer la cantidad de fábricas (filas), la cantidad de meses (colunmas) y de leer la producción (matrii') mostrándola, invocamos a sumaColumnas( ).
Arreglos Bidimensionales 55
En sumaColumnas( ), tomamos cada columna (mes) e inicializamos la suma a cero, recorremos cada una de sus filas (fábricas) acumulando cada valor m[i][j] (producción de la fábrica i en el mes j), luego imprimimos el total de cada columna (mes). Note como las filas deben aumentar más rápido que las colurrmas es por esto que i controla el bucle más interno, note además la necesidad de inicializar sumaCol a cero cada vez que cambiamos de columna.
¡··}~~~11~7141Cbl.~~¡~~í~<~t'"
Observe que la función sumaColumnas( ), imprime los resultados. Si hubiéramos deseado que fuera maine ) quien los imprima, deberíamos añadir como argumento un arreglo unidimensional a sumaColumna( ), declarado con tamaño máximo COL, pero que contendrá e elementos con la suma de cada columna, luego como el arreglo (z) es pasado por referencia, los cambios sobre él hechos por sumaColumnas( ), serán conocidos por maine ). void sumaColumnas(float m[ J[COL), float z[ ) in! f, int e) ( inl i, j; float sumaCol; for (j=0; j
J )
© César Liza Avda. No mates la producción intelectual, no copies éste libro.
vói~~mpMa;tlt:~{1()at ¡rtrlICW,].í~t r,)ntc)
'§t\i~}\!jij'm~jjf!i%'"
i
•
Ingrese .Matriz Nro de Fabricas?: 3 Nro. de Meses?: 4 Ingrese producciJ(n mensual Fabrica 0 . m[O][O]= 1 m[O]U]= 2 m[O][2]= 3 m[OH3]= 4 Fabrica 1
m[ll 101= 5 m[1l111" 6 m[ll [21= 7
m[lH3]= 8 Fabrica 2 m[2][0]= 7 mI2][11= 6 m12] [21=
5
m[2]131= 4
1 5 7 Mes Mes Mes Mes
2 6 6 se ha se ha se ha se ha
0 1 2 a3_
3 7 5 producido producído producído roduci o
4 8 4 13 14
15 16
© César Liza Avila. Mis libros son económicos, no hay razón para cOJ} ia rlos.
I
56 Algoritmos y su Codificación en C++. Volumen 2.
Arreglos Bidimensionales 57
César Liza A vila
Si colocamos la reina en la fila x, columna y, entonces podrá moverse en su misma columna: for 0=0; kMAX; i++) if(i!=x) m[i][y]=l; o en su misma fila for G=O; j=O && j>=O) { m[i][j]=l; i--; j--; }
o avanzando i=x+l;j=y+l; while(kMAX && j
o en diagonal hacia adelante / Leemos la posición de la reina dentro del tablero (x, y) e invocamos a posiciones( ), para marcar con 1, los casilleros a las cuales s.e puede mover la reina.
i=x-l; j=y+ 1; while(i>=O && j=O) ( m[i](j]=I; i++; j--; }
La función inicMatriz( ), llena de ceros la casilla del tablero de ajedrez. Mientras que impMatriz( ) ha sido ligeramente modificada para dar la impresión de ser un tablero. © César Liza A vila. No mates la producción intelectual, no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
1
César Liza Avila
58 Algoritmos y su Codificación en C++. Volumen 2.
Arreglos Bidimensionales 59
Solución: Para sumar 2 matrices es necesario que ambas tengan la misma cantidad de filas y columnas, esto se conoce como concordancia de matrices. Sean A y B dos matrices:
A=
['o
a 0,1
a o, 2
a O,c-1
a 1,0
al,!
a1,2
al, c-1
a 2,0
a 2,1
a 2,2
a 2,c-1
ar-!,o
ar-l.l
ar-l,2
. . . ar-!,c-l
fxc
B
=
["
b 0,1
b 0,2
b 1.0
b 1.1
bu
b l,e-1] b.
b 2,0
b 2,1
bu
b 2, e-I
br-I,o
br_I,1
b tC I,2
b r-I•e- I
o
fxc
La suma de ellas nos da otra matriz e con la rrusma cantidad de filas y columnas (fxc) en donde cada elemento de la m~triz suma es c¡j ;;:: aij+b¡j, esto es: ao.o C=A+B=
+ b 0,0
ao,l+bo,1
al,o+bl,o
a 1,1
+b
1,1
a2,o+b 2,o
az,l+b
2,1
ar-I,o
+ bf-I,o
3r_I,1
+ br_I,1
+ b o. e-I + b O,e-I 2,n-1 + b O,c-I
b O,2
a o, n-I
+ b 1,2 a 2.2 + b 2, Z
a I,n-I
aO,2+
a 1,2
af-1,2
+ b r_I,2
a
. ,. ar_l.c_1 -1-
b
r-I;e-I
fxc Si deseamos obtener la diferencia de matrices solo debemos cambiar el signo + por el signo - en cada término. Realizamos la suma solo si las matrices son concordantes (f1==f2 && c1==c2). © César Liza A vila, No mates la producción intelectuui, ,tu wpies éste libro.
© César Liza Avila, Mis libros son económicos, no hay razón para copiarlos.
60 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
Arreglos Bidimensionales 161
~1Jlh]!ll::i6l!],~
Sean dos matrices A de orden ifllxcJl y B de orden if2xcZ, entonces el pwducto AxlB solo se puede realizar si d es igual a--n; esto es, si la cantidad de columnas de la primera matriz, es igual a la cantidad de filas de la segunda. Dicho producto es otra matriz 1[1' de orden irbc2, (el orden está dado por la cantidad de filas de la primera matriz y la cantidad de columnas de la segunda) tal que cada demento !Pij es igual a: k=c1-1
Pi.i:::
L
aik
bkj
k=O
pO,o == aa,O bo.o + ao,! bl,o + aO,2 bz,o + ... + ao,c1-1 bcJ-I,o PO,I :::: ao,o bo,1 + 30,1 bu + 3o,z bz,1 + ... + 3o,c1-1 bcl-1,1 PO,2 =aa,o bo,2 + 30,1 bu + aO,2 bz,2 + ... + ao,ci-2 bcl - I,2
donde:
P i,j = a¡,O bo,j + ai,l bl,j + a ¡,2 bz,j + ... + a¡,cl-2 bcl-1,j Esto es, cada uno de los elementos de una fila se multiplica con sus correspondientes elementos de la columna y luego se suma. Por ejemplo:
r; ~lxfll? 23~41: ~~ ~ :¡~; ~~~:~~~ ~~~:;~; ~~::~~~8 = U1~ ~~ ~; ~06:1
156 67 liX1+6X5 5x2+6x6 5x3+6)(7 5x4+6xS ·35 46 57 L - 3x2 2x4·~ 3x4
cada elemento se obtuvo aSÍ: c[
En sumaMatrices( ) los dos primeros argumentos (a y b) son las matrices a sumar, mientras que el tercero (s), es la matriz que contendrá la suma y los dos últimos son la cantidad de filas y columnas de las matrices a sumar. Recorremos cada uno de los elementos i, j de ambas matrices y los sumamos, colocando dicha suma en el correspondiente elemento ij de la matriz suma (s[ilÜ] = a[i]ü] + b[iJU]).
in j] =a[i][k]. b[k][j] + a[i][k].b[kUj]
c[O][O] = a[OJ[O]. b[O][O] + a[O][I].b[lJ[O] == Ixl + 2)(5 == H c[O][IJ = a[O][O]. b[O][l] + a[O][1].b"[l][!] '" Ix2 + 2x6 = 1[4 c[O][2] = a[O][O]. b[O][2] + a[O][1].b[l][2] = Ix3 + 2x7 = 17 c[0][3] == a[O][O]. b[O][3] + a[O](l].b[1][3] :: lx4 + 2x8 = 2~ c[lJ[O] := a[l][O]. b[l][O] + a[l][l].b[l][O] '" 3x1 + 4x5 = 23 c(IJ[l] == a[l][O]. b[I][l] + a[l][l].b[l](l] == 3x2 + 4)(6 = 30 c[1][2] == a[IJ[O]. b[IJ[2] + a[1][1].b[1][2] = 3)(3 + 4x7 = 37
c[I][3] :: a[l][O]. b[l][3] + a[1J[1].b[1][3] c[2][O] c[2][1] c[2][2] c[2][3]
== a[2][O]. == a[2][O]. = a[2][O]. = a[2][O].
b[2][O] b[2][1] b[2][2] b[2][3]
= 3x4 + 4x8 = 44 == SxI + 6x5 = 35
+ a[2][I].b[1][O] + a[2J[1].b[1][1] == 5x2 + 6x6 == 46 + a[2][1].b[1][2] == 5x3 + 6x7 = 57
+ a[2][1].b[1][3] = 5x4 + 6x8 = 68
La multiplicación no es conmutativa, es decir A.B i B.A. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avda. Mis libros son económicos, no hay razón para copiarlos.
::J3x3
62 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila
Arreglos Ridimensionules 6J
En nuestro programa, luego de ingresar las matrices, comprobamos J.a: conformidad respecto a la multiplicación (c1==f2), de serlo, mostramos las matrices, procedemos a multiplicarlas e imprimir la matliz producto.
La función multMatrices( ), acepta las dos matrices a multiplicar (a y b), Y la matriz producto p, además de la cantidad de filas y columnas de cada matriz, pero comoc1 es igual a f2, solo pasamos fl, el Y c2 como parámetros. En esta función tomamos cada elemento de la matriz resultante de orden flxc2, con el fin de calcularlos, de ahí la necesidad de los dos primeros bucles i, j. El tercer bucle (k), es el encargado del cálculo, acumulando la suma de los productos respectivos en p[i]U], es por eso la necesidad de inicializarlo a cero (p[ilUl=O). © César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis lihms son económ.icos, no ha\' rm.tÍI1IJara copiar/os.
64 AIF,orümos y su Codificación en C+ +. Volumen 2.
ArreF,los Bidim.ensiollales 65
César Lim A vilo
Solución: Cada matriz cuadrada tiene un número importante llamado determinante, que refleja algunas propiedades. Una de ellas es determinar si un conjunto de ecuaciones simultáneas tiene solución única, como veremos más adelante, La determinante de la matriz A de orden n, se denota por det(A) o ar,n_¡; donde la también IAI y se define como: IAI = I (±) a¡,Oaj,tak,2, sumatoria se hace sobre todas las permutaciones de los primeros subíndices de a, y se toma el signo +, si la permutación es par y - si es impar. 000
etapa de eliminación hacia adelante. Este último método nos 'parece más conveniente de implementar en computadoras y; además, nos servirá para resolver un sistema de ecuaciones lineales homogéneo y calcular la inversa de una matriz, las cuales se verán en los siguientes problemas. La eliminación hacia adelante consiste en aplicar operaciones fundamentales como suma, resta y multiplicación sobre las filas de la matriz, para transformarla en una matriz triangular superior equivalente. Esto es a partir de a 2.0
aO.1 a 1.1 a 2, I
a n_1.0
an-u
a o. o
a 1,0 A
a 0.2
ao,n_l]
al, ni a 2,2 ... a 2.n.1
a 1,2
,
[
an·1,2 , . , an l,n·1
nxn
debemos lograr '
Por ejemplo para una matriz de 2x2 tenemos: IAI ::::
Iaoo
A'=:
alO
Para una matriz de 3x3, la determinante será:
aoo
<101
alO
al1
a02 a12 a21 a22
[
,
aO, o
aO.1
a'O,2
O O
a'!. 1
a'1.2
O
a'2,2
a'2, n-I
O
O
o
a".I,n.1
a,o,n.l] a'I,n.1
nxn
Sin embargo, la obtención de la determinante de una matriz de orden mayor a .., no resulta tan fácil, ya que la cantidad de permutaciones crece rápidamente. Para una matriz de 2 x 2 será 21 = 2 términos, para una de orden 3 será 31=6 términos, para una de 5 será 51 = 120 términos y siempr,e hay que calcular productos, por lo cual no es un método práctico cuando el orden es mayor que 3. Existen otros métodos para obtener la determinante, entre ellos tenemos la Regla de Sarrus que se enseña en el colegio y sirve para obtener determinantes de matrices de 3x3, el cálculo de la determinante por menor complementario, por el adjunto de un elemento y por los cofactores que se puede implementar fácilmente usando una versión recursiva que calcule la determinante de una matriz de orden n-}. La recursión se trata extensamente en mi libro Estructuras de Datos con C/C++. Asimismo podemos aplicar la descomposición LV, () la eliminación de Gauss en su
Cada elemento a'jj, donde i=j se denomina pivote. El elemento pivote es el mayor elemento (en valor absoluto) de la columna en la cual se está haciendo ceros los coeficientes debajo de a'ij. Usar pivote en la eliminación hacía adelante, tiene dos ventajas: Primero, superar la dificultad que presentan los coeficientes nulos en la diagonal, si esto ocurre debemos intercambiar las fijas para que el pivote sea diferente de cero; cuando encontramos ceros en la diagonal no se pueden eliminar el resto de elementos de esa columna, asimismo cuando tratamos de encontrar la solución de un sistema de ecuaciones lineales o la inversa no es posible pues el algoritmo exige que se divida entre este número, al ser cero esa división no se puede realizar. Segundo, cuando escogemos el pivote como el mayor elemento en valor absoluto de la columna; nos permite reducir los errores de redondeo en los cálculos. Una vez conseguida la matriz triangular equivalente, la determinante estará dada por el producto de todos los términos de la diagonal principal, la cllal estará afectada por el signo - si se hizo un número impar de intercambios, o + si se hizo un número par ae intercambios
© César Liz.a Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A \'íla. Mis libros son económicos,
IAI
=
a20
=
all an + alO a21 a02 + a20 aOI an - aoo a21 al2 - aJO aOI a22 - a20 a)) <102 aOO
110
hay razón para copiar/os.
Si por ejemplo desearnos obtener la determinante de la matriz
l
'lomamos la siguiente columna a)
-3 1]
-1 1 -3 -4 5 2
Para la primera columna
ArreRlos Rúlimetlswnales 67
César Liza A vila
66 Algoritmos y su Codificf/ciól1 en CH'. Volumen 2.
a) Buscamos la fila pivote, es decir la fila con el mayor elemento en valor absoluto de la primera columna. El mayor entre 2, -1 y -4, en valor absoluto, es -4, ubicado en la tercera fila. Intercambiamos la primera fila (es la que estamos procesando) con la tercera fila, obteniendo:
b) Mediante operaciones elementales hacemos cero todos los elementos de la primera columna, pero sin alterar la primera fila. Para hacer cero la primera columna de la segunda fila, multiplicamos la primera fila por --(-1/-4) esto es, el elemento actual (-1) entre el pivote (-4), y le sumamos a la segunda fila, colocando el resultado en la segunda fila:
2J
Buscamos la fila pivote, es decir la fila con el mayor elemento en valor absoluto de la segunda columna (pero excluyendo el elemento de la primera fila, pues ya lo procesamos). El mayor entre -1/4 y -112, en valor absoluto es -112, ubicado en la tercera fila. Intercambiamos la segunda fila (es la que estamos procesando) con la tercera fila, obteniendo:
5 2J 4 [ O -1/2 2 O -1/4 -7/2
b) Mediante operaciones elementales hacemos cero todos los elementos de la segunda columna, pero sin alterar la primera y segunda filas. Para hacer cero la segunda columna de la tercera fila, multiplicamos la segunda fila por -(-1/4/-1/2) esto es el elemento actual (-1/4) entre el pivote (-1/2); le sumamos a la tercera fila, colocando el resultado en la tercera fila:
4 5 O -1/2
[ O O
4 -114 5 -7/2 O [ 2 -3 l
Para hacer cero la primera columna de la tercera fila, multiplicamos la primera fila por -(2/-4) esto es, el elemento actual entre el pivote, le sumamos a la tercera fila, colocando el resultado en la tercera fi la:
2J 4 5 [
Como ya conseguirnos mediante operaciones elementales convertir la matriz original en una matriz triangular superior equivalente, podemos calcular fácilmente la determinante multiplicando los elementos de la diagonal y además como hicimos dos intercambios entonces la determinante estará dado por (-1) 1 (-4)(-1/2)( -9/2) () sea -9.
O -1/4 -7/2 O -112
2
César Liza Avila. No mates la producción intelectual,
110
copies éste lihro.
© César Liza Avda. Mis libros son econó¡nic,'s, no ha.'" raeón para copiarlos.
En el programa, una vez ingresada la matriz obtenemos su matriz t;iangular equivalente mediante eliminaAdelante( ), la cual devu~lv~ el .~urnero de intercambios realizados durante el proceso de elmunaclOn, luego imprimirnos dicha matriz. La determinante la calculamos multiplicando todos los elementos de la diagonal principal (det::det*m[i][i}), Y si se realizó un número impar de intercambios ( if(nIntercambio%2==1) ) afectaremos a este producto con el signo menos (det=-det).
#'\~~IJ¿~\~¡df~?~~t~~:f~~~+"
.. , , ' ,
~"lib'll.ptrrexJt(),
'#incl
';i~~i~1~¡~~~;",;;,'}1,p~r fá~S() ,,', "" "'" ",# deÍlne CtJh~,(¡.,i'>
~;~~Lth~i~0V~\~J',~f';': ',' " ' é i C " idp~bjem[FíLJ[€0~}, (jet::,!; ': int ti, hIllt~rcamb; ,
i:
."
.
'~~i~i)~~!;~\,; ,..;.
,
!YYlll"l'lll1~"U~)UU.le U\[ J[COL],ínt f, intc)·
La función pivote( ), se encarga de encontrar la fila (piv) que contiene el elemento de mayor valor absoluto de una columna determinada (i), pero a partir de la fila i, en este caso i es tanto la fila como la columna inicial, a partir de la cual se lmclará la búsqueda (for(j=i+l; j
,,'"
'Véiic11etitM~tt~~k,d(),!lQJ~1, 1[Co.q ,il,1t;'jri;t)¡ , ' voi'hnp~atFJ1/~~Í1b'le [][CpL),inf,jpt);i' "••• '., " ";,Óí ',' " ',ei6~Il}6i~il~s(inf;jpi, irt,dollbJe'( ][<0PLJ)~ , iJ~tp;¡."Qi~t\l}t.f~J,ij?u~leJ] [C8L ]); "'j',:;:,: ' '¡/jI """. '¡fé1a!lt~(~Qup,lerm::pLNt~J1t),; ',,'
~
Arrexi(IS Bidimensionales 69
C¿>sar Liza A vila
68 Algoritmos)' su Codificación en C++. Volumen 2.
~
La función intercambiaFilas( ), tiene como tarea intercambiar la fila i, con la fila pivote, pero como los i primeros elementos no será son cero, necesario su intercambio, ello que es por el empezamos ( intercambio desde for(j=i; j
de (El algoritmo intercambio, fue explicado en el problema 1.1 de mi libro Algoritmos y su Codificación en C++ Volumen 1).
.
'i~~::~¡;)· .
\;';CP\if<;<;"Det~ttnjtiantéj:'« del;
} (e)
:
Clsar Uz.,a Avifa. No mates la producción intelectual,
--------~--'--~'---
..
/lO
copies éste libro.
© César Liza Avila. Mis lihro.1 son económicos,
110
hay m,:ón para copiarlos.
70 Algoritmos y su Codificación en
('+ +.
Vofwnen 2.
Arreglos Bidimen\lflfLulcs 71
César Liza A vila
La función central del nuestro problema es eliminaAdelanle(), la cual reCOITC cada C01UlllJlíl buscando la fila pivote, esto es la fila con el elemento de máximo valor absoluto para la columna deseada. Si esta fila es diferente que estamos procesando (piv!:::i) entonces realizamos el intercambio de la fila i con la fila piv aumentando la cuenta . (nrolnlercamb++). Los bucles para j y k, permiten realizar la resta entre dos filas, y como empezamos a partir de i+ 1, el elemento m[j][i] lo hacemos cero, ya que es el elemento que deseamos eliminar.
Solución:
Un sistema de ecuaciones de n incógnitas tiene la forma: flo.axa + aa,lxl + flo,2Xz + al.OXO + al,lxl + aUx } +
donde a¡.j' son los coeficientes, Xi las incógnitas, y y¡ son los términos independientes. Debe cumplirse que el número de incógnitas es igual al número de ecuaciones y que al menos uno de los términos independientes sea distinto de cero, para que sea considerada un sistema no homogéneo, pues es el único tipo de ecuaciones en donde se puede aplicar la eliminación de Gauss para resolver el sistema. Este método consiste en 2 etapas: la eliminación hacia adelante y la eliminación hacia atrás, Primero, tomamos los coeficientes del sistema de n ecuaciones simultáneas, y le aumentamos el vector columna formado por los términos independientes, formando una matriz de orden n x (n+ 1), a 0.0
aO.1
a 0.2
a 0.11-1
Yo
a 1.0
a 1.1
al "2
a 1.11-1
YI
a 2.0
a 2.1
a 2.2
a 2.11-1
Y2
a,,_1.0
an_!.1
an- I .1
. , , a,,·l.n_1
Y,;-I
nx{n+ 1)
Mediante el proceso de eliminación hacia adelante, explicado en el problema anterior, convertimos esa matriz a una matriz triangular superior. a 0.1
a o. 2
O O
a 1.1
al. 2
.. a o, n.2 .. a 1,11-1
O
a 2."2
a
O O
O O
O O
a 0.0
([) César Liza Avila. No mates fa producció¡¡ i¡¡/efectual, no copies éste libro.
a O,n-I
Yo
a I.n-I
YI
2. n-2
a 2,11-1
Y2
an _2.11-2 O
a n _2,,,.1
Yn·2
an·l.n_1
Yn 1
-
© César Uza A vi/o. Mis libros son ecollómico,\', no Iza)' razón
Tlx(fH 1)
fHl/"II ("o/liarlos.
César Lizo A vila
72 Algoritmos)' su Cod!tiraCÍón en C+ t. Volumen 2.
En esta última matriz, aplicamos el procedimiento de eliminación hada atrás, que consiste en ¡robteniendo las raíces desde la última fila hacia la primera.
Arreglos Bidimensionales B
La funciones leerMatriz( ) e impMatriz( ) ya las hemos usado en varios programas anteriores, mientras que pivote( ) e intercambialmas( ), son las mismas que el problema anterior.
De la última fila obtenemos la incógnita Xo-I. pues: a o.l,o.] ,Xo-I =Yo-]; de donde Xo-l =Yn-JI a n-l,n-l La penúltima fila obtenemos la incógnita Xn-2 haciendo: a o-2.0-2 ,X o-2 + a,,-2.0 . 1 .Xo.J =: Yo-2; de donde Xo-2=( Yn-2 - a o-2,0-1
.Xo-I
)Yn-l l a o -.2,0-2
y así para el resto de incógnitas.
'1~~:f~r;~~~~s~~z~~g~9!2~~W:~S~;.;~;·~·~i. ,:.V~?¡;/'.~
;~~¡i~¡~:i}$~t~;~.,~~}~:~::~~L:': ,,'::"0'; ...
",31' -'\.. "~,.,\ ~;n".>~1·., .... ,leerM;ittti?i( do~ble [][eOb];int, lnt);', ".>~ :.;: ... 'y). , .v6id jmpM~triz ( qol1~lé:r m::oL), jnt, inl}i' ..,.- ,:.f·,,{.; oc;; ,. . . .
': i~t\iiYbte(i.nt, int;9Q~~I~J ]Jf(}lJ12.;; ...·.i·.r:I '::";\" ,) .Ybícijn,tercarnbiaFiIfls(i*~ il.lt,int; i:.' .
'~'
::'c.~
,-J; "'.'
',v.ce
.,'
'.':" '" . . . . . . . ~
'~'.
.
",
'.' C
voló main(void}'
." .....
rd911l:ilym[FI~J[CQL1; .'
,'in.t n; . . ." ..... . .•. •. Lcúut«"N lÍme~o. deécuaci()nes~";CÜli»rr;
,~E~~~~i:?~~~;:"~'í11(
\
...
"·~~.¡rinitri~d~larit~e.!fi,~l~.·:h\::. . .•. .
".e;: cout'::
;;\: >éÍíminaArras(Irt n)'" . ' ; ' ; .. :''',' o.. ' : ' '" : ," • ~-> .~ : >"::j>:,~ ,~:: ·~\~,S. \
'1'.:
Ce:)
>:,j;
César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liz,o A vila. Mis libros son económicos, no hav Tacón para copiarlos.
74 Algoritmos y su Codificación en C+ + Volumen 2.
César Liza Avila
Básicamente la función eliminaAdelante( ), es la misma que la usada al calcular la determinante (problema 2.11), solo que el intercambio ya no se hace solo de la matriz cuadrada de coeficientes sino que incluye la columna de los términos independientes ( intercambiaFilas(i, piv, n+1,m)
) y las operaciones de reducción o eliminación también afectan a esta columna ( for(k=i+l;
Arreglos Bidimensionales 75
Solución: Sean A y B dos matrices cuadradas de orden n, tales que AB = BA = 1, donde 1 es la matriz identidad. Se dice que B es la matriz inversa de A y se denota como B =A't, o lo que es lo mismo A es la inversa de B esto es A = B- I . Una matriz posee inversa cuando es cuadrada (filas = columnas) y la determinante de la matriz es diferente de cero ( IAI ::f:. O ) Para obtener la matriz inversa, tomamos la matriz original, una matriz identidad del mismo orden, y con ellas formamos la matriz aumentada. Luego procedemos a realizar eliminaciones hasta conseguir que la zona ocupada por la matriz original se convierta en una matriz identidad. La matriz inversa estará dada por la zona donde se ubicaba en un prif!~ipio la matriz identidad. Por ejemplo, sean las matrices A y su matriz identidad correspondiente 1:
A=[~¡J
k
A diferencia del problema anterior (cálculo de la determinante),
ahora no nos interesa el número de intercambios, por lo que la función será tipo void.
:O 1 I
Para la primera columna: a) Busco el mayor elemento, en valor absoluto, resulta ser el3 b)
Intercambio las filas 3 4 O 1
1 2 1 O
En eliminaAtras( ), calculamos el valor de cada incógnita, tomando cada fila a partir de la última ( for (i=n-l; i>=O; i--) ) y obtenemos el valor del término independiente ( t = m[i][n] ), al cual debemos restar la sumatoria de todas las otras incógnitas multiplicadas por sus coeficientes ( for(j=i+l; j
c)
Divido la primera fila entre 3 1 4/3 O 1/3
j++) t = t- m[i][j]*x[j]; ).
d)
Multiplico la segunda fila por 1 y resto segunda fila menos la primera, y coloco el resultado en la segunda fila 1 4/3 O 1/3 O 2/3 1 -1/3
El último bucle imprime cada incógnita. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
I=[¿~J i 1O]
1 2 1 O
Para la segunda columna: a) Busco el mayor elemento, en valor absoluto a partir de la fila siguiente i+ 1, res.ulta ser 2/3 b) Inte.rcambio las filas. Como solo tengo una se realiza el intercambio consigo mismo 1 4/3 O 1/3 O 2/3 1 -1/3 c) Divido la segunda fila entre 2/3 1 413 O 1/3 O 1 312 -1/2 d) Multiplico la segunda fila por -4/3, sumo . ambas filas y coloco el resultado en la primera fila 1 O -2 1 O 1 3/2 -112
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
1, \
I
76 Algoritmos y su Codificación en C+ +. Volumen 2.
Arreglos Bidimensionales 77
César Liza Avila
La función pivote( ), es invocada por inversa( ) acepta como primer argumento la fila (y columna) que queremos eliminar.
,~~":"'-:-",..........,.':"~-':'---:~~...,...~-'-~-~~
'
La primer bucle, obtiene el elemento con mayor valor absoluto (un conocido algoritmo nuestro de búsqueda del mayor). Si el elemento mayor resulta ser cero significa que un elemento de la diagonal principal será cero y como se tratará de una matriz triangular superior entonces su determinante dada por el producto de los elementos de la diagonal principal es cero, por lo que no existirá la inversa. El siguiente bucle hace el intercambio de la fila que contiene el mayor valor absoluto y a la vez divide cada elemento entre el pivote con el objeto de hacer 1, el elemento. I
-2 1 ] [ 3/2 -112
Observe que los arreglos son de tipo double puesto que realizamos varias multiplicaciones y divisiones.
Ya en nuestro .jJrograma, leemos la matriz, e invocamos a la función illversa( ).
© César Liza Avila. No mates la producción intele<"fual, no copies éste libro.
,.
© César Liza Avila. Mis libros son económicos, no hay razón pum copiarlos.
78 Algoritmos y su Codificación en C+
+. Volumen 2.
A rreglos Bidimensionales 79
César Liza A vila .]-
I
Esta función, es invocada desde main( ), la variable i es la fila donde queremos obtener el 1, j es la columna cuyos elementos queremos hacerlos ceros, y k es cada una de las columnas de la fila que queremos restar.
Solución: El popular "tres en raya", "triqui" O "michi", se juega con dos jugadores, cada uno de los cuales a su turno marca una casilla originalmente vacía, con un X o un O. El primer jugador que logre
*
Finalmente, la función llena la matriz inv[][] , con los elementos correspondientes a la inversa.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
colocar su símbolo X X en tres caSI'11 as ya O sea horizontal, vertical o diagonal, X gana. Este juego se puede almacenar en una matriz de 3x3 (# define TAM 3). Los casilleros libres se marca como O (# define NADIE O). Las casillas elegidas por el humano se marcan como 1, (# define JUGADOR 1), mientras que las tomadas por el computador se marcan con 2 (# define COMPUTADOR 2).
En main( ), invocamos a inicMatriz( ), la cual toma cada casilla y la marca como no utilizada (m[ilU1 =NADIE). Esto es necesario puesto que cuando declaramos un arreglo cada elemento contiene el valor que en ese momento se encuentre en memoria. Luego entramos a un bucle infinito (do{ } while(l) ) el que terminará cuando haya un ganador ( if ( hayGanador (a) ) break; ); © César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
------_._-----------_......._--------------~--------
80 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
La funcÍón jugador( ), permite ingresar la jugada del humano. Debe indicar una fila y una columna válidas, de no ser así repetirá el bucle desde el inicio (continue); además, . verifica si la casilla está desocupada, en cuyo caso se marcará como utilizada ( m[f][c]= JUGADOR ). Si ha logrado marcar la casilla deseada entonces saldrá del bucle (break). El movimiento de la computadora es bastante simple sólo escoge la primera casilla libre y. la marca. Existen diversos métodos para que la computadora elija la mejor jugada posible; por ejemplo examinando todas las posibilidades aplicando búsqueda exhaustiva, recursión, backtracking, programacIOn dinámica, estrategia minimax, poda alfabeta y árboles o usando alguna heurística. Dichos rñétodos y técnicas están fuera del alcance de este libro y algunos de ellos se ven en mi libro Estructuras de Datos con C++. La función hayLibres( ), busca si existe algún casillero no ocupado, retomado 1 (verdadero) si existe y O (falso) en caso de que todos estén ocupados. Esta función es utilizada por hayGanado:r(), para determinar si hay empate.
Arreglos Bidimensionales
'.
La función hayGanador( ), recorre todas las filas verificando que sean iguales pero diferentes de NADIE. De igual manera recorre todas las columnas, la diagonal principal y la secundaria. Esta función imprime el ganador o si hubo empate y retoma un valor (O ó 1) para que el bucle dentro de maine ) continúe o finalice.
© César Liza Avila. No mares la producción intelectual, no copies éste 'libro.
© César Liza Avíla. Mis libros son económicos, no hay razón para copiarlos .
..
ln
82 Algoritmos y su Codificación en
e + +.
Arreglos Bidimensionales 83
César Liza A vila
Volumen 2.
~~~'-'-~-~---'
vql¡j'6rdP~labras(Í1it n,; ~~ar p[ ltceL))
{,&¿,.:'
,.,''';,i¡,;L;i¡:;J\:
Solución: En este programa utilizamos una matriz de caracteres, debemos resaltar el hecho que cada palabra se almacena en una fila de la matriz, y que solo es necesario utilizar un único subíndice p[i].
Por ejemplo, si ingresamos las palabras "uno, "dos", "tres" y "cuatro", la matriz quedará como: COL ) (
FIL
'u' 'd' 't' 'e'
'n' 'o' 'r' 'u'
'o' '\0' 's' '\0'
oo.
00'
oo'
'e' 's' '\0' 'a' 't' 'r' 'o' '\0' ... oo.
oo.
oo'
o ••
Cada palabra es referenciada por un único subíndice, mientras que cada carácter con dos subíndices. En realidad la representación anterior es una representación lógica, puesto que en computadoras la memoria es más bien lineal, tal como se muestra a continuación: COL *ziseof(char) COL *ziseof(char) ~~r
COL *ziseof(char)
,r
'A.
COL *ziseof(char) -A..
'\
Es por ello la necesidad de pasarle el valor de COL, en la invocación de funciones que usen matrices. (i:)
César Liza Avi/a. No mates la producción intelectual,
/lO
copies éste libro.
© César Liza A vila. Mis libros son económicos,
110
hay razón para (·olúarios.
84 Algoritmos y su Codificación en e + +. Volumen 2.
l. 2.
3.
4.
5.
6. 7.
8.
9. 10.
11. 12.
13.
14. 15.
Arreglos Bidimensionales 85
César Liza Avila
Dado una matriz, obtenga el mayor elemento de cada fila. Muestre aquellos elementos que no pertenecen a la diagonal principal. Una matriz irü;mgular inferior es una matriz cuadrada cuyos elementos encima de la diagonal principal son todos ceros. Escriba una función que diga si una matriz es triangular inferior. Se tiene una matriz con el total de emergencias en un hospital por cada hora y por cada día de la semana. ¿ a qué hora ocurre la mayor cantidad de emergencias? ¿ qué día hay más emergencias? Una matriz diagonal es una matriz cuadrada que son ceros todos sus elementos que no pertenecen a la diagonal principal. Escriba una función que diga si una matriz es diagonal. Obtenga el promedio de cada columna en una matriz. Se almacena la historia de los recorridos de n automóviles durante una semana. Aquellos que ha recorrido más de k kilómetros necesitarán un mantenimiento, ¿ qué automóviles necesitarán mantenimiento al final de la primera semana? ' Se tiene una matriz en la cual las filas indican los articulos, mientras que las columnas son el nivel de ventas por cada día de la semana. ¿ Qué artículo tiene el mejor promedio de ventas? Lea dos matrices y diga si son iguales. Una matri?- cuadrada A se dice que es simetrica si A(i,j)= A(j,i) pára t~do i,. j dentro de tos limites de la' matriz. Escriba un programa que dIga SI una matriz es simétrica. Dada una matriz, escriba una función que permita eliminar unafila. S~ tiene una matriz de ordenfxc y un vector columna (de ordenfxI). Se pIde, remplazar una columna determinada de la matriz por el vector columna. Ordene ascendentemente los elementos de cad,a columna de una matriz. . Muestre todos los elementos que están debajo de la diagonal. Dada una matriz auméntele una fila cuyos elementos contenga la suma de todos los elementos de la cada columna, y auméntele una columna que contenga la suma de todos los elementos de cada fila. Finalmente,
© César Liza A vila. No mates la producción intelectual, no copies éste libro.
complete la matriz llenando la esquina inferior derecha con la suma de todos los elementos de la matriz original. 16. Se tiene. una matriz que contiene todos los elementos iguales excepto que tiene algunas bombas, y un premio mayor. Tiene que descubrir elida casillero, hasta obtener el premio mayor o tocar una bomba. 17. Se tiene un arreglo bidimensional de caracteres, dada una letra muestre las letras que lo rodean. 18. Encripte un mensaje por transposición. Este método consiste en intercambiar las letras de un mensaje según algún patrón. Por ejemplo, podemos usar una matriz para transponer los caracteres del mensaje, así: si la frase es "hola mundo cruel" y utilizamos una matriz de 3 columnas, tendremos: ha 1 a m und ocr ue1 y el mensaje encriptado por transposición será hauouo ncelmdrL Sugerencia: Si fuera necesario llene los espacios no ocupados de la última fila con NULL. 19. Lea un conjunto de palabras, almacene cada p~labra en una fila de una matriz de caracteres, luego invierta cada una de ellas de tal manera que cada fila contenga la palabra original pero con sus caracteres al revés. 20. Defina una matriz cuadrada donde tenga n pares de elementos iguales. Luego juegue a "memoria ", deberá descubrir 2 elementos y si son iguales dejarlos descubiertos, en caso contrario volverlos a cubrir. Al final 4eberá indicar el número de jugadas y cuántos a'ciertos tuvo. Sugerencia: Utilice una matriz auxiliar que contenga 1 si el element,o ha sido descuHierto y O si aun no lo está.
© César Liza A vila. Mis libros son económicos, no hav razón para copiarlos.
_._-"---
Estructuras, Uniones y Enumeraciones
,
,
1 \
Estructuras, Uniones y Enumeraciones 89
Solución: Dentro del campo de los números reales no es posible obtener la raíz cuadrada de un número negativo. Por ejemplo, si deseamos obtener la raíz cuadrada de -9, ésta no existe en IR. Así, se hizo necesario idear una raíz imaginaria tal que elevada al cuadrado resulte -1. Dicha raíz se representa como i y equivale a -Vol, de tal manera que ¡Z =-1. Así -v-9 es igual a ±3i. Pero los números imaginarios acostumbran a venir en pareja con un número real, razón por la cual se les conoce como Conjunto de Números Complejos. Un número complejo, es pues un número que tiene una parte real y una parte imaginaria, por ejemplo 3 + 2i es un número complejo. Estos números aparecen con frecuencia cuando deseamos resolver una ecuación de segundo grado: ax2 + bx + e O, cuya raíz es:
=
- b ±--J b2 - 4ac
x
= -------------------2a
En donde, si la discriminante D = b 2 - 4ae, es negativa, entonces las raíces de la ecuación serán complejas.
Sean los complejos el = a + bi Ye2 = e + di Suma = el+ e2 =(a + e) + (b+d)i La suma de dos complejos se obtiene sumando la parte real del primero con la parte real del ~egundo, y la parte imaginaria del primero con la parte imaginaria del segundo. Diferencia = el - e2 =(a - e) + (b-d)i \ Es una suma donde solo cambiamos el signo + por el -. Producto = e1.e2 =(ae - bd) + (ad + be)i La multiplicación resulta un poco más complicada, (a+bi)(c+di) =ac+adi + bci+bdi 2 = (ac-bd) + (ad+bc)i Cociente = (ac+bd)/(c2 + d 2 ) + (bc-ad)i/(c 2 + d 2) La división de complejos es aun más complicada, pues hay que multiplicar el numerador y el denominador por su conjungada a+bi (a+bi) (e-di) (ac+bd) +(bc-ad)i (ac+bd) (bc-ad)i -------- = ----------------- = ----------------------- =---------- + .-----.--c+di (c+di) (e-di) c 2 + d2 c2 + d2 e 2 + d2 Ce)
César Liza Avila. Mis libros
SO/1
económicos,
/lO
hay razón para copiarlos.
90 Algoritmos V su Codificación en C++. Volumen 2.
Estructuras, Uniones y Enumeraciones 91
César Liza Avila
En nuestro programa definimos una estructura (strud cClmplie.lo) ya que todo número complejo es una única entidad que consta de dos partes. Debe tratar a struct complejo como un nuevo tipo de dato; así, puede declarar variables de ese tipo, pasar variables de ese tipo a funciones, retomar el tipo struct complejo e igualar variables de ese tipo. Puede escribir simplemente complejo en vez de struct complejo; sin embargo, preferimos éste último para recordamos que se trata de un struct. En main( ), usamos la función leeComplejo( ) para leer un número complejo, y sumaComplejo( ) para la suma. Ambas funciones, devuelven un valor del nuevo tipo de dato. Finalmente, invocamos a impComplejo( ), para que se muestre el resultado por pantalla.
a
12n leeComplejo( y, observe como usarnos el opendor . para acceder cada parte de la estructura, y como retomamos los valores leídos como una entidad del tipo struct complejo, mientras que en sumaComplejos( ), pasamos como argumentos dos variables del tipo struct complejo y retomamos una entidad del mismo tipo.
.structcOrhpl~jó\íeeCofuplej.o(vóíd)
r;: >:y .•.• • •.• ·t¡;~~. . . .;l:\bi:;".':;:".,.\., stru<;:tcom¡:íli
c6üt
·~/:~%;~:'~ital;.". , <>,'.~,
.. '
".' /"Ú::
¡~ii:·.; .:/""
.C?ut<~,P~~~J
~'rr~~t~,.girt"1:'*~i' sttüct co
('
Finalmente, en impComplejo( ), para determinar el signo hacemos uso del operador ternario ? Este operador determina si la condición c.imaginaria>O es cierta, en cuyo caso el signo será '+', Y en caso contrario será espacio en blanco" (ASCII 32) puesto que el signo negativo sería parte de c.imagil1aria. © César Liz.a Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos,
Estructuras, Uniones V Enumeraciones 93
César Liza Avila
92 Algoritmos y su Codificación en C++. Volumen 2.
?or cada compra tenemos: el precio unitario y la cantidad comprada; los que unimos en la estructura compra. Como tenemos varias compras, usamos un arreglo (maiz) donde cada elemento almacena una compra. Solución: Sea Pi el precio unitario de compra y c¡ la cantidad de kilos comprados, entonces en cada compra el gasto será PiCj. En n compras tendremos: monto gastado:: poco + P¡Cl + P2C2 + .,. + P .. -ICn-l Por otro lado, el total de maíz adquirido es Co + Cl + C2 + .. , + Cn-l
c[i].cantidad).
si cada kilo lo vendemos a X soles entonces: monto de venta X (eo + Cl + C2 + ... + Cn-l)
=
Para no ganar ni perder debe cumplirse: monto de venta == monto gastado X(co + Cl + C2 + .,. + Cn-l) = Poco + PICl + P2C2 + ... + pn-1Cn-l esto es: i
poco + p]C] + P2C2 + '" + pn-]Cn-I
X
=----------------------------------------Co + C] + C2 + .. , + Cn 1
Luego de leer el número de compras, invocamos a leeCompras( ), para ingresar cada uno de los elementos del arreglo. Preste especial atención a como indicamos el elemento i y cada una de sus partes (c[il.precio y
Ip¡c¡ X ""' --------------
LC;
Es decir que para no ganar ni perder en la venta del maíz, debemos venderla al precio promedio ponderado.
© César' Liza A vila. No mates la producción intelectual, no copies éste libro.
En caJculaPonderado( ), obtenemos cada sumatoria y retornamos su cociente de acuerdo con la fórmula del promedio ponderado. Note la inicialización a cero de spc y sc en una sola línea y el uso del operador += , para . los acumuladores spc y sc. Escribir, por ejemplo: sc+=c[i].cantidad; equivale a: sc=sc+c[i].cantidad; pero se prefiere usar la primera forma, pues da un toque más profesional.
<.
i
.}
., :'1".< ."
.... " .' .'\ >",/'
n ,',
void I¡:eCotTIpias{iut Ui struet compra 9[ { ., ' fór{i~V~Ó;"i1U; if+)" ," .,.~. ';':';;><' . ',..•. ,. { :cóut~<é¡j~I«"CoITlPra;Nl'o.J'«i5;¡;[i].cantidad; } .; .'. }
.:":',.."."....
>,
.
fIoat ealol!JiIl?Qn~étádq{int ti, ¡itruet cq¡p.pra e( D { .' ......,: "'? " .; ..,. , ' . , . '.' ...•
~~-{t~Z!¡ill;~ift)
{ sp¡;hc[i].~@ci()*c[flcÍlntida~·;\··~" ~C.,.+;cti1.cantidad; '."'.-" , j \
© César Liza A vila. litis libros son económicos, no hay raz.ó¡¡ para copiarl,os.
\
I
94 Algoritmos y su Codificación en C++. Valumen 2.
César Liza Avilo
Estructuras, Uniones y Enumeraciones 95
Solución: . . · . y=f( x) = aoxo + alx I -+- 3zXZ -+- ••• -1- a,,_I X ,,-1 Sea e I pol morrno cada uno de sus elementos resulta ser un monomio, los cuales tienen 2 partes: el coeficiente y el exponente; por lo que nuestra estructura será:
struct monomio { float coeficiente; int exponente; }; como son varios términos debemos almacenarlos en un arreglo; stlruct monomio polinomio[MAX]. Para calcular el valor numérico de un polinomio f(x) , necesitamos el valor que toma la variable x (cin»x) y luego calculamos el valor de cada i término acumulándolos; s s + 3¡X , por lo demás el programa es autoexplicativo.
=
,,>,;'o.co~ijfic~C'IO~ltN{C++: e~i9'~tr~!lm;h,>'
»: /.: ' ..
···~~t~:h;~:;.ot: o ':;{,
~~1óti~t,'~1ruc;t lUo~olntoll)~
. "{o:,"
""rc<.inritJ1>ttllct~6I!ó¡yÍi9:. [J,;.flq(l.tJ;.; .-'-"~=-==- ,_.::...._..;;.. .';. 2.~' .' '::'Iv.
. o,
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza AvUa. Mis libros son económicos, no hay razón para copiarlos.
96 Algoritmos y su Codificaci6n en C++. Volumen 2.
César Liza Avila
Estructuras, Uniones y Enumeraciones 97
En leeDias( ), observe como se referencia a carla parte de las estructura anidada. Por ejemplo en x[i].f.dia, representa el miembro día de la fecha (f) de la medición xci]. El doble. lo usamos porque f es una estructura anidada dentro de otra.
Solución: .Primero, definimos la estructura fecha, la que es anidada dentro de struct medicion. Como tomamos la temperatura de varios días necesitamos un arreglo capaz de almacenar la fecha y la temperatura de esa fecha (struct
medicion x[MAX]). En main( ), una vez ingresadas las fechas y sus temperaturas, leemos la fecha (f) cuya temperatura nos interesa. El número de días (n), la fecha (f) cuya temperatura deseamos y los datos de las fechas y temperatura (x) son pasadas como parámetros a buscarFecha( ), la cual devuelve la temperatura del día pedido en caso de encontrar la fecha, ó -1 en caso de no encontrarla. © CésarLiza A vila. No mates la producción intelectual, no copies éste libro.
En buscarFecha( ), no es posible comparar toda la estructura con una sola instrucción, por ejemplo no podemos hacer if(x[i] ""= f), (aunque esto lo podríamos hacer en C++/C#, sobrecargando el operador =::::); es por ello que comparamos cada una de sus partes ii' (x[i].f.d.ia == f.dia &&
x[i].f.mes == f.mes && x[i].f.allnio == f.mmio ).
En caso de encontrar la fecha pedida, retornará su temperatura. Si termina de recorrer todos los elementos sin encontrar la fecha retornamos -1.
© César Liza A vi/a. Mis libros son econámicos, no hay razón para copiarlos.
"
98 Algoritmos v su Codificación en C++. Volumen 2.
Estructuras, Uniones y Enumeraciones 99
César Liz.a Avila
Solución: Este conjunto de operaciones históricamente se les como conoce d.e mantenimiento registros y en la actualidad se le conoce como CRUD (create, read, update, delete). El
programa usa un fecha,· para almacenar fechas y un struct alumno para los datos del alumno. Note como esta última estructura es capaz de almacenar notas de varios trabajos para un mismo alumno, para esto usa el arreglo
struct
trabajo[ ]. El bucle infinito permite (while(l){ que siempre regresemos al menú a menos que la elijamos opción 6.
n,
En caso de seleccionar ingreso (case 1) comprobamos que no rebasemos la cantidad máxima de elementos en el arreglo (nO 11 nreg>n) antes de invocar a consulta( ). De igual manera para modifica( ) y elimina( ), aunque esta última función· puede cambiar el número de alumnos y por ello retoma un nuevo valor para ll.
Las funciones ingreso( ), y consulta( ), se explican por si solas. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
--------------------------~----------------
100 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila
Estructuras, Uniones y Enumeraciones 101
De manera similar en elimina( ), debemos mostrar el registro que deseamos " eliminar para que el usuario tenga la última oportunidad de arrepentirse, en caso de confirmar la eliminación, debemos desplazar cada elemento del arreglo una posición hacia atrás, sobrescribiendo el elemento a eliminar. Evidentemente, tendremos un elemento menos (n--) debiendo comunicar a maine ) el cambio mediante return n. En modifica( ) es recomendable mostrar los datos actuales, por ello invocamos a consulta( ); y luego, sobrescribimos el número de registro deseado mediante la función ingresa( ).
La función listado(), no necesita mayor explicación.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liz.a Avila. Mis libros son económicos, no hay razón para copiarlos.
- - - - - - - - - - - - _ ..
_~---------
102 Algoritmos y su Codificación en C++. Volumen 2.
Estructuras, Uniones y Enumeraciones 103
César Liza A vi/a
Solución: Cada elemento crucigrama es palabra que se despliega a partir de una posición (fil, col) y hacia una dirección (vertical, horizontal). Asimismo, tendrá una descripción que será usada como pista para determinar la palabra, y por último el estado que indica si la ha sido palabra descubierta o no. Para englobar todos estos atributos definimos el struct palabra. Las palabras por adivinar las el colocarnos en arreglo leyenda. Observe el cálculo de nroPreg, en base al operador sizeof( ), dividiendo la cantidad de bytes .de todo el arreglo entre la cantidad de bytes de cada elemento. Esto es recomendable puesto que podernos reducir o aumentar el número de palabras y el programa automáticamente calculará cuántas filas tendrá el arreglo. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
l")or otro lado, es necesario el patrón del crucigrama; es decir, qué casillas podrán contener letras y que casillas no. Esto lo indicamos en cruci[ ][ ] usando y el respectivamente. Luego, calculamos el número de filas n = sizeof(cruci)/ COL. Usamos el operador sizeof( ), para poder alterar la inicialización de cruci[ ][ ], sin preocupamos de cambiar el número de filas. El programa entra en un bucle que termina cuando todas las palabras han sido descubiertas while( verEstado(nroPreg,leyenda)
).
Ya dentro del bucle mostramos el crucigrama, imprimimos la leyenda y permitimos al usuario elegir la pregunta. La función verEstado( ), recorre cada fila de leyenda preguntando si hay alguna palabra sin descubrir. Observe el uso del tipo bool no existente en e, pero si en C++/C# (recuerde que en e, o significa falso, mientras que diferente de cero significará verdadero). © César Liza Avila. Mis libros son económicos, /la hay razón paro copiarlos.
104 Algoritmos y su Codificación en C++. Volumen 2.
Estructuras, Uniones y Enumeraciones 105
César Liza A vila
Las funciones impCruci( ) e impLeyenda( ), no necesitan mayor explicación. Al elegir la opción debemos mostrar si es vertical u . horizontal, y la descripción para que el usuario nos de su respuesta. Si ésta es correcta entonces cambiamos el estado, y llenamos el crucigrama con la Si la palabra. dirección es horizontal avanzamos las columnas llenando letra por letra la palabra. En caso contrario, será vertical y debiendo avanzar por filas.
Solución: Una union, es una forma de organizar nuestros datos, similar a una estructura, pero a diferencia de éstas, los miembros de la union comparten la misma zona de memoria. Como sabe, los miembros de una estructura se almacenan uno detrás de otro, en posiciones consecutivas de memoria, pero en una union, sus miembros se almacenan empezando en la misma posición de memoria, esto es, se solapan en memoria. En otras palabras, en un determinado momento solo puede existir uno de los miembros de la union, mientras que en una estructura existirán todos sus miembros. Esto tiene la ventaja del ahorro de memoria cuando las variables que definimos solo tomarán algún grupo de atributos y otros no. El tamaño de memoria que ocupa una union, s~rá el tamaño de la variable más grande. Al igual que con los miembros de una estructura, para acceder a los miembros de una union se usa el operador punto. Por ejemplo, si modelamos el caso que algunos trabajadores ganan por hora y otros ganan un sueldo fijo, tendremos: Usando una estructura: struct pago { fIoat pagoxhora; double sueldo; };
Usando una union: union pago { fIoat pagoxhora; double sueldo; };
[1111
111111 ,
~ . pagoxhora sueldo
WIIIIII "-v--'
pagoxhora
'---y---I sueldo
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
..
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
106 Algoritmos y su Codificación en C+ +. Volumen 2.
Estructuras, Uniones y Enumeraciones 107
César Liza Avila
Sohlldón: ih~;;;meración, es un tipo de dato formado por un conjunto conocido de constantes enteras a las cuales se le ha dado un nombre. Las enumeraciones proporcionan claridad en el código puesto que es mejor leer su descripción en lugar de un número. Se definen de manera parecida a las estructuras pero en vez de struct usamos la palabra enum. Por defecto, el primer elemento de la enumeración es 0, numerándose consecutivamente, aunque se puede especificar el valor de cada uno de los elementos de la enumeración.
El programa hace lo pedido. Definimos en una misma zona de memoria un entero y un flotante; esto es, ambos ocupan 4 bytes (en compiladores de 32 bits) y, en este caso, toda la unión ocupa 4 bytes. Al leer un entero (cin»x.entero) le indicamos a la computadora que lo ingresado lo almacene en 4 bytes, usando el formato de entero. Luego éstos mismos 4 bytes los mostramos (cout«x.flotante), pero interpretándolos con formato de punto flotante de simple precisión (IEEE 754). ¡Los mismos 4 bytes los interpretamos de dos formas diferentes! En la segunda parte del programa hacemos lo mismo, leemos un flotante, almacenándolo en 4 bytes, luego mostramos esos mismos bytes como enteros. El programa muestra la definición de una enumeración que contiene el número de días, así como la utilización de dicha enumeración. Así, dom=O, lun=l, . '" sab=6. Podríamos haber escrito los números en vez de los nombres, case 4, equivaldría a case jue, pero evidentemente resulta más claro el programa cuando colocamos nombres en lugar de números.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila: Mis libros son económicos, no hay razón para copiarlos.
~-------------------------------
-------
---
-
108 Algoritmos y su Codificación en C++. Volumen 2.
Estructuras, Uniones y Enumeraciones JW9
César Liza Avila
1.
Solución: Las enumeraciones, pueden también tomar valores diferentes de números consecutivos, tal como observamos en este ejemplo:
Calcule la suma de dos fracciones. Simplifique lafracción antes de dar la respuesta. Use estructuras. 2. Se tiene una lista de personas con dni, nombres y apellidos. Muestre aquellas personas que son homónimas. Homónimos son personas que tienen los mismos nombres y apellidos. 3. Se tiene la siguiente estructura: struct alumno { char nrocarnet[11 ], char nombre[25], float prom; };
4. 5.
6.
7. 8.
9. Note que, a diferencia de las estructuras y uniones, los elementos de un tipo enumerado se separan con comas.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
Escriba un programa que lea n alumnos. Luego diga cuál es el promedio general. ¿Quién(es) tiene(n) el mejor promedio y quién(es) el peor? Se tiene un conjunto de datos, se desea obtener una tabla que muestre cuántas veces se repite cada elemento. Use una estructura. La moda de un conjunto de datos es el elemento que más se repite. Encuentre la moda y cuántas veces se repite. Considere el caso en que existan varias modas. Sugerencia: use una estructura que contenga el valor y el número de veces que el elemento se repite. En un almacén, calcule el monto total de inversión y diga qué artículo es el que tiene mayor inversión. Considere la estructura: struct articulo { char descripción[ 30}; float preciounitario; int cantidad; }; \ Se tiene un polinomio y=f(x) se pide obtener su integral y su derivada. Calcule el valor numérico de un polinomio f(x, y). Sugerencia: use un arreglo donde cada elemento es del tipo estructura con atributos: el coeficiente del término, el grado de x, y el grado de y. Se tiene un arreglo con los resultados de los encuentros de un campeonato de fútbol (equipo local, equipo visitante, goles equipo local, goles del equipo visitante). Dado un equipo diga cuál es su
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
~--------------------------------
--~-.--~-
no Algoritmos y su Codificación en C++.
JO.
11.
12.
13.
14.
15.
16.
Volumen 2.
César Liza A vila
puntaje. El equipo ganador obtiene por partido ganado 3 puntos, si hay empate cada equipo obtiene 1 punto. Se desea gestionar una lista de articulos considerando atributos como: código, descripción, cantidad en stock y costo. Defina una lista utilizando estructuras y arreglos donde cada elemento representa un artículo y haga un menú que invoque a las siguientes funciones: (a) ingreso de artículo, (b) mostrar todos elementos de la lista, (e) modificar los datos de un artículo, (d) eliminar, (e) búsqueda y (f) salir. Use un tipo enumerado para cada opción del menú. Por ejemplo . case 1, sería case ingreso. Se tiene la siguiente estructura: struct articulo { float precio Venta; int cantidad Vendida; }; que almacena datos de venta de un determinado producto. Escriba un programa que ajuste los datos históricos de venta a la recta de regresión y= ax + b; luego, estime la cantidad de artículos (y) para precio dado (x). Sugerencia: use las fórmulas de regresión lineal que aparecen en el Capítulo 7 de mi libro "Algoritmos y su Codificación en C++", Volumen 1. Se tiene la siguiente estructura: struct alumno { chal' nombre[40]; int promedio; }; Escriba un menú para (a) leer alumno, (b) ordenar los alumnos por nombre; (c) ordenar los alumnos por promedio, y (d) listar los alumnos. Use una enumeración para cada opción del menú. Cree una enumeración con los estados del agua. Luego lea n temperaturas del agua y determine el porcentaje en que el agua estuvo en cada uno de sus estados. Una fábrica produce pernos. Cada perno debe tener un diámetro d y una longitud 1, pero se permite un error de ± 0.5 mm para el diámetro y ± 1 mm para la longitud. Lea una serie de pernos y muestre aquellos que son defectuosos. Se considera como defectuosos aquellos que no cumplan alguna de éstas condiciones. . Lea un mes y un año y determine cuántos días tiene ese mes, considere los años bisiestos. Use una enumeración para los meses. ¿ Cuántos días hay entre dos fechas? Use un struct y un enum.
© César Liza AviLa. No mates la producción intelectual, no copies éste libro.
Estructuras, Uniones y Enumeraciones] 11
17. Un punto se representa mediante por una abscisa (x) y una ordenada (y). Lea 2 puntos y determine sus coordenadas polares, la distancia entre ellos y, la ecuación de la recta que pasa por esos dos puntos. 18. Un volumen de una biblioteca puede ser fihro o revista. Esrr.iba un programa que permita ingresar un volumen y mostrar sus datos. Cualquier volumen tiene código, mio, fipo (libro O revista). Si es una revista tendrá además un lSSN, nombre, y un mes de publicación. mientras que si es un libro tendrá un !ISBN. autor y el número de capítulos. Sugerencia: use estructuras y uniones. struct volumen { chal' codigo[JO}; int annio; chal' tipo; uníon detalle v;
unían detalle { struct revista; struct libro;
J;
J; struct revista { char ISSN[ll]; chal' nombre[30]; int mes; };
struct libro { charlSBN/ll]; chal' autor/30}; int nroCap; };
19. Una variante de las uniones fue introducida en el C++ y es llamada union anónima. Una union anónima, declara un conjunto de miembros que se inician en la misma dirección de memoria, pero que no se les da un nombre y se puede acceder directamente a sus elementos. El siguiente enunciado sugiere el uso de una unión anónima: En una empresa trabajan empleados y obreros. Los empleados tienen un sueldo fUo, mientras que los obreros ganan por hora. Lea los datos de n trabajadores y luego lístelos mostrando el mOlltopagado a cada uno y el total pagado por la empresa. Sugerencia: use las estructuras mostradas.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
·r 112 Algoritmos y su Codificación en C++. Volumen 2.
struct trabajador { char codigo[6]; int anniolngrpso; char tipo; unton { struct empleado; struct obrero;
J; };
César Liza Al/ita
struct empleado { char puesto[20}; float sueldo;
}; struct obrero { float valorHora; int horasTrab; };
20. Se tienen las siguientes figuras geométricas: circunferencia (radio), cuadrado (ladlJ) y rectángulo (2 lados). Escriba un menú en el que el usuario escoja unafigura geométrica y calcule su área. Use una union y una enumeración.
Manipulación de Bits
\
© César Liza A vi/a. No mates la producción intelectual, no copies éste libro.
Manipulación de bits] 15
Solución: Antes de presentar el programa necesitamos comprender como se almacenan los números enteros. Almacenamiento de números enteros El formato de almacenamiento de enteros reserva el último bit (el bit más significativo, esto es el bit más a la izquierda) para indicar que el número es negativo. Si el último bit es O, el número será positivo, si es 1 será negativo. A manera de ilustración tomaremos el tipo short ¡nt (16 bits). Por ejemplo, si el número es a = 239, entonces se almacenará corno: a = 0000 0000 1110 1111 Los números negativos se almacenan, por cuestiones de eficiencia, como complemento a dos. Para calcular el complemento a dos, tomamos el positivo del número, invertimos sus bits y luego le sumamos 1. Por ejemplo, si el número es -240, y tomamos su positivo, tendremos: 240 en binario es : 0000 0000 1111 0000 invirtiendo sus bits: 1111 1111 0000 1111 + le sumamos 1 0000 0000 0000 0001 tendremos
1111 1111 0001 0000
es decir el short ¡nt -240 se almacenará como 1111 1111 0001 0000. Note como el bit más significativo es 1, con lo cual el número se interpreta como negativo, y por ende estará almacenado como complemento a dos.
© César Liza Avila ..Mis libros son económicos, no hay razón para copiarlos.
116 Algoritmos y su Codificación en C++. Volumen 2,
César Liza Avila
La razón para que las computadoras usen el complemento a dos es para calcular fácilmente una diferencia, como si fuera una suma. Asf,
Manipulación de bits H 7
NEGACIONLos bits O se pasan al, Y los bits 1 pasan a ser O.
~
0000 0000 1111 0000 + 1111 1111 000 1 0000
240+ 240
o
7'
I 0000 0000 0000 0000
el desbordamiento se pierde
OPERADORES A NIVEL DE BITS Los operadores a nivel de bits trabajan sobre cada uno de los bits de la variable, y los operandos deben ser de algún tipo entero (char, short, int, long y sus combinaciones con signed y unsigned).
Si a= 000000001110 Illl -a= 1111 1111 00010000 como el negado de a, tiene el ultimo bit en 1, se interpreta como complemento a dos, o sea: si negamos tendremos le sumamos 1:
1111 1111 0001 0000 0000 0000 1110 1111 ----------------------.".---
0000 0000 1111 0000 7
6
5
4
que en decimal será: 2 + 2 + 2 + 2 = 240, como tenía el bit del signo en 1, entonces será -240.
Para entender como se obtiene cada resultado, necesitamos mostrarlos a nivel de bits. Tomemos por ejemplo, a= 239 y b=3, que en binario serán: a = 0000 0000 1110 1111 0000 0000 0000 0011 b
=
Veamos cada una de las operaciones sobre sus bits:
CONJUNCION &: Si los dos bits están en 1, entonces la conjunción resultará 1, en los defilás casos dará O. .. I Si a= 239 y b=3, entonces su conjunción a nivel de bits será: \
=
a 000000001110 1111 & b = 0000 0000 0000 0011 a & b = 0000000000000011 que en decimal será: 2 1 + 2° ::: 3
© César Liza A vUa. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
H8 Algoritmos y su Codificación en C++. Volumen 2.
Manipulación de bits 119
César Liza A vila
DESPLAZAMIENTO A IZQUIERDA «
DISYUNCIONI Si los dos bits son 0, entonces la disyunción resultará 0, en los demás casos resultará 1.
El operador « desplaza los bits de un número, una cantidad de posiciones a la izquierda, haciendo que los bits más significativos (los más a la izquierda) desaparezcan y los bits menos significativo se rellenen con O. Si a= 239 y b=3, entonces:
Si a= 239 y b=3, entonces su disyunción a nivel de bits será: a 0000 0000 1110 1111 b 0000 0000 0000 0011
=
a
= =
000000001110 1111
a« b = 00000111 0111 1000 10
que en decimal será: 2 + 29 + 28 + 26 +2 5 +24 +23 = 1912
alb= 0000000011101111
Note que desplazar una posición a la izquierda equivale a multiplicar el número por 2 (en el ejemplo 239*23 = 1912).
EXCLUSION A Si los dos bits son diferentes, entonces la exclusión resultará 1, en los demás casos resultará «J.
DESPLAZAMIENTO A DERECHA » El operador » desplaza los bits de un número, una cantidad de posiciones a la derecha, haciendo que los bits menos significativos (los más a la derecha) desaparezcan y los bits más significativo se rellenen con o. Si a= 239 y b=3, entonces:
Si a= 239 y b=3, entonces su exclusión a nivel de bits será:
a b a
1\
b
= 0000 0000 1110 1111 =
a
1\
= 0000
0000 1110 1111
a» b = 0000 0000 0001 1101
0000 0000 0000 0011
= 0000000011101100
que en decimal será: 2 + 2 + i + 2 +2 = 236
Note que desplazar una posición a la derecha equivale a dividir el número entre 2 (en el ejemplo 239/2 3 = 29).
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
7
6
3
2
c_~.~
~~_-"CC~~c·~·
_ _ _- - - "_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
120Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
Manipulación d(' bits j 21
El programa muestra el resultado de aplicar cada uno de los operadores a nivel de bits, compárelos con los resultados obtenidos en la explicación anterior.
Solución: Antes de resolver el problema hablemos un poco sobre los números hexadecimales y su relación con los números binarios. Números Hexadecimales En base 16, son válidos los dígitos 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, R, F; donde A=lO, B=11, C=12, D=13, E=14 Y F=15.
e, D, E Y
En programación de bajo nivel (cercana a la máquina) se acostumbra a utilizar la base 16, en lugar de la base 2, ya que presenta un equivalente directo con la base 2, pero al contrario de ésta última, es mucho más fácil de manejar. En base hexadecimal cada 4 dígitos binarios equivalen a un dígito hexadecimal. Por ejemplo: El número en base 2: 0001 1111 equivale a lF en hexadecimal. El número en base 16: A2 equivale a 10100010 en binario. Hemos utilizado el tipo snort ini: (16 bits) solo pues cuestiones didácticas, bien podríamos haber utilizado cualquier tipo entero. Debe recordar que los operadores a nivel de bits solo pueden ser usados con tipos enteros.
En C/C++/C# los números hexadecimales van precedidos de Ox, así lF se escribirá OxlF, mientras que A2 se escribirá OxA2. Note que:
No debe confundir los operadores « y » cuando se usan con con! o con el ciD, para evitar confusiones en el programa anterior usamos los paréntesis.
Un byte usará 2 dígitos hexadecimales Dos bytes usará 4 dígitos hexadecimales Cuatro bytes usará 8 dígitos hexadecimales Así el entero 1 (de tipo int o sea 4 bytes) será en hexadecimal OxOOOOOOOl Un valor en base lOse puede imprimir en hexadecimal dentro de un programa mediante hex, por ejemplo couí«hex«28, imprimirá le. Las direcciones de memoria se acostumbran a mostrar en hexadecimal.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vi/a. A1is libros son económicos, /10 hay razón para cO/liar/os.
Manipulación ¡de hiTS 123
César [,iza /lvi/a
J22 AI¡;oritmos v su Codificación en C+ + Volumen 2,
Ya en nuestro programa leemos un entero (32 hits) e invocamos a la función impBits( ) con el positivo y el negativo del valor leído.
Por ejemplo, si ingresamos 79 en binario será
En impBits( ), se define una máscara en la cual solo 1 bit está encendido 1000 0000 0000 0000 0000 0000 0000 0000, sabiendo que 4 dígitos 'binarios equivalen a un dígito hexadecimal tendremos: 80000000, que en C/C++/C# se representaría como Ox80000000.
79(10\ 0= 1001111(2)
El impresión se basa en tomar la máscara y hacer un AND a nivel de bits (&) con cada bit de
11, para ver cuales están en 1. Es necesario que la máscara sea del tipo unsigned puesto que si fuera int, el último bit se reservaría para el signo y, por lo tanto, no podría almacenar un número tan grande como Ox80000000 arrojando la inicialización un error pues estaría fuera del rango válido de enteros.
Sin embargo, para obtener la representación de -79 en 32 bits tendremos: Luego de asignar 1 al bit más conIlÜCACloN EN'C+¡.··.· , mask significativo ( # includé. Ox8000000 ), mediante un bucle .\lbig inlpBits(int); recorremos de izquierda a , \:,oid máin(~í?íd) derecha, cada uno de Jos 32 bits { int ni ." ." .... cout«"iÍlgr,esenro: ";.' cin»n.; de n, haciendo un AND con co~t« n«"="~ imPBits( n)) .. cada uno de ellos. Sólo en caso cóut<:<:-n«i;= "; i!J'1pBitS{"I1); .. · que n tenga el bit } . ,.. . 1 se correspondiente en void impBits(in.f ti), imprimirá 1, en caso contrario { llnsig~e~jntmaskFQx8Óoó6oqo; se imprime 0, para luego fót (i~tj ",,0;]<32;j++).' . .....>, desplazar el bit encendido de la { i(d%4;:=0) éOllt«"'''r " máscara una posición a la if «o & rnásk) > O) cOllt<:< 1; derecha y evaluar el siguiente else CQLit« O; rnask ,,=(ri}~sk» 1); , '. bit de 11.
=
La comprobacÍón j %4==0 solo sirve para colocar un espacio cada 4 dígitos binarios.
•
•
'
oo.
••
..'
79 en binario es: lo negamos le sumamos 1 :
0000 0000 0000 0000 0000 0000 0100 '111 1 11111111111111111111111110110000 1
-79 se almacena como:
11111111111111111111111110110001
valores que son correspondientes con la salida del programa:
·cout<<:end!; }
© César Li'.a A vi/a. No mates /a producción intelectual, no copies éste libro.
© Césor Liza Avila, Mis libros son económicos,
110
hay mz:ón para copiarlos.
124 Algoritmos)' su Codifical'i6n en C+ +. Volurnen 2.
César Liza A vila
. Solución: La exclusión a nivel de bits XOR tiene la propiedad que al ser aplicado dos veces se obtiene el valor originaL Por ejemplo:
MmufJulaci6n de bits !25
Es fácil modific:lr el programa para que imprima lo que ocurre cada Ve! que aplicamos el XORy darnos cuenta de cómo va trabajando la computadora. El lector deberá intentar producir esta salida modificando este programa y la función impBits( ) del Problema 402. Tenga en cuenta que ahora los datos son tipo char, esto es 1 byte .
Bits originales 01010101 XOR Clave 0000111l Resultado
01011010
Aplicando nuevamente XOR Resultado Clave
01011010 XOR 00001111
Bits originales 01010101 En el programa ingresamos la palabra y la clave. La función encripta( ), toma cada uno de los caracteres del mensaje y le aplica un XOR con la clave reemplazándolos. Así, cada caracter es cambiado y, al ser un arreglo, main( ) reconoce los cambios hechos sobre él. Para desencriptarlo, aprovechamos la propiedad del XOR discutida líneas arriba, volviendo a aplicar el XOR con la misma clave, obteniendo el mensaje original.
',c:~~li&f~n(:x)C:, r' ." '
'·"·I::(Juti<';in~t:%se.~Afl,ye; cin»a¡ '0. cncripta(il,t,.·a);·o ,·cout«"Enc,ripÚidó; V<"~x<'
foJ' (fIlt i""O; l-f:n; 1++) x[i],=:x[iJAclaye; ..
© César Liza Avda. No mates la produccián intefectual, no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay razrín para copiarlos.
i26 !\l¡;oritmos v su Cod(jil'ación en
e + +,
Volumen 2.
Manipulaf'Íón de hits 127
Chal U,;a Avila
m2!liJrn( J, definimos un arreglo de 52 elementos, generamos la baraja e imprimimos las cartas. En gene!l"a( ), tomamos cada palo, y para cada uno de ellos generamos valores del 1 al 13 y su color. La impresión no necesita mayor explicación. En
Solución: Podemos utilizar una estructura para especificar la cantidad de bits que ocupará cada miembro, llamándose a este tipo particular de estructura
campos de bits. struct nombreCampoBits Sintaxis: {
nomb1: nroBitsl; nombN: nroBitsN;
};
El tipo solo puede ser integral (int, unsigned, short, char, o una enumeración) Los campos de bits permiten una mejor utilización de la memoria al guardar directamente los datos en los bits que deseamos. Ya pensando en nuestro programa, si deseamos almacenar los datos de una carta, razonaremos así: • Cada carta tiene un palo, los posibles palos son 4: espadas, diamantes, tréboles, corazones, por lo que necesitarnos log2 4 =2 bits • Cada palo tiene 13 valores por lo que necesitaremos log2 13 =3.7 bits;::: 4 bits • Cada carta puede tener 2 posibles colores por lo que necesitamos log22 = 1 bit Entonces nuestro campo de bits será: struct carta { unsigned palo: 2; unsigned valor: 4; unsigned color: 1; };
Observe como con solo 7 bits almacenamos las características de cada carta, pudiendo usar el bit restante para indicar si la carta ya salió. Así, struct carta o simplemente carta es un nuevo tipo de dato, por lo que podríamos pasar como argumento de funciones o retornar un valor del tipo carta, o manejar arreglos del tipo carta, e inclusive igualar dos variables tipo carta.
CODIFICAcmN EN eH #iBcJude <:iosiream.h> .. # ín~ll.lde strUCt carta unsjgned palo :2; '.
. ,upsignep valor:4;' •.•··llflsignedcolbr:l;
1: ...•..... " ....•.....'...
yo~d g~n§r.a.(struct s#ia(]}; •. . Yoi.djtnpfírñir(str~ct~~({Íl [J);
Yoid lJlaiil(vQid).
-.
.
'... ~trU<;tcaita oaraJa[52J; ";;cgt!n,éra(ha.raja); ...•......•. ¡~. I!npt:i 1l!ir(ba taJa);,
lO! .' ...
>",
'~~
"()ig.g~nera(str~cléa~taQLá •
J·L.}·'·Zi •.• .•,·.· •.•. · ....,.....~¡. mt.k.,.,Q,..., .......' Jo;( iM páis\='=Q;PctLOs4;palo*+)·· ..
, ,. '. for( i ~t valor=J ; válokH.: ~~lór7+)
..
Lkt~J.paI(p~::~~f1f'
.,. . . ,. ,
.'p[kJ;valóf'2Val~r; . .···
.h(k].c,019r ,fPa!()%2;
::,;;< rk ++; .,,~;/; ,.
lh,~nfui'(s~"f~~~él)
.•..
cout«"PCl1él .va19f·Solot"<<;:endJ'
, . f91'( ir¡ti~O;i<52;H-+f' ". " :
-'cóut «setw(2l«b[ij.pálo
«setw(6j«h!i).vaÍor
<
© César Li2.:a Avda. Mis libros son económicos, no hay razón para copiarlos, © César Liza Avda. No mates la producción intelectual, no copies éste libro,
César Liza A vila
128 Algoritmos y su Corj¡ficaci6n en C·++. Volumen 2.
Manipulación de bits .129
HH MM SS
Solución: Los archivos que tenemos en nuestros discos tienen como' uno de sus atributos la hora de grabación y; además, en el caso del Windows, la hora de la última modificación. Para su almacenamiento .el DOS y el WINDOWS, y muchos programas como el WinZip, que almacena las horas de sus archivos comprimidos, utilizan un mismo formato para almacenar la hora, el cual explicaremos a continuación. Cómo almacenan la hora los Sistemas Operativos Es conocido que existen 24 horas al día, cada hora tiene 60 minutos, cada minuto tiene 60 segundos. Entonces: HH MM SS 00
00
00
23
59
59
Luego para almacenar las 86,400 combinaciones necesitamos IOg2 86400 =16.3987 bits, esto es más de dos bytes.
Sin embargo, no es necesario almacenar la hora de grabación de un archivo con la precisión de 1 segundo, ya que nuestra reacción a la acción de grabar tardará al menos ese segundo, por lo que será suficiente almacenar la hora de grabación del archivo con al menos 2 segundos de diferencia, por lo tanto: Cesar Liza A vi/a. No mates la producción intelectllOl, no copies éste Ubro.
_ _ _ _ _ _ _ _ _ _ _ _ _ _= _..
00
00
23
59
29
24 x 60 x 30 = 43,200 combinaciones
por lo que necesitamos IOg2 43200 =15.3987 bits, esto es menos de dos bytes, entonces para almacenar la hora con precisión de dos segundos necesitaremos a lo más 2 bytes. Como en nuestra codificación cada minuto tiene 30 seg, cada hora tendrá 60x30 = 1800 segundos. Entonces: Si cada segundo equivale a seg/2 tendríamos: La hora 00:00:00 equivaldría a O La hora 00:00:01 equivaldría a O La hora 00:00:02 equivaldría a 1 La hora 00:00:03 equivaldría a 1 La hora 00:0 1:01 equivaldría a 31 La hora 22:3 1:08 equivaldría a 22(1800) + 31 x 30 + 8/2 =40534 Es decir la hora 22:31:08 se almacenaría en dos bytes como 40534, pero en computadoras, es preferible hablar en potencias de 2, así en vez de utilizar los factores 2, 30 y 1800; utilizamos los factores 2, 32 y 2048 con los cuales obtendremos el número que representa a la hora.
24 x 60 x 60 = 86,400 combinaciones
(1.)
00
..
c.~.- ~.~-.-
Número = Horas*2048 + Minutos*32 + Segundos/2 Por lo que su representación en memoria es más adecuada y quedaría en 2 bytes como: 15
14
13
12
11
10
9
8
7
5
4
3
2
o
'-____.....,...._---J '----------v------......' '---------v-----~ horas
minutos
© César Liza A vi/a. Mis libros son económicos,
•..
6
llO
seg/2 har Tazón para copiarlos.
130 Al[?oritmos y su Codificación en (>++. Volum.en 2.
Manipulación de bits 131
César Liza A vila
Por ejemplo: para la hora 22:31:08 el número sería en realidad: 22*2048+31 *32+8/2 =46052, que se almacenaría como:
'--------v--~
'---...".-------" --------'---y-- - - _ . /
horas
minutos
segl2
De esta manera se puede obtener el número a partir de la hora, o l~ .hora a partir del número, realizando rápidamente djvisione~ ~or .2 utIlIzando desplazamientos de bits hacia la derecha, y multIplIcacIOnes por 2 utilizando desplazamientos de bits hacia la izquierda. Leemos las horas minutos y segundos. Las horas deben ocupar los 5 bits más altos, por lo que hay que desplazar el valor leído 16 -5 = 11 posiciones a la derecha (h.h «11). Los minutos se ubican desde el 6to bit por lo que hay que desplazarlos 5 posiciones (h.m «5). La cantidad de segundos ingresados debe guardarse por la mitad, por ello 10 dividimos entre 2, mediante el desplazamiento de una posición a la derecha.
Solución: Los archivos que tenemos en nuestros discos tienen como uno de sus atributos la fecha de grabación y; además, en el caso del Windows, la fecha de la última modificación. Para su almacenamiento el DOS y el WINDOWS, y muchos programas como el WinZip que almacena las fechas de sus archivos comprimidos, utilizan un mismo formato para almacenar la fecha, el cual explicamos a continuación.
"~: \, v
,·i··'
Cómo almacenan la fecha los S.O. • Como los días sólo pueden ser della 31, serán necesarios 5 bits, pues log2 (31) ::::: 5 (2 elevado a la 5 es 32) Como los meses sólo pueden ser 12, entonces necesitamos 4 bits, pues log2 (12) ::::: 4 (2 elevado a la 4 esl6) Como en 2 bytes hay 16 bits de los cuales ya utilizamos para el día y el mes 5+4=9; nos quedan 7 bits con los cuales podemos representar hasta 128 años (2 elevado a la 7). 15
14
13
12
11
10
9
8
7
6
5
4
3
2
O
'------y---~./ '---------v---------' ' - - - - - - - y - , - - -
año
mes
día
La primera PC (Personal Computer) fue presentada en otoño de 1981 por
IBM, y por lo tanto en una PC no pueden existir archivos anteriores a esta fecha. Por esto el DOS y Windows toman como año de referencia a 1980, y cuando creamos un archivo se almacena solo la diferencia respecto a este año.
© Cónr Liz.a Avila. Nu mates la producción intelectual, no copies éstf' lil"!ro.
© César Liza Avila. Mis lihros son económicos. no hay
ra2:ÓI1
para capiar/os,
1.12 Algoritmos y su Codificación en
e t +.
De esta manera podríamos manejar como máximo con este formato y a nivel de almacenamiento de archivos, fechas del sistema desde 1980 hasta 2108 (1980+ 128); sin embargo, la üJtima fecha posible de manejar con estos sistemas operativos es 31/12/2099. Para almacenar la fecha Día/Mes/Año se almacenará un número de 2 bytes dado por:
NroFecha =(Año-1980)*512 + Mes*32 + nía
El factor 32 (2 elevado a la 5) se incluye porque hay que desplazar 5 bits al mes (el día ocupa los primeros 5 bits y no podemos sobreescribirlos). El factor 512 (2 elevado a la 9) se incluye porque hay que desplazar 9 bits al año (el día ocupa los primeros 5 bits, el mes los 4 siguientes y no podemos sobreescribirlos). Por ejemplo, para almacenar la fecha 25 diciembre de 2005, se almacenará el número entero sin signo: NroFecha = (2006-1980)*512 + 12*32 + 25
Dec 25 12 26
Día Mes Año -1980
15
14
13
12
11
10
9
8
6
5
4
3
2
O
elt~;);fl~}E;:l~~YJ~1~¡Cl~1~2tt;\:U~:º·~tl;;1iiJ BIr1;,{;(a])i3\\~rt;p!<,;t~;{t:·.t[:'í1ii~H~;':':l:ffQ,:);tU '----~-v-~--_../
año
'--.---"y"'------..../'--------..y-------..--/
mes
.,--,,..-----,--~= ... '"""-'':'-'':--"---::--''~''-----"-''''-''''""''''-
CODIFICÁCIONiÉN en
Una vez leído el número de dos bytes
#ifl6ítide,
s~iJ6~¡f~c;h$
(cin»nroFecha)
t:
tomamos solo los bits que necesitamos para obtener el día, el mes y el año ..
J;:~wtAh~.iOi . voi(f!Ílaih( voig)
Para el día, sabemos que se almacena en los 5 primeros bits, por lo tanto aplicaremos un AND con una máscara de 0000 0000 0001 111] que equivale en hexadecimal a OxOOlF.
<~.!~~2rf~Ch~f; •.
'..', " , ' . ' , .,,~ri~igned sh$rt ifl~nr()F~cha; .... ',
. ,'C;Ott~«";N'u.!l1et:9. C¡u~i¿prer¡ehta f~cI{a? ";
i: ;i3r~r~~~~~~a~6x6ii Fi!~~:i'
'::
•. f;trl~<~{;;' 'érit'óF'ec~a &:pxOlaO)'>'?5;···.
'S'fi~nnio'7((n¡;oFec;há'&ºxFEqº»>9) t 19$0;. e
•
;~
";;." ,,; .. ;~.;, . ',.0.
, ;.~ ...
e
=13721
Bin 11001 1100 11010
7
Manipulación
César Liza A. vilo
Volumen 2.
día
Todos los sistemas compatibles con DOS y WINDOWS y con su forma de almacenar archivos FATo VFA T, utilizan 2 bytes para almacenar la hora y 2 bytes para la fecha.
© César Liza Avila. No mates la producción intelectual, no copies éste lihro.
En el caso del mes, debemos determinar el valor de los bits nombrados como 5, 6, 7 Y 8, para esto aplicaremos un,a máscara de 0000 000 J 1110 0000, que en hexadecimal eqllivale a Ox01EO, pero hay que desplazarlo 5 posiciones a la derecha para obtener su verdadero valor decimal. Finalmente, el año ocupa los 7 últimos bits por lo que aplicaremos un AND con máscara 111 1 1110 0000 0000 que equivale a OxFEOO en hexadecimal. Dicho valor debe desplazarse 9 posiciones a la izquierda para obtener su verdadero valor decimal y además hay que añadirle el año base, 1980.
N:mero Que representa fecha? 137211 Dva:25 ~ .
-
© César Liza A vila. Mis libros son económicos,
.. ··.'1
~
Mes: 12
fho:2006 !lO
.
hav razón para copiar/os.
------------------------------- -_ _-_ ....
..
~
134 Algoritmos y su Codificación en C+ +. Volumen 2.
César tiza A vila
Manipula!'Íón de bits J 35
Soludón: Solución: Este es el mismo problema que el 4.5, pero implementado usando campos de bits, con ellos podemos acceder directamente al conjunto de bits definidos, ahorrándonos el trabajo con desplazamientos, y la máscara, pero no podemos evitar la división entre dos (s»1) para almacenar sólo los segundos pares. Leemos las horas, minutos y segundos y los llevamos al miembro (hora) de la union horaUnion hora, como son campos de bits, se alinearán en los bits apropiados, luego accedemos a los mismos bits pero ahora interpretados como short un unsigned (hora.nroHora)
-------'-=---,--,# inch.ide<íostteal11.h> .
Tanto los flotantes como los enteros ocupan" bytes (en compiladores de 32 bits), por lo que bastará llenar el miembro flotante (f) de la union e imprimir los bits del miembro entero.
llniotttlotante
••...•
:;"i~~g";~;,
vojdifrip:B,'its(mt);, . YQi~
m!lbiC
..
·{utli;lÍ
¿oUt . C~rl»h(;
Por ejemplo, si ingresamos el flotante -123.75, tendremos: uü!Sn MialJnióhl10rá; lf¡fh, m,s;,_c' '. ...... 'éotit«"lfCiÍ."a>.· ':',1.•. cin»h' ....
.c.,o.u.t~.·.
v
.hora.hora.h.=c h; •.........
hóra.hora.m = rn~i<
••... '
.
hórá.hQt¡¡.:s == (s~>J); '. l/divide, por 2 é9ut«"NúlTIem 9u~t~presenta la hora:" •.
.
«(hdra.nrp!fQrrr.;:~¡~j;ldl;'.
.
Pero usted se preguntará ¿cómo esos O y 1 se interpretan para formar un número con decimales? En el siguiente problema darnos la respuesta. (c)
César Liza A vila. No mates la I'rodw"ción inlelectual, no copies ésre lihro.
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
Manipulación de bits 137
César Liza A vila
»36 Algoritmos y su Cod(ficación en C+- +- Volumen 2.
Mantisa: Cuyo valor en bits es 11101111000000000000000, se interpreta como la parte fraccionaria, por lo tanto será: fracción
1
1
1
2
2
2
= --- + ---2 + ---3 + O + ---5 + ---6 + ---7 + ---8 + O = (2
Solución: El formato IEEE 754, ocupa 4 bytes, distribuidos de la siguiente manera:
e~to
es 32 bits los cuales son
3130292827262524 2322212019181716 1;:' 14131211109 8 7 6 5 4 3 21 o ~¡;I(;fnS'I~i~ ~f~';'I.i(hq(¡~I;IJ-:l_JdI~~ H~"8§!
signo
exponente
2
3
2
2
2
+ 2 + 2 + 2 + 2 + i + 2° ) /2 6
5
2
8
Obtención del número: Por defecto todo número en este formato tiene un 1 implícito, que hay que sumar a la parte fraccionaria, luego lo multiplicamos por 2 elevado al exponente obtenido (6), y añadirle el signo, por lo que el número será: nro = - [ 1 + (2 7 + 2 6 + 2 5 + 2 3 + 2 2 + i + 2° ) / 28 )* 26 = _(2 8 + 27 + 26 + 25 + O +2 3 + 22 + 2 1 + 2° ) 2/ 22 (256 + 128 + 64 + 32 + 8 + 4 + 2 + 1) /2 = - 495/4
=-
= - 123.75
mantisa
Por ejemplo, de acuerdo al problema anterior, para el número -123.75 los bits serán:
1100 0010 1111 OIII 1000 0000 0000 0000 signo : 1 exponente: 100001 Ol mantisa 11101111000000000000000
Signo: Como está en 1 el número será negativo. Exponente: 7 2 En decimal será: 2 + 2 + 2° 133 Pero como está sesgado a 127, hay que restarle este valor 133-127 6, que viene a ser el verdadero exponente. Este sesgo es necesario para poder tener exponent~s negativos, por ejemplo si los bits almacenados fueran 120, entonces el exponente sería 120-127 = 7.
=
7
=
© César Liza Avda. No mates la producción inte1ectual no copies éste fibra .
El programa usa el struct simplePrecision, para indicar los bits que forman cada parte del formato flotante. La union es necesaria, puesto que nro sirve para almacenar el flotante leído, pf reparte lo leído, según los bits del formato y, n sirve para imprimir los bits, puesto que los operadores de bits solo pueden ser usados con tipos enteros.
© César Liza A vila. Mis libros son económicos, no hay razón pora copiarlos.
._~-~~--------------------
138 Algoritmos y su Codificación en C+ +. Volumen 2.
Manipulación de bits 139
César Liza Avila
l.
2. 3. 4. 5. 6. 7.
8. 9. 10.
11.
12.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
Haciendo uso de operadores a nivel de bits, escriba una función que diga si un número entero eS negativo o positivo. Si el último bit esta en 1 el número se considera negativo, y estará expresado como complemento a dos. Lea un mensaje y usando una función encríptelo por negación de bits. Luego desencriptelos. Escriba una función que divida un entero entre una potencia de 2, y otra función que multiplique un entero por una potencia de 2. Lea un entero y diga los valores de los bits pares. Se desea codificar n posibles valores ¿ Cuántos bits son necesarios? Muestre todas las combinaciones a nivel de bits posibles. Dado una variable tipo char muestre cada uno de sus bits. Lea una variable tipo double y muestre cada uno de sus bits. Sugerencia: en algunos compiladores una variable long int ocupa 8 bytes de tal manera que' usando una unión podría leer un double y mostrarlos como un long int, sin embargo en VC++ y Dev-C++, un long inl ocupa solo 4 bytes (32 bits), de tal forma que no podrá tomar los 64 bits que ocupa el double, para solucionar esto podría usar un arreglo de tipo unsigned inl con dos elementos. Dada un número que representa una hora obtenga las horas minutos y segundos. Dada una fecha, obtenga el número que la representa en elformato de codificación del DOS y Windows. Se sabe que para convertir una letra minúscula en mayúscula se resta 32, es decir si el (/0 bit (25=32) esta en 1, entonces debemos convertirlo a O. Utilizando esta idea convierta una letra minúscula a otra en mayúscula. Un artículo tiene un código numérico almacenado en 8 bits, los 3 primeros indican la famUiq, los 2 siguientes la subfamilia, mientras que los 3 últimos son un número correlativo. Dado un número que representa el código, diga ClI;ál es sufamilia, subfamilia y correlativo. Dados dos bytes, coloque l~~ 4 bits superiores del primer byte junto a los 4 bits inferiores del seguhdo byte, y los 4 bits inferiores del primer byte, junto a los 4 bits superiores del segundo byte. I
© César Liza A vila. Mis libros son económicos, no hay razóit'para copiarlos.
R410 AIRorilmos y su Codificación en C++. Volumen 2.
César Liza Avila
.13. Lea una matriz de 8x8 caracleres que solo ceros y unos y almacénelos en 8 bytes. 14. En un uw;signed ch,al!' intercambie los 4 bits inferiores con los superiores. J5. Se tiene el siguiente mensaje encriptado char msje! J ={12, J 7, 29, 22, 11, 88, 77, 88, 8, 12, 23, 11, NUIL}; se sabe que ha sido encriptado con el método de XOR con clave de un carácter y que dicho caracter es una letra. ¿ Cuál es el mensaje original? ¿ Cuál es la dave? 16. Se sabe que las letras del código ASCII, van del 65 al 92 para las mayúsculas y del 97 al 122 para las minúsculas, es decir siempre son menores que 128 o sea que solo son necesarios 7 bits para enviar un mensaje de texto. Podemos entonces almacenar 8 caracteres en 7 bytes, pues como en cada byte solo utilizamos 7 bits nos sobra J bit y en 7 bytes nos sobraran 7 bits los que podemos utilizar para almacenar un carácter más. Lea 8 caracteres y almacénelos en 7 bytes. Note que esta forma de almacenar los datos nos produce una compresión del orden 1/8 =: 1.2.5%. 17. Implemente el siguiente algoritmo de encriptamiento. Lea 8 bytes que forme en mensaje. Tome los bits O de cada byte y tendrá el primer byte encriptado, tome los bits 1 de cada byte y tendrá el segundo byte encriptado, tome los bits 2 de cada byte y tendrá en tercer byte encriptado y así sucesivamente hasta completar los 8 bits de cada byte. 18. Encripte y desencripte un mensaje por rotación de bits a la izquierda por ejemplo, si el carácter es 01000001 y rotamos 2 veces a la izquierda tendremos el nuevo carácter 00000101. A diferencia del desplazamiento, en una rotación los bits que salen se insertan por el otro lado. 19. Encripte y desencripte un mensaje por inversión de sus bits, esto es colocarlos en orden inverso al que se encuentran. Por ejemplo, si el carácter es 00001010 luego de la inversión será 01010000. No se confunda con la negación de bits. 20. En modo consola una variable tipo char ocupa 1 byte mientras que una de tipo unsigned short in! ocupa 2 bytes. Así, es posible leer 2 char y colocarlos en un entero sin signo. Escriba una función que dados 2 chars los coloque en un unsigned short int, y otra función que dado un unsigned short ¡nt obtenga los charo
© César Liz.a Avila. No mates la producción intelectual, no copies éste libro.
Generación de Números Aleatorios y Simulación
Generación de Números Aleatorios y Simulación 143
Solución: Un número aleatorio es un número que es seleccionado al azar, por lo que es imposible que las computadoras, donde todó esta predeterminado, generen verdaderos números aleatorios; sin embargo, pueden generar secuencias de números que estadísticamente sean aleatorias, es por ello que se denominan "números pseudoaleatorios", Esta secuencia debe cumplir con dos propiedades importantes. Primero, la Uniformidad, es decir que todos los números tengan la misma probabilidad de ser escogidos. Segundo, la Independencia, esto es, dos números de la serie no deben tener correlación. Existen pruebas estadísticas que comprueban la Uniformidad, entre las más usadas tenemos los test de KolmogorovSmirnov y Ji-cuadrada (chi-square). Mientras que, para probar la Independencia, tenemos el test de corridas y el test de Poker. Además, es deseable que el generador de la secuencia aleatoria ,tenga un periodo largo, es decir que genere una cantidad grande de aleatorios antes que la secuencia se repita; asimismo, debe ser un algoritmo rápido y que sea capaz de volver a generar la misma secuencia de pseudoaleatorios con el propósito de depurar programas. Antes de las computadoras, los números aleatorios se generaban utilizando ruedas giratorias, lanzando dados, tomando una carta, etc. Técnicas más "modernas" consistían en imprimir unas tablas de números, uno apuntaba con un lápiz y donde éste caía sería el ~úmero escogido. Hacia 1946, Jhon Von Newman con su método del cuadrado central (ver ejercicio propuesto 5.1), abrió el· camino para permitir que las computadoras generen números pseudoaleatorios. Hacia 1951, Lehmer, propuso el Método Congruencial Lineal basado en la ecuación Xi= (a.xi.] + c) % m. donde a, e, y m, son valores que deben ser elegidos con cuidado para que pueda ser considerado un buen generador. Note que los números generados bajo este esquema serán enteros desde O hasta m-l. Como se © César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
144 Algoritmos y su Codificación en C++. Volumen 2.
Generación de Números Aleatorios y Simulación 145
César Liza Avila
tiene una serie finita de valores que pueden producirse (O, ... , m-.1), después de haber generado una serie aleatoria, alguno debe repetlfSe y por consiguiente toda la serie empieza a repeti~se; así es. q~~ debemos encontrar valores de a, m y e, tales que para cualqUIer valor inIcIal de x (xo llamado semilla del generador de números aleatorios), el núm~ro de v~lores aleatorios que se puedan generar antes que empiece a repet1fse la sen e sea grande. Un criterio es que m debe ser un número primo grande, tal como 231 _1 2147483647 (para 32 bits). Unos valores adecuados son c:= O ya::::: 7 5=16807. . Todos los lenguajes de programación modernos, incluyen métodos para generar números aleatorios que han ,ido cuidadosamente probados, de tal manera que, podemos suponer gil, producen secuencias estqdísticamen~e aleatorias. Sin embargo, aun podemos verificar la calidad de la secuenCIa generada usando las pruebas estadísticas ya mencionadas.
=
En la función IinealCongruencial( ), enviamos un valor inicia] a la fórmula, dicho valor es conocido como semilla (seed). La variable semilla al ser de tipo static, solo se inicializá la primera vez que se ejecuta la función, en las siguientes llamadas toma el valor que tuvo en la última invocación de la función. Los valores de a=32719, c=3 y m=32749, son para obtener aleatorios de 16 bits y han sido usados por algunos compiladores, su periodo, es decir la cantidad de números generados antes que se repita la secuencia, es 32748, que es suficiente en la muchos casos pero no en simulaciones de gran envergadura. © César Liza A vila. No mates la producción intelectual, no copies éste libro.
Solución: Cuando hablamos de un intervalo discreto nos referimos a generar números
enteros dentro de ese intervalo. Además se trata de generar números con distribución uniforme, esto es que todos tengan la misma probabilidad de salir elegidos. Como ya se explicó, para generar una secuencia aleatoria es necesario inicializar la semilla del generador de números aleatorios. En C/C++ se delega esa tarea a la función srand( ), cuyo prototipo esta en stdlib.h, la cual acepta un entero sin signo para, a partir de ese número, empezar a generar la serie pseudoaleatoria. Es evidente que si la semilla es la misma, generará la misma secuencia de números aleatorios. Sería muy tedioso que el usuario fuera el que ingrese siempre la semHhi, lo más conveniente es que la computadora la escoja. Pero ¿cómo logramos que la computadora cambie la semilla cada vez que se ejecute un programa? Para esto, tomamos la hora del sistema. La función time( ), incluida en el archivo time.h, devuelve el tiempo transcurrido, medido en segundos, desde las O horas O minutos y O segundos del tiempo universal coordinado (GMT) del lro de enero de 1970. Esta medida se llama el "tiempo de calendario". Como time( ) devuelve un long y srand( ) necesita un unsigned inl es necesario que hagamos la conversión de tipos, de ahí el uso del cast (unsigned). La función encargada de generar un aleatorio comprendido dentro del intervalo de O y RAND_MAX (en VC++ 6.0 es Ox7FFF ó 32767) es rand( ), cuya declaración prototipo se encuentra en stdlib.h. Por ejemplo sí quisiéramos simular el lanzamiento de una moneda haríamos: srand( (unsigned) time (NULL) ); d = rand( ) % 2; ya que solo devolverá 2 valores (O ó 1). © César Liza Avda. Mis libros son económicos, no hay razón para copiarlos.
César Liza A vi/a
146 Algoritmos y su Codificación en C++. Volumen 2.
Generación de Números Aleatorios y Simulación 147
Si quisiéramos generar varios aleatorios debemos hacer: srand( (unsigned) time(NULL) ); for (i = O; i< n; i++) cout<
Es necesario que la inicialización de la semilla se haga fuera del bucle, puesto que si se ejecuta dentro la semilla (el tiempo), no cambiará durante un segundo, produCiendo siempre el mismo valor durante ese segundo. En el programa, la función rand( ), usa la semilla inicializada con srand( ), y calcula, mediante su fórmula interna, el primer aleatorio.
Solución: Un intervalo continuo significa que dentro de dos números siempre existirá otro. Esto en computadoras no es debido al de almacenamiento para flotantes el IEEE 754, no es posible representar los infinitos números reales, de ahí que se hable de precisión. Ya en el código, luego de inicializar la semilla con srand( ), generamos un aleatorio entre O y 1, como el valor que puede producir rand( ) está entre O y RAND_MAX, si lo dividimos entre RAND_MAX el cociente estará entre O y ]. El cast (double) es necesario para convertir el número generado a double y luego dividirlo con un entero dando como resultado un double.
La expresión rand( ) % a obtiene un aleatorio entre O y a-l, pues este es el intervalo posible de residuos que podemos obtener al dividir cualquier entero entre a. Si queremos obtener un aleatorio entre O y a debemos hacer rand( ) %(a+l). La expresión a + rand( ) % (b-a+1), obtiene un aleatorio entre a y b inclusive, ya que si el residuo es O, entonces el limite inferior es a, mientras que si el residuo es boa, entonces el superior será a + b-a,estoesb.
La expresión a + (b-a)*(double)rand( )/RAND_MAX, genera un aleatorio dentro de los números reales y en un intervalo continuo desde a hasta b, puesto que (double)rand( )/RAND_MAX, es un numero entre O y 1, cuando vale O entonces el aleatorio será a + (b-a)*O = a, mientras que cuando es 1, será a + (b-a) =b, cualquier valor intermedio produce una fracción de (b-a), que sumado con a dará el valor requerido.
© César Liza Avila. No mates la producción intelectual, no copies éste lihro,
© César Liza Avila. Mis lihros son económicos, no hay razón para copiarlos.
-----------------------------------------------------
148 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A víla
Solución: Este es un clásico ejemplo de Cad
e iniciamos caminata
la
gen~rando
un aleatorio O ó 1. De ser 1 avanzamos un paso a la derecha en dirección a su casa (x++); en caso contrario irá a la izquierda, en dirección a la cantina (x-~). Esto se repetirá mientras no llegue a la coordenada -k
Generación de Números Aleatorios y Simulación 149
Solución: Si generamos n números aleatorios dentro de mi intervalo, (por ejemplo entre O y 1) donde n es un número grande, entonces la suma de los n elementos será aproximadamente n/2, y el promedio será muy cercano a 0.5. Esto es llamado la "ley fuerte de los grandes números" que se puede describir como "con toda certeza (probabilidad 1) el promedio de una sucesión de variables independientes e idénticamente distribuidas convergerá a su media". Una forma en que se manifiesta esto es cuando se lanza una moneda muchas veces, la cantidad de sellos que salgan será aproximadamente igual a la cantidad de caras. El los casinos, siempre' cada juego tiene probabilidades a favor de la banca. La "ley fuerte de los grandes números" nos asegura que aunque ganemos en un determinado momento (suerte), si seguimos jugando, perderemos inevitablemente.
ók.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
César Liza A vila
150 Algoritmos y su Codificación en C++. Volumen ¿
Generación de Números Aleatorios y Simulación 151
Solución: Dibujemos una circunferencia inscrita en un cuadrado.
Solución: 3 Sea [(x) = x + 2x - 3, integral entre a y b será: .
b
b
4
3
2
J(x +2x-3)dx::: X /4+ x /2-3xl
a
a
por definición la integral es: b
lím t:\x-?
o
I
f(x)
~x
a
La relación entre las áreas de la circunferencia y del cuadrado será:
a
ÓX
I
b
rectángulos más Mientras tomemos, más cerca estaremos al verdadero valor del área. Si tomamos n rectángulos entonces ~x=(b-a)/n, por lo que: b
1,
lím
•
I
f(x) ~x
Area o --------AreaD
=
de donde:
b
lím
I
f(x) (b-a)/n
= (b-a).
t:\x-? O a
I
Hm o-?
00
f(x) / n a
Si tomamos una gran cantidad de alturas (n) de manera aleatoria, entonces f(x) I n, debido a la ley fuerte de los grandes números, converge al promedio de las alturas de f(x) y (b-a).I f(x)/n converge por lo tanto al área pedida. .
I
Aquí, usamos la simulación para determmar ~a integral de una función fácilmente integrable; sm embargo, la verdadera utilidad se da cuando la función no es integrable por métodos manuales, como puede ser la Distribución Normal vista más adelante.
1t*radio*radio
= ----------------- lado*lado
1t
=
1t ~);(1éIio~,ádio
1t
(2*ré!01~ )*(2*n}ófo)
4
, , ----------,------------r =
4*Area o ----------AreaD
Si aleatoriamente escogemos muchos puntos dentro del cuadrado, algunos caerán dentro del círculo y otros fuera de él. La razón de los puntos que . caen dentro del circulo respecto al total de puntos tomados, es aproximadamente igual a la relación entre sus áreas, es más también lo es a la probabilidad que un punto tomadoé.al azar caiga en dichas áreas; es decir:
1t
Area o
#ptos. dentro del o
Probo que caiga en o
AreaD
#ptos. dentro del D
Probo que caiga en
=4. --------- =4. -----------------------.. = 4. -------------------------¡j
,"¡
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
\. ~-
© César Liza Avila. Mis libros son económin)s, no hay raznll para copiarlos.
César Liza A vila
152 Algoritmos y su Codificación en C++. Volumen 2.
Generación de Números Aleatorios y Simulación 153
Podemos generar puntos aleatoriamente dentro de un cuadrado de radio 1 e ir contando todos aquellos que caen dentro de la circunferencia. Para esto ., de [a Clrcunlerencra . e . hacemos uso de 1a ecuaClOn x 2 + y 2 r2 .
=
El valor de 1t se obtiene dividiendo el número de puntos que caen dentro del círculo, entre el número de puntos que caen dentro del cuadrado y, multiplicando este cociente por 4. El método es más exacto cuantos más puntos tomemos, esto debido a la ley fuerte de los grandes números, ya explicada. Este método, puede aplicarse para calcular áreas de figuras irregulares y que son difíciles de medir directamente como por ejemplo ¡la superficie de nuestro beUo país! Los cartógrafos tomarían las coordenadas de los hitos y con ellos harían un mapa a escala. interior del mapa, sería pintada de un parte exterior de otro, luego genr:~ramos muchos puntos dentro del rectángulo y preguntamos su color, la relación de puntos de un color respecto al total de puntos será proporcional a las áreas.
© César Lizo Avifa. No mates la pn"frtel'Íóll intelectual, no copies ésre libro.
Solución: Hasta ahora hemos visto como generar números aleatorios, tanto enteros como reales, con distribución uniforme, donde todos los números tienen la misma probabilidad de ser escogidos. Pero ¿qué pasa si algunos números tienen mayor probabilidad de salir, pues cumplen con una determinada distribución de probabilidad ya sea discreta o continúa? Por ejemplo si una moneda está cargada de tal manera que pueda salir cara con mayor probabilidad, evidentemente ya no será uniforme. En este y en los siguientes ejemplos veremos como generar un número aleatorio con distribuciones no uniformes, a partir de un número aleatorio con distribución uniforme, para esto haremos uso del método de la transformada inversa.
g
Transformada Inversa para valores discretos Supongamos que tenemos la . ruleta mostrada, las "**'' ' I probabilidades que salga cada 4 2 número se muestran en la tabla. 3
3 4
0.125 0.125
A partir de esta tabla se obtenemos la siguiente:
:V:ilól:, '-nt~habiítdJ~.· j~.:A~ftffii\la:dai;!;;i~~~tYidó;!ó::; Luego, generamos un número aleatorio uniformemente distribu ido entre O y 1, Y buscamos el intervalo al cual pertenece, obteniendo el valor. que le corresponde. Este valor será el aleatorio deseado. Si por ejemplo, generamos un aleatorio entre O y 1, y resulta 0.6111 entonces el valor pedido es 2. Note como a partir de un número aleatorio con distribución uniforme obteriemos un aleátorio con distribución no uniforme. !
2 3 4
0.500 0.250 0.125 0.125
0.500 0.750 0.875 1.000
[0.000,0.500> [0.500,0.750> [0.750,0.875> [0.875, 1.000]
© César Liw A vila. Mis libros SO/1 económicos.
/lO
hav rm:ón pora copiarlos.
154 Algoritmos y su C"d(ficación en
c~
+. Volumen 2.
César Liza Avila
Generación de Números Aleatorios y Simulación 155
E"te método lo podemos usar para simular el resultado del siguiente sorteo de la Tinka a partir de los datos históricos. La siguiente tabla muestra cada bolita y el número de veces que ha salido (hasta la fecha en que escribí este programa, diríjase a ~Y.\Y_:'eYJi!lj!lklLq!DJ. para datos más actualizados).
(P ;1i•. ¿ :'!li~lad~~, ;;,:,:rJÍt;;I\¡QI(;::-;\;' Para obtener cada [ 0.00 - 0.02 :::.. -~--
124 185 240 289 339 408 464 511
0.05 0.07 0.10 0.12 0.14
__.Q:.!2.-.
684 743 815 871 934 1005 1073 1127
~. 1245 1294 1356 1419 1479 1530 1579 1635 1676 1735 1796 1861 1924 1971 2024 2096 2127 2158 2203 2236 2262 2295 2324 2340 2364____
@
César Liza Avila. No
11laleS
0.19 0.21 0.24 0.26 0.28 0.31 0.34 0.36 0.39 0.42 0.45 0.47 0.50 0.52 0.54 0.57 0.60 0.62 0.64 0.66 0.69 0.70 0.73
---ü.75 0.78 0.81 0.83 0.85 0.88 0.89 0.91 0.93 0.94 0.95 0.97 0.98
----~ 1.00
[0.02 - 0.05 > [ 0.05 - 0.07 > 0.07 -0.10 > 0.10-0.12> [0.12-0.14> 0.14-0.172_ 0.19-0.21 > 0.21 - 0.24 > [0.24 - 0.26-';:0.26 - 0.28 > 0.28 - 0.31 > 0.31 -0.34> [ 0.34 - 0.36 > 0.36 - 0.39 > 0.39 - 0.42 > 0.42 - 0.45 > [ 0.45 - 0.47 > 0.45 - 0.50 > [ 0.50 - 0.52 > 0.52 - 0.54 > [ 0.54 - 0.57 > [0.57 - 0.60 > 0.60 - 0.62 > 0.62 - 0.64 > [ 0.64 - 0.66 > 0.66 - 0.69 > 0.69 - 0.70 > 0.70 - 0.73 > [0.73 - 0.75 > 0.75 - 0.78 > 0.78 - 0.81 > [0.81-0.83> [ 0.83 - 0.85 > 0.85 - 0.88 > 0.88 - 0.89 > 0.89 -0.91 > 0.91 - 0.93 > [0.93 - 0.94 > [0.94 - 0.95.? 0.95 - 0.97 > [ 0.97 - 0.98 > [0.98 - 0.99 > [0.99 - 1.00 J
una de las bolitas que saldrán en el siguiente sorteo, generamos un número aleatorio entre O y 1 distribuido uniformemente, buscamos la en última columna el intervalo en el cual se encuentra, y nos dirigimos a la primera columna y vemos a qué número corresponde. Esto es lo que hace el siguiente programa, . el cual toma como datos de entrada, el número de veces que salió cada bolita. Por sencillez, el programa no verifica si las bolitas se repiten. Esto tendrá que añadirlo el lector.
la producción intelectual, no copies éste libro.
I
I I I
© César Liza Avila. Mis libros son económicos, no fUlv razón para copiarlos.
Generación de Números Aleatorios y Simulación 157
César Liza Avila
156 Algoritmos y su Codificación en C++. Volumen 2.
!)i deseamos simular cuántos eventos (i) OCUlTen en una unidad de tiempo, haremos uso del "Método de la Transformada Inversa", esto es generamos un número aleatorio r uniformemente distribuido entre O y 1 Y buscamos el valor de i correspondiente al intervalo en donde se encuentra r.
Solución: La Distribución de Poisson aparece en muchos fenómenos y es una distribución discreta; esto es, el mímero de ocurrencias es un número entero. Una variable aleatoria X, 'lile sigue una Distribución Poisson, significa que en cada unidad de tiempo es posible que ocurra una cantidad entera de eventos, como la llegada de un cliente, bajo los supuestos a) los eventos ocurren de manera independiente de otros eventos, b) los eventos no ocurren simultáneamente, c) la tasa promedio de eventos es constante. El número de eventos en un tiempo determinado es entonces una variable aleatoria de Poisson con parámetro A. Una e .i. Af variable aleatoria X con éstas características tiene Pi = P{X=i} = ~ _____ _ la función de densidad de probabilidad (fdp) l. mostrada, donde i = O, 1,2, .... , ye =2.71828. En otras palabras ésta fórmula nos dice que si la tasa media en que ocurren los eventos es A, entonces la probabilidad que ocurra: O eventos es: Po:::': e'; 2 % ! 1 1 eventos es: PI:= t") 2 /1! 2 eventos es: P2 ~.; e ·í. 2212! y así sucesivamente. Observe que: Á La probabilidad de que ocurran i eventos es: Pi = e· ;'}íi!, La probabilidad de que ocurran i + 1 eventos es: Pi+l = e ·Ie A,i+l¡Ci+ 1)1 La razón entre dos términos consecutivos será: Pi+1 e'AA,i+ll(i+l)! A,
.,
=
----------~-~-~=~
=
Pi e 'A 'A,i¡ i! i+ ] factor que usaremos en nuestro programa para facilitar los cálculos. Podemos demostrar que es una verdadera distribución de probabilidad pues: PO+P¡+P2+ ... = e ·i, 2% ! + e" ).//1! + e .J. 2212! + ... e ·i. (2 % ! + }///! + 2212! + ... )
=
=e .í'.e!. =/
© César Liza A Fila. No mates la producción intelectual,
110
copies éste libro.
La función poisson(), genera un número entero aleatorio que cumple una Distribución de Poisson con parámetro lambda. Primero, generamos un número aleatorio (r) uniformemente distribuido entre O y 1, e iniciamos la búsqueda de i. Si i=O, entonces la probabilidad correspondiente p es e .,1 (pues A,°=l y 0!=1) Y la probabilidad acumulada F es igual a p. Debemos ir acumulando las probabilidades hasta encontrar el primer valor de probabilidad acumulada que sea mayor a r, cuando esto ocurra terminará el bucle y el valor de i será la cantidad buscada. Observe el uso del factor Iambda/(i+1) para el cálculo de las probabilidades de 2 números consecutivos. © César Liza A vila. Mis libros
SOIl
económicos, no hay razón para copiarlos.
"1 I 158 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila
Generación de Números Aleatorios y Simulación 159
Exponencial, es una distribución continua, por lo que puede tomar valores fraccionarios. La Distribución Exponencial es una verdadera distribución de probabilidad pues:
Solución: , . Antes de mostrar el programa, veamos como utilizamos el metodo de la transformada inversa para valores continuos y luego hablemos sobre la distribución exponencial.
F(x)
= of'"/le"'" = e-"x lo'" =1 - e""'" =1
Para simular una variable aleatoria (x) con distribución exponencial, haremos uso del "Método de la Transformada Inversa". Empezamos generando un número aleatorio (r) entre O y 1 uniformemente distribuido y nos preguntamos que valor de x, hace que la función de densidad acumulativa (fda) sea igual a r. Siendo una distribución continua, la función de densidad acumulativa (fda) desde O hasta x, será:
Transformada inversa para valores continuos . Note que el método de la transformada inversa para v~lo~es dIscretos, consiste en ir sumando las probabilidades de cada valor. SI dIchos valores son continuos entonces la sumatoriase transforma en una integral. Si X es una variable aleatoria continua, y p(x) es la probabilidad de cada resultado x, entonces las probabilidades acumuladas están dadas por: x
F(x) = J p(x) dx o si generamos un valor aleatorio r, uniformemente distribuido, .e.ntonces nos debemos preguntar para que valor de x, la suma de las probablhdades da r. Entonces debemos resolver la ecuación: r=
x
I p(x) dx o
resolviendo la ecuaCIon, el valor de x, será el aleatorio generado que cumpla con la distribución p(x). La Distribución Exponencial . . Es una distribución continua que está completamente defmIda cuando se conoce la media de la distribución 1IJ.l. Está dada por f(x) =J.l.e""X, donde e es la base de los logaritmos neperianos e = 2.718281828, J.l es el .valor esperado (tasa media), 1/J.l es la media de la distri~ución. Una. propIedad importante de la Distribución Exponencial, es que no tIene memona; esto es, lo que ocurra en el futuro no depende de lo ocurrido anteriormente (s~n independientes) y la mayoría de sus valores son m~nores de .la .me?~a esperada (1/,..). A diferencia de la Distribución de POIsson, la Dlstnbuclon
x
(
F(x)
x
=of /le'''' = e-J.Ix lo =1- e""X
-"x.
Esta expresión debe igualarse al número aleatorio generado: r = 1- e lo que equivale a: e""x = 1 - r, aplicando logaritmos neperianos para despejar x tenemos: In(e""X) = In(1-r), esto es: -J.l.x.ln e In(l-r), puesto que In e =1, tendremos:
=
x
=-In(l-r)/ J.l
·1
En otras palabras si generamos un número aleatorio r distribuido uniformemente entre O'y 1, aplicando la fórmula anterior podremos obtener un número aleatorio x distribuido exponencialmente. y
La gráfica de una Distribución Exponencial se muestra en la figura adjunta. Aquí poderhos observar el valor medio 1/J.l y el área r encerrada bajo la curva f(x) y los límites O y x.
r -j--t---I---='~
1/11 © ('ésar Liza Avila. No mates la producción intelectual, no copies éste libro.
I
x
© César Liza Avila, Mis libros son económicos, no hay razón para copiarlos,
X
l'
160 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vi/a
Generación de Números Aleatorios JI Simulación 161
La distribución exponencial, es muy usada en la simulación de líneas de espera (colas) para generar el tiempo de atención; en la teoría de confiabilidad, para estimar el tiempo entre fallas de los componentes eléctricos; así como en la desintegración de partículas radiactivas, como por ejemplo para calcular la edad de los fósiles mediante el carbono 14. Existe una estrecha relación entre la Distribución Exponencial y la Distribución de Poisson, puesto que si la distribución exponencial modela el tiempo (continuo) entre dos eventos, para un tiempo t habrán ocurrido una cantidad entera de eventos que estará dada por la Distribución de Poisson.
La función exponencial( ), se encarga de generar una variable aleatoria que sigue una Distribución Exponencial. Aquí, generamos un número aleatorio (r) uniformemente distribuido entre O y 1, para luego aplicar directamente la fórmula y obtener la variable aleatoria distribuida exponencialmente.
l['
f
Note que si r es un número aleatorio uniformemente distribuido entre O y 1, entonces 1 - r también lo es, por lo que podríamos haber optimizado el programa y escribir -Iog (r) en vez de log (l-r). Recuerde que en C/C++ la función log( ) obtiene el Logaritmo Natural o Neperiano, mientras que el Logaritmo Decimal o de Briggs lo podemos obtener
Solución: . La Distribución Normal, llamada también campana o curva de Gauss, es una distribución continua muy utilizada en estadística, pues muchos fenómenos naturales se comportan en forma normal. Esto es evidente puesto que la mayoría de características de los fenómenos presentes en la natura)eza son similares y cercanos a la media, algunos poco se desvían de ello. Por ejemplo, las tallas y pesos de los seres vivos, el efecto de una dosis de algún fármaco, las notas de los alumnos, errores de medición, la demanda de productos, etc. (X-I1- )'
I
I(x) =-----::.:::::--- e uY2n
=valor medio = desviación estándar 2 . U = vananza n = 3.1415 ... J1 u
x e
=valor de la abscisa
=2.717282818
La Distribución Normal, esta definida por dos parámetros su 'media y su desviación estándar; N(I-l, u) Características: • Puede tomar cualquier valor (- 00, +00). • Son más probables los valores cercanos al valor medio. • Conforme nos separamos del valor medio, la probabilidad va decreciendo de igual forma a derecha e izquierda (es simétrica). • Conforme nos separamos del valor medio, la probabilidad va decreciendo de forma más o menos rápida dependiendo de un parámetro cr, que es la desviación estándar. . © César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
~-~~----------------------------~--~.- --~_.
-~
-_o -
f"
162 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila Generación de Números Aleatorios y Simulación 163
Para generar una variable aleatoria que siga una DistribuCión Normal, podríamos hacer uso de la transformada inversa, pero como se habrá dado cuenta dicha integral no es calculable por métodos manuales, debiendo hacer uso del cálculo y de los métodos numéricos, o de la misma simulación, tal como se mostró en el Problema 5.5. Una forma más fácil de generar esta variable aleatoria, se basa en el Teorema del Límite Central: la suma de variables aleatorios tiende a una distribución norm~l, cuando la cantidad de variables es grande. Según este teorema, si los valores "o, Xl. . . ., X n están distribuidos independientemente con media J.l y varianza ci entonces la variable (XO+XI+" .+xn) - nJ.l Zn ...¡ (n 0'2)
= ---------.--.---.-------
está aproximadamente distribuida en forma normal con media O y varianza 1, cuanto mayor sea el valor de n mejor será la aproximación. Sabiendo que una variable aleatoria x que sigue una distribución uniforme de O a 1, tiene una media 1/2 y una varianza de 1/12, si generamos n números aleatorios uniformemente distribuidos entonces, la expresión anterior se transforma en: (XO+XI+" .+xn) - 012 Zn = ------------_.-.-------. ...¡ (n /12)
(
y puede usarse para calcular para calcular una variable aleatoria normal estándar con media O y varianza 1.
'"
Esta fórmula funciona bien para valores pequeños de n, por lo general se escoge n=12 para eliminar la raíz cuadrada, quedando como: n=11
Zn = :Ex¡- 6, i=O
';
. ~I
donde Xi es un aleatorio distribuido uniformemente, Zn es la variable aleatoria Normal Estándar, por lo que si deseamos una variable aleatoria Z con Distribución Normal, con media J.l y varianza 0'2 usamos el resultado anterior haciendo: Z= J.l+O'Zn relación que utilizamos en nuestro programa. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos . .,
110
hay ra zon ' para Coptar.os. . 1
164 Algoritmos y su Codificación en C++. Volumen 2.
Generación de Números Aleatorios y Simulación 165
César Liza A vila
Solución: Si tenemos 3 elementos {I, 2, 3} al mezclarlos podemos obtener 3! 6 resultados diferentes que son:
=
Solución: La cantidad total de combinaciones o formas diferentes de agrupar n elementos tornados de r en res: n!
c nr =
{ 1, 2, 3}, (l, 3, 2}, {2, 1, 3}, {2, 3,
I
10..,
(n-r)! r!
l}, {3, 1, 2}, {3, 2, l}; ha esto se le conoce como permutación, si de todas estas posibles formas de ordenar los elementos tomarnos una al azar, estaremos hablando de una permutación aleatoria. Como recordará (hablamos de permutaciones en mi libro Algoritmos y su Codificación en C++, Volumen 1), la cantidad de permutaciones crece rápidamente, por esto no es recomendable obtener todas las permutaciones para elegir una. Es mejor aplicar el siguiente método: Tomemos el primer elemento e intercambiémoslo por olro ubicado en una posición aleatoria. Tomemos el segundo elemento ti intercambiémoslo por otro ubicado en una posición aleatoria, y así sucesivamentehasta el último elemento. Esto es lo que hace la función permutaAleatoria( ). Note que no se repite ningún elemento, puesto que solo los cambiamos de lugar aleatoriamente. Podemos necesitar una permutación aleatoria en varias situaciones: al barajar las cartas, para determinar el orden en que llegarán los caballos en una carrera donde todos tienen la misma probabilidad de ganar, el orden en que llegarán los alumnos al salón de clase; es decir, en todas aquellas situaciones en donde necesite mezclar un conjunto de elementos.
Las combinaciones aleatorias están presentes en muchas situaciones, por ejemplo, si tengo n individuos y deseamos tomar una muestra de r elementos, escogida al azar, en la generación de una cartilla de Bingo en donde para cada columna debemos escoger 5 números dentro de J5 posibles; en el popular juego de la Tinka, si deseamos simular que bolitas saldrán en el sorteo cuando todas tienen la misma probabilidad de salir, pues de 45 bolitas sacamos 6 sin que se repitan, entre otras situaciones.
© César Lizo Avila. No mates la prodUlociólI intelectual, no copies éste libro.
© César Liza A vda. Mis libros son económicos, no Iza,.
Si tenemos 3 elementos { 1, 2, 3} podemos escoger 2 de ellos de:
C
3!
3 2
= ----------- = 3 formas diferentes (3-2)! 2!
al igual que con las permutaciones, obtener todas las combinaciones y escoger una de ellas, resulta computacionalmente costoso. Sin embargo, podemos modificar ligeramente el algoritmo del problema anterior: Tomemos el primer elemento e intercambiémoslo por otro ubicado en una posición alea~oria. Tomemos el segundo elemento e intercambiémoslo por otro ubicado en una posición aleatoria, y así sucesivamente hasta el elemento r-ésimo.
Como notará la única diferencia, es que solo llegamos hasta el elemento r y no procesamos el resto de elementos. Esto es lo que hace la función combAleatoria( ).
f"CI;::ÓIl
para copiarlos.
166 Algoritmos y su Codificación en C++. Volumen 2.
Generación de Números Aleatorios y Simulación 167
César Liza Avila
A propósito la Tinka puede producir: C456
45! 45*44*43*42*41*40~39! = ____________ = _______ ;~----------------!-----(45-6)! 6! ,3'9! * 6*5*4*3*2
=8' 145,060 resultados diferentes
l.
de los cuales solo uno es el ganador del premio mayor, esto es la probabilidad de sacarse la Tinka es 1 entre 8'145,060, o sea: 0.000000123
I
2. 3. 4. 5.
6. 7. 8.
9.
10.
© Cés~r Liza Avila. No mates la producción intelectual, no copies éste libro.
Un generador de números aleatorios desarrollado por Jhon Von Neumann hacia 1946, y que mostró el camino para generar números aleatorios por computadora, es conocido como "método del cuadrado central". Consiste en elevar al cuadrado el anterior aleatorio generado y extraer los dígitos centrales. El valor inicial es conocido como semilla y el usuario lo ingresa o lo toma de la computadora a partir del reloj interno. Por ejemplo, si la semilla eS 121, la elevamos al cuadrado tendremos 14641, extraemos sus 3 dígitos centrales se obtiene 464, a partir del cual se obtendrá el siguiente aleatorio. Genere n aleatorios con este método. Aunque no es un método adecuado, pues genera secuencias muy cortas de números y parece existir correlación entre ellos; tiene un valor histórico, puesto que fue el primer método implementado en computadora y nada menos que por el creador de la arquitectura en la que se basan las computadoras modernas. Genere aleatoriamente una fecha válida. Considere los años bisiestos. Genere aleatoriamente un entero y luego permute sus dígitos. Se coloca una reina en un tablero de ajedrez vacío, muestre una caminata aleatoria de la reina. El volumen de una esfera es v=4/3 nr3, supongamos que no conocemos la constante C=4/3, estime por simulación el valor de C sabiendo que la ecuación de la esfera es x 2 +l+l=1. Sugerencia: extienda el Problema 5.7, dado en dos dimensiones, hacia el espacio tridimensional. Genere una baraja de cartas y luego mézclelas. Genere aleatoriaf1!-ente una cartilla de Bingo. En que orden llegarán n alumnos a unflula de clases. Considere el código y el nombre del alumno. Sugerencia: Use una permutación aleatoria de un arreglo de estructuras. Se desea emparejar n hombres con m mujeres pero de manera aleatoria, muestre una forma en que pueden emparejarse. ¿Quiénes quedan sin pareja? Sugerencia: Use una permutación aleatoria. Coloque en una matriz de nxn los números del 1 al n 2 aleatoriamente, sin que se repitan. A esto se le llama Cuadrado Latino.
© César Liza Avila. Mis libros son económicos, no hay rqzón para copiarlos.
ló8 Algoritmos y su Codificación en C++. Volumen 2.
César
Lií:Ci
A vila
¡ l. Se desea realizar un estudio médico paro comprohar la efectividad de un nuevo medicamento. Para ello se divide a los voluntarios en dos grupos, uno llamado grupo de control, al cual solo se les dará un plarebo (una sustancia que no causa ningún efecto) y al otro se le aplica el nuevo medicamento. Si tenemos iJl voluntarios selecóone aleatoriamente un grupo de n/2 personas, ai cual se va a aplicar el nuevo medicamento. Sugerencia: use una combinación aleatoria. 12. Se tiene m carreras con 11 caballos en cada carrera, todos los caballos tienen la misma probabilidad de ganar. Muestre el orden en que llegarán en cada carrera e indique cuántas veces va ganando cada caballo. Sugerencia: use una permutación aleatoria por cada carrera. 13. Genere n elementos de un arreglo, luego elimine todos los elementos aleatoriamente. Muestre el orden en que se van eliminando. 14. Un dispositivo incluye dos componentes. La probabilidad que uno de ellos falle es p, el dispositivo deja de funcionar si los dos componentes fallan. Escriba un programa que simule el encendido del equipo 11 veces y diga cuántas veces falla. 15. El juego Memoria contiene n pares de figuras diferentes y consiste en descubrir dos figuras; si son iguales, éstas se retiran y se continúa escogiendo otra pareja. Escriba un programa que permita jugar 2 memoria. Sugerencia: llene un arreglo de nxll elementos con n f2 pares de elementos iguales, y fuego mézclelos. 16. Muestre a un cuerpo que se mueva aleatoriamente, dentro de los límites de una matriz, y una casilla a la vez. Debe verificar los límites de la matriz. Este movimiento aleatorio se llama movimiento Browniano y representa el nwvimiento de una partícula muy pequeña (diámetro inferior de una milésima de milímetro) en un fluido debido al impacto de los átomos o moléculas del fluido sobre la partícula. Por ejemplo el humo que se dispersa por el aire o la tinta que se difunde en el agua o los granos de polen en el agua. 17. Un Bull o Diana contiene n círculos concéntricos cada uno de radio conocido, se lanza un dardo que cae en alguno de los círculos, cada uno de ellos tiene un puntaje definido. Simule el lanzamiento de d dardos en el Bull y muestre el puntaje total obtenido. Sugerencia: Genere dos números aleatorios dentro del intervalo continuo desde cero hacia el radio mayor, use la ecuación de la circunferencia 2 X2 +l=r , para determinar dentro de qué círculo cayó. © César Liza A vi/a. No mates la producción intelectual, no copies éste libro.
Generación de Números Aleatorios y Simulación 169
18. Se tiene una cerradura compuesta de tres dígitos (del O al 9). Se ingresa la clave, pero ésta se olvida. Si generamos claves al azar ¿En cuántos intentos habremos logrado abrir la cerradura? Asegúrese de no repetir una combinación ya probada. 19. Se realiza un minicampeonato defútbol con 6 equipos: A, B, C, D, E Y F; en el cual cada equipo juega 5 partidos. Se desea simular los resultados de dicho campeonato generando aleatoriamente el número de goles que consiguen y recibe cada equipo. El puntaje es 3 puntos para el ganador y 1 punto para cada uno, si empatan. El Campeón será aquel que tenga el mayor puntaje, o si hay empate la mejor diferencia dé goles. Asuma que en un partido no puede haber más de 12 goles en total ya sea recibidos o anotados por ambos equipos. 20. Una empresa de carga, dispone de un camión y está considerando comprar otro camión. En la actualidad alquila los camiones adicionales necesarios a 250 soles al día. El camión de la empresa ocasiona un gasto de 150 soles al día se use o no. La compra de un nuevo camión ocasionaría el gasto de 175 soles al día. De los datos históricos se sabe que el número de camiones requeridos sigue la siguiente distribución: liiNtQ}Q¡¡rmp¡J~il, ~tlti''1J.b~bJ(¡iilii#~.
O 1 2 3
0.10 0.40 0.35 0.15
Realice una simulación para decidir si es conveniente la compra de un nuevo camión.
© César Liza Avda. Mis libros SOI1 económicos, no hay razón para copi~lrlos.
Punteros y Asignación Dinámica de Memoria !
i
l·
Punteros 173
§oludón: La memoria puede verse como una serie de casilleros consecutivos que tienen un número que las identifica, llamado dirección de memoria, cada casillero ocupará ¡ byte. Estas direcciones de memoria se acostumbran a escribir en hexadecimal.
¿Cómo se almacenan las variables en memoria? Si declaramos las variables: int a = 50; char b = 'M'; fioal e = 3.75f; double d = 2.1;
se almacenan en memoria tal como se muestra: OxOO 12FF6B OxOO 12FF6C
0,00"'1'60
L-
c=
d
~
OxOOl2FF6E OxOOl2FF6F 2.1_ OxOOl2FF70 OxOOl2FF71 [ OxOOI2FF72 OxOO12FF73 OxOO12FF74 OxOOl2FF75 OxOOl2FF76 OxOOI2FF77 I OxOOi2FF78 ~b
c= c::-=:-
§, "
OxOOl2FF79
Primer byte libre de memoria ;".
' ~:", .~/.
/,-
"
OxOOI2FF7A OxOOJ2FF7B OxOO12FF7C OxOO 12FF7D ~ OxOOl2FF7E ~ OxOOI2FF7F
}
Los 3 bytes quedan libres, pues las variables se alinean a un múltiplo del tamaño de la palabro del sistema (32 bits)
I © César Liza Avila. Mis libros son económicos, no hay razón para copwrlos.
b
174 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila
Punteros 175
Es decir la computadora almacena las variables primero en las posiciones más altas de la memoria, alineándolas al inicio del tamaño de la palabra. En nuestro caso la primera dirección de memoria libre fue Ox0012FF7F, pero puede variar para usted, pues dependerá de la gestión de su sistema operativo.
En muchas ocasiones necesitamos almacenar una dirección de memoria, es por ello que surgen variables capaces de hacerlo. A este tipo de variables se les conoce como punteros. .
¿Qué son punteros? Son variables como cualquier otra. Pero NO almacenan un valor sino que almacenan una Dirección de Memoria
Si tenemos el nombre de la variable, podemos obtener fácilmente la dirección de memoria en la cual se inicia. Para esto usamos el operador & colocado delante de III variable. No debemos confundir con el AND a nivel de Bits. Si declaramos la variable int a=50; podemos mostrar su dirección de memoria de la siguiente manera: cout«&a«endl;
p
Ox0012FF70
una variable común
una variable puntero
Declaración de punteros Se declaran como cualquier variable pero se coloca un asterisco (*) antes del nombre de la variable:
I
int *p; 11 p guardará una dirección de memoria, en dicha dirección habrá un entero double *r; 11 r guardará una dirección de memoria, en dicha dirección habrá un doble El tipo de dato nos dice que si vamos a la dirección de memoria almacenada en la variable puntero, en esa zona encontraremos un valor del tipo declarado.
El Operador Dirección: & Ya dijimos que un & (ampersand) colocado delante de la variable, nos da la dirección de la memoria de la variable. En la declaración anterior, p y r son variables (que almacenan direcciones de memoria), y por lo tanto también ocupan memoria, cuya- dirección de inicio podemos obtener mediante &p y &r.
Al imprimir la dirección de una variable char, tal como b, nos sale la dirección de memoria pero en decimal, por lo que tuvimos que imprimirla en hexadecimal mediante hex. © César Liza A vila. No mates la producción intelectual, no copies éste libro.
Por ejemplo: int *p, x; cout«&x; cout«&p;
1/ p es un puntero, x es un entero
II imprime la dirección de memoria la variable x II imprime la dirección de memoria la variable p
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
176 Algoritmos y su Codifiracíón en C+ +. Volumen 2.
César Liza A vila
Punteros j 77
Podemos hacer: p = &x; puesto que &x es uña dirección de memoria y p es una variable capaz de almacenar una dirección de memoria. El Operador Indirección: * Nos da el contenido de la dirección de memoria almacenada en la variable puntero.
Por ejemplo: float x=5, *z; z == &x cout«*z;
En la última línea el * aplicado a una variable puntero z, significa "ve a la dirección de memoria que almaceno y toma el valor que encuentres". No confundir el * en las declaraciones de un puntero (como en float *z), con el operador * en las instrucciones (obtén el contenido de, como en cout«*z), ni en las operaciones aritméticas donde * es el signo de multiplicación. Como vemos, cada variable ocupa una dirección de memoria. Esto es cierto para otros elementos, así las funciones también tienen una dirección de inicio, los arreglos de igual manera, y cualquier tipo de dato definido por el programador como las estructuras, y las enumeraciones. Asimismo, se puede declarar una variable que almacena una dirección de memoria en cuya celda hay otra dirección de memoria, o sea un puntero a un puntero.
Recuerde: Todo puntero debe ser inicializado antes de ser utilizado, sino se tendrá un puntero que apunta a una dirección desconocida.
Solución: Aunque las funciones puedan contener varios retum, no es posible para una función devolver más de un valor. Para que la función "devuelva" más de un valor se debe usar punteros. Ha~ dos formas de pasar parámetros a funciones. La primera es el paso de parame.tros por valor, en donde el valor de la variable se coPia a la zona de memOrIa aSIgnada a la función. Todo cambio que se haga en esta zona, solo afectará a la función, y no será conocida por maine ). La segunda forma, es el paso por referencia, en donde a la función, se le envía la dirección de memoria de las variables, de tal manera que la función pueda acceder a esa zona de memoria y modificar su contenido, para esto se usan los punteros y su operador & para obtener la dirección y * para obtener el contenido. Recuerd~ ,que to~~ arreglo se pasa por referencia, es por ello que cuando una funclOn modIfIca los datos del arreglo, main( ) conoce los cambios. Podríamos escribir el siguiente programa: # include void swap(int, inO; void main(void) { int a, b; cout«"Ingrese a: "; cin»a; cout«"Ingrese b: "; cin»b; swap(a, b); couk<"a= " «a «endl; cout«"b== " «b «endI; void swap( ¡nt a, ¡nt b ) { int t; t == a; a == b; b == t;
esperando que funcione correctamente, sin embargo, no se producirá ningún cambio. Veamos por que ocurre esto: © César Liza A vila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
178 Algoritmos y su Codificación en C++. Volumen 2,
César Liza A vila
Punteros 179
Cuando se envía parámetros por valor a una función, significa que se copia el valor que actualmente tiene la variable, hacia otra zona de memoria asignada a la función que se invocó.
,wap(~
b)
1:q:~ 1
Consideremos que en main( ), las variables a y b son de tipo int, mientras que en swap( ), sus variables locales a y b son punteros a enteros int *, y mostremos gráficamente lo que debemos hacer:
1 1:~'1Y.1 1 d
Se invoca a la función enviándole las direcciones de memoria de las variables swap(&a, &b)
t
maine )
swap( )
swap( ) t
]
=a
maine )
Una forma común de representar las variables punteros locales de swap( ) a y b, es usar una flecha, que significa "el a local de swap( ), apunta a la dirección de memoria donde se ubica el a de main( )" y "el b local de swap( ), apunta a la dirección de memoria donde se ubica b de main( )" swap( )
a-de swap( ), apunta a
a de main(). b de swap( ), apunta a b de main().
a =b
maine )
swap( )
IQNi I
$r--r-:--;r1'-------'
(
maine )
swap( )
b =t
termina el código de la función y por lo tanto la memoria asignada a esta, se libera, perdiéndose los cambios locales.
t
maine )
swap( ) a
b
20
20
'" llY
~
f'
*a
swap( )
maine )
swap( )
=*a
, t
maine )
= *b
.","
?
~2~(
1 ~r--r-:--r-;I---r----1~o 1 1
-----maine )
Para que no se pierdan los cambios realizados, la función debe ser capaz de modificar los datos en las direcciones de memoria que correspondan con las variables de main( ). Esto solo se puede hacer cuando se realiza el paso de parámetros por referencia, es decir mediante el uso de punteros. © César Liza Avila. No mates la producrión intelectual, no copies éste libro.
*b
swap( )
=t
..
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
Se libera la memoria asignada a swap( ), y main( ) conoce los cambios
Punteros 18J
César Liza Avila
180 Algoritmos y su Codificación en C++, Volumen 2.
swap()
main( )
,.....
r[FrJ
De esta manera, los cambios, que hicimos en la función swap( ), sobre el contenido de a y b, afectan a main().
Solución: Sea el punto (a, b) del sistema de coordenadas cartesianas. y
El programa que hace esto, se muestra a continuación:
b
(.,b)
/' P/ ./
;Z V,U
lA 1 a
x
las coordenadas polares están dadas por un ángulo (9), y la distancia del punto al origen (p). Matemáticamente: 8 =arctg(b/a) p = ...JCa2 + b2) . relaciones que usaremos en nuestro programa. Como la función debe ser capaz de cambiar más de un valor, entonces debemos usargl pasQ por referencia/,es decir punte~os. La invocación a LeeCartesianasC&x, &y), indica que se le envía las direcciones de me1f1oria correspondientes a las variables a y b, que maine ) conoce, de tal manera que la función pueda realizar los cambios al contenido de esas direcciones. La invocación aPolares(x, y, &a, &b), indica que x yy, se pasan por valor, es decir los cambios que haga sobre estas variables la función aPolares( ), no serán conocidas por maine ); mientras que a y b, se pasan por referencia, es decir los cambios que haga la función aPoJares( ), serán conocidos por maine ). @
.... '1
César Liza A vi/a. No mates la preducción intelectual,
/lO
copies éste libro.
@
César Liza A vi/a. Mis libros son eronómicos, no hay razón para copiarlos.
182 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vi/a
Punteros 183
La función LeeCartesianas( ), lee la abscisa y ordenada, ya través de los punteros, cambia los valores en la zona de memoria correspondiente a main( ), de tal manera que cuando se libere la memoria asignada a LeeCartesinas( ), los valores leídos, sean conocidos por main( ). En aPolares( ), x y y no deben ser cambiados por la función,entonces se pasan por valor, mientras que el ángulo y la distancia calculados en aPolares{ ), se pasan por referencia, puesto que se debe modificar los valores conocidos por main(). .
Solución: Existe una estrecha relación entre punteros y arreglos, lo primero que debemos recordar es que el nombre del arreglo indica la dirección de memoria a partir del cual se almacenan sus elementos, y que estos se almacenan en posiciones consecutivas de memoria. Es decir si hacemos: int x[] = {lO, 20, 30}; cout « x « endl; imprimiremos una dirección de memoria, osea x es un puntero, pero como este valor es asignado por la computadora y, es el punto de entrada a un conjuntwde elementos, no podemos cambiarlo a nuestro gusto, por ello se dice que el nombre de una arreglo es un puntero constante. Para mostrar el valor contenido en ese puntero usamos el operador indirección esto es cout«*x, por lo que *x será el valor del primer elemento o sea 10 Si deseamos el segundo elemento, tendremos que sumar a la dirección x la cantidad de bytes que ocupa el primer elemento. Esto lo hace automáticamente la computadora utilizl).ndo aritmética de punteros, basta con hacer *(x+l). Así, la dirección de memoria del elemento x[i] será (x+i), mientras que el valor será *(x+i). Aritmética de punteros Suma Al incrementar un puntero, se suma a la dirección almacenada, la cantidad de bytes que tiene el tipo del puntero. Por ejemplo: double x[] = {1O.1, 20.3, 5.7}; double *p =x; cout« p «endl; /1 imprime la dirección de 1er elemento (equivale a imprimir x) cout«*p «endl; II imprime 10.1 (equivale a usar 'x) p++; /1 p=p+ 1; avanza al siguiente elemento tipo double (suma 8 bytes) cout« p «endl; /1 imprime dirección del2do elemento (equivale a imprimir x + 1) cout«*p «endl; /1 imprime 20.3 (equivale a imprimir '(x+ 1) )
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
184 Algoritmos y su Codificación en
e + + Volumen L
Punteros 185
César Liza Avila
El programa sabe cuántos bytes debe sumar a la variable puntero, pues toma los bytes del tipo declarado p<1ra el puntero, en este caso dmib!e, le sumará 8 bytes. No es posible sumar i,a cantidad de bytes que deseemos, sino que el compilador suma la canti(Íi:ld de ~)ytes según el tipo del puntero.
¿Cómo se almacena un arreglo? Si declaramos un arreglo y un entero: int x[] ={65, 66, 67, 68}; int n=4; se almacenan en memoria tal como se muestra
Por ejemplo, en: !¡11 :: P -1- 2; suma 2*8 =16 bytes a p, pues p apunta <1 01,1 dOll~bje o sea apuntará al tercer elemento ppr lo que *p será 5.7, (también pudimos haber usado *(x+2) ). La diferencia entre usar x y usar p, es que x, al ser el nombre de un arreglo, es un puntero constante, es decir no podemos cambiar su valor, mientras que p es un puntero, es decir pode1I1os cambiar su valor. Por ejemplo, no podríamos hacer x++, pero si p++.
Resta Al decrementar un puntero, se resL) a la dirección almacenada, la cantidad de bytes que tiene el tipo del puntero. Por ejemplo: float x[] = {lO.lf, 20.3f, 5.7f}; fIoat *p =x+2; cout« p «endl; // imprime dirección del último elemento cout«*p «endl; 11 imprime 5.7 p-~; II resta 4 bytL, apunta al elemento anterior cout« p «endl; // imprime dimrción del penúltimo elemento cout«*p «endl; 11 imprimA ;¡n J
Primer byte libre de, memoria ~
El programa sabe cuántos bytes deh' restar a la variable puntero, pues toma los bytes del tipo declarado para e, puntero, en este caso float, restará 4 bytes. No es posible restar una cantidad de bytes que deseemos, sino que el compilador resta la cantidad de bytes según el tipo del puntero. Por ejemplo: p p - 2; II resta 2*4 =8 bytes a p, pues p apunta a un float
Aquí está el programa pedido. Para cada elemento del arreglo, sólo mostramos las direcciones de memoria y sus contenidos, así como la dirección de n.
=
Recuerde tener cuidado en NO acceder a una zona de memoria que no ha sido asignada a alguna de las variables del programa. Esto era posible en Modo Real (DOS), pero no es posible en Modo Protegido (Windows). @
OxOO12FF6C OxOO12FF6D OxOO12FF6E OxOO12FF6F OxOO12FF70 OxOO12FF71 OxOO12FF72 OxOO12FF73 OxOOI2FF74 OxOO12FF75 OxOO12FF76 OxOO12FF77 OxOO12FF78 OxOO12FF79 OxOO12FF7A OxOO12FF7B OxOO12FF7C OxOO12FF7D OxOO12FF7E OxOO12FF7F
La memoria se asigna desde las posiciones más altas, pero el primer elemento del arreglo se asigna desde la más baja
¡nnAllde~j~~e!!~:¿AcI~N~Nc++;)',~-l #~n~l~d¡f<¡()fu~l1¡p.h> , yoid' rl1a1?(Void): .' ..' .' '., .... .{ il1rxI'l~ {º5,;6~,o7;;68};{ •
~~tr~tbir~~di¿~~alil1¡Cí&·r¡<~)«<éndl;•.••
.\ {;qJ:Ít<;~
Ofv.tn J""J,J,)<:
,", . .'
- r O l " . , .,', '
' ' ' " ' " . "·'::úv(lo)~i*(x+i)<
'~hri! }i«&n«~ndh
César Liza A vila. No mates la I'mducciál1 intelectual, no copies éste libro.
©César Liza Avda. Mis libros son económicos, no hay razón para copiarlos .
•
.., 186 Algoritmos y su Codificación en C++. Volumen 2.
Punteros 187
César Liza A vila
¿y qué ocurre si declaramos n antes que x? . int n=4; int x[ ] = {65, 66, 67, 68};
Primer byte libre de memoria
>
OxOOl2FF6C OxOOl2FF6D OxOO12FF6E . OxOOl2FF6F OxOO12FF70 OxOOI 2FF7 1 OxOO12FF72 OxOO12FF73 OxOO12FF74 OxOO1 2FF75 OxOO12FF76 OxOO12FF77 OxOO12FF78 OxOO12FF79 OxOOI2FF7A OxOO12FF7B OxOO12FF7C OxOO12FF7D OxOO12FF7E OxOOI2FF7F
Solución: El programa usa la notación puntero, para indicar· cada elemento. Por lo demás es similar al mostrado en el capítulo sobre arreglos de mi libro "Algoritmos y su Codificación en C++", Volumen 1.
o sea que si leemos x[4] ¡sobre escribiríamos el valor de n!
Note que en la función int *p, es equivalente a escribir int p[ ]. Ahora puede entender porque, cuando una función hace cambios sobre un arreglo, main ( ) conoce los cambios, pues en realidad se pasa un puntero en IngresoDatos(n, x), donde x es el nombre del arreglo, es decir la dirección de inicio del arreglo. Recuerde que todos los arreglos se pasan por referencia.
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
188 Algoritmos y su Codificación en C++. Volumen 2.
Punteros 189
César Liza A vila
fD,l])lllL~d<ÓÍll1l,:
~:
. Si ingresamos la palabra "lhloN¡¡¡", el carácter a buscar C:f!lJr "" H, Y el carácter de reemplazo c~j[, entonces podríamos representar la memoria ocupada, como:
Para resolver este problema debemos colocar dos punteros al inicio de la cadena: char *r; r=p;
memoria asignada a maine )
I I o 11 I a I NULL I
palabra h
car
[2J
LBJ
E I e I 01 N I o
I e ¡ E ¡ R I NULL 1
~~r avanzamos uno de ellos.hasta que apunte al último elemento: while (*r) r++;
memoria asignada a reemplazar( )
p
car
[2J
c~
El bucle while(*p ), recorre cada letra hasta encontrar el terminador NULL. Si la letra es igual a la buscada (*p==car), se reemplaza por otra (*p=c). En cada vuelta siempre se avanza hacia la siguiente dirección de memoria (p++).
r--;
IRIElclolNlolclEIRINULO
rb
cÍJ,
p Comparamos el contenido apuntado por r y p, avanzando un elemento y mientras p < r:
I R I E I e I o I N I o I e I E I R I NULL]
p~kr en caso de encontrarse en el camino con contenidos de r y p diferentes entonces retornará O. De haber terminado el bucle entonces será palíndroma, retornando 1. Esto es lo que hacemos en el programa. Note que el p de maine ) es constante y no puede cambiar pero el p de EsPaJindroma ( ) es una variable puntero por 10 que podemos modificarla.
© César Liza A vda. No mates la prodUCción intelectual, no copies éste libro.
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
190 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
Solución: Coloquemos un puntero a cada palabra:
J
lo
11
Solución: _
I al NULL I Leemos un arreglo x[ J, de n elementos mediante IngresoDatos( ). Cada una de las direcciones de memoria de los elementos de x[ J, los asignamos a un arreglo de punteros (p[ D, mediante
p
Imlu In Id lo I NULL I q
Punteros 191
rrf
A vanzamos hasta la primera posición libre de la primera palabra.
qm
1m lulnldlolNuLLI
lIenaArregloPunteros( ).
a partir de la poslclOn actual de p, empezamos a copiar cada letra apuntada por q, (*p=*q), y avanzamos p y q una posición (p++, q++)
Los punteros almacenados en p[ J serán los que cambiaremos, para que apunten a los elementos de tal manera que se muestren ordenados.
I h I oI 1 I aI mi uIn I d I~ I p I m I u I n I d I 01 NULL I
q[iJ
Finalmente, colocamos el NULL, que toda cadena debe tener (*p=NULL). Ih lo 11 la Imlu In Id lo I NULLt"W
p
Note que pal1[ ], debe tener el tamaño suficiente para almacenar las dos palabras. © César Liza Avila. No mates la producción intelectual, no copies éste libro.
---------------------~ ---~-----~
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
192 Algoritmos y su Codificación en C++. Volumen 2.
Punteros 193
César Liza A vila
La
función asigna la dirección de memoria de cada elemento del arreglo (fjll+i), en cada elemento del arreglo de punteros (y[i]=p+i). i¡e¡¡aArreg¡oPun~ems( ),
La
función
IOrdenaDatosPunteros( ), usa el
método de la burbuja y compara los valores del arreglo ( *pU-l] > *p[i] ), pero hace el intercambio en el arreglo de punteros. El equivalente eliminando la notación de arreglos es: if ( **(p+j-l»**(p+j) ) { temp == *(p+ j-l);
*(p+j-l)= *(p+j); *(p+j) == temp;
puesto que p + j - 1 es la dirección del elemento al aplicarle un * obtendremos un puntero, y al aplicarle el segundo * obtenemos el valor al cual apunta. Sin embargo, en esta función, hemos preferido dejarlo en su notación como arreglo, para resaltar el hecho que estamos trabajando con un arreglo de punteros. En ImprimeOrdenadoPunteros( ), tomamos cada elemento del arreglo de punteros y mostramos su contenido, es decir mostramos el arreglo de datos ordenados según lo indicado en el arreglo de punteros. © César Liza A vi/a. No mates la producción intelectual, no copies éste libro.
Solución: Este ejemplo, pretende explicar el uso de punteros a estructuras. Recordará que cuando tenemos una estructura como: struct empleado emp; codigo emp
I
sueldo
I
y queremos acceder a uno de sus miembros usamos el operador ., cada miembro puede ser accedido mediante emp.codigo y emp.sueldo Pero si tenemos un puntero a una estructura tal como: struct empleado *q;
q~ codigo sueldo . . I y queremos acceder a uno de los miembros de la estructura al cual apunta, usamos el operador o>, así sus miembros serán q->codigo y q->sueldo. Esto es lo que hacemos en el programa, solo que el puntero avanza t.omando cada elemento del arreglo. Note que pudemos usar p[iJ.codigo ó (p+i)->codigo © César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
194 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vila
Solución: . Cuando definimos un arreglo, le indicamos la máxima cantidad de elementos que puede contener. Esta . cantidad puede ser demasiado grande para los datos que utilicemos trayendo como consecuencia el desperdicio de memoria, o lo que es peor, puede quedamos demasiado corta, teniendo que ir al código "fuente modificarlo y volverlo a compilar. A esta forma de trabajar la memoria, se denomina estática, y el tipo de enlace con sus elementos se denomina enlace estático o temprano (antes de compilar). Es por ello que se ideó un mecanismo que, en tiempo de ejecución, llamado también enlace tardío o dinámico, el usuario pueda decirnos cuánta memada requerirá. Esta forma se llama asignación dinámica de memoria, y consiste en pedirle a la computadora que nos de una zona de memoria capaz de almacenar la variable que le pidamos. Esta nos devolverá un puntero a la -zona de memoria asignada. Para ello usamos el operador new, mientras que para liberar la memoria asignada usamos delete, tal como se muestra en el programa.
Punteros 195
Solución: Las funciones también tienen una dirección de inicio en memoria, y por lo tanto podemos almacenar esa dirección en un puntero. Al igual que los arreglos el nombre de la función nos da la dirección de InICIO asignado por la computadora, el cual no se puede cambiar, por lo que no está permitida la aritmética de punteros. En el programa declaramos un puntero a una función que recibe dos argumentos enteros y que devuelve un entero int (*ptr)(int, int); es necesario escribir (*ptr), puesto que, si eliminamos los paréntesis, diríamos al· compilador que ptr es una función que devuelve un puntero a un dato tipo int, que no es lo deseado. La dirección de inicio de la función suma es asignada a ptr (ptr=suma), luego leemos los valores a operar e invocamos a la función, mediante el puntero ptr(x, y ). Como la función suma( ) retoma ~n valor hacemos s=ptr(x, y). De manera similar para la función resta( ) Invocamos a ptr(x, y), e imprimimos el resultadQ. '
La asignación dinámica de memoria y los punteros a estructuras son usados ampliamente cuando trabajamos con listas enlazadas. Este tema lo tratamos en mi libro "Estructuras de Datos con C++".
El uso de punteros a funcione~ es .un mecanismo muy potente, ya que podemos tener arreglos de p.unteros a funcIOnes y hasta podemos· usar funciones como argument?s de funcIOnes. En los lenguajes más recientes los punteros a funciones se denomman delegados.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay razón para copi({rlo~.
]96 Algorilnlo" \. su Cod(ficación en C+ +. Volumen 2.
§oRución: .-,-....,..,,.,...,...-_....._---_._._-_.-.... Trabajar con punteros suele ser muy :éaI)I~~ICAboNÉÑc_l:+· delicado, puesto que podemos acceder #fuclllde
La misma zona de memoria tiene 2 nombres a y b, si modificamos b, estaremos modificando a, y viceversa
en este caso b es una referencÍ¡i, y se declara anteponiendo el signo & (ampersalld) delante de la variable. En realidad b es un puntero a la dirección de memoria ocupada por a, pero con la ventaja que ocultamos la complejidad eliminando los *. Las referencias fueron introducidas con el C++. Los lenguajes de programación más recientes, dicen no usar punteros pero si referencias, pero como ve, las referencias son "punteros encubiertos" . El programa que presentamos es el mismo que el ejemplo 6,1, pero usando el operadOl' I"t'fercnda. @
PUllteros 197
Césw Li"a A vilo
César Liz:u A vi/a, No mates la producción intelectual, no copies ésfe libro.
l.
2.
3.
4.
5.
6.
7.
8. 9.
10.
Escriba una función que acepte un puntero x de lal manera que rada vez que se llame a la función duplique su valor. Llame a esa función varias veces y muestre los valores de x. Escriba una función de tipo void que reciba un entero positivo y obtenga la suma de todos los números hasta dicho entero. El resultado deberá imprimirlo desde maine ). Escriba una función que debe ser invocada como máximo II veces y cada vez que se invoque, ésta debe mostrar cuántas veces le queda por ejecutarse. La función debe restar el contador. Escriba una función que lea los tres lados de un triángulo y, otra función que determine qué tipo de triángulo es según sus lados (isósceles, escaleno, equilátero). Las funciones deben ser invocadas desde maine ), y el resultado impreso desde maill( ). Note que la primera función necesita 3 punteros y la segunda ninguno. Lea dos fracciones y calcule su su/na, el resultado debe ser mostradc' como una fracción simplificada. Use una función que lea el numerador y denominador, luego una función que los sume y otra que simplifique la fracción. Las funciones deben ser invocadas desde maíllO y el resultado impreso en maíllO. Escriba una función que lea las horas, minutos y segundos, y otra que le sume una cantidad de segundos. La nueva hora deberá ser impresa desde maill( ). Escriba una función que lea los coeficientes (a, by c) de una ecuación 2 de 2do grado (ax + bx + c)y otra función que "devuelva" sus raíces reales. Las raíces deben ser impresas desde maine ). Considere que las raíces podrán tener parte imaginaria. Escriba una función que calcule la parte entera de cualquier número real y también la parte decimal del mismo. Escriba Ulla función que lea una fecha y otra que tome como parámetros el día, mes y año y obtenga la fecha del día siguiente. La fecha debe imprimirla desde maillO. Se tienen 3 valores a, b y e, cada vez que se invoca a la función, a fnma el valor de c, b toma el valor de a, y e el de b. Escriba una función que haga este trabajo, luego imprima desde maine ) los valores de a, b y c
© César Liza Avila. Mis libros son económicos. no hay razón para copiar/os.
198 Algoritmos y su Codificación en C++. Volumen 2.
11.
12. 13. 14.
15. 16.
17.
18.
19.
20.
(f)
César Liza Avi/a
cada vez que se invoque a la función. La función se invocará 3 veces. Por ejemplo, si a, b y e son 1, 2 Y 3, en las tres llamadas debe imprimir 312, 231 Y 123. ¿En qué dirección de memoria se encuentra el menor elemento de un arreglo y cuál es el valor? Deberá imprimir esta dirección y el valor desde maine ). Sugerencia: Escriba una función que busque el menor elemento y retorne un puntero. Ya en maine ), imprima el puntero y su contenido. Usando punteros, escriba una función que calcule la longitud de una cadena. Muestre los resultados desde maine ). Usando punteros, escriba su propia función que determine que cadena es mayor. Muestre el resultado desde maine ). Se tiene un arreglo con valores desde a hasta b. Se pide llenar en otro arreglo las direcciones de memoria de los números primos en el intervalo dado. Luego muéstrelos desde maine ). Escriba una función para invertir los elementos de' una cadena. Muestre la cadena final desde maine ). Lea dos cadenas de caracteres e intercale sus caracteres, esto es la cadena resultante tendrá un carácter de la primera cadena, seguida de un carácter de la segunda, a continuación otro carácter de la primera, seguida de otro carácter de la segunda, y así sucesivamente, hasta terminar las cadenas. La cadena resultante deberá imprimirse desde maine ). Escriba unafunción que genere aleatoriamente un arreglo dinámico de n elementos tipo char, con las letras del abecedario, y otra función que mezcle sus elementos. Deberá imprimir el resultado desde maine ). Use punteros. Se tiene un conjunto de datos, y se desea mezclarlos pero sin alterar el arreglo original. Para esto, se crea un arreglo de punteros, los cuales se mezclan. Escriba un programa que haga esto. Cree un arreglo de caracteres de manera dinámica, luego usando punteros y funciones lea n caracteres, imprima todos los caracteres, y muestre sus direcciones de memoria. Escriba un programa en el que maine ) invoque a una función que lea la cantidad de filas y columnas de una matriz; luego, asigne memoria dinámicamente para almacenar la matriz, invoque a una función que lea los datos y finalmente, imprima las direcciones de memoria de cada elemento de la matriz.
Archivos
César Liza Avila. No nw!es la producción intelectual, no copies éste libro.
i
l
J
An:hivos 201
Solución: Hasta el momento solo hemos almacenado datos en memoria, ya sea cuando utilizábamos variables, arreglos o estructuras. Cada dato 10 leíamos desde el teclado o lo asignábamos dentro de nuestro programa. Sin embargo, también podemos leer los datos desde un archivo, mejor aún hacer persistentes los datos y resultados de nuestros programas almacenándolos en archivos. Existen diversos métodos para manejar archivos. Nosotros hablaremos solo de la forma estándar, y dentro de ellas el acceso byte x byte y en bloques. No hablaremos de los objetos stream para manejo de archivos, ni sus métodos pues esto implicaría una explicación adicional sobre programación orientada a objetos, la cual tratamos en otra obra. En su lugar utilizaremos las funciones estándar provistas por el archivo de cabecera stdio.h, y eventualmente en io.h. ¿Qué es un flujo o corriente? Son canales de comunicación entre el archivo y el programa. El C/C++/C# ve cada uno de los archivos simplemente corno un grupo de secuencia de bytes sobre los cuales hay que abrir una corriente o flujo (stream). El flujo de entrada estándar (stdin) permite que un programa lea datos desde el teclado, el flujo de 'salida estándar (stdout) permite que un programa imprima datos en la pantalla, rtúentras que un flujo se relaciona con un archivo utilizando una operación de apertura fopen( ), y se desliga del archivo con una operación de cierre felose(). Todos los archivos terminan con un marcador de fin de archivo, dado por la constante EOF o por la función feof( ). Formas de apertura de archivos Podemos abrir archivos en dos diferentes formas: Texto: Utiliza conversiones de algunos caracteres. Es decir, puede no haber correspondencia entre lo que se envía al flujo y lo que se escribe o lee en el archivo. Por ejemplo: el salto de línea en archivo se representa por 2 caracteres: retorno de carro (ASCII 10) y salto de línea (ASCII 13). © César Liza AvUa. Mis libros son económicos, no hay
rt.l,:ÓIl
para copiarlos.
202 Algoritmos y su Codificación en C++. Valumen 2.
César Liza A vila
Archivos 203
Binario: Tiene una correspondencia directa entre lo que se envía y lo que
se escribe en el archivo, es decir los bytes que lees (o escribes) son idénticos a los que están almacenados en disco. Sugerencia: Si usa un archivo .txt utilice el modo texto en caso contrario use el modo binario.
feof( )
Devuelve verdadero si fin del if( !feof(pfich) ) archivo
Modo de Apertura de archivos Un puntero a un dato tipo FILE, nos permite leer y escribir un flujo o corriente (stream). Este tipo es una estructura definida en stdio.h. Sintaxis: FILE * fopen(char *f, char *modo); Ejemplo: FILE *pfich=fopen("c:\\a.txt", "w");
Nuestro programa hace lo pedido. La función crearArchivo( ), abre una corriente ( fopen( ) ) para grabar en modo texto ("wt") y usa la getchar( ), macro definida en stdio.h, para capturar el carácter pulsado y mostrarlo por pantalla, dicho carácter es colocado hacia el fichero ( putc(car, fichero) ). Esto se repite mientras este no sea el final del ingreso (while(car!=EOF)). El terminador del ingreso se puede producir mediante el teclado, presionando la tecla F6, o en su defecto con la combinación de teclas Ctrl + Z, seguida por la tecla ENTER. Finalmente, cerramos la corriente con fclose( ).
_.12~I!U~~~~~~~()~()_t~JS~~.~l~~c_~i.~<,)9e.be ~_~.
____ __
"w" ó "wt" : Abre para escritura modo texto. Si archivo no existe se crea. , existe se crea de.................. nuevo .... _.' ~~~
""···~-···""·~."~~··Y_"
.".~
.. ' .
. ........_.........." ,.. ............. ,-_ .........._.'" ~
~
..
... .
"a Ó Itat" ; :A.~~~. p.~~~. .~~.~.rtº~.~. .~, .f.~~.~~.. ~~.~ . ~.~.~.~.~.Y..9.. .9.~. . ~.~.?S~.9..~.. §.~...~lO existe se crea. "rb" ..... A~E~ llI1a.~~~i.y()b.iI1¡¡Ei()Pa.rélJ(!(;tll~i1~]~J_a.Ec:;!~iy()~~~~existir. . tl
"wb" "ab"
¡
Contenido del archivo {
Abre un archivo binario para escritura. Si el archivo no existe se
.1 sr.~3l:~si ~~i.~t,~~~~e..~t~y~ pa~¡¡ <::E~¡¡r.!(). .~~.I1Il.~Y9.·. .......... : Se abre un archivo binario para escribir al final de él. Si el archivo i no existe se crea.
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
El ingn'sn termina prcsinmllldo F{i
© César Liza A vila. Mis libros son económicos, no hay razón para copiarlos.
204 Algoritmos y su Codifiración en C++. Volumen 2, .
Archivos 205
César Liza Avila
Solución: . La función verArchivo( ), abre un archivo para lectura en modo texto ( fopen(arch, "rt") ), cuyo camino y nombre se almacena en arch. El flujo que nos dirige hacia el contenido del archivo lo indica el puntero de tipo FILE llamado pfich. En caso de no encontrar el archivo, sonará un pitido na'), mostrará el correspondiente y retornará a maine ). En caso contrario, toma cada carácter ( caracter=getc(pfich) ) y lo muestra en pantalla. Algunas funciones y macros tales como getc( ), devuelven -1, cuando detectan el fin del archivo, ya que la constante EOF que indica el final de un archivo ha sido definida en stdio.h como -1. Esto lo utilizamos para termiriar la lectura. Finalmente, cerramos el camino hacia los bytes del archivo con fclose( ).
Solución: En la función copiaArchivo( ), abrimos el archivo de origen en modo de lectura en binario ("rb"), pues deseamos una copia idéntica, sin que haya una conversión de caracteres. El archivo destino tendrá que ser abierto para escritura ("wb").
El bucle simplemente toma cada byte de un archivo y los envía hacia otro. En vez de usar la constante EOF, pudimos haber usado la función feof( ), para detectar el final del archivo, tal como se continuación: while( !feof(pfich 1) ) {
Finalmente cerramos archivos abiertos fcIose( ).
(J César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza A vila. Mis libros son económicos, no hay razón para copiar/os,
206 Algoritmos y su Codificación en C+ +. Volumen 2.
César Uza A vila
Archivos 207
Luego de abrir el archivo e indicar la cantidad de bytes que contendrá cada uno de los archivos finales, y ya entrando al bucle, debemos generar el nombre del primer archivo destino, que tendrá el mismo nombre que el archivo origen, pero agregándole la extensión ".0", es por la necesidad de las instrucciones
Solución:
itoa(nro, eNro, 10); strepy(destino, origen); streat(destino, ". "); streat(destino, eN ro);
la función itoa( ), cuyo prototipo se encuentra en stdlib.h, toma el primer argumento entero y lo convierte en una cadena que almacenaremos en el segundo argumento. El tercer argumento indica la base en la cual se expresará el resultado. Una vez generado el nombre del archivo lo abrimos para grabar en binario. El bucle for( ), toma n caracteres del archivo origen (car=getc(pf» y si no se llega al final de archivo ( if (feof(pt) ), lo colocamos en el archivo de salida actual ( putc(car, ps) ). @
César Liza Avila. No mate,', . producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
-
I
1 208 Algoritmu,\ y su Codificación en C++. Volumen 2.
Archivos 209
César Liza Avila
Para unir los archivos debemos ingresar el primer archivo en que fue dividido el archivo original, o sea el de extensión "Jl". La función Unir( ), abre primero este archivo y copia cada uno de sus bytes hacia el archivo destino, luego toma el archivo con extensión ".1", copia sus bytes, toma el archivo con extensión ".2" y así sucesivamente, hasta ya no encontrar más archivos correlativos. La creación de los nombre~ correlativos, 10 hace las instrucciones: nro++; itoa(nro, cNro, 10); strcpy(origen,origenO); strcat(origen, "."); strcat(origen, cNro);
El bucle while(l»), muestra el archivo que esta siendo procesado, tomando cada uno de los bytes (while(!feof(pf) ) y los guarda en el archivo de salida, luego intenta abrir el siguiente archivo, si no lo encuentra sale del bucle (if (pf==NULL) break;)
© César Liza A vila. No mates la producción intelectual, no ('opies éste libro.
Solución: En este programa usamos dos nuevas funciones: read( ) y write( ). Función fl'cad: Sintaxis: size_t fread(void ·puntero, size_t tamaño, size_t nregistros, FILE ·fichero); Trabaja con registros de longitud fija. Lee desde un fichero uno o varios registros de la misma longitud ya partir de la posición actual en el archivo. El valor de retomo es el número de registros leídos, de tipo size_t que está definida como tipo unsigned ¡nt. Sus parámetros son: • Un puntero a la zona de memoria donde se almacenarán los datos leídos • El tamaño de cada registro El número de registros a leer • Un puntero a la estructura FILE del fichero del que se hará la lectura
Función fwritc Sintaxis: size_t fwrite(void ·puntero, size_t tamaño, size.J nregistros, FILE ·fichero); Trabaja con registros de longitud constante y forma pareja con fread. Escribe uno o varios registros de la' misma longitud hacia un fichero. El valor de retomo es el número de registros escritos, de tipo size._t que est¿ definida como tipo unsigned int Sus parámetros son: • • • •
Un puntero a la zona de memoria donde se encuentran los datos El tamaño de-eada registro El número de registros a grabar Un puntero a la estructura FILE del fichero del que se grabará
Dentro de un archivo un conjunto de bytes pueden representar un registro.
© César Liza A vi/a. Mis libros son económicos, no hal' rm,ón para copiarlos.
210 Algoritmos y su Codificación en C++. Volumen 2.
Archivos 211
César [,iza A vi/a
El programa guarda los datos en c:\datos.txt. En fopen( ), son necesarios las 2 barras invertidas \\, pues una barra, indica un carácter de escape. Luego, leemos n elementos de tipo struct registro y los guardamos en el archivo c:\datos.txt, mediante fwrite( ), como el primer argumento debe ser la dirección de memoria donde se encuentran los datos hacemos ®. El segundo argumento, es el tamaño en bytes que debemos tomar a partir de esa dirección de memoria; el tercer argumento, indica cuántos bloques de ese tamaño vamos a grabar y, el cuarto y último argumento, es el stream, al cual dirigimos nuestros bytes. Ahora procedemos a leer los datos del archivo y mostrarlos por pantalla. La función fread( ), lee bloques de bytes desde el stream indicado. El primer argumento es la dirección de memoria a partir de la cual se llenarán los datos; el segundo, es el tamaño en bytes de cada bloque, en este caso es el tamaño del registro; el tercero, es el número· de bloques a leer, en este caso leemos de registro en registro por lo que será 1, Y el cuarto argumento, es el stream desde donde vienen los datos. En base a este programa, podemos escribir las funciones
Solución: La función verArchivoHexa( ), luego de abrir el archivo para lectura en binario, lee en bloques de 16 bytes mediante fread( ), y mientras no sea fin de archivo. Esta función toma 4 parámetros, el primero, es la zona de memoria en donde se almacenará lo leído; el segundo indica el tamaño de cada elemento, el tercero la cantidad de elementos que serán leídos; el cuarto y último argumento el stream desde donde serán leídos los datos.
La función fread( ), intenta leer un bloque de bytes de tamaño conocido (en este caso 16), y devuelve la cantidad de realmente elementos leídos.
para grabar y leer en disco, y combinarlas con las funciones del Problema 3.5 para obtener una base de datos.
© César Liza A vila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
212 ALgoritmos y su Codificación en C++. Volumen 2.
Archivos 213
César Liza A vila
El primer bucle for, imprime cada byte leído en 3 espacios y en hexadecimal. La expresión setw( 3*(16-bytesLeidos) + 2) permite dejar una cantidad de espacios en blanco adecuada entre los códigos ASCII impresos en hexadecimal y sus respectivos símbolos. Esto es particularmente necesario en el último bloque leído, puesto que los bytes tomados pueden ser menores que 16, debiendo imprimir varios espacios en blanco' '. El segundo bucle for, imprime los símbolos, para esto pregunta si el código ASCn del carácter es mayor que 31, en cuyo caso será un carácter imprimible procediendo a mostrarlo; mientras que, en caso contrario, será un carácter de control por lo que solo imprime un punto (.).
Solución:
Finalmente, cerramos la corriente mediante fdose( ).
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
'1
214 Algoritmos y su Codificación en C+ +. Volumen 2.
Archivos 215
César Li:¿a Avila
La función comprimir( ), toma cada carácter y lo compara con el (ir anterior (carAnt==carAct) ), en caso de ser el mismo 'aumenta la cuenta de caracteres iguales si esta (Iargo++), cantidad es más que 255, lo grabamos puesto el número máximo que podemos almacenar en un byte es 255. Si dos caracteres consecutivos son diferentes, y la cantidad de veces que se repite carAnt es mayor que 3, entonces los guardarnos codificados. Si no es mayor que 3 (1 ó 2) los guardamos sin ningún tipo de codificación. Al salir del bucle
Si por ejemplo, tenemos el archivo a.txt en la unidad C:, cuyo contenido se I muestra con el Notepad.
y ejecutamos el programa,
resultará
el cual en realidad ya no es un archivo de texto puesto que contendrá caracteres no imprimibles que no se presentan en los archivos de texto. Así que nos ayudamos de nuestro Visor Hexadecimal (Problema 7.5), para ·mido. uedaría el archivo
while.(carAct!=EOF),
nos aseguramos grabar los últimos caracteres, es por ello la repetición fuera de while del bloque if else.
Finalmente, cerramos todos los archivo mediante fcIoseall( ).
© César Liza A vilo.. No mates la producción intelectual, no copies éste libro.
Por ejemplo, las repeticiones del carácter a, han sido codificados como OxO, Ox8 y Ox61, donde O es el indicador de codificación, 8 es la cantidad de repeticiones, y Ox61 o sea 6*16+1 =97 en decimal, es el código ASCII del carácter 'a', los 3 espacios en blanco Ox20 (32 en decimal) no se codifican, pues codificados ocuparán también 3 bytes. © César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
Archivos 217 216 Algoritmos y su Codificación en C++. Volumen 2.
César Liza Avila
La función descomprimir(), es bastante simple, lee un byte y pregunta si es O, en caso de serlo pregunta por el siguiente byte, el cual indica la cantidad de veces que se repite el carácter siguiente, y luego lee el siguiente byte que es el carácter a repetir. Mediante el bucle for, graba el carácter tantas veces corno se indique.
Solución: Para mostrar los datos de un archivo guardados con algún formato comercial, es necesario conocer dicho formato.
El Formato ZIP Todo archivo .zip consta de 3 zonas bien definidas: La Data, el Directorio y el Control. La Data En esta zona, se almacena cada uno de los archivo~ comprimidos. Para cada "1 ar a 1a mostrad a en 1a ta bl a sIgUIente: arc h'IVO, eXIste una estructura SUTIl Byte
O 1 2 3 4567 8 9 10 11 12 13 14 15 16 17 18 192021 22232425262728 29
Carac 80 75 03 04 ? ? ? ? M ? H H F F C C C C T T T T I
I
I
I
L ? ?
1
30 ...
? NomArch Dalos Compr
Bytes O al 3: Contienen los caracteres P (ASCII 80) Y K (ASCII 75), seguidos por los ASCII 03 Y 04 que confirman el inicio de un archivo comprimido. Byte 8: indica el método de compresión utilizado. Si contiene el ASCII O, no se utilizó ningún método, solo se almacenó el archivo porque es muy pequeño y no necesita compresión. El Winzip utiliza el ASCn 8, para indicar su método de compresión. Bytes 10 y 11: Guarda el número que representa la hora de almacenamiento del archivo original y como es costumbre en la familia PC utilizan el almacenamiento inverso; es decir, el byte signado como 10, es el menos significativo, mientras que el más significativo es signado como 11. De esta manera, el número que representa la hora será: NroHora (byte 11)*256 + (byte 10) Además sabemos, por lo descrito en el Capitulo 4 sobre manipulación de bits, que: NroHora =Horas*2048 + Minutos*32 + Segl2 Por lo tanto si conocemos NroHora, podemos obtener las horas como la parte entera resultante de dividir NroHoras/2048. Esto lo conseguimos por corrimiento de bits.(un corrimiento de un bit a la derecha es una división
En caso de no ser O, entonces el carácter se coloca directamente en el archivo destino.
=
© César Liza Avda. Mis libros son económicos, no hay razón para copiarlos.
© César Liza Avda. No mates la producción intelectual, no copies éste libro.
•
-----l 218 Algoritmos y su Codificación en C+ +. Volumen 2.
Archivos 219
César Liza Avila
Byte 27: Almacena la longitud en bytes de la ruta yel nombre del archivo. Byte 30 y siguientes: Guardan la ruta y el nombre del archivo. Bytes después del nombre del archivo: Inmediatamente después del nombre del archivo, se encuentra el contenido del archivo, pero compriuúdo.
entre 2), lo mismo es válido para obtener los Minutos y los Segundos. En lenguaje C/C++/C#, sería Horas:: NroHora»]], pues 2048 es '.'2 5, pues ahora sólo nos mteresa los núme;os menores o iguales a 2047 (07FFh, en hexadecimal) pero divididos entre 32 o sea "2 elevado a la 5", Y finalmente Segundos == (NroHoras& Ox001F)*2, ya que ahora solo nos interesa los números menores o iguales . que 31 (001Fh, en hexadecimal) pero multiplicados por 2. Bytes 12 y 13: Almacenan el número que representa a la .fecha en la que se creó el archivo, siempre en formato inverso, es decIr el b~te menos significativo es el nombrado como 12. De esta manera el numero que representa la fecha será: NroFecha == (byte 13)*256 + (byte 12) . . Recordar( por el estudio sobre manipulación de bits q.ue hIZO en el Capítulo 4, que los 2 bytes de NroFecha, con sus 16 bIts, e~pezando desde la derecha significan: los 5 primeros bits representan el dJa (0-32), los 4 bits siguientes el mes (0-16), y los 7 bits restantes el año (0-128). Para toda la fauúlia IBM= PC, los años se cuentan a partir de 1980, por lo cual conociendo NroFecha podemos 9btener el día, mes y año de la siguiente forma:
•
I
EL DIRECTORIO Es una zona muy similar a la DATA, pero sin los datos comprimidos, además contienen algunos bytes adicionales. Leyendo esta zona, podemos obtener rápidamente el listado de todos los archivos comprimidos, lo cual es realizado por nuestro programa. Para cada archivo comprimido, existe una estructura como la mostrada en la figura si uiente, ubicados una a continuación de otra.
El CONTROL
Día =(NroFecha &Ox001F); Mes =(NroFecha& Ox01EO) »5; Año =(NroFecha & OxFEOO) »9 + 1980;
Bytes 14, 15, 16 y 17: Estos bytes almacenan el che~ksu,m. o suma de comprobación (también llamado Código de RedundanCIa Clcltca o ~~C) los cuales son una serie de bytes que se generan durante la compreslOn e identifican al archivo en prevención de alguna alteración. Cuando .se intenta descomprimir el archivo, se vuelve a calcular estos b~te.s, SI resul~an diferentes de los almacenados, entonces el archivo compnmldo ha sufndo alguna alteración y no podrá ser descomprimido.
Bytes O a 3: Se inician con los caracteres P y K, seguidos por los ASCII 05 Y 06 que confirman el inicio del Control. Bytes 8 y 9: Indican el número de archivos comprimidos contenidos en el archivo ZIP, como siempre en almacenauúento inverso. Bytes 10 y 11: Contienen Una copia de los bytes 8 y 9. Esta redundancia se coloca con fines de recuperación. Bytes 16, 17, 18 y 19: Contienen el byte de inicio del EL DIRECTORIO del archivo comprimido, almacenado inversamente. El byte de inicio de EL DIRECTORIO será:
Bytes 18, 19, 20 y 21: Guardan el tamaño original del archiv.o .en byt~s, usando el almacenamiento inverso. De esta manera el tamaño ongmal sera: TamNormal = (Byte21)*256*256*256 + (Byte20)*256*256 + (Byte19)*256 + (Byte18) o también: TamNormal = (Byte21)<<24 + (Byte20)«16 + (Byte19)«8 + (Byte18)
ByteInicio = (Byte 19)*256*256*256 + (Byte 18)*256*256+ (Byte 17)*256 + (Byte 16) o también: ByteInicio = (Byte 19)«24 + (Byte 18)«16+ (Byte 17)«8 + (Byte 16)
Bytes 22, 23, 24 y 25: Almacenan el tamaño en bytes del archivo compriuúdo en formato inverso, esto es:
Este byte nos servirá de punto de entrada a nuestro programa, pero antes de mostrarlo, necesitamos conocer algunas nuevas funciones:
TamComprim ,,;, (Byte25)*256*256*256 + (Byte24)*256*256 + (Byte23)*256 + (Byte22) o también TamComprim = (Byte25)«24 + (Byte24)«16 + (Byte23)«8 + (Byte22)
© César Liza A vila. Mis libros son económicos. no hay razón para copiarlos.
© César Liza Avila. No mates la producción intelectual. no copies éste libro.
.
~.
Archivos 221 220 Algoritmos y su Codificación en C++-. Volumen 2.
César Liza A vila
Función fseek: Sintaxis: int fseek(FILE *fichero, long in! desplazamiento, in! origen); Ubica el cursor del fichero para leer o escribir en una posición deseada. Retoma cero si tuvo éxito, y diferente de cero si hubo error.
Parámetros: • Puntero tipo FILE del fichero cuyo cursor de lectura/escritura deseamos mover • el valor del desplazamiento • el punto desde el que se calculará el desplazamiento
El parámetro origen puede tener tres posibles valores: SEEK_SET se desplaza desde el principio del fichero. El primer byte del fichero tiene un desplazamiento cero. SEEK_CUR el desplazamiento se cuenta desde la posición actual del cursor. SEEK_END el desplazamiento se cuenta desde el final del fichero.
Función ften: Sintaxis:
long int ftell(FILE *fichero);
Nos da la posición actual del cursor de lectura/escritura de un fichero El valor de retomo será la posición actual, o -1 si hay algún error. El parámetro de entrada es un puntero a una estructura FILE del fichero cuya posición deseemos.
El programa lo presentamos a continuación, cada línea está debidamente comentada por lo que no necesita mayor explicación.
© César Liza A vi/a. Mis libros son económicos, no hay razón para copiarlos.
© César Liza Avila. No mates la producción intelectual. no copies éste libro.
•
222 Algoritmos y su Codificación en C++. Volumen 2.
César Liza A vi/a
Archivos 223
Solución: El ingreso y salida de bajo nivel, está provisto en io.h, y en direct.h, algunas de sus funciones las mostramos aquí.
c::t'''c:\fJO(uments: <11m Si'lting:e;;\Atlmifi:!$trilÜor\D~boo\Vptp
Ingrese archIvo· MultMatrlces.exe MultMatrices. cpp a.txt 19/11/2005 Press any key to
{ó:!&()f'T?~;:~*,
'"
~~
-,
i®~,~o;¡;~
'<
.,,"]
c:\z.zip 23/7/2005 2:5:26 192583 bytes 38089 bytes 94 2317/2005 2: 9: 58 1493 bytes 531 bytes dc 2:57:4 18 bytes 18 bytes e55belde continue
El
© César Liza Avila. No mates la producción intelectual, no copies éste libro.
© César Liza Avila. Mis libros son económicos,
•
110
hav razón para copiarlos .
Archivo:; 225
224 .Alf!,oritrno~ )' su Codifi,'acíón
f!n
C++, Volumen 2,
('ésar Uza A vila
El programa lee una carpeta, y nos cambiarnos a ella mediante 3hdr( ), para luego poder listar cada entrada de este directorio.
1. Lea un carácter y muestre todas las posiciones dentro del archivo en las que se encuentra. 2. Lea un archivo y guarde la suma de verificación en otro archivo. La suma de verificación (cltecksum) es una serie de bytes que ident~fican al archivo, existen diversos algoritmos, lo más fácil, aunque no muy recomendable, es sumar los UNICODE de cada carácter del archivo. 3. Lea 2 archivos y únalos hacia un tercer archivo. 4. Dos archivos parecen ser iguales. Encuentre los bytes que son diferentes indicando su ubicación dentro del archivo. 5. ¿ Cuántas líneas y espacios en blanco aparecen en un archivo de texto determinado? 6. Busque todas las ocurrencias de una palabra dentro de un archivo. 7. Se desea esconder un mensaje corto dentro de un archivo (esta técnica se conoce en general como esteallografía). Para ello se elige el siguiente algoritmo: lea el mensaje y coloque cada una de sus letras en las posiciones dadas por los números primos, el resto de posiciones serán dadas por el contenido del archivo original. Escriba una función que haga esto y otra que muestre el mensaje oculto. Por ejemplo si el mensajefue "hola mundo", en e/archivo se almacenara como:
En Iist.aDir( ), definimos una variable del tipo struct _finddata, definida en ¡o.h, puesto que para cada archivo tenemos un conjunto de 'propiedades a los cuales podemos acceder. La función _ñndfirst( ), obtiene el primer archivo cuyo nombre cumpla con el patrón indicado, en este caso "*. *", todos los archivos; y coloca sus datos a partir de la dirección de memoria indicada por el segundo argumento, en nuestro caso &archivo. Esta función devuelve un entero largo, que servirá como manejador o handle (nuestra variable listo), para las futuras referencias. Si listo, es diferente de cero, significa que encontró algún archivo con el patrón indicado, procediendo a mostrar sus datos. Si es un directorio (archivo.attrib ==_A_SUBDlR), se indicará como , luego se procede a buscar el siguiente archivo que cumpla con el mismo patrón de la primera bÚsqueda, ahora indicada por listo, mostrando su nombre y hasta no encontrar más archivos. Aquí, sólo mostramos el contenido de un directorio específico, pero es posible recorrer todos los archivos del disco, incluyendo todos sus subdirectorios, para ello necesitará usar recursión, terna que es tratado extensamente en mi libro Estructura de Datos con C++.
8.
9.
Una forma de encriptamiento muy utilizado es sustituir cada carácter por otro. El programador puede definir una tabla que contenga el carácter original y otra tabla que contenga el carácter por el cual se va a sustituir. Escriba un programa que permita encriptar y desencriptar un ARCHIVO por SUSTITUCION con una tabla de equivalencia definida por el programador. Un virus es identificado mediante una cadena característica extraída de su código. Un antivirus inspecciona los ficheros en busca de cadenas asociadas a virus. Escriba un programa que lea una serie de códigos ASCII correspondientes al código viral y los busque en un archivo.
© César Liza Avda. Mis libros son económicos, no hay razón para copiarlos.
© César Liza Avila, No mates la producción intelectual, no copies éste libro.
.
226 Algoritmos y su Codificaci6n en C++o Volumen 2.
Archivos 227
César Liza A vilo
Bytes O, 1,2,3 contiene la palabra "RIFF" Bytes 4, 5, 6 7 tamaño total del archivo en bytes menos 8 (esto es porque no incluye los primeros 8 bytes) Bytes 8, 9, JO, 11, 12, 13, 14 y 15 contiene la palabra "WAVEJ1nt " note que hay un espacio después de la t. Bytes 16,17,18 y 19 Formato para PCM vale 16 Bytes 20, 21 Fonnato para PCM vale 1 Bytes 22 y 23, si es 1 es mono, 2 es estéreo. Bytes 24, 25, 26 y 27, frecuencia de muestreo puede valer 11025, 22050 ó 44100. Bytes 28, 29, 30 y 31 indica el número de bytes por segundo que se debe intercambiar con la tarjeta de sonido para una grabación o reproducción. Bytes 32 y 33, número de bytes por captura 1, 2 ó 4. Bytes 34 y 35, número de bits por muestra, 8 ó 16 Bytes 36, 37, 38 y 39 contiene la palabra "data" Bytes 40,41,42 y 43, número total de bytes que ocupan las muestras. Lea un archivo WA Vy muestre los datos de su cabecera 16. Escriba una función que grabe hacia· un archivo los alumnos aprobados y en otro los desaprobados (código, apellido paterno, apellido materno, promedio) 17. Escriba un programa que lea los datos de alumnos almacenados en un archivo, los ordene alfabéticamente (código, apellido paterno, apellido materno, promedio) y los guarde ordenados en otro archivo. 18. ~e tiene un código de libro char(5), un título de libro char(30), y una ~lsta ~~ palabras clave char(70) que indican aquellas palabras que Identifican al contenido de un libro. Cree un archivo con varios registros y luego dada una palabra diga en que libros se encuentra. 19. Escri?a una función que elimine lógicamente un registro (código, apellldo paterno, apellido materno, estadoJ, de un archivo. Luego, escriba una función que empaquete un archivo que almacena los registros de n alumnos, Empaquetar significa crear un archivo con todos aquellos alumnos que no han sido eliminados lógicamente. Sugerencia: use el estado para indicar si esta o no eliminado. 20. Cree su propio formato que permita almacenar la definición de la estructura de registro (nombre del campo, tipo de dato, longitu.d, etc.), luego lea n registros y guárdelos a un archivo usando suformato.
10. ¿Cuántos caracteres de cada tipo hay en un archivo dado? Sugerencia: defina un arreglo de 256 elementos, el subíndice indicará el valor ASCII del carácterleido 11. Escriba un programa que dado un archivo diga que software lo creó. Su programa debe identificar al menos 5 formatos. Sugerencia: Lea los primeros bytes del archivo y busque algo que identifique a todo el conjunto de archivos creados con el mismo programa. Por ejemplo, los archivos creados con WinZip empiezan con PK, todos los ejecutables empiezan con MZ, ya que al salir el DOS 2.0 Mark Zbikowski le puso su siglas a los ejecutables. 12. Cree un archivo que contenga una cabecera que ocupe 15 bytes. La cabecera está formada de la siguiente manera: los 3 primeros para las iniciales de la empresa que creo el software, los 2 siguientes para las iniciales del software, los 2 siguientes la versión del software, los 4 siguientes el año, los 2 siguientes el mes y finalmente los 2 siguientes el día de lanzamiento. Luego de los 15 bytes aparecen los datos almacenados. Lea un archivo cualquiera y verifique si tiene el formato definido, en cuyo caso muestre su cabecera. 13. Un archivo en formato DBF (FoxPro, Visual FoxPro, Dbase, FoxBase, Clipper, etc.) tiene una cabecera de 32 bytes (O al 31) al inicio del archivo, que indica lo siguiente: " Bytes con subíndice 7, 6, 5 y 4: cantidad de registros de la tabla Bytes con subíndices 9 y 8: byte a partir del cual se inician los datos Bytes con subíndices 11 y JO: longitud del registro, incluyendo la marca del borrado. Escriba un programa que lea un archivo DBF y muestre la cantidad de registros, el byte de inicio de los datos, la longitud de cada registro. Recuerde que los números se almacenan en formato inverso, tal como se vio en el Problema 7.8. 14. Es costumbre que cada sistema tenga un archivo de usuarios, cada uno de los cuales tiene un password. Sin embargo, dicho password no se guarda en disco, sino que en su lugar se almacena un valor hash, o similar. Cree un archivo que contenga los ID de usuario y la firma del password (un hash, checksum o un CCV). Luego lea login y el password y diga si es un usuario registrado. 15. El formato WAV de Microsoft para almacenar sonidos, tiene una cabecera de 44 bytes y contiene el tipo y organización de las muestras de sonido. @
© César Liza Avila. Mis libros son económicos, no hay razón para copiarlos.
César Liza Avila. No mates la producción intelectual, no copies éste libro.
+
Acerca del Autor César Liza Avila, es un reconocido Creador de Software, investigador neto y difusor del conocimiento técnico. Tiene doble forrnación universitaria: Ing. Industrial (UNT) e In9. de Computación y Sistemas (UPAO), las cuales estudió en forma simultánea, y sin convalidaciones, obteniendo en ambas meritorios lugares. Debido a ello, es que combina los conocimientos adquiridos en ambas carreras, en la mejora de la productividad de la organización mediante herramientas automatizadas, trayendo como consecuencia la simplificación de procesos y el ahorro derivado de ellos. Ha recibido varias distinciones, por haber ocupado los primeros puestos en diversos concursos. Así, obtuvo el 1er. Puesto en el examen de ingreso UNT, en la Carrera Profesional de Ingeniería Industrial y el 3er. Puesto en el Cómputo General entre más de 17,000 postulantes" el 4to. Puesto en el examen de ingreso Ing. de Computación y Sistemas UPAO. Otras distinciones obtenidas son: 2do. Puesto en el 11 DESAFIO DE BOLSA NIVEL REGIONAL, ("1990), 'ler. Puesto a Nivel Regional del 11 DESAFIO NACIONAL DE BOLSA Y el 3er. Puesto a Nivel Nacional en el mismo .concurso (1994). Además en Octubre del 2001 recibió el reconocimiento público del Colegio de Ingenieros del Perú - Filial Trujillo, por su destacada labor de investigación y difusión del conocimiento técnico. Es un experto programador en C++, una muestra de ello son sus programas publicados en la revista "Sólo para Creadores", revista especializada en difundir la creación técnica de software. En ella muestra la sencillez didáctica de su código y su efectividad para realizar tareas que nos parecen tan misleriosas como: reparar una Boa! Record, implementar esquemas de protección contra copias, recuperar datos en discos duros que no tienen Boot, FAT o directorios, visualizar archivos en diversos formatos como los ZIP, redefinición de caracteres en modo texto, entre algunos otros. Otros lenguajes en donde presenta gran dominio son Assembler, FOXPRO, Pascal y Visual C#. Es autor del .libro "El error del milenio", y de los programas 81082000 (escrito en eH), que verifica si una computadora es compatible con el año 2000 y SOL2000 (escrito en Assembler) que resuelve el problema de compatibilidad de las computadoras respecto al año 2000, y cuyos códigos fuentes fueron incluidos en el libro y publicados por la revista Soluciones del Diario Expreso, con el objetivo de contribuir a un mejor conocimiento del problema y que la comunidad informática lo enfrente con éxito. Este libro ha recibido elogiosos comentarios de autoridades universitarias de Trujillo y de la Capital, mereciendo sendos artículos en La Industria (Trujillo) , Diario Expreso (Lima), Diario OJO (Lima), entre otros .
•
.... ~-30RITMOS y SU CODIFICACION EN C++-. Este libro nace con la idea de servir como texto introductorio a un curso de programación. Tiene caracterfsticas importantes dignas de resaltar que'lo diferencia de otros. Contiene abundantes ejemplos divididos por temas en donde la complejidad va gradualmente en aumento, con explicaciones detalladas de la lógica utilizada, diagramas Nassi-Schneiderman (N/S) y codificación en C++. Sus 109 problemas resueltos y 121 propuestos hace que esta obra sea eminentemente práctica y por la manera como está estructurado el libro podrá detenerse en cualquier momento, para reflexionar sobre lo aprendido y tomar un merecido descanso con la certeza que al retomar su lectura no habrá perdido la secuencia del. mismo.
Ha desarrollado sistemas de información en diversas instituciones tales como: Escuela de Ingeniería Industrial y Sistemas-UNT, Carrocerías Morillas, Universidad Privada "Antenor Orrego", Municipalidad Distrital "El Porvenir", Municipalidad Distrital Gran Chimú - Cascas, Consorcio Pesquero Cal'Olina. Asimismo, a realizado Au~itorias a sistemas de información en instituciones como la Municipalidad Distrital "El Porvenir", e ITOS-SHELL No sólo es un experto en la construcción técnica de sistemas de información, sino de software de . propósito general como un utilitario para la verificación la integridad de archivos DBF (VERDBF.EXE) que es más rápido y más versátil que el FileFix® de las Utilidades Norton y el NPACK.EXE un software de compresión de datos utilizado por el Sistema Compuleg de Editora Normas Legales, ambos programados en y Assembler.
e
Contenido: 1. La estructura secuencial 2. La estructura condicional simple 3. La estructura de selección múltiple 4. La estructura repetitiva mientras 5. La estructura repetitiva hacer mientras 6. La estructura repetitiva para 7. Arreglos unidimensionales 8, Funciones definidas por el usuario
Ha ocupado diversos cargos como Jefe de Sistemas del Convenio UNTMDP, responsable de Sistemas del Area Contable de la Universidad Privada "Antenor Orrego", Director de Proyectos de Sistemas de Información en MastersoftPerú, Director de la revista Sólo para Creadores. Se ha desempeñado como asistente de cátedra en los cursos de Software de Aplicación I y Software de Comunicación en Universidad Privada "Antenor Orrego"; ha dictado los cursos Lenguaje de Programación y Manejadores de Base de Datos en la Unidad de Tecnología de la Información y de Sistemas UTISYS (Ex-Centro de Cómputo de Ingeniería Industrial de la Universidad Nacional de Trujillo); asimismo, los cursos de Análisis y Diseño de Sistemas, y Lenguaje de Programación 111, en el 1ST ABACO-Trujillo; también, cursos como: Algoritmos, Técnicas de Programación, Estructura de Datos Avanzados y Programación Orientada a Objetos en la Universidad Privada del Norte, entre muchos otros. Ha sido expositor en más de 30 eventos a nivel nacional destacando entre ellos el VII CONEIS realizado en septiembre de 1999 en Cajamarca con el tema: Principios dé Construcción de Software y el Error del Milenio; y el '11m CONE~S realizado en agosto de 2000 en Trujillo, con el tema: Esquemas de Pro(eccíón Contra Copia. También ha expuesto el Lenguaje Unificado de Modelado en diversas ciudades como Trujillo, Chimbote, Chiclayo, entre otras.'
ALGORITMOS Y SU CODIFICACION EN C++ Las Estructuras de Datos son un tema fundamental en la formación de los estudiantes de Ingeniería de Sistemas, Computación e Informática, y afines, pues nos dan un conocimiento técnico para elegir la mejor y más eficiente forma de organizar nuestros datos para la solución de problemas de uso común en prog;amación. En este libro encontrará más de 85 ejemplos completo:> 'de programas y más de 140 ejercidos propuestos que sin duda, serán de valiosa ayuda, tanto para los estudiantes como de lo:; docenfes, y constituirán fuente inagotable par3 la eld::eralJén d;;· práctica:; y exámenes. Toda:: aquella:> 'opc;ones de scftw3re .que usted corP~ programador, siempre q!Jiso i:nplementar son puestas a su alca,Ice y de manera ~&ncilla en estGcbra.
Actualmente, se dedica a la construcción de software a medida, a la asesoría en sistemas y a la difusión del conocimiento técnico mediante artículos y dictado de clases. Puede comunicarse con él a la siguiente dirección electrónica: [email protected] o visitar su pagina web: www.geoclties.com/cesaciíz
I
I !
L
l
Conteaido: 1. Recursividad 2. Ordenamiento 3. Blísqueda 4. Listas Enlazadas 5. Pilas 6. Colas 7. Arboles Binarios de Búsqueda
Otras obras del Autor
Distribución y Ventas
MODELANDO CON UML. Principios '1 Apiicadcmes. El Lenguaje Unificado de Modelado (UML) es un lenguaje simbólico para representar sistemas, Y es de uso obligatorio a nivel mundial. Todos los profesionales en Tecnologías de Información deb~~ utilizarlo Y dominarlo. Este libro le permltlra aprender rápidamente los conceptos del U~!L y sobre todo aplicarlos. A diferencia de otros libros sobre el tema, aquí encontrará más de 7íll ejemplos desarrollados Y más de 15 ejercicios propuestos que le irán guiando en el aprendizaje del UMl.
Ventas al por mayoH:: Sra. Serafina Pari de AqtH9nO Av. Wilson # 1122. Teléfono 4243986. librería Nueva fErra Jr. Gerard0.un~er# 195 (Frente a la UN!). Teléfono 3813219. lima-Perú. Sra. Al'ltoi1lia Titl) ~~~~azonas # 426 (Frente al Campo Ferial de Libros). Tlf. 4262280. limaCreaSoft Av. América Norte # 401. Teléfono (044) 221363. Trujillo-Perú.
Contenido: 1. Introducción al UML
Ventas al por menor:
2. 3. 4. 5. 6.
A NIVEL NACIONAL: UBUN En sus tiendas de todo el Pení.
Diagramas de Casos de Uso Diagramas de Clases Diagramas de Secuencia Diagramas de Colaboración Diagramas de Estado 7. DiagramasdeActividad 8. Diagramas de Componentes 9. Diagramas de Despliegue
EL ERROR DEL MILENIO Este libro además de contener una descripción completa del Problema Informático del Año 2000, aporta con soluciones, incluyendo un diskette con 01 programa para verificar si su computadora es compatible con el año 2000, Y 02 programas para resolverlo, entregando el CODIGO FUENTE de los programas.
' La gran cantidad de divertidas ¡lustraciones Y el lenguaje sencillo utilizado hacen amena su lectura, siendo de sumo interés para cualquier persona que esté inmersa en el mundo moderno, pues es imposible que no tome alguna acción respecto a este problema, ya sea que tenga responsa~ilidad en ;:;Iguna empresa o como un simple usuario.
Contenido; 1. Descripción del Y21<. 2. Sistemas no informáticos 3. Costos y aspectos jurídicos del Y2K 4. ¿Quéeslamoshaciendo? 5. Problemas con el hardware 6. Problemas con el software 7. Técnicas de solución 8. Curiosidades del milenio.
liMA: Av. Wilson En los puestos de venta libros de toda la Avenida Wilso~. Puestos Frente a la UNI En los puestos de venta de libros ubicados frente a la UNI . (
TRUJILlO: Librería El Retablo. Av. España # 2116 Librería IDEAL (Stock). Jr. Orbegoso y'Francisco Pizarro #624. CHIMBOTE: . . Fer!a de Libros. Av. Pardo # 765y # 637. Teléfono 327155 F~r/a ~opul.ar d~ Li~ros Ma!10lo. Av. Pardo # 673. Teléfon~ 342991. librerla UniVersitaria. Jr. Ellas Aguirre# 32B. Teléfoilo 344713Z. , CHICLAYO: Feria de Libros. Elias Aguirre # 413 Librería Nuevo Mundo. . Librerías de la Plazuela Elias Aguirre. PIURA: C&J Representacion:s. Centro Comercial Tres CUlt:';~3';.