Está en la página 1de 418

i

Conceptos y Ejercicios de Programación


Segunda edición
Incluye ejercicios para resolver en el juez virtual de Universidad Mayor de San Andrés

Jorge Humberto Terán Pomier


ii

M.Sc.Jorge Humberto Terán Pomier


email:teranj@acm.org
La Paz - Bolivia

Este trabajo está licenciado bajo la licencia de Reconocimiento-No comercial-Compartir bajo la misma licencia Creative
Commons 3.0.
A continuación se muestra un resumen de la licencia.
Usted es libre de: Compartir, copiar, distribuir y comunicar públicamente la obra Rehacer, hacer obras derivadas
Bajo las condiciones siguientes:
Reconocimiento. Debe reconocer los créditos de la obra de la manera especificada por el autor o el licenciador (pero no
de una manera que sugiera que tiene su apoyo o apoyan el uso que hacer de su obra).
No comercial. No puede utilizar esta obra para fines comerciales.
Compartir bajo la misma licencia. Si altera o transforma esta obra, o genera una obra derivada, sólo puede distribuir la
obra generada bajo una licencia idéntica a ésta.
Al reutilizar o distribuir la obra, tiene que dejar bien claro los términos de la licencia de esta obra.
Alguna de las condiciones puede no aplicarse si se obtiene el permiso del titular de los derechos de esta obra.
Nada en esta licencia menoscaba o restringe los derechos morales del autor.

1o Edición - Marzo 2011


2◦ Edicion aumentada y corregida - Julio 2019
Esta obra puede ser descargada gratuitamente en formato digital de http://www.academia.edu
iii

La Paz - Bolivia
Prefacio
La programación de computadoras representa un reto, para todos los estudiantes. El problema principal al
que se enfrentarán en su vida profesional es el de resolver problemas. Las capacidades que debe lograr
un estudiante son el poder enfrentarse a un problema desconocido y encontrar una solución y finalmente
traducir ésta solución a un programa de computadora. Su vida profesional requerirá de éstas habilidades
permanentemente.
En este texto se ha desarrollado los conceptos en un orden secuencial, introduciendo problemas que gradual-
mente aumentan su dificultad en base a los conocimientos de capı́tulos anteriores. A diferencia de los textos
clásicos de programación, el énfasis está puesto en los ejercicios que el estudiante debe resolver. Se espera
que estos ejercicios les permitan obtener las habilidades requeridas en la programación. Los ejercicios están
pensados para que el estudiante vea una serie de problemas diversos que lo lleven a mejorar sus capacidades.
Los problemas presentados están pensados para que se pueda realizar una prueba y comprobar si la solución
provee un resultado correcto, verificando diferentes casos de prueba. Este método de verificación es utilizado
ampliamente por sistemas de evaluación automáticos. Ayuda al estudiante a detectar sus errores cuando se
somete el programa a diferentes casos de prueba, dando una respuesta instantánea sobre los casos de prueba.
Los problemas que se han escogido, algunos han sido desarrollados por el autor y otros son una recopilación
de ejercicios de concursos de programación y otros publicados en el internet. El material presentado en este
libro puede utilizarse libremente para fines educativos, haciendo mención del autor.
Para el desarrollo del curso no requiere conocimientos avanzados de matemáticas. Solo requiere los conoci-
mientos básicos de geometrı́a, aritmética y álgebra que todo estudiante llevó en el colegio. En los primeros
capı́tulos se plantean los ejercicios en base de estos conceptos porque en opinión del autor es más fácil
programar soluciones a problemas de los cuales uno conoce las soluciones.
Los capı́tulos del 1 al 4 contienen una serie de ejercicios que ayudarán al estudiante a comprender la sintaxis
del lenguaje y codificar programas. Desde el capı́tulo 5 los ejercicios motivan a problemas reales utilizando
conceptos del lenguaje de programación. Luego se tienen algunos temas relacionados a los números primos,
que nos llevan a pensar en la eficiencia de los programas. Se incluye un capı́tulo de números de Fibonacci
que ayuda al concepto de inicialmente resolver los problemas antes de ponerse a codificar las soluciones. Se
da una pequeña introducción a la construcción de métodos y procedimientos. No es el propósito del libro
ahondar en la programación orientada a objetos.
EL libro Problemas y Ejercicios de programación fue escrito pensando en los estudiantes de los primeros
cursos, pensando en aquellos que sin experiencia previa quieren adentrarse en el campo de la programación.
La segunda parte temas que son importantes para comenzar a trabajar con algoritmos, con un enfoque en la
eficiencia. Estos temas son problemas recolectados de las clases de programación, para complementar los
conocimientos adquiridos.
Se comienza a resolver problemas recursivos que es un conocimiento necesario para trabajar ecuaciones
de recurrencia. Las ecuaciones de recurrencia son una forma de representar soluciones a problemas de
Programación Dinámica.

v
vi

Se presentan los problemas básicos para entender los fundamentos de la programación dinámica. Esta
presentación se realizó con problemas clásicos. El enfoque trata de introducir los problemas mostrando en
primera instancia un grafo dirigido ası́clico que ayude a plantear las ecuaciones de recurrencia usando menos
la intuición.
Los programas han sido desarrollados en el lenguaje de programación java y se incluyen los listados para
que el lector pueda comprender la traducción del pseudo código de las explicaciones a un código ejecutable.
Los ejercicios planteados en el texto pueden resolverse y enviarse al juez virtual de la Universidad Mayor de
San Andrés que está disponible en la dirección web http://jv.umsa.bo/.
Espero que este texto pueda ayudar a estudiantes que quieren incursionar en las competencias de programa-
ción, como un primer texto para comenzar el aprendizaje de algoritmos más complicados.
Agradecimientos
Para la edición de este texto se ha contado con la revisión y aportes de varios docentes y estudiantes de
la universidad Mayor de San Andrés, a los que agradezco por los aportes recibidos. Va un agradecimiento
especial al Lic. Lucio Torrico que ha tomado mucho de su tiempo para revisar este texto.
M.Sc. Jorge Terán Pomier.
Nomenclatura utilizada en el texto
En el desarrollo del texto a partir del capitulo de recursividad se ha utilizado la siguiente nomenclatura en la
presentación de los algoritmos.

Módulo: EL operador módulo se refiere a hallar el resto de una división. Hallar el resto de a/b se puede
representar como a mód b. En el texto se ha utilizado el operador % que normalmente se utiliza en
varios lenguajes de programación y se presenta como a %b.
Nomenclatura para estructuras repetitivas: En los lenguajes de programación la estructura repetitiva for
que comúnmente tiene el siguiente formato: for(i=a; i¡b; i++). Esta instrucción nos indica que valores
puede tomar la variable i.
En el transcurso del texto se ha utilizado la nomenclatura for i=(a,b). Esto significa que la variable i
toma valores que van desde a hasta b, sin incluir los valores a, b.
Si se quiere incluir a y excluir b. Utilizamos corchetes para indicar que ese valor se incluye y paréntesis
para los valores que se excluyen. Por ejemplo for i=[a,b), significa que i toma valores desde el valor a
inclusive hasta b que no esta incluido.
De donde se ve:

for i=(a,b) i no toma los valores de a y b


for i=[a,b) i toma el valor de a y el de b no
for i=[a,b] i toma el valor de a y el de b
for i=(a,b] i no toma el valor de a y toma el de b

vii
Índice general

Prefacio V

1 Introducción 1
1.1 El lenguaje y compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Construir y compilar un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Herramientas de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.1 Instalación de Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.2 Construir y hacer correr un programa . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.3 Estructura de directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4.1 JV-1232: Hola mundo! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4.2 JV-1366: Imprimir Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.3 JV-1423: Imprimir Potencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2 Tipos de Datos 15
2.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Entender la actividad de la programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Reconocer errores de sintaxis y de lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4.1 Ubicación en la memoria de la computadora . . . . . . . . . . . . . . . . . . . . 19
2.4.2 Variables y constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4.3 Tipos de datos implementados en clases . . . . . . . . . . . . . . . . . . . . . . . 20
2.5 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5.1 Interpretación de los datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5.2 Salida por pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5.3 Despliegue de números con formato . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.5.4 JV-1424: Codigo Ascii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.5.5 JV-1425: Imprimir PI E Au . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3 Operadores aritméticos y lectura de teclado 29


3.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2 Trabajando en binario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.1 El operador de desplazamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.2 El operador lógico and . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2.3 El operador lógico or . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

viii
ÍNDICE GENERAL ix

3.2.4 El operador lógico xor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31


3.2.5 Aplicación de manejo de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3 Trabajando con variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3.1 Ejemplos de expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.2 La clase Math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.4 Operadores de asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.5 Convertir el tipo de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.6 Lectura del teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.7 Errores de redondeo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.8.1 JV-1222: Suma de 2 números . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.8.2 JV-1223: Suma de 3 números . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.8.3 JV-1227: División entera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.8.4 JV-1230: Descomponer el tiempo . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.8.5 JV-1231: Sume un segundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.8.6 JV-1265: Monedas Británicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.8.7 JV-1370: Bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.8.8 JV-1247: Número binario reverso . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.8.9 JV-1429: Cambiar un bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.8.10 JV-1228: Mayúsculas y minúsculas . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.8.11 JV-1428: Distancia entre dos puntos . . . . . . . . . . . . . . . . . . . . . . . . . 49

4 Estructuras de control 51
4.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.2 Agrupamiento de instrucciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.3 Estructuras de control condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3.1 Estructura de control if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3.2 Operadores condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.3.3 Estructura de control if else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.3.4 Estructura de control if else if . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3.5 Conectores lógicos and, or . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3.6 Prioridad de los operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.7 Propiedades y equivalencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.8 Estructura de control ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.3.9 Estructura de control switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.4 Estructuras de control iterativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.1 Ciclo for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.2 Ciclo while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.4.3 Ciclo do while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.4.4 Ciclos anidados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.5 Lectura de secuencias de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.6.1 JV-1224 Máximo de dos números . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.6.2 JV-1225: Máximo de tres números . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.6.3 JV-1226: Temperatura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
x ÍNDICE GENERAL
4.6.4 JV-1372: Años Bisiestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.6.5 JV-1221: Tres Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6.6 JV-1373: Clasificación de Caracteres . . . . . . . . . . . . . . . . . . . . . . . . 74
4.6.7 JV-1374: Intervalos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.6.8 JV-1249: Primeros números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.6.9 JV-1247: Número binario reverso . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.6.10 JV-1248: Números armónicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.6.11 JV-: 1289: Lectura1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.6.12 JV-1290: Lectura2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.6.13 JV-1291: Lectura3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.6.14 JV-1292: Lectura4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.6.15 JV-1250: Validar fechas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.6.16 JV-1018: Operadores Relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.6.17 JV-1384: Fanático del refresco . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.6.18 JV-1385: Puertas Vaivén . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4.6.19 JV-1263: Adivinanzas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.6.20 JV-1266: Polinomios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.6.21 JV-1444: Pares de unos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

5 Cadenas 91
5.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.2 Recorrido de la cadena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.3 Métodos de la clase cadena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.4 Lectura del teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.5 Convertir de cadena a Integer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.6 Manejo de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.7 Como procesar una lı́nea de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.8 Ejemplos de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.9.1 JV-1229: Comparar Palabras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.9.2 JV-1279: Cifrado César . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.9.3 JV-1012: Palı́ndrome Extendido . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.9.4 JV-1006: Cadena Bailarina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.9.5 JV-1330: Rotando Cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.9.6 JV-1246: Contar letras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.9.7 JV-1110: Chewbacca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.9.8 JV-1066: Jakiado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.9.9 JV-1375: Buscando el Oro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
5.9.10 JV-1435: El k-esimo dı́gito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.9.11 JV-1436: El mayor posible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.9.12 JV-1348: Esperanto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.9.13 Tapices de Colores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
5.9.14 JV-1363: Flecha más Larga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
5.9.15 JV-1364: Fácil SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
5.9.16 JV-1445: Quitar puntuación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
ÍNDICE GENERAL xi

5.9.17 JV-1446: Espejo del Reloj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

6 Aritmética Básica 119


6.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.2 Sistema de numeración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6.2.1 Teorema fundamental de la numeración . . . . . . . . . . . . . . . . . . . . . . . 120
6.2.2 Composición y descomposición de números . . . . . . . . . . . . . . . . . . . . 121
6.2.3 Cambio de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.2.4 Logaritmos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
6.3 Series Simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.3.1 Serie aritmética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.3.2 Serie geométrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.3.3 Propiedades de la sumatorias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.3.4 Series importantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.4 Secuencias Importantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
6.4.1 Números Triangulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.4.2 Números cuadrados o cuadrados perfectos . . . . . . . . . . . . . . . . . . . . . 126
6.4.3 Números de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.4.4 Números Primos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.5 Divisibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.6 Aritmética modular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.7 Exponenciación Rápida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.8 Máximo común divisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
6.9 Mı́nimo común múltiplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.10 Ejemplos de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
6.11.1 JV-1091 Divisibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.11.2 JV-1269 Fracciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.11.3 JV-1275 Cuatro Dı́gitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.11.4 JV-1281 Divisores en Orden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.11.5 JV-1389 Suma de Dı́gitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.11.6 JV-1395 Factorial de N Otra Vez . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.11.7 JV-1399 Inverso Factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.11.8 JV-1412 Divisibilidad -1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.11.9 JV-1415 Pantalla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
6.11.10 JV-1431 Casi son fibos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.11.11 JV-1432: K-sumatoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

7 Arreglos unidimensionales - vectores 145


7.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
7.2 Recorrido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.3 Valores iniciales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.4 Ejemplos de aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.5 Métodos disponibles para vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
xii ÍNDICE GENERAL
7.6.1 JV-1397: Dos Unos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
7.6.2 JV-1214: Golf Club . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.6.3 JV-1294: Suma en rango . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
7.6.4 JV-1402: Cortando Postes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.6.5 JV-1405: Evaluando Promedios . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.6.6 JV-1406: Promedios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.6.7 JV-1407: Cordillera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
7.6.8 JV-1500: Array Palı́ndrome? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
7.6.9 JV-1520: Sucesion de Farey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

8 Arreglos multidimensionales 165


8.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
8.2 Ejercicios clásicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.3 Dimensión de tamaño variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
8.4 Arreglos dinámicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.5 Arreglos de más de dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.6 Ejemplo de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
8.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
8.7.1 JV-1322 Tablero de Ajedrez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
8.7.2 JV-1323 Matrices de Suma Máxima . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.7.3 JV-1324 Matrices Simétricas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
8.7.4 JV-1327 Desproporción Diagonal . . . . . . . . . . . . . . . . . . . . . . . . . . 179
8.7.5 JV-1362 Rotar Cuadrados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
8.7.6 JV-1453 Invertir Diagonales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
8.7.7 JV-1525 Matriz Flip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

9 Métodos, funciones, procedimientos y clases 187


9.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.2 Herramientas de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.2.1 Sintaxis para escribir funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
9.2.2 Sintaxis para escribir procedimientos . . . . . . . . . . . . . . . . . . . . . . . . 189
9.3 Variables locales y globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
9.4 Definición de métodos en archivos separados . . . . . . . . . . . . . . . . . . . . . . . . . 190
9.5 Ejemplos de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
9.6 Cuando hay que usar métodos y clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

10 Números Primos 199


10.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
10.2 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
10.3 Test de primalidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
10.4 Generación de primos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.5 Criba de Atkin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
10.6 Criba Lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
10.7 Aplicaciones de la Criba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
ÍNDICE GENERAL xiii

10.8 Factorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206


10.9 Prueba de la primalidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
10.10Teorema de Fermat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
10.11Prueba de Miller - Rabin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
10.12Números Grandes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
10.12.1 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
10.13Lecturas para Profundizar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
10.14Ejemplos de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
10.15Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
10.15.1 JV-1033: Contando Primos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
10.15.2 JV-1080: Raı́z digital Prima . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
10.15.3 JV-1301: Números Feos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
10.15.4 JV-1366: Factorizar un Número Grande . . . . . . . . . . . . . . . . . . . . . . . 222
10.15.5 JV-1337: Primos Redondos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
10.15.6 JV-1347: Pares de Ruth-Aaron . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
10.15.7 JV-1636: Casi Primos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225

11 Números de Fibonacci 227


11.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.2 Programando la secuencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
11.3 Fibonacci y el triángulo de Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
11.4 Propiedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
11.5 Números de Pisano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
11.6 Los números de Lucas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
11.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
11.7.1 JV-1142: Fibonacci Modificado . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
11.7.2 JV-1342: Fibonacci Modular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
11.7.3 JV-1343: Patrones de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
11.7.4 JV-1457: Base Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
11.7.5 JV-1549: Fibonacci y Pitágoras . . . . . . . . . . . . . . . . . . . . . . . . . . . 238

12 Algoritmos de búsqueda y clasificación 239


12.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
12.2 Algoritmos de búsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
12.3 Clasificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
12.4 Clasificación en Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
12.5 Algoritmos de clasificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
12.5.1 Método de la burbuja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.5.2 Clasificación por inserción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
12.5.3 Ordenación por selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
12.5.4 Algoritmo de clasificación rápida . . . . . . . . . . . . . . . . . . . . . . . . . . 251
12.5.5 Algoritmos lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
12.5.6 Laboratorio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
12.6 Ejemplo de aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
12.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
xiv ÍNDICE GENERAL
12.7.1 JV-1029: Escoger Equipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
12.7.2 JV-1031: Mediana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
12.7.3 JV-1058: Ordenando números . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
12.7.4 JV-1093: Intercambio de Trenes . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
12.7.5 1333: Easy Suffix Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
12.7.6 1191: Suma Exacta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
12.7.7 JV-1379: Juego de Niños . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265

13 Recursividad 267
13.1 Estructura de datos pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
13.2 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
13.3 Cálculo del factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
13.4 Invertir los datos de entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
13.5 Máximo común divisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
13.6 La secuencia de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
13.6.1 Recordar los valores calculados anteriormente . . . . . . . . . . . . . . . . . . . 274
13.6.2 Eliminando la recursión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
13.6.3 Eliminando el almacenamiento de los cálculos intermedios . . . . . . . . . . . . . 275
13.7 Coeficientes binomiales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
13.7.1 Solución recursiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
13.7.2 Eliminando la recursión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
13.8 Recursiones que requieren tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
13.9 Puntos a tomar en cuenta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
13.10Generación de objetos combinatorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
13.10.1 Generación de secuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
13.10.2 Generación de las permutaciones de una secuencia . . . . . . . . . . . . . . . . . 280
13.10.3 Generación de todos los subconjuntos . . . . . . . . . . . . . . . . . . . . . . . . 280
13.10.4 Hallar todos los subconjuntos que sumen un valor . . . . . . . . . . . . . . . . . 282
13.11Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
13.12Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.12.1 Cálculo del factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.12.2 Invertir un archivo de entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
13.12.3 Fibonacci solución recursiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
13.12.4 Fibonacci solución con caching . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
13.12.5 Fibonacci solución no recursiva . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
13.12.6 Fibonacci solución utilizando solo dos variables . . . . . . . . . . . . . . . . . . 287
13.12.7 Coeficientes binomiales solución recursiva . . . . . . . . . . . . . . . . . . . . . 288
13.12.8 Coeficientes binomiales con caching . . . . . . . . . . . . . . . . . . . . . . . . 289
13.12.9 Coeficientes binomiales solución iterativa . . . . . . . . . . . . . . . . . . . . . . 289
13.12.10 Recursión utilizando una tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
13.12.11 Generar todas las secuencias de 1..k . . . . . . . . . . . . . . . . . . . . . . . . . 291
13.12.12 Hallar todas las permutaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
13.12.13 Hallar todos los subconjuntos recursivamente . . . . . . . . . . . . . . . . . . . . 293
13.12.14 Hallar todos los subconjuntos iterativamente . . . . . . . . . . . . . . . . . . . . 294
13.12.15 Hallar todos los subconjuntos que suman un valor . . . . . . . . . . . . . . . . . 295
ÍNDICE GENERAL xv

13.13Ejercicios Resueltos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296


13.13.1 Cálculo de potencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
13.13.2 Descomponer un número . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
13.13.3 Representación binaria de un número . . . . . . . . . . . . . . . . . . . . . . . . 297

14 Backtracking 299
14.1 Problema Laberinto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
14.1.1 Solución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
14.1.2 Implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
14.2 Problema de las 8 reinas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
14.2.1 Solución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
14.3 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
14.3.1 Hallar el camino . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
14.3.2 Problema de las reinas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305

15 Un problema sencillo 309


15.1 El sub vector máximo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
15.2 Solución trivial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
15.3 Solución cuadrática . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
15.4 Divide y vencerás . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
15.5 Solución eficiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
15.6 Extendiendo el problema a dos dimensiones . . . . . . . . . . . . . . . . . . . . . . . . . . 312
15.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
15.7.1 La mejor empresa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
15.7.2 Concurso de televisión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
15.7.3 La submatriz más larga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
15.7.4 Limpiar Bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
15.7.5 Campos de Footbal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
15.8 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320

16 Programación Dinámica 325


16.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
16.2 La Programación Dinámica y la Teorı́a de Grafos . . . . . . . . . . . . . . . . . . . . . . . 326
16.3 Grafos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
16.4 Grafo dirigido de la Secuencia de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . 326
16.5 Pagar con Monedas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
16.5.1 Enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
16.5.2 Solución General . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
16.5.3 Solución basada en un arreglo unidimensional . . . . . . . . . . . . . . . . . . . 330
16.5.4 Solución basada en un arreglo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
16.5.5 Solución basada en arreglo bidimensional . . . . . . . . . . . . . . . . . . . . . . 332
16.5.6 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . 333
16.6 Corte de troncos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
16.6.1 Enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
16.6.2 Solución utilizando Programación Dinámica . . . . . . . . . . . . . . . . . . . . 337
xvi ÍNDICE GENERAL
16.6.3 Solución top-down recordando soluciones anteriores . . . . . . . . . . . . . . . . 338
16.6.4 Solución bottom-up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
16.6.5 Reconstruyendo la solución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
16.6.6 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . 340
16.7 Distancia de edición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
16.7.1 Enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
16.7.2 Solución con Programación Dinámica . . . . . . . . . . . . . . . . . . . . . . . . 346
16.7.3 El grafo que representa la distancia de edición . . . . . . . . . . . . . . . . . . . 347
16.7.4 Reconstruir las acciones realizadas . . . . . . . . . . . . . . . . . . . . . . . . . 348
16.7.5 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . 349
16.7.6 Solución recursiva al problema distancia de edición . . . . . . . . . . . . . . . . 349
16.8 La subsecuencia común más grande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
16.8.1 Enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
16.8.2 Solución por fuerza bruta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
16.8.3 Solución utilizando programación dinámica . . . . . . . . . . . . . . . . . . . . . 355
16.8.4 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . 358
16.9 Problema de la suma de subconjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
16.9.1 Enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
16.9.2 Grafo que representa la solución . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
16.9.3 Ecuación de recurrencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
16.9.4 Solución iterativa utilizando Programación Dinámica . . . . . . . . . . . . . . . . 362
16.10Reconstruyendo la Solución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
16.10.1 Programas mencionados en el texto . . . . . . . . . . . . . . . . . . . . . . . . . 365
16.11Ejercicios Propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
16.11.1 Super Suma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
16.11.2 Recaudando Fondos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
16.11.3 La Subsecuencia ascendente más larga . . . . . . . . . . . . . . . . . . . . . . . 370
16.11.4 Secuencias que suben y bajan . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
16.11.5 Dulces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373
16.11.6 Palı́ndrome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
16.11.7 Computadora de cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
16.11.8 Distancia de Edición y Proceso de Transformación . . . . . . . . . . . . . . . . . 378
16.11.9 Secuencia de Búsqueda en una Base de Datos de biologı́a molecular . . . . . . . . 379

A Juez Virtual de la Carrera de Informática 381


A.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.2 Comenzar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.3 Registrarse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.4 Ingresar con mi cuenta de usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.5 Envı́o de problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
A.6 Competencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386

B Errores comunes en tiempo de ejecución 387

Bibliography 387
ÍNDICE GENERAL xvii

Índice de figuras 390

Índice alfabético 395


1 Introducción
Aprender programación no es solamente conocer un lenguaje de programación. También hay que conocer
metodologı́as para resolver problemas en forma eficiente.
En este curso de programación usamos como base el lenguaje Java para el desarrollo de la programación por
tres motivos. El primero porque es un lenguaje que hace una verificación fuerte de los programas detectando
una gran variedad de errores. Segundo porque es utilizado mayoritariamente en las universidades y colegios
para enseñar programación. Finalmente porque es el lenguaje más utilizado en el mundo desde la llegada de
los dispositivos móviles que utilizan el mismo, para el desarrollo de aplicaciones.
Java es un lenguaje orientado a objetos que fue desarrollado por Sun Microsystems, empresa que fue
adquirida por Oracle que es la que ahora continua con el Java que creo Sun. Permite la ejecución de un
mismo programa en múltiples sistemas operativos, sin necesidad de recompilar el código. Provee soporte
para trabajo en red. Es fácil de utilizar.
Existen dos corrientes de desarrollo del lenguaje, la de Open Java que es una plataforma de código abierto
disponible en http://openjdk.java.net/. La segunda corriente es la de Oracle, que desarrolla una plataforma de
código propietario, https://www.java.com/en/download/. En estas dos corrientes existen pequeñas diferencias
en algunas librerı́as que se van compatibilizando poco a poco.

1.1. El lenguaje y compilador


Para poder programar en Java es necesario tener un compilador y un entorno de ejecución denominado
máquina virtual. El compilador puede descargarse de uno de los dos sitios mencionados,
Existen dos versiones Java Stantard Editión y Java Enterprise Edition. La versión que se denomina Standard
Edition, también se conoce como Java Development Kit (JDK), es la versión básica de Java. La versión
entrerprise esta pensada para desarrollos escalables en servidores.
Cuando revise la literatura es posible que todavı́a encuentre textos denominados Java1 estos se refieren a las
versiones posteriores a la 1.2 de Java. En esta versión se incluyen cambios al lenguaje, en los cuales muchas
partes fueros reescritas siguiendo la filosofı́a orientada a objetos. Se recomienda que instale la versión SDK
de java 1.8.

1.2. Construir y compilar un programa


Para construir un programa Java es suficiente utilizar el editor de textos del sistema operativo, o uno de su
preferencia. Los pasos a seguir son los siguientes:

1. Copiar el programa en el editor de textos.


2. Guardar el mismo con extensión java. Tome en cuenta que las letras minúsculas y mayúsculas se
consideran diferentes. Tome en cuenta que la codificación del archivo debe ser utf-8 o ansii. Si utiliza
block de notas en Ms Windows, gedit, vi, u otro editor en Linux se grabará en este formato.

1
2 Capı́tulo 1 Introducción
3. Compile el programa con el comando javac NombreDelPrograma.java. Si no hay errores el momento
de compilar se creará un archivo con extensión class.
4. Para hacer correr el programa use el comando java NombreDelPrograma. Note que hemos obviado la
extensión.

Para ejemplificar el proceso construyamos un programa básico y expliquemos las partes que lo constituyen.

1. Todo en Java está dentro de una clase. Una clase se define

public class Nombre {


}

2. El código fuente se guarda en un archivo ascii, y debe tener el mismo nombre de la clase y la extensión
.java.
3. El compilador genera un archivo con extensión .class por cada una de las clases definidas en el archivo
fuente.
4. Para que un programa se ejecute de forma independiente y autónoma, deben contener el método main().

public class Nombre {


public static void main(String[] args){
}
}

5. Vea que hemos incluido el método main en el interior de la clase Nombre.


6. La palabra public indica que el método es público, vale decir, visible o accesible en toda la clase.
7. La palabra static indica que el método está ligado exclusivamente a esta clase.
8. Luego tenemos void que significa que no se devuelve ningún valor al programa que lo llamó. En nuestro
caso el programa que invoca a este es el sistema operativo.
9. Las instrucciones de nuestro código se ingresan en el método main().
10. Una instrucción para mostrar un texto en pantalla es:

System.out.println();

11. El texto a mostrar se coloca entre comillas como un parámetro de la instrucción

System.out.println("hola");

El programa terminado queda como sigue:

public class Nombre {


public static void main(String[] args){
System.out.println("hola");
}
}

12. Es muy importante aclarar que las letras en mayúsculas y minúsculas se consideran diferentes.

Una descripción de las partes que constituyen un programa se ven en la figura 1.1.
1.3 Herramientas de desarrollo 3

Figura 1.1 Estructura de un programa Java

1.3. Herramientas de desarrollo


Existen una variedad de herramientas de desarrollo integrado (ide) para programas Java. Son ambientes de
edición con facilidades para el programador. Las caracterı́sticas de una herramienta de desarrollo en relación
a un editor de textos convencional son:

1. Más fácil al momento de escribir el programa.


2. Ayuda sobre las diferentes clases y métodos del lenguaje.
3. Depuración de programas en forma sencilla.
4. Es más fácil probar los programas.
5. Es más fácil el imponer normas institucionales.
6. Menos tiempo y esfuerzo.
7. Administración del proyecto.

Para el desarrollo elegimos la herramienta Eclipse. Las razones para esto son las siguientes:

1. Es gratuito y viene con una licencia GPL. se puede descargar de

http://www.eclipse.org/downloads/

Para utilizar el ambiente para Java debe descargar Eclipse, de 32 bits o 64 bits de acuerdo al sistema
operativo y hardware que tenga.
2. Eclipse es una plataforma completa de desarrollo. En el curso solo utilizaremos algunas funcionalidades,
sin embargo, es deseable comenzar aprendiendo en un entorno de desarrollo profesional.
3. Eclipse tiene muchos complementos que fácilmente ayudan a extender el ambiente de desarrollo a otros
lenguajes. Se puede trabajar con HTML, PHP, UML, etc. Másw información se encuentra disponible en
http://www.eclipse.org/.
4 Capı́tulo 1 Introducción

Figura 1.2 Especificar el área de trabajo en Eclipse

4. Extensas ayudas para aprender JAVA.


5. Fácil de aprender:
6. Puede funcionar tanto en Linux, Windows y Mac.
7. Está desarrollado en Java.

1.3.1. Instalación de Eclipse


La instalación de Eclipse es muy simple sea su ambiente de desarrollo Linux, Windows o Mac, descargue la
herramienta y copie a un directorio de su disco. Luego ejecute Eclipse. El único requisito es que el java esté
instalado previamente y accesible desde la lı́nea de comando.

1.3.2. Construir y hacer correr un programa


El proceso de construir un programa utilizando Eclipse es sencillo. Hay que tener en cuenta una serie de
conceptos que se explican a continuación:

1. Una vez iniciado Eclipse obtendremos la pantalla que se muestra en la figura 1.2. El workspace es el área
de trabajo. Esto significa el directorio donde se crearan sus proyectos y programas. Nos muestra la ruta
del directorio donde está. A esta pantalla podemos llegar también con la opción switch workspace que
se encuentra en el menú file. Si desea hacer una copia de respaldo de todos sus proyectos es suficiente
copiar este directorio.
2. Cuando se inicia por primera vez Eclipse se presentará una pantalla de bienvenida que provee informa-
ción y ayuda para su uso (figura 1.3). Para salir de esta pantalla se marca la X que está al lado de la
pestaña que dice Welcome. Esta pantalla aparecerá solo la primera vez.
3. Cerrando la pantalla de bienvenida se obtiene el ambiente de trabajo que se muestra en la figura 1.4.
Esta pantalla tiene varias partes:
La ventana que dice Package. Esta sección nos muestra los proyectos y programas que vamos
creando.
La ventana Outline nos muestra los métodos de nuestro programa.
1.3 Herramientas de desarrollo 5

Figura 1.3 Pantalla de bienvenida de Eclipse

La parte inferior cuando ejecutemos el programa nos mostrará la consola. También se muestra la
documentación Javadoc, Problemas y declaraciones.
Al centro se ve un espacio donde se escribirá el código del programa.
4. Para empezar a crear un programa el primer paso es crear un proyecto. Dentro del proyecto estarán
todos los programas del mismo. Desde la pestaña File escogemos new → java P roject y obtenemos
la pantalla de la figura 1.5. Ingresamos el nombre del proyecto y escogemos Finish. En este momento
podemos cambiar varios aspectos del proyecto, como ser, la versión de java que se utilizará. Un aspecto
muy importante que hay que resaltar es que si crea un proyecto que no es de java no podrá ejecutar un
programa java.
5. Una vez que ha creado un proyecto, es necesario crear un archivo que contendrá su programa. En eclipse
se denomina crear una clase. Para esto colocamos el cursor de ratón en el nombre del proyecto con el
botón derecho del ratón entramos al menú. Escogemos new → class. Obteniendo la pantalla de la figura
1.6. Aquı́ ingresamos el nombre del programa y escogemos la opción public static void main(String[]
args) para indicar que es un programa ejecutable. Obteniendo la pantalla de la figura 1.7
6. En esta pantalla ya vemos un programa con una clase que tiene el mismo nombre del archivo, y un
método principal que es donde se introducirán las instrucciones. Las lı́neas

/**
* @param args
*/

Es donde colocamos los comentarios generales del programa, tales como parámetros, fecha, autor. Todos
los bloques de comentarios (múltiples lı́neas) comienzan con /∗ y terminan con ∗/.
6 Capı́tulo 1 Introducción

Figura 1.4 Entorno de trabajo de Eclipse


1.3 Herramientas de desarrollo 7

Figura 1.5 Opciones para crear un proyecto


8 Capı́tulo 1 Introducción

Figura 1.6 Opciones para crear una clase

Figura 1.7 Plantilla de programa inicial


1.3 Herramientas de desarrollo 9

Figura 1.8 Programa para mostrar un texto

Los comentarios que comienzan con @ son instrucciones que el programa javadoc utiliza para generar
la documentación. En este caso @param args indica que se pueden pasar valores desde la lı́nea de
comando. Esto no se procesa solo sirve para documentar.
7. Cuando un comentario se pone en una sola lı́nea, no es un conjunto de lı́neas, se utiliza //. El comentario
// TODO Auto-generated method stub solo dice que es un comentario auto generado. Aquı́ describimos
que hace el programa.
8. A continuación podemos escribir las instrucciones que se ejecutarán. En el ejemplo pusimos

System.out.println("Hola");

que especifica que queremos mostrar la palabra ”Hola”por pantalla.


9. Antes de ejecutar el programa vea el mismo finalizado (figura 1.8). Para ejecutar el mismo, con el botón
derecho del ratón en el nombre del programa escogemos Run as → Java Aplication. Ahora veremos
la salida del programa en una ventana que se llama Console.
Si en algún caso no tenemos la consola visible vamos a W indow → Show view → Console.

1.3.3. Estructura de directorios

Cuando creamos un proyecto y un programa java obtenemos la siguiente estructura de directorios:

En el área de trabajo (workspace) una carpeta con el nombre del proyecto.


En el interior de la carpeta con el nombre de proyecto dos carpetas bin y src.
10 Capı́tulo 1 Introducción

Figura 1.9 Salida del programa

En la carpeta bin los programas compilados con la extensión .class.


En la carpeta src los programas fuentes que tienen la extensión .java.

Al recorrer las carpetas con el explorador de proyecto podremos ver esta estructura, ası́ como, recorriendo
las carpetas desde la lı́nea de comando.

1.4. Ejercicios
1. Instalar el compilador Java.
2. Instalar el Editor Eclipse.
3. Escribir un programa que muestre su nombre utilizando Eclipse.
4. Busque en qué lugar está el directorio workspace.
5. Describa los directorios creados en el proyecto.
6. Compile y haga correr el programa desde la lı́nea de comando.
7. Compile y haga correr el programa desde Eclipse.

El juez virtual que contiene los ejercicios que deben realizarse está en la dirección web http://jv.umsa.bo.
Los problemas presentados están descritos, primero con el número de problema del juez virtual seguido del
nombre asignado.
1.4 Ejercicios 11

1.4.1. JV-1232: Hola mundo!


Escriba un problema que escriba una lÃnea con el mensaje ”Hola mundo!”

Entrada
No hay entrada, el programa no lee nada.

Salida
Escriba una lÃnea con el texto solicitado

Ejemplos de entrada Ejemplos de salida


Hola mundo!
12 Capı́tulo 1 Introducción

1.4.2. JV-1366: Imprimir Java


El problema consiste en escribir un programa que ustilizando solamente la instrucción
System.out.println()
Escriba el texto mostrado en el ejemplo.

Entrada
No hay datos

Salida
Escriba lo que se muestra en el ejemplo

Ejemplos de entrada

Ejemplos de salida
J A V V A
J A A V V A A
J J AAAAA V V AAAAA
JJJJ A A V A A
1.4 Ejercicios 13

1.4.3. JV-1423: Imprimir Potencias


El problema consiste en escribir un programa que utilizando solamente la instrucción
System.out.println();
Para imprimir un número elevado al cuadrado es suficiente imprimir el número multiplicado por si mismo.
Por ejemplo
System.out.println(3*3);
Escriba el texto mostrado en el ejemplo. Entre cada columna se debe imprimir un tabulador. Esto se hace
imprimiendo ”⁀”.

Entrada
No existen datos de entrada

Salida
La primera columna es un número la segunda columna es el número al cuadrado, y el tercer el el número al
cubo. Cada columna viene separada por un tabulador.

Ejemplos de entrada Ejemplos de salida


1 1 1
2 4 8
3 9 27
4 16 64
2
2.1.
Tipos de Datos
Introducción
Seguramente usted ha utilizado una computadora para realizar una serie de tareas. Y son muy buenas para
las tareas repetitivas. Puede realizar las mismas una y otra vez sin cansarse.
Un equipo debe ser programado para realizar tareas. Diferentes tareas requieren diferentes programas. Por
ejemplo para que se pueda escribir un programa primero ha tenido que construirse un editor que nos permite
introducir el código y guardarlo en un archivo.
Las computadoras solo ejecutan un conjunto de operaciones básicas muy rápidamente. Con este conjunto
básico es que se construyeron los programas que nos permiten posteriormente hacer tareas más complejas.
Las instrucciones básicas pueden obtener datos de un lugar de la memoria, sumar dos números, guardar en
la memoria, si el valor es negativo continuar en otra instrucción.
Para construir los programas que se usan hoy, tal es el caso de Java, en base de estas instrucciones básicas se
crearon lenguajes con muchas más instrucciones para hacer la tarea de programación más sencilla.
Un programa de una computadora es una secuencia de instrucciones necesarias para realizar una tarea.

2.2. Entender la actividad de la programación


La actividad de la programación consiste en escribir algoritmos para resolver problemas o tareas.
Por esto es necesario definir con precisión que entendemos por algoritmo.
Un algoritmo es una secuencia de pasos que tiene un inicio y un final. Quiere decir que finaliza en algún
momento. Los pasos deben ser precisos.
Por ejemplo si queremos guardar en B la suma de A + 2 el programa podrı́a ser como sigue:

1. Condiciones iniciales A tiene un valor,el valor de B no nos interesa-


2. Obtener el valor de A.
3. Sumar 2.
4. Guardar el resultado en B.
5. Condiciones finales B contiene el valor de A + 2.

Analizando el ejemplo podemos ver que las instrucciones son precisas y el orden en el que se ejecutan es
importante. Si cambiamos el orden seguro que el resultado será diferente.
En los algoritmos no se permiten valores no cuantificados claramente. Por ejemplo un algoritmo para realizar
una receta de cocina podrı́a ser:

1. Poner 1 taza de harina.


2. Agregar una taza de leche.

15
16 Capı́tulo 2 Tipos de Datos
3. Agregar un huevo.
4. Poner una cucharilla de sal.
5. Mezclar.
6. Hornear a 220 grados.

En este ejemplo la secuencia es precisa. Si por ejemplo si especificamos sal al gusto, deja de ser un algoritmo
dado que el concepto al gusto no es un valor preciso.
Otro caso en que una secuencia deja de ser un algoritmo es cuando después de una cantidad de pasos no
termina

1. Inicio.
2. Asignar a N el valor de 100.
3. Repetir hasta que N mayor que 256.
4. Asignar a N el valor N/2.
5. Fin de repetir.
6. Fin del algoritmo.

Claramente se ve que el algoritmo no termina puesto que cada vez vamos reduciendo el valor de N en
lugar de que vaya creciendo. Aún cuando se ha definido un inicio y un final, el algoritmo no termina, estará
ejecutando continuamente.
Como dijimos los algoritmos son secuencia de pasos, no importando el lenguaje que se utiliza, o como se
expresó el mismo. En el curso expresaremos los algoritmos en lenguaje Java. Muchas veces usamos una
mezcla de lenguaje español con Java para explicar una tarea, esto se denomina pseudo lenguaje.
El conocimiento de un problema puede ser expresado de forma declarativa o imperativa. El conocimiento
declarativo expresa hechos conocidos que explican el problema, por ejemplo la raı́z cuadrada de x es un
número y tal que y ∗ y es x. El conocimiento imperativo es la receta con la cual obtenemos el resultado. Por
ejemplo para obtener la raı́z cuadrada, podemos hacer como sigue: adivinar un número g que suponemos es
la raı́z cuadrada de x. Luego si g ∗ g está próximo a x ya tenemos la respuesta. En otro caso hallar el promedio
de x/g y g. Repetir hasta que hallemos el resultado.
Como se ve el conocimiento imperativo es el algoritmo o receta para resolver un problema. El proceso de la
programación o solución de un problema consiste en convertir el conocimiento declarativo en imperativo.

2.3. Reconocer errores de sintaxis y de lógica


Cuando escribimos un programa existen dos tipos de errores, los de sintaxis y los de lógica. Los errores
de sintaxis son aquellos en los que existen errores en la construcción de las instrucciones. Por ejemplo: la
carencia de un punto y coma al final de una instrucción, una palabra que debe comenzar con mayúsculas y
se escribió con minúsculas, un error ortográfico, etc.
Este tipo de errores es detectado por el compilador cuando compilamos el programa. El entorno de desarrollo
Eclipse lo marca como un error ortográfico. Para ejecutar un programa no pueden existir errores de este tipo.
Los errores de lógica son más difı́ciles de descubrir dado que el compilador no puede detectarlos. Estos
errores se producen cuando el programa no hace lo que deseamos. Por ejemplo supongamos que queremos
2.4 Tipos de datos 17
contar el número de números que hay en una secuencia y hemos realizado la suma de los números, claramente
es un error de lógica.

2.4. Tipos de datos


Cada valor que utilizamos en Java tiene un tipo. Por ejemplo ”Hola” es de tipo cadena, un número puede ser
de tipo entero. Otro ejemplo es System.out que tiene un tipo PrintStream,.
¿Como definimos datos en un programa? Para definir un dato la sintaxis que se utiliza en Java es:

Nombretipo nombreVariable = valor;

Los nombres se construyen bajo las siguientes restricciones:

1. Comienzan con una letra mayúscula o minúscula.


2. Pueden contener letras y números.
3. También puede incluir el sı́mbolo guión bajo ( ) o el sı́mbolo dolar ($).
4. No se permiten espacios.

Algunos nombres válidos son nombre, Caso 1, cedulaIdentidad. Por convención de los nombres deben
comenzar con una letra minúscula.
El signo = se denomina operador de asignación y se utiliza para cambiar el valor de una variable. En el
ejemplo nombreVariable cambia su contenido con el contenido de valor.
Hay dos tipos de datos, los del núcleo del lenguaje denominados tipos primitivos o básicos y los implemen-
tados en clases. Los tipos básicos son:
Tipo Descripción Tamaño
int Números enteros en el rango -2,147,483,648 a 2,147,483,647 4 bytes
byte Descripción de un número entre -128 a 127 1 byte
short Un entero en el rango de -32768 a 32767 2 bytes
long Un entero en el rango de -9,223,372,036,854,775,808 a 8 bytes
9,223,372,036,854,775,807
double Número de precisión doble de punto flotante en el rango de ±10308 8 bytes
con 15 dı́gitos decimales
float Número de precisión simple de punto flotante en el rango de ±1038 4 bytes
con 7 dı́gitos decimales
char Caracteres en formato Unicode 2 bytes
boolean Un valor que representa verdadero o falso 1 bit

Los caracteres de la tabla Ascii normalmente se representan en un byte, sin embargo, para poder representar
caracteres en diferentes lenguajes (español, inglés, árabe, etc.), se utiliza una norma denominada Unicode.
Un byte representa 8 bits o sea 8 dı́gitos binarios. Un número entero de 4 bytes utiliza un bit para el signo
y 31 para almacenar un número. De esta consideración se deduce que el número mas grande que se puede
almacenar es 231 − 1 = 2, 147, 483, 647.
Para definir los valores numéricos podemos utilizar una de las siguientes sintaxis.
18 Capı́tulo 2 Tipos de Datos
tipo nombre;
tipo nombre = valor;

Vea que cada instrucción termina con un punto y coma. Por ejemplo:

int i;

Esto permite definir una variable de nombre i de tipo entero que no tiene un valor inicial. En este caso el
valor inicial es null;
Si definimos:

int i=3;

Significa que estamos definiendo una variable de nombre i con valor inicial 3
En el caso de las variables de punto flotante es necesario poner un punto decimal para indicar que los valores
son del tipo con decimales. Por ejemplo:

double f=10.0;

No podemos colocar solo 10 porque este es un número entero y es necesario que los tipos igualen en toda
asignación.

2.4.0.1. Errores de desborde


Cuando realizamos una operación con una variable, y excedemos el valor máximo que podemos almacenar,
se produce un desborde. Supongamos que en una variable de tipo short tenemos almacenado el número
32767. Si agregamos 1 a ésta, no obtendremos 32768, el resultado es -32768 porque hemos excedido el
número máximo que podemos almacenar en este tipo de variable.
Para entender ésto, vemos que pasa, convirtiendo ésto a números binarios. Si al valor 32767 = 01111111,
sumamos uno la respuesta es 10000000 que en la representación de la computadora equivale a −32768
El desborde lo identificamos cuando al realizar una operación el resultado es incorrecto. Por ejemplo si
esperamos un resultado positivo y obtenemos un resultado negativo.

2.4.0.2. Errores de redondeo


Cuando representamos un número en notación de punto flotante, el resultado se expresa de la forma
enteros.decimalesEexponente. Por ejemplo 5,6666664E7 significa 5,6666664x107 . Con esta representa-
ción las conversiones no son exactas. Por ejemplo 10/3 es 3,3333333333333333 sin embargo en la compu-
tadora nos da 3,3333333333333335. Esto podemos probar con el siguiente código:

double a = 10.0/3;
System.out.println(a);

Debido a estos errores que se producen hay que tener mucho cuidado como se manejan los números de punto
flotantes para no obtener resultados erróneos. Esto es principalmente crı́tico en cálculos financieros donde no
se permiten errores por la precisión de la representación de los números en la computadora.
2.4 Tipos de datos 19

2.4.1. Ubicación en la memoria de la computadora


Todas las variables se almacenan en la memoria de la computadora en forma secuencial. Esto quieres decir
uno a continuación del otro.
Cuando tenemos una definición, por ejemplo, int a = 1234; el contenido de la variable a es 1234. El nombre
a representa la dirección de memoria donde está ubicado. En este caso donde comienzan los 4 bytes de la
variable a.

Figura 2.1 Direccionamiento de las variables en la memoria

En la figura 2.1 podemos ver que el nombre de una variable básica es un apuntador a su contenido, que se
almacena en la cantidad de bytes que corresponde al tipo de la variable.
Cuando el tipo no corresponde a uno básico, el nombre de la variable no apunta al contenido. Consideremos
como ejemplo una cadena. Una cadena de caracteres puede tener cualquier longitud. Por esto no es posible,
que el nombre apunte al contenido. Lo que se hace es que el nombre apunte a un lugar, donde se encuentra
la dirección de memoria donde está la cadena.
Lo que decimos es que la variable es un puntero a la dirección de memoria donde está el contenido. Si
hacemos una operación con ésta variable, por ejemplo sumar 1, se estarı́a cambiando la dirección del
contenido. Para evitar ésto todas las operación sobre variables que no corresponden a los tipos básico se
realizan con métodos.

2.4.2. Variables y constantes


Se pueden definir dos tipos de datos, los variables y los constantes. Variables son aquellos que pueden
cambiar su valor durante la ejecución del programa. Constantes son aquellos que no pueden cambiar su
valor durante la ejecución del programa.
Las variables se definen como vimos en la definición de variables. Simplemente se coloca el tipo y el nombre
de la misma.
Para definir un valor constante por ejemplo

double PI=3.1416;

puede realizarse como una variable normal. Esta definición no permitirı́a evitar que cambiemos el valor de
P I. Si alteramos el valor por un error de lógica será difı́cil de hallar el mismo. Para especificar al compilador
que no se puede modificar el valor utilizamos la palabra f inal en la definición quedando:

final double PI=3.1416;


20 Capı́tulo 2 Tipos de Datos
Ahora, si tratamos de modificar esta variable obtendremos un error de compilación.

2.4.3. Tipos de datos implementados en clases

Para los tipos de datos básicos existen clases que aportan una variedad de métodos para muchas tareas
habituales, esta clases son:
Tipo Descripción
Integer Clase que define números de tipo int
Byte Clase que define números de tipo byte
Short Clase para definir datos de tipo short
Long Clase para definir datos de tipo long
Double Clase para definir datos de tipo double
Float Clase para definir datos de tipo float
Boolean Clase para definir datos de tipo boolean
Vea que el tipo comienza con letra mayúscula. Por convención todas las clases comienzan con un letra
mayúscula.
El propósito de estos tipos como dijimos es acceder a sus métodos. Para acceder a un método se coloca el
nombre de la variable un punto y el nombre del método. Definamos una variable de tipo Integer

Integer entero=5;

Ahora si se desea conocer cuantos bits tiene la variable entero utilizamos el método SIZE. El código siguiente
nos permite comprobar que el número de bits de una variable de tipo entero, es 32.

Integer entero=5;
System.out.println(Integer.SIZE);

Cada clase usada para definir datos tiene sus propios métodos. Para ver la lista de métodos utilizando el editor
Eclipse escribimos el nombre de la variable, punto, y obtenemos una lista de métodos de los cuales podemos
escoger el que necesitamos. Una parte de la ayuda para Integer se muestra:

Entre los métodos más comunes tenemos:


2.5 Caracteres 21

Tipo Descripción
toString() Convertir un entero a una cadena
M AX V ALU E El valor máximo que puede almacenar
M IN V ALU E El valor mı́nimo que puede almacenar
reverse(int value) Invertir los bits del número
toBinaryString(int value) Convertir a una cadena en binario
toHexString(int value) Convertir a una cadena en hexadecimal
toOctalString(int value) Convertir a una cadena en octal
Es necesario aclarar algunos aspectos sobre como han sido construidos los nombres de los métodos de
acuerdo a las convenciones del Java:

1. Los métodos comienzan con una letra minúscula


2. Cuando tienen más de dos nombres el segundo nombre comienza con una letra mayúscula, por ejemplo
toString()
3. Los paréntesis significan que es un método y puede o no tener parámetros. Por ejemplo toString() no
tiene parámetros. En cambio toBinaryString(int value) recibe un parámetro que es un valor entero y
devuelve su equivalente en binario. Lo que especifica el método, es que el parámetro es un valor de tipo
int;
4. Cuando el nombre está todo en letras mayúsculas significa que es un valor constante.

Por ejemplo si queremos mostrar el equivalente del número 12345 en binario el código serı́a el siguiente:

System.out.println(Integer.toBinaryString(12345));

2.5. Caracteres
Como habrá notado no existe una clase Char, el tipo de datos es char y no tiene métodos. Los caracteres
corresponden a los caracteres de la tabla ascii. Mostramos algunos de los caracteres de la tabla:
0 1 2 3 4 5 6 7 8 9
4 ( ) * + , - . 0 1
5 2 3 4 5 6 7 8 9 : ;
6 ¡ = ¿ ? @ A B C D E
7 F G H I J K L M N O
8 P Q R S T U V W X Y
9 Z [ / ] ˆ ‘ a b c
10 d e f g h i j k l m
11 n o p q r s t u v
Los caracteres están acomodados correlativamente, si vemos el carácter 0 es número 48 el carácter 1 es el 49
y ası́ sucesivamente. Si vemos las letras mayúsculas comienzan en el 65 hasta el 90.
Para definir un carácter tenemos dos opciones:

Asignamos a la variable el valor numérico del caracter. Por ejemplo para definir la letra A podemos
escribir

char letraA=65;
22 Capı́tulo 2 Tipos de Datos
si no conocemos el valor ascii del carácter podemos escribir el mismo entre apóstrofes. Para definir la
misma letra A escribimos

char letraA=’A’;

2.5.1. Interpretación de los datos


Cuando escribimos un número éste puede representar tanto un carácter como un número. ¿Cómo sabe el
compilador si es un número o un caracter?
El compilador no puede determinar esto. Uno debe decir que es lo que representa. Para esto se utiliza un
concepto que en Java se denomina cast. La sintaxis consiste en colocar el tipo entre paréntesis delante de la
variable.
Por ejemplo si queremos asignar el caracter 80 de una variable entera a una variable caracter debemos hacer
un cast. En una asignación ambos lados deben ser del mismo tipo. Un ejemplo es:

int i=80;
char c=(char)i;

Lo mismo ocurre con todos los tipos de variables, por ejemplo para asignar una variable int a una long

int i=80;
long l=(long)i;

2.5.2. Salida por pantalla


Para mostrar los datos por pantalla se utiliza la clase System.out con el método print. Esta clase solo puede
mostrar secuencias de caracteres en la pantalla. Cuando imprimimos un número primero se convierte en una
cadena de caracteres y luego se imprime. Por ejemplo si tenemos int i = 97 y deseamos imprimir el valor de
i primero se debe imprimir el carácter 9 luego el 7 para que se muestre el número 97. Si se envı́a directamente
el 97 se mostrará por pantalla la a.
El código es el siguiente:

int i=97;
System.out.print(i);

Ahora, si queremos que en lugar de 97 se imprima la letra a hacemos un cast, el código es el siguiente:

int i=97;
System.out.print((char)i);

Para imprimir un texto, se coloca el mismo entre comillas.

System.out.print("hola");

Si se quiere imprimir textos, y números debe concatenar ambos. Esto se hace con el sı́mbolo +. Por ejemplo

int i=123;
System.out.print("El valor de i es "+i);
2.5 Caracteres 23
En el ejemplo tenemos una cadena y luego i es convertido a cadena con el método toString(), dando un
resultado que es una cadena. Si codifica

int i=123;
System.out.print(i+" es el valor de i");

Se producirá un error de sintaxis. Solo se pueden concatenar cadenas. La instrucción trata de concatenar
un entero con una cadena. Para eliminar el problema colocaremos una cadena al principio. Esta cadena de
longitud cero, porque no encierra nada, se denomina cadena de valor null. La forma correcta de codificar es:

int i=123;
System.out.print(""+i+" es el valor de i");

2.5.2.1. Caracteres especiales


Existen algunos caracteres especiales que son:
Carácter Descripción
\t El carácter de tabulación, que se denomina tab. En la tabla ascii es el
carácter 9
\r Retorno de carro, Significa volver al principio de la lı́nea, es el carácter
13. Este lo denominamos cr
\f Avance de lı́nea, es el carácter 12, denominado lf
Cuando usamos System.out.print los resultados, se muestran en una lı́nea de la pantalla pero no se avanza a
la siguiente lı́nea. Si queremos que los caracteres siguientes contienen en la próxima lı́nea debemos indicar
esto con los caracteres especiales.
Dependiendo del sistema operativo hay diferencias. En Windows de utiliza cr y lf para avanzar una lı́nea
y regresar al principio. En Linux es suficiente lf . En Mac cr. Para evitar este problema podemos utilizar la
instrucción System.out.println. El método println indica que una vez completada la instrucción se imprima
los caracteres necesarios para avanzar una lı́nea e ir al comienzo.
Si deseamos incluir estos caracteres en una cadena solo lo incluimos en medio del texto. La instrucción
System.out.print(”que \r dice”) hará que se imprima que en una lı́nea y dice al principio de la siguiente
lı́nea.
Como vimos el carácter \indica que a continuación hay un caracter de control. Si queremos imprimir el
carácter \entonces debemos colocar dos \\.

2.5.3. Despliegue de números con formato

Consideremos el siguiente código:

double total = 35.50;


System.out.println("Total ="+total);

Al ver la respuesta vemos que el resultado es T otal = 35,5. Bien aún cuando representa el mismo número
la salida presenta un solo decimal. El formato de salida elimina los ceros de la izquierda y los de la derecha
después del punto decimal.
24 Capı́tulo 2 Tipos de Datos
Para imprimir valores numéricos con un formato especı́fico se utiliza la instrucción System.out.printf() que
tiene la siguiente sintaxis:

System.out.printf("formato", variable);

En formato se escriben los textos y las caracterı́sticas de formato de la variable que queremos imprimir. Por
ejemplo para imprimir Total = 35.50 en el campo de formato escribimos ”Total %5.2f”. Esto significa que
imprimiremos la palabra Total seguida de un espacio luego viene una variable de punto flotante de tamaño
fijo de 5 caracteres de los cuales 2 son decimales. Para el ejemplo la instrucción es:

System.out.printf("Total %5.2f",total);

Los formatos más comunes se muestran en la siguiente tabla:


Código Descripción Ejemplo
d Decimal 123
x Entero hexadecimal 4B
o Entero octal 12
f Punto flotante 35.50
e Punto flotante con exponente 1.25e+2
s Una cadena Hola
n Fin de lı́nea independiente del sistema operativo
- Alineación a la izquierda 1.23 seguido de espacios
0 Mostrar los ceros de la izquierda 0012
+ Mostrar el signo + en los números +123
( Los números negativos se muestran entre parénte- (123)
sis
, Mostrar separadores de miles 1,234
Veamos unos ejemplos:
Formato Salida
System.out.printf(” %x”,1234) 4d2
System.out.printf(” %6.2f %6.2f”,12.12,25.45”) 12,12 25,45
System.out.printf(” %+d”,-1234) -1234
System.out.printf(” %(d”,-1234) (1234)
System.out.printf(” %,d”,1234) 1,234
System.out.printf(” %06d”,1234) 001234
2.5 Caracteres 25

2.5.4. JV-1424: Codigo Ascii


El siguiente ejercicio es para conocer los códigos ascii más de cerca. Se le pide imprimir varios números
cada uno en una lı́nea. Para este ejercicio es necesario recordar lo siguiente:
Un caracter es un número entero. Para indicar al compilador que entienda el número como caracter debe
utilizar casting. Esto es colocar antes del número entre paréntesis la palabra char. Por ejemplo:
(char) 64
De la misma forma para que un caracter se entienda como número se coloca (int). Por ejemplo
(int) ’a’
Enunciado:

Imprima el código ascii de los números 0,1,2,3,4,5,6,7,8,9 separados por espacio.


Imprima la diferencia (distancia) entre el ascii de la letra A mayúscula a la Z mayúscula.
Imprima código ascii del espacio.
Imprima el código ascii del caracter @ (arroba)
Imprima el caracter cuyo código ascii es el 99
Imprima el caracter cuyo código es el 64

Entrada
No existen datos de entrada

Salida
Imprima en una lı́nea cada una de las lı́neas especificadas en el enunciado. EL ejemplo de salida muestra
solo la primera lı́nea de de la salida.

Ejemplos de entrada Ejemplos de salida


48 49 50 51 52 53 54 55 56 57
26 Capı́tulo 2 Tipos de Datos

2.5.5. JV-1425: Imprimir PI E Au

El número π (pi) es la relación entre la longitud de una circunferencia y su diámetro, en geometrı́a euclidiana.
Es un número irracional y una de las constantes matemáticas más importantes. Se emplea frecuentemente en
matemáticas, fı́sica e ingenierı́a. El valor numérico de ?, truncado a sus primeras cifras, es el siguiente:

π ≈ 3, 14159265358979323846 . . .

La constante matemática e es uno de los más importantes números reales que aparece en diversas áreas de la
matemática. Es aproximadamente igual a 2,71828 y se relaciona con muchos interesantes resultados, como
ser la base de los logaritmos naturales y su aparición en el estudio del interés compuesto.
El valor de e truncado a sus primeras cifras decimales es el siguiente:

e ≈ 2, 71828182845904523536...

El número áureo (también llamado número de oro, razón extrema y media, razón áurea, razón dorada, media
áurea, proporción áurea y divina proporción) es un número irracional, representado por la letra griega φ (phi)
(en minúscula) o Φ (Phi) (en mayúscula) en honor al escultor griego Fidias.
La ecuación se expresa de la siguiente manera:


1+ 5
φ= ≈ 1, 61803398874988...
2

Entrada
No existen datos de entrada.

Salida
Imprima e con 2, 3, 5, 7, 11 y 13 decimales de precisión cada una en una lı́nea.
Imprima un salto de lı́nea.
Imprima PI con 2, 6, 10 y 14 decimales de precisión cada una en una lı́nea.
Imprima un salto de lı́nea.
Imprimir el número áureo con 15 decimales de precisión en una lı́nea.
Para este problema utilice System.out.prinf(), para especificar la cantidad de decimales solicitados.
2.5 Caracteres 27

Ejemplos de entrada Ejemplos de salida

2.72
2.718
2.71828
2.7182818
2.71828182846
2.7182818284590

3.14
3.141593
3.1415926536
3.14159265358979

1.618033988749895
33.1.
Operadores aritméticos y lectura de
teclado
Introducción
En este capı́tulo se explica como trabajar con los valores enteros utilizando operaciones tanto en bits
individuales como en números. Como convertir de un tipo de datos a otro y finalmente como podemos
ingresar los mismos por teclado.

3.2. Trabajando en binario


Cuando sea posible es preferible trabajar en binario, dado que, las computadoras trabajan en números binarios
y esto mejorará el tiempo de proceso. Los operadores disponibles para trabajar manejo de bits estan en la
tabla 3.1. Analicemos uno a uno esto operadores y veamos el uso de cada uno de ellos.

3.2.1. El operador de desplazamiento

El operador de desplazamiento es el que permite recorrer bits a la izquierda o derecha. Si definimos un


número entero int i = 10 la representación binaria del contenido de la variable i es 1010.
El operador de desplazamiento es un operador binario. Esto significa que tiene dos operadores, un número y
la cantidad de bits a desplazar. Veamos el código siguiente:

int i = 10;
i=i<<1;

Esto significa que los bits de la variable i recorrerán a la izquierda un lugar. El resultado será 20 en decimal
o 10100 en binario.
Si utilizamos el operador i >> 1 se desplazaran los bits a la derecha dando como resultado 101 cuyo
equivalente decimal es 5.
La base de los números binarios es 2, por lo tanto, agregar un cero a la derecha es equivalente a multiplicar por
dos. Eliminar un bit de la derecha es equivalente a dividir por 2. Recorrer 2 bits a la izquierda es equivalente
a multiplicar por 2 dos veces o sea multiplicar por 22 , recorrer 3 bits, multiplicar por 23 , ası́ sucesivamente.
Similarmente ocurre lo mismo al recorrer a la derecha, pero dividiendo en lugar de multiplicar.
Para ejemplificar mostramos las potencias de 2 obtenidas por medio de desplazamientos:

29
30 Capı́tulo 3 Operadores aritméticos y lectura de teclado
Cuadro 3.1 Operadores Binarios
Operador Descripción Ejemplo
<< Recorrer bits a la izquierda, insertando Si tenemos 101 y recorremos un bit a la
un bit 0 por la derecha. Los bits sobran- izquierda el resultado es 1010
tes a la izquierda se pierden.
>> Recorrer bits a la derecha, insertando un Si tenemos 101 y recorremos un bit a la
bit 0 por la izquierda. Los bits sobrantes derecha el resultado es 10
a la derecha se pierden.
& Realiza una operación lógica and bit a 101&110 = 100
bit
| Realiza una operación lógica or bit a bit 101|110 = 111
ˆ Realiza una operación lógica xor bit a 101 ˆ 110 = 011
bit

Desplazamiento Resultado Binario Equivalente Decimal


1 << 0 1 1
1 << 1 10 2
1 << 2 100 4
1 << 3 1000 8

y de forma similar podemos ver el desplazamiento a la derecha:

Número Decimal Desplazamiento Resultado


1 1 << 1 1
2 1 << 1 1
4 1 << 1 2
8 1 << 2 8

3.2.2. El operador lógico and


El operador lógico and se representa con el sı́mbolo & y permite realizar esta operación lógica bit a bit. La
tabla siguiente muestra los resultados de realizar una operación and entre dos bits:

Operación resultado
0&0 0
0&1 0
1&0 0
1&1 1

Si nos fijamos en el operador & vemos que solo cuando ambos bits son 1 el resultado es 1. Esto es equivalente
a multiplicar ambos bits. ¿Cuál es el uso para este operador? Este operador se utiliza para averiguar el valor
de uno o más bits. Por ejemplo si tenemos una secuencia de bits y realizamos una operación and

xxxxyxyyxxyx
000011110000
3.2 Trabajando en binario 31
------------ and
0000yxyy0000

No conocemos que son los bits x y y pero si sabemos que el resultado será el mismo bit si hacemos & (and)
con 1 y de la tabla podemos ver que el resultado será 0 si hacemos la operación & (and) con 0. La secuencia
de bits 000011110000 del ejemplo se denomina máscara.
Si queremos averiguar si un número es par, podemos simplemente preguntar si el último bit es cero y esto se
puede hacer con una operación and donde todos los bits se colocan en cero y el bit de más a la derecha en 1.
Si el resultado es cero el número será par, si es uno impar.

3.2.3. El operador lógico or

El operador lógico or se representa por | (barra vertical) y realiza la operación que se muestra en la tabla bit
a bit:

Operación resultado
0|0 0
0|1 1
1|0 1
1|1 1

Esta operación permite colocar un bit en 1 vea que cada vez que se hace | con 1 el resultado es siempre 1.
Cuando queremos colocar bits en uno usamos esta instrucción.
Por ejemplo si tenemos 10100101 y deseamos cambiar los dos primeros bits de la derecha a 1, debemos
hacer or con la mascara 11.

3.2.4. El operador lógico xor

El operador lógico xor se representa por ˆ (acento circunflejo) y realiza la operación xor que se muestra en
la tabla bit a bit:

Operación resultado
0ˆ0 0
0ˆ1 1
1ˆ0 1
1ˆ1 0

Esta operación es equivalente a sumar los bits. Si realizamos una operación xor con de una variable consigo
misma es equivalente a poner la variable en cero. Una de las aplicaciones del xor es el cambiar todos los bits
de una variable, lo unos por ceros y los ceros por unos. Si realizamos una operación xor con todos los bits
en 1 se obtendrá esto. Por ejemplo 1010 ˆ 1111 dará el resultado 0101.
Otra caracterı́stica es que si realizamos xor con un valor dos veces se obtiene otra vez el valor original.
Veamos, por ejemplo, 43 = 101011, 57 = 111001 si hacemos 43 ˆ 57 el resultado es 18 = 010010, ahora si
hacemos 43 ˆ 18 obtenemos otra vez 57. Del mismo modo 57 ˆ 18 = 43.
32 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.2.5. Aplicación de manejo de bits


Una de las aplicaciones del manejo de bits es el de mostrar un número entero por pantalla en binario.
Tomemos por ejemplo el número 43 = 101011. Vemos el código

int i = 43;
System.out.print(i);

Por pantalla se verá el número 43. Ahora si queremos mostrar el equivalente en binario, tenemos que trabajar
bit a bit, y realizamos lo siguiente:

1. Creamos una máscara de tamaño igual a un entero, con un 1 en el primer bit que debemos mostrar
2. Realizamos una operación and
3. Recorremos este bit al extremo derecho
4. Mostramos el bit
5. Recorremos la máscara un lugar a la derecha
6. Repetimos el proceso con todos los bits

Para el ejemplo el código será:

int i = 43;
System.out.print(i +"=");
// mostramos el bit 5
int mascara = 32;
int bit = i & mascara;
bit = bit >> 5;
System.out.print(bit);
// mostramos el bit 4
mascara= mascara >>1;
bit = i & mascara;
bit = bit >> 4;
System.out.print(bit);
// mostramos el bit 3
mascara= mascara >>1;
bit = i & mascara;
bit = bit >> 3;
System.out.print(bit);
// mostramos el bit 2
mascara= mascara >>1;
bit = i & mascara;
bit = bit >> 2;
System.out.print(bit);
// mostramos el bit 1
mascara= mascara >>1;
bit = i & mascara;
3.3 Trabajando con variables 33
bit = bit >> 1;
System.out.print(bit);
// mostramos el bit 0
mascara= mascara >>1;
bit = i & mascara;
System.out.print(bit);

Para mostrar una variable entera en binario podemos usar la clase Integer y el método toBinaryString con lo
cual Integer.toBinaryString(43) = 101011, pero como dijimos, muchas veces es más eficiente resolver
un problema utilizando operaciones en binario.
Un segundo ejemplo puede ser empaquetar atributos en una sola variable. Suponga que tiene tres atributos
un archivo: permiso para borrar, permiso para leer, permiso para escribir. Esto podemos almacenar de la
siguiente forma:

Número Binario Equivalente Decimal Permisos


000 0 ninguno
001 1 lectura
011 3 lectura y escritura
111 7 lectura, escritura y borrar

Esto no solo reduce el almacenamiento, también simplifica la comparación y búsqueda. Si queremos


preguntar si tiene los tres atributos: lectura, escritura y borrar es suficiente preguntar si la variable es igual a
7.

3.3. Trabajando con variables


Para trabajar directamente en números sin preocuparnos de que internamente están definidos en binario,
tenemos las siguientes operaciones binarias:

Operación Descripción
+ Suma de dos variable
- Resta de dos variables
* Multiplicación de dos variables
/ División de dos variables
% Hallar del resto de una división

y las siguientes operaciones incrementales

Operación Descripción
++ Incrementa en uno
-- Decrementa en uno

Se denominan operadores binarios, porque, tiene dos operadores como en la multiplicación de a ∗ b. Toman
el nombre de unarios los que tienen un solo operador, como en −a.
Para realizar operaciones aritméticas entre dos variables del mismo tipo simplemente utilizamos el operador
deseado y asignamos el resultado a otra variable. Por ejemplo para sumas A con B y guardar el resultado en
C escribimos C = A + B.
34 Capı́tulo 3 Operadores aritméticos y lectura de teclado
La expresión 2 + 3 ∗ 5 puede dar dos resultados de acuerdo al orden en que se realicen las operaciones.
Si primero hacemos la suma 2 + 3 = 5 y multiplicamos por 5 el resultado es 25 si primero hacemos la
multiplicación 3 ∗ 5 = 15 y luego la sumamos 2 el resultado es 17. El resultado debe evaluarse en función de
la prioridad de los operadores. Primero se evalúa la multiplicación y después la suma.
Los operadores que realizan operaciones incrementales, solo trabajan sobre una variable. Por ejemplo A + +
incrementa la variable A en uno.
Los operadores se evalúan de acuerdo al orden de precedencia que se muestra en la siguiente tabla. Si tienen
el mismo orden de precedencia se evalúan de izquierda a derecha.

Prioridad Operador
1 ()
2 ++ − −
3 * /%
4 +-
5 >>, <<
8 &
6 ˆ
7 |

3.3.1. Ejemplos de expresiones

Las siguientes expresiones se evalúan de acuerdo a la prioridad de los operadores y dan los resultados
mostrados:
Expresión Resultado
3+2∗3 Primero se realiza la multiplicación luego la suma el resultado es 9
(3 + 2) ∗ 3 Primero se realizan las operaciones entre paréntesis luego la multiplicación el
resultado es 15
(3 + 2 ∗ 3) ∗ 2 Primero se realiza lo que esta entre paréntesis de acuerdo a la prioridad de
operaciones, dando 9 luego se multiplica por 2 el resultado es 18
3/2 ∗ 3 Como las operaciones son de la misma prioridad primero se hace 3/2 como son
números enteros el resultado es 1 luego se multiplica por 3 dando 3
3 ∗ 3/2 Primero se hace 3 ∗ 3 = 9 dividido por 2 da 4
3,0/2,0 ∗ 3,0 Como los números son de punto flotante 3,0/2,0 el resultado es 1,5 por 3 da 4.5
++a+b Primero se incrementa el valor de a y luego se suma b
a+b++ Primero se suma a + b luego se incrementa el valor de b

3.3.2. La clase Math

Como se ve no hay operadores para hacer raı́z cuadrada, exponencial, funciones trigonométricas, etc. Para
estas funciones disponemos de la clase Math. La tabla muestra algunos métodos de la clase Math.
3.4 Operadores de asignación 35

Método Descripción
Math.abs(a) Devuelve el valor absoluto de a
Math.sqrt(a) Devuelve la raı́z cuadrada de a
Math.pow (a,b) Devuelve el valor de ab
Math.PI Devuelve la constante PI

Math.hypot(a, b) Devuelve a2 + b2
Math.max(a,b) Devuelve el máximo entre a y b
Math.min(a,b) Devuelve el mı́nimo entre a y b

Por ejemplo, dados dos puntos A, B dados por sus coordenadas a1 , a2 y b1 , b2 ,para hallar la distancia entre
dos puntos estos puntos escribimos la expresión: M ath.sqrt((a1 − b1 ) ∗ (a1 − b1 ) + (a2 − b2 ) ∗ (a2 − b2 )).

3.4. Operadores de asignación


Para facilitar la escritura de expresiones se han creado los operadores de asignación. Por ejemplo si
necesitamos realizar una operación con una variable y deseamos guardar el resultado en la misma variable
utilizamos los operadores de asignación. Por ejemplo a+ = b es equivalente a a = a + b.

Ejemplo Equivalencia
a+ = expresion a = a + expresion
a− = expresion a = a − expresion
a∗ = expresion a = a ∗ expresion
a/ = expresion a = a/expresion
a % = expresion a = a %expresion

3.5. Convertir el tipo de datos


Para realizar conversiones de datos es necesario hacer una cast. Esto se logra colocando el tipo de dato al que
queremos convertir entre paréntesis delante de la variable. Veamos algunos ejemplos.

Para eliminar los decimales de una variable de punto flotante la llevamos a una de tipo entero como
sigue

float f = 34.56;
int i = (int)f;

Si deseamos que una variable int se convierta en long

int i = 34;
long l = (long)i;

Si hacemos el proceso inverso vale decir convertir de long a int, solo se obtiene el resultado correcto si
el valor puede ser almacenado en una variable entera. El código siguiente claramente da un resultado
equivocado.

long l = Long.MAX_VALUE/100;
int i = (int)l;
System.out.println(l);
36 Capı́tulo 3 Operadores aritméticos y lectura de teclado
System.out.println(i);

El valor de l es 92233720368547758 y el resultado en la variable i es 2061584302. Claramente


incorrecto.
En los números de punto flotante se trabaja de la misma forma

double d = 55.34;
float f = (float)d;

Si el valor no puede almacenarse en la variable f el resultado es Infinity-

Los caracteres están representados por su equivalente ascii, que es un número entero. si tenemos una variable
entera de 8 bits podemos escribir el caracter equivalente por medio de cast. El ejemplo siguiente muestra por
pantalla la letra A.

int i=65;
System.out.println((char)i);

3.6. Lectura del teclado


Para leer datos del teclado utilizamos la clase Scanner. Para poder utilizar esta clase es necesario indicar al
compilador donde se encuentra. Esto se realiza con la instrucción import java.util.Scanner que se incluye
al principio del programa. También se puede colocar import java.util.* Esto último indica que queremos
importar todas las clases de import java.util. La conveniencia de especificar una por una las clases es mejorar
el tiempo de compilación en programas grandes.
Seguidamente es necesario crear una instancia de la clase Scanner para poder utilizar los métodos de la
clase y leer del teclado. Esto podemos hacer con Scanner lee =new Scanner(System.in). El nombre System.in
indica que los datos se ingresaran del teclado.
Para leer una variable entera el código queda como sigue:
import java . u t i l . Scanner ;
p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e =new S c a n n e r ( System . i n ) ;
int i = lee . nextInt ( ) ;

Cuando creamos una instancia de la clase Scanner lo que hacemos es iniciar un flujo de datos nuevo desde
el teclado. Cada lectura de una variable entera inicia después de los espacios y termina cuando se encuentra
el primer espacio, que se utiliza como delimitador entre número. Por ejemplo para leer dos números i, j que
están en una lı́nea

123 987

Codificamos:
3.6 Lectura del teclado 37

import java . u t i l . Scanner ;


p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e =new S c a n n e r ( System . i n ) ;
int i = lee . nextInt ( ) ;
int j = lee . nextInt ( ) ;

Si los datos de la entrada están definidos en múltiples lı́neas el resultado es el mismo.


En caso de que la entrada no iguale con la del tipo de datos que queremos leer el Java dará el error
java.util.InputMismatchException.
Para leer datos del teclado existen los siguientes métodos:

Método Descripción
next() Leer una cadena de caracteres hasta encontrar un espacio
nextInt() Leer un número entero
nextFloat() Leer un número de punto flotante
nextDouble() Leer un número de tipo Double.
nextLong() Leer un número de tipo Long
nextLong(base) Leer un número de tipo Long escrito en la base especificado.
nextShort() Leer un número de tipo Short
nextLine() Leer toda una lı́nea y almacenar en una cadena.

Por ejemplo si queremos convertir un número de escrito en base octal, a decimal, podemos utilizar el método
nextLong(8) luego al imprimir obtendremos el equivalente en decimal. Si ingresamos 120120 al ejecutar el
siguiente programna obtendremos 420 que es su equivalente en decimal.

import java . u t i l . Scanner ;


p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e =new S c a n n e r ( System . i n ) ;
Long i = l e e . n e x t L o n g ( 8 ) ;
System . o u t . p r i n t l n ( i ) ;

Como ve conocer y utilizar los diferentes métodos disponibles simplifica el desarrollo de programas.
38 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.7. Errores de redondeo


Tipo Descripción Tamaño
int Tipo entero con un rango desde −2, 147, 483, 648 hasta 4 bytes
2, 147, 483, 647
byte Describe un solo byte con un rango desde −128 hasta 127 1 byte
short Entero pequeño con un rango desde ?32768 hasta 32767 2 bytes
long Entero grande con un rango des- 8 bytes
de −9, 223, 372, 036, 854, 775, 808 hasta
9, 223, 372, 036, 854, 775, 807
double Doble precisión de punto flotante con un rango de ±10308 pre- 8 bytes
cisión de 15 dı́gitos decimales
float Simple precisión de punto flotante con un rango de ±1038
char Tipo carácter representando la codificación ascii: definida en el 2 bytes
equipo
boolean Valor que admite solo verdadero o falso 1 bit

En muchos casos se producen errores de redondeo cuando no se pueden convertir exactamente los datos.

double f = 4.35;
System.out.println(100 * f);
// Imprime 434.99999999999994

Utilice el método round para redondear números de punto flotante.

long redondeado = Math.round(balance);

Para convertir de tipo utilice cast colocando el tipo entre paréntesis.

(int) (balance * 100)

En las variables de punto flotante hay que tomar una atención especial a la precisión. Como las variables de
punto flotante no se convierten a binario en forma exacta el siguiente programa dará como respuesta falso.

double x = 1.0 - 0.1 - 0.1 - 0.1 - 0.1 - 0.1;


System.out.println(x == 0.5);

3.8. Ejercicios
3.8 Ejercicios 39

3.8.1. JV-1222: Suma de 2 números


Escriba un programa que lea dos números enteros separados por un espacio e imprima la suma.

Entrada
La entrada consiste de dos números separados por un espacio.

Salida
La salida consiste de una lı́nea con la suma de los números.

Ejemplos de entrada Ejemplos de salida


22 44 66
40 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.8.2. JV-1223: Suma de 3 números


Escriba un programa que lea tres números enteros separados por un espacio e imprima la suma.

Entrada
La entrada consiste de una linea con tres números enteros separados por un espacio.

Salida
La salida consiste de una lı́nea con la suma de los tres números.

Ejemplos de entrada Ejemplos de salida


22 44 -10 56
3.8 Ejercicios 41

3.8.3. JV-1227: División entera


Escribir un programa que lea dos números naturales a y b, con b > 0, e imprimir el cociente d y el resto r,
de dividir a entre b. Recuerde que, por definición, d y r deben ser números enteros, tal que 0 ≤ r < b y
d ∗ b + r = a.

Entrada
La entrada consiste de dos números enteros a, b > 0.

Salida
Imprimir una lı́nea con la división entera y el resto de a dividido por b, separados por una espacio.

Ejemplos de entrada Ejemplos de salida


32 6 5 2
42 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.8.4. JV-1230: Descomponer el tiempo


Tenemos una máquina que tiene una pequeña pantalla en el que indica cuantos segundos lleva funcionando.
El problema radica en que para los humanos es muy difı́cil leer el tiempo solamente en segundos, es por eso
que debes hacer un programa que descomponga este número de segundos a un formato que sea mas fácil de
leer.
Este formato consiste en descomponer ese número de segundos en horas, minutos y segundos. Ademas no
puede ser cualquier descomposición, debe ser una descomposición válida, una descomposición es válida si
no podemos tomar alguna cantidad de segundos de la descomposición y convertirla en un minutos, y no
podemos tomar alguna cantidad de minutos de la descomposición y convertirla en horas.

Entrada
La entrada consiste en un número entero que representa la cantidad de segundos que indica la máquina.

Salida
Escriba en la salida la cantidad de horas, minutos y segundos separados por un espacio.

Ayuda
5 59 4 ⇒ Formato válido
5 61 4 ⇒ Formato inválido, se pueden tomar 60 min. y convertirlo en 1 hora
27 15 40 ⇒ Formato válido
3 5 123 ⇒ Formato inválido, se pueden tomar 2 minutos(120 seg.) del contador de segundos.

Ejemplos de entrada Ejemplos de salida


3661 1 1 1
3.8 Ejercicios 43

3.8.5. JV-1231: Sume un segundo


Dadas las horas, minutos y segundos se le pide sumar un segundo.

Entrada
La entrada consiste de 3 números enteros separados por un espacio que representan las horas minutos y
segundos. Donde horas < 24, minutos < 60, segundos < 60.

Salida
Imprima en la salida la nueva hora del reloj en formato mostrado en el ejemplo. Las horas, minutos y segundos
deben estar en el siguiente formato: XX:YY:ZZ
Se debe poner ceros a la izquierda de los números si es necesario.

Ejemplos de entrada Ejemplos de salida


11 33 15 11:33:16
44 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.8.6. JV-1265: Monedas Británicas


Antes del año 1971, Gran Bretaña utilizó un sistema de monedas que se remonta a la época de Carlomagno.
Las tres principales unidades de la moneda británica fueron el penique, el chelı́n, y la libra. Se tenı́an las
siguientes equivalencias, existen 12 peniques en un chelı́n y 20 chelines en una libra. Dado una cantidad de
monedas peniques se quiere convertir esta cantidad en libras, chelines y peniques para esto se procede de la
siguiente manera, la primera conversión será de monedas de peniques a su equivalencia máxima en libras, y
luego convertir la mayor cantidad de peniques restantes como sea posible hacia su equivalente en chelines
y el restante se mantiene en peniques. Devuelve estos datos en un vector de enteros con tres elementos que
contiene la equivalencia en monedas de libras, monedas de chelines, y el número de monedas de un penique,
en ese orden.

Entrada
La entrada de datos consiste en un entero A que representa la cantidad de monedas disponible en peniques,
el número está definido como se indica 0 ≤ A ≤ 10000
En la entrada hay un solo dato. Se muestran múltiples datos como ejemplo.

Salida
Escriba para cada caso de prueba tres números, los cuales son el equivalente en libras, chelı́n y peniques.

Ejemplos de entrada Ejemplos de salida


533 (2, 4, 5)
0 (0, 0, 0)
6 (0, 0, 6)
4091 (17, 0, 11)
10000 (41, 13, 4)
3.8 Ejercicios 45

3.8.7. JV-1370: Bits


Las computadoras operan en números binarios. Casi todos los cálculos se realizan manipulando 0’s y 1’s.
Para que las computadoras puedan utilizar los números que le damos hay que convertirlos de la base 10 que
normalmente usamos, a la base binaria (2). En muchas ocasiones es útil determinar cuantos bits se requieren
para representar un número, con la finalidad de ahorrar espacio. Por ejemplo cualquier número menor a 256
se puede representar con 8 bits.
Para hallar el equivalente decimal de un número binario procedemos como sigue: Para cada número 1
sumamos las potencias 2i donde i el el número de dı́gitos a la derecha del uno. Por ejemplo el equivalente
decimal del número binario 10100 se halla como sigue: a la derecha del primer 1 hay 4 dı́gitos dando 24 = 16,
a la derecha del segundo 1 hay dos dı́gitos que representa 22 = 4. Sumando ambos tenemos su equivalente
decimal que es 20
.

Entrada
Los datos de entrada consisten de varios casos de prueba. Cada caso de prueba viene en una lı́nea que contiene
el número que queremos representar en binario.
En el ejemplo se muestran varios casos de prueba. La entrada consiste de uno solo.

Salida
Por cada caso de prueba escriba en una lı́nea el número mı́nimo de bits que se requiere para representar este
número.

Ejemplos de entrada Ejemplos de salida


32 6
12 4
1 1
1500 11
46 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.8.8. JV-1247: Número binario reverso


Escriba un programa que lee un entero e imprime su representación binaria en forma reversa.

Entrada
La entrada consiste de un número entero natural

Salida
Imprima la representación binaria en forma reversa con el número de ceros a las izquierda como se requiera

Ejemplos de entrada Ejemplos de salida


16 00001
3.8 Ejercicios 47

3.8.9. JV-1429: Cambiar un bit


El problema pide cambiar un determinado bit a un numero entero. Recordamos que los números enteros son
de 32 bits.
Supongamos que tenemos el número 10 cuya representación binaria es 1010. Ahora nos piden cambiar el
tercer bit a 1, el resultado será 1110 cuya representación decimal es el 14.

Entrada
La entrada consiste de un número entero y el bit que queremos cambiar. En el ejemplo de entrada hay
múltiples ejemplos, sin embargo cada caso de prueba solo trae un caso.

Salida
En la salida escriba en una lı́nea dos números separados por un espacio. El primero el resultado de colocar
el bit solicitado a uno y el segundo el resultado de cambiar el bit solicitado, si es uno cambie a 0 y si es cero
cambie a 1.

Ejemplos de entrada Ejemplos de salida


1234 7 1234 1106
10 3 10 2
10 2 14 14
48 Capı́tulo 3 Operadores aritméticos y lectura de teclado

3.8.10. JV-1228: Mayúsculas y minúsculas


Se pide que lea una letra y si es minúscula la convierta en mayúscula y viceversa.
Para leer un caracter del teclado se utiliza la siguiente secuencia de instrucciones:
Scanner lee=new Scanner(System.in);
char c = lee.nextLine().charAt(0);

Entrada
La entrada consiste de una letra en una lı́nea.

Salida
La salida consiste de una letra de acuerdo al enunciado.

Ejemplos de entrada Ejemplos de salida


a A
3.8 Ejercicios 49

3.8.11. JV-1428: Distancia entre dos puntos


Dadas las coordenados de dos puntos P 1 = (x1, y1) y P 2 = (x2, y2) hallar la distancia entre dos puntos.
La fórmula para la distancia de dos puntos es

p
distancia = ((x1 − x2)2 + (y1 − y2)3 2)

Entrada
La entrada consiste de cuatro números con decimales, que representan las coordenadas de los dos puntos
x1, y1, x2, y2 respectivamente.

Salida
Escriba una lı́nea con la distancia de los dos puntos con dos decimales.

Ejemplos de entrada Ejemplos de salida


1,1 2,3 -2,2 3,33 3.46
4
4.1.
Estructuras de control
Introducción
Los lenguajes de programación implementan tres tipos de instrucciones

Instrucciones de asignación
Instrucciones de iteración
Instrucciones de comparación

Los operadores de asignación ya fueron explicados, permiten asignar un valor a una variable. Los operadores
de iteración permiten repetir bloques de instrucciones. Y los operadores de condición se utilizan para
comparar valores. En el capı́tulo explicaremos éstas instrucciones y el uso en la resolución de problemas.

4.2. Agrupamiento de instrucciones


Las instrucciones se pueden agrupar en bloques con el uso de corchetes {}. Esto permite que las definiciones
de variables tengan un ámbito de vigencia. Vemos el código siguiente:

{
int i=1;
System.out.println(i);
}
System.out.println(i);

La variable i está definida dentro de un bloque y por esto solo es accesible en su interior. Esto se puede
apreciar en las instrucciones System.out.println(i). La primera instrucción permite mostrar el valor de i en
pantalla. La segunda que esta fuera del bloque muestra el error cannot be revolved que indica que es una
variable no accesible en este lugar.
Si queremos hacer esta variable disponible dentro y fuera del bloque hay que definirla fuera del bloque.

int i=1;
{
System.out.println(i);
}
System.out.println(i);

Las variables que se definen dentro del bloque se denominan variables locales. En otros casos se denominan
variables globales.

51
52 Capı́tulo 4 Estructuras de control

4.3. Estructuras de control condicionales


Las estructuras de control condicionales son las que nos permiten comparar variables para controlar el orden
de ejecución de un programa. Estas estructuras de control implementan con la instrucción if, if else, if else if, y
?. Estas instrucciones nos permiten decidir que ciertas instrucciones que cumplan una determinada condición
se ejecuten o no.

4.3.1. Estructura de control if


El diagrama 4.1 muestra el flujo de ejecución de una instrucción if.

falso
if(condición)

Verdadero

Bloque de instrucciones

Figura 4.1 Flujo de ejecución de un if

La sintaxis de Java es:

if (condición) {
bloque de instrucciones
}

if (condición)
instrucción;

Note que después de los paréntesis del if no se coloca un punto y coma.

4.3.2. Operadores condicionales


Los operadores condicionales para comparar variables se muestran en la tabla siguiente:
4.3 Estructuras de control condicionales 53

Operador Descripción
== igual
< menor que
<= menor o igual
> mayor que
>= mayor o igual
!= no igual
! negar

Los operadores condicionales trabajan con dos variables y el resultado puede ser verdadero (true) o falso
(false). Por ejemplo a > b, a == b, etc. Hay que tomar en cuenta que los operadores ==, <=, > 0, ! = se
escriben sin espacios.

4.3.2.1. Ejemplo.
Supongamos que queremos resolver la ecuación ax + b = 0. Se ingresan los valores de a, b por teclado, y
se imprime el valor calculado de x. La solución de esta ecuación es x = −b/a. Como sabemos solo existe
solución si a es mayor que cero. Para resolver esto realizamos el siguiente programa:

import java . u t i l . Scanner ;


p u b l i c c l a s s Ecua {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t a= l e e . n e x t I n t ( ) ;
i n t b= l e e . n e x t I n t ( ) ;
i f ( a !=0)
System . o u t . p r i n t l n ( ( f l o a t )−b / a ) ;
}
}

La instrucción if (a!=0) verifica que se realizará una división por cero. Cuando a es diferente a cero se calcula
el resultado de x. Cuando a = 0 no obtiene ningún resultado.

4.3.3. Estructura de control if else

El diagrama 4.2 muestra el flujo de ejecución de una instrucción if else.


La sintaxis de Java es:

if (condición) {
bloque de instrucciones 1
}
else {
bloque de instrucciones 2
}

o
54 Capı́tulo 4 Estructuras de control

Verdadero
falso (else)
if(condición)

Bloque de instrucciones 1 Bloque de instrucciones 2

Figura 4.2 Flujo de ejecución de un if else

if (condición)
instrucción1;
else
instrucción2;

Esta instrucción nos dice que si cumple la condición se ejecuta el bloque de instrucciones 1 y si no se cumple
se ejecuta la secuencia de instrucciones 2.
Consideremos el ejemplo anterior para hallar la solución de ax + b = 0. Utilizando la estructura if else
podemos mostrar un mensaje indicando que no existe solución. El código es:
import java . u t i l . Scanner ;
p u b l i c c l a s s Ecua {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t a= l e e . n e x t I n t ( ) ;
i n t b= l e e . n e x t I n t ( ) ;
i f ( a !=0)
System . o u t . p r i n t l n ( ( f l o a t )−b / a ) ;
else
System . o u t . p r i n t l n ( ” No e x i s t e una s o l u c i ó n ” ) ;
}
}

4.3.4. Estructura de control if else if


Muchas veces debemos anidar las instrucciones if. Esto se puede hacer como sigue:
i f ( condicion1 ){
instrucciones1
i f ( c o n d i c i ó n 2 ) {
4.3 Estructuras de control condicionales 55

instrucciones2
}
else {
instrucciones2
}
else {
instrucciones3
}

Este forma de anidar se puede repetir las veces que sea necesario sin un lı́mite. Ahora podemos mejorar el
programa para resolver ax + b = 0 considerando el caso en que a = b = 0 en el cual pondremos un mensaje
apropiado. El programa queda como sigue:

import java . u t i l . Scanner ;


p u b l i c c l a s s Ecua {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t a= l e e . n e x t I n t ( ) ;
i n t b= l e e . n e x t I n t ( ) ;
i f ( a !=0)
System . o u t . p r i n t l n ( ( f l o a t )−b / a ) ;
else {
i f ( b ==0)
System . o u t . p r i n t l n ( ” No p u e d e i n g r e s a r a y b en 0 ” ) ;
else
System . o u t . p r i n t l n ( ” No e x i s t e una s o l u c i ó n ” ) ;
}
}
}

4.3.5. Conectores lógicos and, or


Las expresiones lógicas pueden asociarse a través de conectores lógicos and y or. En java éstos conectores
representan por && y || respectivamente. Vea que son diferentes a las operaciones con bits que tienen un
solo sı́mbolo.
Con estas instrucciones podemos realizar comparaciones más complejas. Por ejemplo (a > b)&&(c > d).
Que significa que debe cumplirse la primera condición y la segunda condición simultáneamente.
La tabla siguiente muestra como trabaja el operador and.
Expre.A Operador Expre.B Resultado
true && true true
true && false false
false && true false
false && false false
56 Capı́tulo 4 Estructuras de control
La tabla siguiente muestra como trabaja el operador or.
Expre.A Operador Expre.B Resultado
true || true true
true || false true
false || true true
false || false false
La tabla siguiente muestra como trabaja el operador not.
Operador Expre. Resultado
! true false
! false true

4.3.6. Prioridad de los operadores


Los conectores lógicos se procesan de acuerdo al siguiente orden de prioridades: Primero los paréntesis las
instrucciones and y después las or. Veamos un ejemplo: Sea a = 1, b = 2, c = 1 y deseamos evaluar la
expresión (a > b)&&(b < c) || (a == c)
a>b Oper. b<c Oper. a == c
false && false || true
Primero realizamos las operaciones and y tenemos f alse&&f alse cuyo resultado es f alse. Continuamos
con la operación or y tenemos que f alse || true que da true. Para los valores dados esta expresión dará
como resultado verdadero.
Si queremos cambiar el orden de evaluación debemos usar los paréntesis quedando (a > b)&&((b < c) ||
(a == c)). En este caso operación or da f alse || true es true y luego haciendo and con f alse el resultado
es f alse, que como ve, es diferente al anterior.

4.3.7. Propiedades y equivalencias


En la utilización de los operadores lógicos es necesario conocer las siguientes equivalencias.

Expresión Equivalencia Descripción


!(!a) a Doble negación
!(a&&b) !a ||!b negación de un and
!(a || b) !a&&!b) negación de un or

Las equivalencias dos y tres, se denominan leyes de De Morgan. Cuando tenemos una serie de condiciones
podemos aplicar estas equivalencias para simplificar las expresiones lógicas.

4.3.8. Estructura de control ?


La estructura de control ? es equivalente a una estructura if else. La sintaxis es:

varibale=condicion ? expresión por verdad : expresión por falso;

Por ejemplo si deseamos hallar el máximo entre a y b y guardar el resultado en la variable m, utilizando if
else la codificación es como sigue:
4.3 Estructuras de control condicionales 57
if (a>b)
m=a;
else
m=b;

Utilizando la estructura ? la sintaxis para hallar el máximo es:

m=a>b?a:b;

Lo que expresa esta instrucción es que si a > b entonces m toma el valor de a en otros casos toma el valor
de b.

4.3.9. Estructura de control switch


Para introducir la instrucción switch supongamos que tenemos las notas de 0 − 5, y queremos imprimir
el texto reprobado para las notas 1, 2, 3, el texto raspando cuando la nota es 4, y aprobado cuando es 5.
Utilizando una secuencia de instrucciones if else se codifica como sigue:

import java . u t i l . Scanner ;


public c l a s s EjeSwitch {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t nota= lee . nextInt ( ) ;
i f ( n o t a ==1)
System . o u t . p r i n t l n ( ” R e p r o b a d o ” ) ;
else
i f ( n o t a ==2)
System . o u t . p r i n t l n ( ” R e p r o b a d o ” ) ;
else
i f ( n o t a ==3)
System . o u t . p r i n t l n ( ” R e p r o b a d o ” ) ;
else
i f ( n o t a ==4)
System . o u t . p r i n t l n ( ” Raspando ” ) ;
else
i f ( n o t a ==5)
System . o u t . p r i n t l n ( ” Aprobado ” ) ;
}
}

Este código como ve es muy poco entendible, poco modificable, y propenso a error.
La instrucción switch permite resolver estos problemas. La sintaxis es

switch (variable) {
case valor entero : instrucciones; break;
case valor entero : instrucciones; break;
case valor entero : instrucciones; break;
58 Capı́tulo 4 Estructuras de control
}

Esta instrucción trabaja exclusivamente sobre un valor entero. En cada instrucción case se compara el valor
indicado con el valor de la variable. Si es menor o igual se ingresa a las instrucciones que están después de
los dos puntos. La instrucción break hace que la secuencia continúe al final del bloque. El ejemplo anterior
utilizando la instrucción switch se codifica como sigue:

import java . u t i l . Scanner ;


public c l a s s EjeSwitch {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t nota= lee . nextInt ( ) ;
switch ( nota ){
case 1:
case 2:
c a s e 3 : System . o u t . p r i n t l n ( ” R e p r o b a d o ” ) ; b r e a k ;
c a s e 4 : System . o u t . p r i n t l n ( ” Raspando ” ) ; b r e a k ;
c a s e 5 : System . o u t . p r i n t l n ( ” Aprobado ” ) ; b r e a k ;

}
}
}

Como se ve es una codificación más simple de entender. ¿Que significan las instruciones case dejadas en
blanco? Estas lineas no haran ningún proceso. Cuando nota sea 1 o 2 pasara directamente al caso 3, y
mostrara reprobado. También podemos eliminar esta instrucciones y obtener el mismo resultado.

4.4. Estructuras de control iterativas


Las instrucciones de iteración, son instrucciones que permiten repetir varias veces un bloque de instrucciones.
Se conocen como estructuras de control repetitivas o iterativos. También se conocen como bucles o ciclos.
Nos permiten resolver diversos tipos de problemas de forma simple.
Supongamos que deseamos sumar una cantidad de números leı́dos del teclado. Es posible escribir un
programa que lea un número y sume, luego lea otro número sume, etc. Esta estrategia genera los siguientes
problemas:

El código serı́a muy largo


Si aumentamos más datos a sumar debemos aumentar las instrucciones
Es propenso a tener errores
Si hay que hacer cambios puede ser muy moroso
Muchas partes del programa van a estar duplicadas.

4.4.1. Ciclo for

El ciclo for tiene la siguiente sintaxis:


4.4 Estructuras de control iterativas 59
for (expresión 1; expresión 2; expresión 3) {
bloque de instrucciones
}

El proceso de esta instrucción es como sigue:

1. Se ejecutan las instrucciones definidas en expresión 1


2. Se evalúa la expresión 2. Si es verdadera se ejecuta el bloque de instrucciones
3. Se ejecutan las instrucciones de expresión 3 y se vuelve a 2

El diagrama 4.3 muestra un diagrama de como se ejecuta el ciclo for. Veamos algunos ejemplos de utilización.

Expresión 1

falso
Expresión 2

Verdadero

Bloque de instrucciones

Expresión 3

Figura 4.3 Diagrama de la instrucción for

Queremos calcular el factorial de un número n, que se define como n! = (n)(n−1)(n−2)...,1. Para esto hay
que construir un bucle con un valor que varı́a desde 1 hasta n. Dentro del ciclo se realiza la multiplicación.
En la expresión 1 crearemos una variable que nos servirá para contar el número de iteraciones, que en este
caso es n. Esto hacemos con la instrucción int i = 1. Al poner int como tipo para la variable i hacemos que
i sea una variable local dentro del bloque de instrucciones.
60 Capı́tulo 4 Estructuras de control
Luego definimos la expresión 2 que regula la finalización del ciclo. En este caso debemos hacer variar el
valor de i hasta que tome el valor de n. La condición para controlar la finalización es i <= n. Mientras que
se cumpla esta condición se ejecuta el bloque de instrucciones.
Finalmente viene la expresión 3. Aquı́ incrementamos el valor de i en uno con la instrucción i + +.
Ahora el código que resuelve el problema. Iniciamos una variable que utilizaremos para hallar el factorial.
Esta variable la denominaremos f y la inicializamos en 1. Luego podemos multiplicar por i y ası́ al finalizar
el ciclo for f tendrá el factorial de n. El código para calcular el factorial de 5 es:

public class fact {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =5 , f = 1 ;
f o r ( i n t i = 1 ; i <=n ; i ++){
f=f∗ i ;
}
System . o u t . p r i n t l n ( f ) ;
}
}

Hay que ver que la variable f y la variable n fueron definidas fuera del ciclo. Si nos fijamos luego de la
instrucción for no se coloca un punto y coma para evitar que la misma termine antes de iniciado el bloque de
instrucciones a ejecutar.
Una forma abreviada de escribir puede ser:

public class fact {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t f =1;
f o r ( i n t i =1 , n = 5 ; i <=n ; f = f ∗ i , i + + ) ;
System . o u t . p r i n t l n ( f ) ;
}
}

En este código en el primer bloque de instrucciones hemos agregado la definición de la variable n con su
valor inicial 5. En el tercer bloque hemos agregado la instrucción que calcula el factorial. Con esto no se
requiere definir un bloque, todo queda contenido en la instrucción for. Esta es la razón por la que colocamos
un punto y coma al final la instrucción.
Veamos otro ejemplo. Se quiere hallar el resultado de la expresión:
i=10
X
s= i2
i=0

Para este problema inicializamos una variable s en 0. Luego con un for podemos variar i en todo el rango de
la suma. El código resultante es:

p u b l i c c l a s s suma {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t s =0;
4.4 Estructuras de control iterativas 61

f o r ( i n t i = 0 ; i <=10; i ++){
s +=( i ∗ i ) ;
}
System . o u t . p r i n t l n ( s ) ;
}
}

Ahora si conocemos una expresión que nos de directamente el resultado de la suma, el programa es más
eficiente. Dado que demora mucho menos tiempo de proceso. En muchas expresiones esto es cierto, en
cambio para otras no es posible. En el ejemplo que mostramos es posible:
i=n
X
s= i2 = n(n + 1)(2n + 1)/6
i=0

4.4.2. Ciclo while


El ciclo while permite ejecutar las instrucciones de un bloque hasta que se cumpla cierta condición. la
codificación de esta instrucción es como sigue:

while (expresión) {
bloque de instrucciones
}

El diagrama 4.4 muestra como se ejecuta el ciclo while. Para asegurar que se llegue a la condición de

falso
Expresión

Verdadero

Bloque de instrucciones

Figura 4.4 Diagrama de la instrucción while

finalización debe existir algo que haga variar la condición dentro del ciclo. El ejemplo que realizamos para
hallar el factorial también puede realizarse utilizando una instrucción while. el programa siguiente muestra
esta implementación.
62 Capı́tulo 4 Estructuras de control
public class fact {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =5 , f =1 , i = 1 ;
w h i l e ( i <=n ) {
f=f∗ i ;
i ++;
}
System . o u t . p r i n t l n ( f ) ;
}
}

La relación que existe entre un ciclo for y while es como sigue:

for (expresión 1; expresión 2; expresión 3) {


bloque de instrucciones;
}

Equivale a

expresión 1;
while(expresión 2) {
bloque de instrucciones;
expresión 3;
}

4.4.3. Ciclo do while


El ciclo do while se utiliza para garantizar que el flujo del programa pase una vez por el bloque de
instrucciones. La sintaxis es como sigue:

do {
bloque de instrucciones
} while (expresión);

El diagrama 4.5 muestra como se ejecuta el ciclo do while. El programa de factorial mostrado puede también
resolverse utilizando un ciclo do while. El programa siguiente muestra la forma de resolver el mismo.
public class fact {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =5 , f =1 , i = 1 ;
do {
f=f∗ i ;
i ++;
}
w h i l e ( i <=n ) ;
System . o u t . p r i n t l n ( f ) ;
}
}
4.4 Estructuras de control iterativas 63

Bloque de instrucciones

Expresión

Verdadero
falso

Figura 4.5 Diagrama de la instrucción do while

4.4.4. Ciclos anidados


Los ciclos pueden anidarse, esto podemos escribir un ciclo dentro de otros ciclos. La forma de anidar bucles
se muestra en la figura 4.6.

Ciclo 1 Ciclo 1

Ciclo 2 Ciclo 2

Bloque de Instrucciones Bloque de Instrucciones

Anidación Valida Anidación Invalida

Figura 4.6 Como anidar Ciclos

Veamos un ejemplo de anidar dos ciclos for:

p u b l i c c l a s s Anidar {
64 Capı́tulo 4 Estructuras de control
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
f o r ( i n t i = 0 ; i <5; i ++){
f o r ( i n t j = 0 ; j <5; j ++){
System . o u t . p r i n t ( ” ( ” + i + ” , ” + j + ” ) ” ) ;
}
System . o u t . p r i n t l n ( ) ;
}
}
}

El resultado que produce es:

(0,0) (0,1) (0,2) (0,3) (0,4)


(1,0) (1,1) (1,2) (1,3) (1,4)
(2,0) (2,1) (2,2) (2,3) (2,4)
(3,0) (3,1) (3,2) (3,3) (3,4)
(4,0) (4,1) (4,2) (4,3) (4,4)

El ciclo exterior hace variar la variable i de 0 a 4. El ciclo interior varı́a 0 ≤ j ≤ 4. La instrucción


System.out.print(”(-i+”,-j+”) ”) imprime los valores de i, j sin avanzar a la siguiente lı́nea. Cuando termina
el bucle interior avanzamos una lı́nea.
Podemos anidar todo tipo de bucles, respetando siempre que un bucle exterior encierra completamente un
ciclo interior. No deben existir bucles cruzados como se muestra en la figura 4.6.

4.5. Lectura de secuencias de datos


Generalmente no se lee un solo valor y debemos leer una secuencia de datos. A esto llamamos proceso de
lotes. Supongamos que queremos hallar el promedio de una secuencia de números. Para hacer posible ésto
ingresaremos primero la cantidad de datos y luego los datos.

5
10 5 80 60 40

Los datos vienen separados por espacios para hacer posible que el método nextInt() de la clase Scanner pueda
leer los mismos. Recordamos que el promedio se calcula con la fórmula
Pi=n
di
p = i=1
n
Para leer estos datos y calcular el promedio construimos el siguiente programa:

import java . u t i l . Scanner ;


p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
int n = lee . nextInt ( ) ;
i n t s =0 , d a t o ;
f o r ( i n t i = 0 ; i <n ; i ++){
4.5 Lectura de secuencias de datos 65

dato= lee . nextInt ( ) ;


s=s+dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
}
}

Esto está de acuerdo a la entrada de datos y procesa un solo conjunto de datos. Una posibilidad es que se
tengan que ingresar muchas secuencias de las que calcularemos el promedio. Por ejemplo podrı́amos optar
por varias opciones para definir la cantidad de secuencias, Veamos tres posibilidades:

1. Primero especificar la cantidad de casos por ejemplo:

2
5
10 5 80 60 40
4
90 20 15 80

Hemos anotado en la primera lı́nea la cantidad de secuencias, que son 2, inmediatamente hemos
colocado las dos secuencias como en el ejemplo anterior. La solución viene dada por dos ciclos for
como se muestra en el programa:
import java . u t i l . Scanner ;

p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
int n = lee . nextInt ( ) ;
f o r ( i n t i = 0 ; i < n ; i ++) {
int m = lee . nextInt ( ) ;
i n t s = 0 , dato ;
f o r ( i n t j = 0 ; j < m; j ++) {
dato = lee . nextInt ( ) ;
s = s + dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
}
}
}

2. Una segunda opción es colocar un señuelo al final que indique que hemos llegado al final. Por ejemplo
podemos colocar un cero al final para indicar que no hay más secuencias sobre las que queremos trabajar.
Para esto colocamos los datos de prueba como sigue:

5
10 5 80 60 40
4
66 Capı́tulo 4 Estructuras de control
90 20 15 80
0

Podemos hacer esto de varias formas: Utilizando la instrucción for, con la instrucción while, y también
con do while. Expliquemos primero como aplicar un for para este caso. Nuestra expresión inicial será
leer el número de casos n, luego si éste es mayor a cero, no estamos al final y procesamos el bloque de
instrucciones. Finalizado el proceso del bloque de instrucciones, leemos otra vez el número de casos y
continuamos. El programa siguiente muestra esta lógica.

import java . u t i l . Scanner ;

p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
f o r ( i n t n= l e e . n e x t I n t ( ) ; n >0; n= l e e . n e x t I n t ( ) ) {
i n t s =0 , d a t o ;
f o r ( i n t j = 0 ; j <n ; j ++){
dato= lee . nextInt ( ) ;
s=s+dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
}
}
}

Para resolver esto con la instrucción while, primero leemos un valor de n luego, iniciamos un while para
verificar que llegamos al final. Cuando termina el bloque de instrucciones leemos el próximo valor de
n. Esto nos da el código siguiente:

import java . u t i l . Scanner ;


p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
i n t n= l e e . n e x t I n t ( ) ;
w h i l e ( n >0){
i n t s =0 , d a t o ;
f o r ( i n t j = 0 ; j <n ; j ++){
dato= lee . nextInt ( ) ;
s=s+dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
n= l e e . n e x t I n t ( ) ;
}
}
}
4.5 Lectura de secuencias de datos 67
Finalmente utilizando la instrucción do while. Primero leemos la cantidad de datos del primer caso de
prueba. Procesamos y cuando llegamos al while leemos otra vez para saber si hay más casos de prueba.
El código es el siguiente:

import java . u t i l . Scanner ;


p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
int n;
n= l e e . n e x t I n t ( ) ;
do {
i n t s =0 , d a t o ;
f o r ( i n t j = 0 ; j <n ; j ++){
dato= lee . nextInt ( ) ;
s=s+dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
}
w h i l e ( ( n= l e e . n e x t I n t ( ) ) > 0 ) ;
}
}

3. La tercera posibilidad es que al final esté especificado como no hay mas datos de prueba, vale decir, el
final del archivo de datos. Los datos estarán dados ası́

5
10 5 80 60 40
4
90 20 15 80

Para este caso requerimos de un método de la clase Scanner. El método hasNext que nos indica que si
hay más datos de entrada o no hay más datos. Lo primero que hacemos es colocar un while que controle
si hay más datos y luego sigue el conjunto de instrucciones. El programa es como sigue:

import java . u t i l . Scanner ;


p u b l i c c l a s s Promedio {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
while ( l e e . hasNext ( ) ) {
i n t n= l e e . n e x t I n t ( ) ;
i n t s =0 , d a t o ;
f o r ( i n t j = 0 ; j <n ; j ++){
dato= lee . nextInt ( ) ;
s=s+dato ;
}
System . o u t . p r i n t l n ( ( f l o a t ) s / n ) ;
}
68 Capı́tulo 4 Estructuras de control
}
}

En este método debemos considerar dos casos, primero si leemos del teclado directamente siempre
se considera que hay más datos en la entrada. Segundo, cuando es un archivo del disco entonces
efectivamente se terminan cuando no hay más datos.
Para que se lean los datos desde un archivo se procesa de la lı́nea de comando como sigue:

java Programa < archivo

El sı́mbolo < indica que la lectura ya no es del teclado y proviene del archivo. Esto se denomina
redireccionar la entrada.

4.6. Ejercicios
4.6 Ejercicios 69

4.6.1. JV-1224 Máximo de dos números


Escriba un programa que lea dos números enteros separados por un espacio e imprima el mayor.

Entrada
La entrada consiste de una lı́nea con dos números enteros separados por un espacio

Salida
La salida consiste de una lı́nea que contiene el máximo de los números.

Ejemplos de entrada Ejemplos de salida


22 44 44
70 Capı́tulo 4 Estructuras de control

4.6.2. JV-1225: Máximo de tres números


Escriba un programa que lea tres números enteros separados por un espacio e imprima el máximo.

Entrada
La entrada consiste de una lı́nea con tres números enteros separados por un espacio.

Salida
La salida consiste de una lı́nea que contiene el máximo de los números

Ejemplos de entrada Ejemplos de salida


22 44 -10 44
4.6 Ejercicios 71

4.6.3. JV-1226: Temperatura


Escribir un programa que lea un número entero que representa una temperatura dada en grados Centı́grados,
y que le informe si el clima es cálido, si hace frı́o, o si está bien. Suponemos que hace calor si la temperatura
es superior a 30 grados, que es frı́o si la temperatura es inferior a 10 grados, y que está bien lo contrario. Por
otra parte, Adicionalmente imprima una lı́nea que diga, que el agua a esa temperatura hierve, o se congela.
Se supone que el agua hierve a los 100 grados y se congela a los 0 grados.

Entrada
La entrada consiste en un entero que representa la temperatura en grados Centigrados.

Salida
Se debe imprimir una o dos lı́neas, en la primera se debe imprimir: ”hace calor”, ”hace frio”, .esta
bien”dependiendo del caso, y en la segunda se debe imprimir ”hierve.o ”se congela”si fuera el caso.

Ejemplos de entrada Ejemplos de salida


-5 hace frio
se congela
72 Capı́tulo 4 Estructuras de control

4.6.4. JV-1372: Años Bisiestos


Los años bisiestos tienen 366 dı́as. y son aquellos que son múltiplos de 4 y no terminan con dos ceros y que
después de quitar los dos ceros del final son divisibles por 4. Por ejemplo 1800, y 1900 no son años bisiestos,
sin embargo el año 2000 si lo fue.

Entrada
La entrada consiste de números naturales en el rango de 1800 y 9999.

Salida
En la salida escriba una palabra que diga SI si fue bisiesto y NO si no fue bisiesto,

Ejemplos
Ejemplo de entrada 1

1800

Ejemplo de salida 1

NO

Ejemplo de entrada 2

2000

Ejemplo de salida 2

SI

Ejemplos de entrada Ejemplos de salida


1800 NO
4.6 Ejercicios 73

4.6.5. JV-1221: Tres Números


Escriba un programa que lee tres números enteros separados por un espacio y los imprima en una lı́nea
separados por un espacio en forma ordenada de menor a mayor.

Entrada
La entrada consiste de tres números enteros separados por un espacio.

Salida
La salida consiste de los números impresos de menor a mayor.

Ejemplos de entrada Ejemplos de salida


3 1 2 1 2 3
74 Capı́tulo 4 Estructuras de control

4.6.6. JV-1373: Clasificación de Caracteres


Escriba un programa que lea una letra, y diga si esta letra es minúscula, ó mayúscula. También debe indicar
si es una vocal o consonante.
Para leer un caracter del teclado se utiliza la siguiente instrucción:
char c = (char) System.in.read();

Ejemplos
Ejemplo de entrada 1
a

Ejemplo de salida 1
minuscula
vocal

Ejemplo de entrada 2
X

Ejemplo de salida 2
mayuscula
consonante
Entrada

Entrada
La entrada consiste de una letra.

Salida
En la salida escriba si es una letra minúscula o mayúscula. En otra lı́nea escriba si es vocal o consonante.

Ejemplos de entrada Ejemplos de salida


a minuscula
vocal
4.6 Ejercicios 75

4.6.7. JV-1374: Intervalos


Escriba un programa tal que dados dos intérvalos, calcule el intérvalo que corresponde a su intersección o
indica si está vacı́o.

Entrada
La entrada consiste de cuatro enteros a1,a2,b1,b2 que representan los intérvalos [ a1 , b1 ] y [ a2 , b2 ] .
Asuma que a1 ≤ a2 y b1 ≤ b2.

Salida
Imprima [] si la intersección es vacı́a o [x,y] si la intersección no esta vacı́a.

Ejemplos de entrada Ejemplos de salida


20 30 10 40 [20,30]
76 Capı́tulo 4 Estructuras de control

4.6.8. JV-1249: Primeros números


Entrada
La entrada consiste de un número entero N.
Salida
La salida consiste en los números desde en 0 hasta N. Cada uno en una lı́nea

Ejemplos de entrada Ejemplos de salida


5 0
1
2
3
4
5
4.6 Ejercicios 77

4.6.9. JV-1247: Número binario reverso


Escriba un programa que lee un entero e imprime su representación binaria en forma reversa.

Entrada
La entrada consiste de un número entero natural

Salida
Imprima la representación binaria en forma reversa con el número de ceros a las izquierda como se requiera

Ejemplos de entrada Ejemplos de salida


16 00001
78 Capı́tulo 4 Estructuras de control

4.6.10. JV-1248: Números armónicos


Los números armónicos se definen como:

1 1 1 1
Hn = + + ...... +
1 2 3 n

Donde Hn es el n-simo número armónico.

Entrada
La entrada consiste en un número ”n”.

Salida
Imprima el n-simo número armónico, con 4 decimales de precisión.

Ejemplos de entrada Ejemplos de salida


2 1.5000
4.6 Ejercicios 79

4.6.11. JV-: 1289: Lectura1


Se tiene una lista de números y te piden hallar la suma de los mismos. Se sabe que la suma no excede un
número entero.
Sugerencia
Para recorrer todos los casos de prueba se suguiere usar

for( N=leer.nextInt(),i=0;i<N;i++){

//Codigo

Entrada
La primera lı́nea contiene un número T que indica el número de casos de prueba 0 ≤ T ≤ 100. Cada caso
de prueba consiste de dos lı́neas, la primera lı́nea tiene un numero M (0 ≤ M ≤ 1000) que representa la
cantidad de números que hay que leer. La segunda lı́nea contiene los M números separados por un espacio.

Salida
Por cada caso de prueba escrita en una lı́nea la suma de los números.

Ejemplos de entrada Ejemplos de salida


3 10
4 40
1 2 3 4 5
10
9 8 7 6 5 4 3 2 1 -5
5
-1 -3 5 3 1
80 Capı́tulo 4 Estructuras de control

4.6.12. JV-1290: Lectura2


Se tiene una lista de números y te piden hallar la suma de los mismos. Se sabe que la suma no excede un
número entero.
Sugerencia:

Para leer un caso de prueba use

while ((n=leer.nextInt()) != 0)

Entrada
La entrada consiste de múltiples casos de prueba. La primera lı́nea contiene un número N que indica el
número de casos de prueba. Cada caso de prueba consiste de una secuencia que viene en una lı́nea con
números separados por un espacio. Todas las secuencias terminan con un número cero.

Salida
Por cada caso de prueba escriba en una lı́nea la suma de los números.

Ejemplos de entrada Ejemplos de salida


3 10
1 2 3 4 0 40
9 8 7 6 5 4 3 2 1 -5 0 5
-1 -3 5 3 1 0
4.6 Ejercicios 81

4.6.13. JV-1291: Lectura3


Se tiene una lista de números y te piden hallar la suma de los mismos. Se sabe que la suma no excede un
número entero.
Sugerencia:

Para leer todos los casos de prueba use

while (leer.hasNext())

Entrada
La entrada consiste de múltiples casos de prueba. La entrada termina cuando no hay más casos, es decir fin
de archivo. Cada caso de prueba consiste de una secuencia que viene en una lı́nea con números separados
por un espacio. Todas las secuencias terminan con un número cero.

Salida
Por cada caso de prueba escriba en una lı́nea la suma de los números.

Ejemplos de entrada Ejemplos de salida


1 2 3 4 0
9 8 7 6 5 4 3 2 1 -5 0
-1 -3 5 3 1 0
82 Capı́tulo 4 Estructuras de control

4.6.14. JV-1292: Lectura4


Se tiene una lista de números y te piden hallar la suma de los mismos. Se sabe que la suma no excede un
número entero.
Sugerencia:

Para recorrer todos los casos de prueba se siguiere usar

while ((M=leer.nextInt())>0)

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba consiste de dos lı́neas, la primera
lı́nea tiene un número M que representa la cantidad de números que hay que leer. La segunda lı́nea contiene
los M números separados por un espacio. Los casos de prueba terminan cuando M es cero.

Salida
Por cada caso de prueba escriba en una lı́nea la suma de los números.

Ejemplos de entrada Ejemplos de salida


1 2 3 4 0
9 8 7 6 5 4 3 2 1 -5 0
-1 -3 5 3 1 0
4.6 Ejercicios 83

4.6.15. JV-1250: Validar fechas


Se le pide escribir un programa para validar fechas del calendario Gregoriano. Es decir él que utilizamos
comúnmente. Los años bisiestos tienen 366 dı́as. y son aquellos que son múltiplos de 4 y no terminan con
dos ceros, ó que después de quitar los dos ceros del final son divisibles por 4. Por ejemplo 1800, y 1900 no
son años bisiestos, sin embargo el año 2000 si lo fue.

Entrada
La entrada consiste de varias lı́neas. La primera lı́nea contiene un número N que indica cuantas fechas hay
que verificar. Luego siguen N lineas con las fechas a verificar. Cada lı́nea contiene tres números enteros que
representan el dı́a, mes y año respectivamente.

Salida
En la salida imprima Fecha correcta si la fecha es correcta, o por el contrario Fecha incorrecta

Ejemplos de entrada Ejemplos de salida


2 Fecha correcta
30 11 1971 Fecha incorrecta
29 2 2001
84 Capı́tulo 4 Estructuras de control

4.6.16. JV-1018: Operadores Relacionales


Algunos operadores verifican la relación entre dos valores y estos operadores se denominan operadores
relacionales. Dado dos valores numéricos su trabajo es solamente determinar la relación entre ellos (i) El
primero es mayor que el segundo (ii) El primero es menor que el segundo o (iii) El primero y el segundo son
iguales.

Entrada
La primera lı́nea del archivo de entrada es un número entero (t < 15), que indica cuantos pares de números se
tiene en la entrada. Cada una de las siguientes t lı́neas, contiene dos enteros a y b (kak, kbk < 1000000001).

Salida
Para cada lı́nea de entrada se produce una lı́nea de salida. Esta lı́nea contiene alguno de los siguientes
operadores relacionales ✮✮”, ✭✭.o -”, los cuales indica la relación apropiada entre los dos números.

Ejemplos de entrada Ejemplos de salida


3 <
10 20 >
20 10 =
10 10
4.6 Ejercicios 85

4.6.17. JV-1384: Fanático del refresco


Juan es fanático del refresco, pero no tiene suficiente dinero para comprar refrescos. La única forma legal que
tiene de adquirir más refresco es juntar las botellas vacı́as y cambiarlas por más refresco. Adicionalmente a
las que consume recolecta botellas en la calle.

Input
La entrada de datos son tres números e, f, c con e < 1000 que representa el número de botellas vacı́as que
posee, f representa el número de botellas que halló en la calle f < 1000. c representa el número de botellas
vacı́as requeridas para adquirir un refresco. La entrada termina cuando no hay más datos.

Output
Escriba para cada caso de prueba cuantos refrescos pudo tomar en ese dı́a. Cada número debe imprimirse en
una lı́nea.

Ejemplos de entrada Ejemplos de salida


9 0 3 4
5 5 2 9
86 Capı́tulo 4 Estructuras de control

4.6.18. JV-1385: Puertas Vaivén

Todos hemos visto las puertas con vaivén antes, tal vez en la entrada de una cocina. Echa un vistazo a la figura
de abajo para dejar las cosas claras. Esta puerta con bisagras en particular, funciona como sigue: La posición
de reposo es la lı́nea continua , la puerta se inserta en un extremo, y se balancea creando un ángulo (en la
imagen, corresponde al primer swing. Luego, cuando se libera la puerta, se desliza hacia el otro lado (esto
se muestra en la imagen como segundo swing). Pero esta vez, el ángulo que oscila se reduce a una fracción
conocida del ángulo anterior. Luego, se invierte la dirección de nuevo y, una vez más, el ángulo reducido por
la misma fracción. Una vez que el ángulo se reduce a 5, 0 grados o menos, la puerta no oscila más, sino más
bien, vuelve a la posición ”de descanso”.

Figura 4.7 Oscilación de la puerta vaivén

Crear una programa que dado un ángulo inicial a desplazarse y una reducción y devuelve un entero
correspondiente al número de veces que la puerta se balancea antes de llegar al reposo.
Por ejemplo si se desplaza 50 grados y se reduce en 2 cada vez. Entonces al soltar la puerta el ángulo
inicial se ve reducido de (1/2) ∗ (50) = 25 grados en el primer vaivén. En este punto, la puerta debe
revertir la dirección, y el oscilará a través de un ángulo de (1/2) ∗ (25) = 12, 5 grados. Continuando de
esta manera, la puerta se girará una vez más a través de (1/2) ∗ (12, 5) = 6, 25 grados, y luego a través
de (1/2) ∗ (6, 25) = 3, 125 grados. En este punto, la puerta se va a la posición de reposo. Por lo tanto, la
respuesta correcta es de 4, ya que la puerta tomó 4 cambios antes de llegar al descanso.

Input

La entrada consiste de varios casos de prueba cada uno en una lı́nea. Cada caso de prueba consiste de
dos números enteros separados por un espacio. El primero corresponde al desplazamiento d de la puerta
0 ≤ d ≤ 90. Y el segundo a la reducción x en cada oscilación 0 ≤ x ≤ 10. La entrada termina cuando no
hay más datos.

Output

La salida está dada por un número entero en una lı́nea que representa el número de oscilaciones que hará la
puerta.
4.6 Ejercicios 87

Ejemplos de entrada Ejemplos de salida


50 2 4
45 6 2
23 3 2
3 3 0
88 Capı́tulo 4 Estructuras de control

4.6.19. JV-1263: Adivinanzas


Un juego de adivinanzas muy popular es suponga el número, donde una persona selecciona un número en un
rango conocido, y otra persona intenta descubrir que número es. Después de cada intento, la primera persona
revela si la suposición era correcta, demasiado alta, o demasiado baja.
Muy pronto uno aprende que la mejor estrategia es suponer el número medio entre los cuales aún no han
sido descartados. Si no existe un único número medio, entonces existen dos números para seleccionar. En
ese caso, nosotros seleccionamos el más pequeño de esos números.
El algoritmo se puede describir ası́:

extremo inferior y limite superior


Repetir
x = ( el extremo inferior + el lı́mite superior )/2
(redondee hacia abajo si es necesario)
Haga x su suposición
Si x es demasiado baja, establezca extremo inferior a x+1
Si x es demasiado alta, establezca lı́mite superior a x-1
Hasta que x sea correcto

Por ejemplo, suponga que los lı́mites inferior y superior son 1 y 9 , respectivamente. El valor medio es 5.
Si esto es demasiado bajo, los nuevos lı́mites se convierten en 6 y 9. Este rango contiene cuatro números, y
como no existe ningún número medio único. De los dos números en el centro que son 7 y 8 escogemos el
más pequeño de éstos que es 7 , ası́ nuestra suposición próxima entonces se convierte en 7.
Construya un programa que permita leer el lı́mite superior del rango (el extremo inferior siempre es 1), y
el número seleccionado por la primera persona. El programa deberá devolver un entero que representa el
número total de intentos requerido por la segunda persona para adivinar el número correcto.

Input
La entrada consiste de varios casos de prueba. Cada caso de prueba consiste de dos números el número
superior 1 ≤ s ≤ 1000 y el número escogido 1 ≤ e ≤ s. La entrada termina cuando no hay más datos.

Output
Por cada caso de prueba escriba en la salida un solo número que indica el mı́nimo numero intentos para
adivinar el número escogido.

Ejemplos de entrada Ejemplos de salida


9 6 3
1000 750 2
643 327 7
157 157 8
128 64 1
4.6 Ejercicios 89

4.6.20. JV-1266: Polinomios


Se desea un programa que pueda evaluar polinomios de la forma: a1 xn + a2n−1 xn−1 + a3n−2 xn−2 ......an .
Por ejemplo si queremos evaluar polinomio 1x2 + 2x + 3 con x = 1 toma el valor de 6

Input
En la entrada existen varios casos de prueba. La primera lı́nea tiene el valor 0 ≤M≤ 20 que es el número
de elementos del polinomio. Separada por un espacio está el número 0 ≤ v ≤ 50 que es donde queremos
evaluar el polinomio.
Las siguientes lı́neas son los coeficientes del polinomio. La entrada finaliza cuando no hay más valores.

Output
Por cada caso de prueba imprima una lı́nea con un número que representa evaluar el polinomio en el punto
dado.

Ejemplos de entrada Ejemplos de salida


3 1 6.0
1 2 3 9.0
4 2 31.0
1 0 0 1 40.0
4 3
1 0 1 1
4 3
1 1 1 1
90 Capı́tulo 4 Estructuras de control

4.6.21. JV-1444: Pares de unos


Los números en su representación binaria están formados por unos y ceros. Se quiere conocer cuantas pares
de unos seguidos existen en un número. Por ejemplo el número 7 en binario es 111 y existe un par. El número
3 tiene una pareja. El número 15 es el 1111 y tiene dos pares. El número 10 decimal no tiene pares de unos.
Dado un número decimal menor a 225 contar cuantos pares de unos existen en su representación binaria.

Entrada
La entrada consiste de varios casos de prueba y termina cuando no hay más datos. Cada caso de prueba
consiste en un número decimal menor a 22 5.

Salida
Escriba en la salida el número de pares de unos que tiene el número.

Ejemplos de entrada Ejemplos de salida


3 1
10 0
15 2
20 0
255 4
5
5.1.
Cadenas
Definición
Se dice cadenas a las secuencias de texto (caracteres) con las que se quiere trabajar. Todos estos textos
generalmente están en ascii. Uno puede crear su propia representación, pero, para mostrar por pantalla debe
estar en un conjunto de caracteres definido en el sistema. En el español, es ascii, con extensiones para los
caracteres latinos y se denomina latin-1 en Windows e iso-8859-15 en Linux y otros sistemas operativos. En
java se denominan Strings y pertenecen a la clase String.
Como ve no es un tipo básico, es una clase. Por lo tanto, se define como clase y tienen métodos para trabajar
con ella. La sintaxis de java es:

String nombre="cadena";

La definición se la comienza con el nombre de la clase que es String, luego el nombre de la variable y el
contenido es el conjunto de caracteres encerrado entre comillas. Por ejemplo:

String saludo="Hola";

5.2. Recorrido de la cadena


Las cadenas se pueden ver como un conjunto de caracteres uno a continuación del otro. El nombre de la
cadena representa la dirección de memoria donde se encuentran los caracteres que conforma la cadena. Por
esta razón no es posible acceder directamente a sus valores y se requiere utilizar los métodos de la clase.
Consideremos la siguiente definición:

String saludo=”Hola”;
String saludo2=saludo;

Lo que se hizo primero es definir la cadena saludo que apunta al texto Hola, segundo se asigno a la dirección
de la cadena ”Hola” al nombre saludo2. Esto significa que solo existe un ”Hola”, cualquier cambio en
saludo es un cambio en saludo2.
Ahora si comparamos saludo == saludo2 el resultado es verdadero porque ambos apuntan a la misma
cadena.
Si deseamos tener dos cadenas ”Hola” es necesario definir como sigue:

String saludo="Hola";
String saludo2=new String("Hola");

Ahora si comparamos saludo == saludo2 el resultado es falso porque ambos apuntan a diferentes cadenas.
Para recorrer una cadena se tiene el método charAT(posicion). Vemos la cadena ”HOLA”.

91
92 Capı́tulo 5 Cadenas

H O L A
Posición 0 1 2 3

Para mostrar toda una cadena en pantalla tenemos la instrucción que vimos:

System.out.print(cadena)

Si queremos mostrar la misma, carácter por carácter, utilizamos charAt. Por ejemplo para mostrar la cadena
saludo recorremos la cadena con un for como sigue:

String saludo="Hola";
for (int i = 0; i< 4; i++)
System.out.print(saludo.charAt(i));

5.3. Métodos de la clase cadena


La clase cadena tiene varios métodos de los que describimos los más utilizados:
5.3 Métodos de la clase cadena 93

Método Descripción Resultado


charAt(int) Devuelve el carácter ubicado en la po- un carácter
sición
compareTo(str) Compara la cadena con str 0 cuando son iguales, negativo
si es menor y positivo si es ma-
yor
compareToIgnoreCase(str) Compara la cadena con str tomando 0 cuando son iguales, negativo
todas las letras como mayúsculas y si es menor y positivo si es ma-
minúsculas como iguales yor
concat(str) concatena la cadena con str una cadena
contains(str) Busca si existe la cadena str verdadero o falso
endsWith(str) Verifica si la cadena termina con str verdadero o falso
equals(str) Compara si dos cadenas son iguales verdadero o falso
equalsIgnoreCase(str) Compara si dos cadenas son igua- verdadero o falso
les considerando las mayúsculas y
minúsculas como iguales
indexOf(str) Busca la posición donde comienza la devuelve la posición o -1 si no
cadena str existe
indexOf(str, int) Busca después del ı́ndice int la posi- devuelve la posición o -1 si no
ción donde comienza la cadena str existe
lastIndexOf(str) Busca la última posición donde co- devuelve la posición o -1 si no
mienza la cadena str existe
lastIndexOf(str, int) Busca después del ı́ndice int la últi- devuelve la posición o -1 si no
ma posición donde comienza la cade- existe
na str
length() Retorna el tamaño de la cadena un entero
replace(char1,char2) Reemplaza todos los caracteres char1 cadena
por char2
startsWith(str) Indica si la cadena comienza con str verdadero o falso
startsWith(str, int) Indica si la cadena comienza con str verdadero o falso
después del ı́ndice int
substring(int) Obtiene la cadena desde la posición cadena
hasta el final de la cadena int
substring(int, int) Obtiene la cadena desde la posición cadena
int hasta la posición int
toLowerCase() Convierte todas las letras a minúscu- cadena
las
toUpperCase() Convierte todas las letras a mayúscu- cadena
las

Para ejemplificar el uso de los métodos descritos consideremos las siguientes definiciones:

String cad1 = "AbCdEf";


String cad2 = "aBcDeF";
String Cad3 = "abcabcabc";
94 Capı́tulo 5 Cadenas
System.out.println(cad.charAt(0)); da A
System.out.println(cad1.compareTo(cad2)); da −32
System.out.println(cad1.compareToIgnoreCase(cad2)); da 0 son iguales
System.out.println(cad1.concat(cad3)); da AbCdEf abcabcabc
System.out.println(cad3.contains(”ab”)); da true
System.out.println(cad3.endsWith(”ab”)); da f alse
System.out.println(cad3.startsWith(”ab”)); da true
System.out.println(cad3.indexOf(”bc”)); da 1
System.out.println(cad3.indexOf(”bc”,3)); da 4
System.out.println(cad3.substring(2,5)); da cab
System.out.println(cad3.lastIndexOf(”bc”)); da 7
System.out.println(cad3.replace(‘a’,‘x’)); da xbcxbcxbc
System.out.println(cad1.toLowerCase()); da abcdef
System.out.println(cad3.length()); da 9

5.4. Lectura del teclado


Utilizando la clase Scanner se tienen los siguientes métodos para cadenas

Método Descripción
next() Lee los siguientes caracteres delimitados por espacio y los almacena en una cadena
nextLine() Lee toda una lı́nea, no separa por espacios
hasNextLine() Pregunta si hay una siguiente lı́nea
hasNext() Pregunta si hay mas caracteres en la entrada

Veamos algunos ejemplos considerando la entrada como sigue:

la casa
de la escuela
es

El siguiente código

Scanner lee = new Scanner(System.in);


String cad1 = lee.next();
String cad2 = lee.next();
String cad3 = lee.next();

hará que:

cad1 = "la";
cad2 = "casa";
cad3 = "de";
5.5 Convertir de cadena a Integer 95
Si leemos por lı́neas:

Scanner lee = new Scanner(System.in);


String cad1 = lee.nextLine();
String cad2 = lee.nextLine();
String cad3 = lee.nextLine();

hará que:

cad1 = "la casa";


cad2 = "de la escuela";
cad3 = "es";

Ahora si

Scanner lee = new Scanner(System.in);


String cad1 = lee.next();
String cad2 = lee.next();
String cad3 = lee.nextLine();

hará que:

cad1 = "la";
cad2 = "casa";
cad3 = "";

La tercera lectura quedó en blanco porque lo que se hizo es leer hasta el final de la lı́nea y no existı́an más
caracteres. El método nextLine() lee desde la posición actual hasta el fin de lı́nea. En cambio el método next
salta los caracteres de fin de lı́nea.

5.5. Convertir de cadena a Integer


Supongamos que hemos leı́do un número entero con el método next(). En este momento tenemos una variable
de tipo String que contiene un número. Para convertir este a un número entero utilizamos la instrucción
Integer.parseInt(cadena). El resultado sera un número entero. La tabla siguiente muestra como convertir
de cadena a diferentes tipos de datos.
Integer.parseInt Convertir de cadena a tipo int
Double.parseDouble Convertir de cadena a tipo double
Float.parseFloat Convertir de cadena a tipo float
Long.parseLong Convertir de cadena a tipo long
Si la cadena a convertir está en una base diferente a la decimal, por ejemplo en binario, podemos utilizar un
segundo parámetro para especificar la base. Por ejemplo Integer.parseInt(”101”,2) da como resultado 5. De
esta forma se puede incorporar un número en cualquier base.
Como ejemplo construyamos un programa para convertir el número 1234567 de base octal a decimal y
binario. Para convertir a decimal escribimos el código siguiente:

int i = Integer.parseInt("1234567",8);
96 Capı́tulo 5 Cadenas
y para llevar a binario:

System.out.println(Integer.toBinaryString(i));

que puede verificar que produce los resultados 342391 en decimal y 1010011100101110111 en binario.

5.6. Manejo de excepciones


Que ocurre si el dato de entrada tiene un error, por ejemplo Integer.parseInt(”12ab”) o los caracteres no
corresponden a la base especificada. El resultado será un error NumberFormatException. El lenguaje Java
nos obliga a que el programa haga las previsiones necesaria para tratar con éstos y otros errores. Esto se
denomina manejo de excepciones.
Existen dos tipos de formas de manejar las excepciones. La primera es la de decidir no hacer nada y hacer
que el programa arroje el error. Esto se realiza colocando un claúsula throws en el método. Si se produce el
error el java se encargará de mostrar el error. La sintaxis es como sigue:

public static void main(String[] args)


throws NumberFormatException{

La segunda alternativa es que uno se preocupe de manejar el error, para esto, se utiliza una secuencia de
instrucciones try y catch. La instrucción try especifica: intente hacer las instrucciones incluı́das en el bloque,
si da error pasa al bloque catch. La sintaxis es:

try {
instrucciones
}
catch (excepción variable) {
manejo de error
}

Expliquemos como controlar el error al convertir un número almacenado en una cadena a una variable entera.
Considere el programa siguiente:

import java . u t i l . Scanner ;


p u b l i c c l a s s TryCatch {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
S t r i n g cad1 = l e e . n e x t L i n e ( ) ;
int i ;
try {
i = I n t e g e r . p a r s e I n t ( cad1 ) ; }
c a t c h ( NumberFormatException n ) {
System . o u t . p r i n t l n ( ” E r r o r ”+ n ) ;
i =0;
}
System . o u t . p r i n t l n ( i ) ;
5.7 Como procesar una lı́nea de texto 97

}
}

El flujo normal es cuando ingresa al bloque de instrucciones del try. El bloque de instrucciones del catch
trata el error. Primero catch (NumberFormatException n) especifica el nombre de la excepción que vamos a
manejar. La variable n indica donde se recibirá el texto del mensaje de error. En el ejemplo del caso de error
pusimos i = 0 y además mostramos el error.

5.7. Como procesar una lı́nea de texto


En muchas ocasiones se quiere procesar una lı́nea de texto, por ejemplo separar por palabras que vienen
separadas por espacios. Este tipo de palabras se denominan tokens. Supongamos que tenemos cad1 = ”la
escuela esta de fiesta” para separar por palabras, podemos usar la clase Scanner otra vez. Definimos una
variable de tipo Scanner y decimos que la entrada es cad1 en lugar de System.in. Después usamos todos los
métodos de la clase y podemos resolver el problema. El ejemplo siguiente muestra como podemos imprimir
cada palabra de cad1 en una lı́nea.

import java . u t i l . Scanner ;


public c l a s s Divide {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) t h r o w s N u m b e r F o r m a t E x c e p t i o n {
S t r i n g c a d 1 = ” l a e s c u e l a e s t a de f i e s t a ” ;
S c a n n e r l e e = new S c a n n e r ( c a d 1 ) ;
while ( l e e . hasNext ( ) ) {
System . o u t . p r i n t l n ( l e e . n e x t ( ) ) ;
}

}
}

5.8. Ejemplos de aplicación


1. Consideremos los celulares y los nombres que escribimos por el teclado. El teclado tiene 9 botones de
los cuales tienen números, 8 tiene asignadas letras, como se muestra:

ABC DEF
GHI JKL MNO
PQRS TUV WXYZ

El problema consiste en leer un nombre e imprimir los números que se deben apretar para escribir este
nombre. Para resolver el problema, requerimos un mecanismo para recorrer todos los elementos de la
cadena ingresada, una sección de código para determinar a que número corresponde el carácter, y un
proceso de concatenación para armar la respuesta.
La cadena la recorremos con un for, el lı́mite se halla con el método length(), los caracteres se obtienen
con charAt. Las instrucciones if else hallamos el número que le corresponde. La concatenación la
98 Capı́tulo 5 Cadenas
hicimos con el operador +. El código que se muestra solo considera letras mayúsculas. Para que trabaje
con letras minúsculas podemos utilizar el método toUpperString, para primero llevar todo a mayúsculas.
El código resultante es:
import java . u t i l . Scanner ;
p u b l i c c l a s s Eje1 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
while ( l e e . hasNext ( ) ) {
S t r i n g nombre = l e e . n e x t ( ) ;
String resp =””;
f o r ( i n t i = 0 ; i < nombre . l e n g t h ( ) ; i ++){
i f ( nombre . c h a r A t ( i ) <= ’C ’ )
resp=resp +”2”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’F ’ )
resp=resp +”3”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’ I ’ )
resp=resp +”4”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’L ’ )
resp=resp +”5”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’O’ )
resp=resp +”6”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’S ’ )
resp=resp +”7”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’V’ )
resp=resp +”8”;
e l s e i f ( nombre . c h a r A t ( i ) <= ’Z ’ )
resp=resp +”9”;
}
System . o u t . p r i n t l n ( r e s p ) ;
}
}
}

Ejemplo de entrada

UMSA

Ejemplo de salida
8672

2. Supongamos que queremos contar cuantas veces se repite la primera palabra en una lı́nea de texto. Para
esto leemos la primera palabra y guardamos ésta para comparar con todas las siguientes. Esto se hace
con el método equals. Para saber si llegamos al final de la lı́nea usamos el método hasNext. Veamos el
código
5.8 Ejemplos de aplicación 99

import java . u t i l . Scanner ;


p u b l i c c l a s s Eje2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
while ( lee . hasNextLine ( ) ) {
i n t cont =1;
String l=lee . nextLine ( ) ;
S c a n n e r p a l a b r a = new S c a n n e r ( l ) ;
S t r i n g p1= p a l a b r a . n e x t ( ) ;
while ( p a l a b r a . hasNext ( ) ) {
S t r i n g p2= p a l a b r a . n e x t ( ) ;
i f ( p1 . e q u a l s ( p2 ) )
c o n t ++;
}
System . o u t . p r i n t l n ( c o n t ) ;
}
}
}

En este programa hemos utilizado dos flujos de entrada con Scanner. El primer flujo se crea una sola vez
para leer lı́nea a lı́nea del teclado. El segundo se crea cada vez que leemos una cadena para hacer recorrer
todas las palabras de la lı́nea. El contador comienza en uno que corresponde a la primera palabra.

Ejemplo de entrada

hola la hola cosa

Ejemplo de salida
2

3. Se quiere buscar el primer texto que diga ”error”, puede ser una palabra separada por espacios o no. La
búsqueda se realiza con el método indexOf cuyo resultado es un entero que indica la posición donde se
inicia la cadena. Cuando es negativo indica que no encuentra ésta cadena. El método substring permite
obtener la primera parte del texto y también la segunda mitad. Concatenando ambas se obtiene una
cadena donde se eliminó la palabra ”error”.
import java . u t i l . Scanner ;
public class eje3 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
while ( lee . hasNextLine ( ) ) {
String l=lee . nextLine ( ) ;
i n t i n i c i o = l . indexOf (” e r r o r ” ) ;
i f ( i n i c i o >=0)
l = l . s u b s t r i n g ( 0 , i n i c i o −1)+ l . s u b s t r i n g ( i n i c i o + 5 ) ;
System . o u t . p r i n t l n ( l ) ;
}
100 Capı́tulo 5 Cadenas
}
}

Ejemplo de entrada

abcerrordef

Ejemplo de salida
abcdef

5.9. Ejercicios
5.9 Ejercicios 101

5.9.1. JV-1229: Comparar Palabras


Escriba un programa que lea dos palabras e imprima el signo ¡(menor que), ¿(mayor que) o = (igual que) de
acuerdo al orden lexicógrafo.

Entrada
La entrada consiste de dos palabras a, b con caracteres en minúsculas, en una sola lı́nea, separadas por un
espacio.

Salida
Imprima una lı́nea indicando si a < b, a > b o a = b.

Ejemplos de entrada Ejemplos de salida


anna xavier anna < xavier
102 Capı́tulo 5 Cadenas

5.9.2. JV-1279: Cifrado César


Escriba un programa que codifique mensajes con el cifrado de César. Este cifrado lo utilizó Julio César para
comunicarse con sus generales.
Dada una constante K, cada letra del mensaje original se remplaza por una letra que está alfabeticamente K
posiciones a la derecha , en forma circular si pasa la última letra del alfabeto.
Por ejemplo si K=5 debemos cambiar a por f, b por g, ... y z por e.

Entrada
La entrada consiste de varios casos de prueba. Cada caso de prueba comienza con un número natural k > 0,
seguido de solo letras minúsculas. Existe un separador de palabras pero no un espacio. El separador puede
ser un caracter que no sea una letra minúscula.

Salida
Para cada caso de prueba escriba una lı́nea con el texto cifrado en mayúsculas. Reemplace los guiones bajos
por espacio. Deje los caracteres de separación de palabras sin cambio.

Ejemplos de entrada
1 i_am_an_example
22 veni,vidi,vinci
26000031 yzznhzzn-eznczo-wjiz-yjnzaz_ypqzhv-zidozhjnn

Ejemplos de salida
J BN BO FYBNQMF
RAJE,REZE,REJYE
DEESMEES-JESHET-BONE-DOSEFE DUVEMA-ENITEMOSS
5.9 Ejercicios 103

5.9.3. JV-1012: Palı́ndrome Extendido


Una cadena palı́ndrome, es una cadena que se lee igual cuando es invertida. Por ejemplo ORURO, ABBA
son palı́ndromes, pero ABB no lo es.
En este problema tú debes agregar caracteres a la derecha del string dada y convertirla en palı́ndrome
(Obviamente si ya es palı́ndrome no es necesario hacer nada mas).

Entrada
Entrada terminara con el string END, cada lı́nea tendrá un string no vacı́o de letras minúsculas. La longitud
de la cadena será menor a 100000.

Salida
Para cada caso de prueba imprimir el palı́ndrome del string dado.

Ejemplos de entrada Ejemplos de salida


aaaa aaaa
abba abba
amanaplanacanal amanaplanacanalpanama
xyz xyzyx
END
104 Capı́tulo 5 Cadenas

5.9.4. JV-1006: Cadena Bailarina


Una cadena se llama bailarina si y solo si la primera letra es mayúscula y cada una de las demás letras es lo
opuesto a la anterior letra (mayúscula, minúscula, mayúscula, minúscula, ..., etc.).
Por ejemplo AbCd es una cadena bailarina, la primera letra es A mayúscula, la segunda letra es b minúscula,
la siguiente letra es C mayúscula y por ultimo d es minúscula.

Entrada
La entrada consiste en un entero T número de casos de prueba, seguido por T+1 lı́neas, cada una contiene
una cadena, puede ser que este vacı́a.

Salida
Imprimir T+1 lı́neas que contienen las cadenas bailarinas.

Ejemplos de entrada Ejemplos de salida


5 O
o AaAaBbBbAaAa
aaaabbbbaaaa ReTwEeTeD
Retweeted LiKe Si ReSoLvIsTe El PrObLeMa
Like si resolviste el problema A
A S d FfD aA sDs
s d ffd aa sds
5.9 Ejercicios 105

5.9.5. JV-1330: Rotando Cadenas


Definimos una rotación a la derecha de una cadena S como tomar el último caracter de la cadena y llevarlo
al inicio. Por ejemplo dada la cadena ”qwerty”, una rotación a la derecha dará la cadena ”yqwert”, dos
rotaciones a la derecha dara ”tyqwer”, tres rotaciones a la derecha ”rtyqwe”, etc.

Entrada
La cadena S y un número entero R que representa el número de rotaciones a la derecha a realizar sobre la
cadena S. S no tendrá espacios y tiene longitud L. (1 ≤ L <= 40, 0 ≤ R ≤ L)

Salida
La cadena S después de realizar R rotaciones a la derecha.

Ejemplos de entrada Ejemplos de salida


SuperNintendoChalmers 8 ChalmersSuperNintendo
106 Capı́tulo 5 Cadenas

5.9.6. JV-1246: Contar letras


Escriba un programa que lea una letra y una frase y cuente cuantas veces se repite la letra en la frase.

Entrada
La entrada consiste de dos lÃneas. La primera lÃnea contiene el caracter a contar. La segunda lÃnea contiene
la frase toda en minÃo sculas.

Salida
En la salida escriba la cantidad de veces que el caracter aparece en la frase.

Ejemplos de entrada Ejemplos de salida


i 2
universidad mayor de san andres
5.9 Ejercicios 107

5.9.7. JV-1110: Chewbacca


Queremos saber si ”HanSolo” o ”Chewbacca” está hablando, ¿Cómo distinguimos quién está hablando?.
Se sabe que Chewbacca sólo conoce dos letras la á’y ’r’. Se te dará una cadena y debe imprimir quién está
hablando, si está hablando Chewbacca imprimir ”Chewbacca” (sin comillas) y si está hablando Han Solo
imprimir ”HanSolo” (sin comillas). Solo se proporcionaran letras minúsculas.

Entrada
Una cadena de longitud no mayor a 100000.

Salida
”HanSolo” si está hablando Han Solo y ”Chewbacca” si está hablando Chewbacca.

Ejemplos de entrada Ejemplos de salida


este es el problema facil Han Solo
108 Capı́tulo 5 Cadenas

5.9.8. JV-1066: Jakiado


Le jackiaron su contraseña de Facebook a Botas, y publicaron en Infoamigos que es gay. Ahora debe crear
una nueva contraseña y esta vez no debe ser tan obvia. Se dice que una contraseña es segura cuando tiene al
menos una letra mayúscula, una minúscula, un número y un caracter especial. Ayuda a verificar si la nueva
contraseña de Botas es segura o no.

Entrada
La entrada comienza con T casos de prueba (1 ≤ T ≤ 100000). Cada caso de prueba es una contraseña
con máximo 20 caracteres y que solo consta de minúsculas, mayúsculas, números y los caracteres especiales
arroba, punto , barra baja, menor, mayor y guión.

Salida
Si la contraseña es segura, imprimir ”Dale no te jackiaran esta vez.”sin comillas. Caso contrario imprimir
”No va dar Botas.”.

Ejemplos de entrada Ejemplos de salida


5 No va dar Botas.
Botas123 No va dar Botas.
75803727 Dale no te jackiaran esta vez.
B0t45_Rul35 No va dar Botas.
31-07-1996 Dale no te jackiaran esta vez.
D0R4_l4_Expl0r4D0r4_I_<3_U
5.9 Ejercicios 109

5.9.9. JV-1375: Buscando el Oro


En este programa debes buscar la primera palabra ORO en una cadena.
Luego imprimir la posición donde comienza y donde acaba. Por ejemplo para la cadena: abcdef OrOef g La
salida será:

6 8

Entrada
La entrada consiste de una cadena en letras mayúsculas y minúsculas.

Salida
Imprima en la salida la posición donde está la palabra ORO, puede ser en mayúsculas o minúsculas
indistintamente. Sino existe la palabra imprima -1

Ejemplos de entrada Ejemplos de salida


abcdefOrOefg 6 8
110 Capı́tulo 5 Cadenas

5.9.10. JV-1435: El k-esimo dı́gito


Dado un número entero N averiguar la cantidad de dı́gitos que tiene este número, y además determinar cual
es su k-esimo dı́gito.
Por ejemplo para N = 18421 y k = 3, el número tiene 5 dı́gitos y el tercer dı́gito es 4.

Entrada
La entrada consta de dos números N (1 ≤ N ≤ 1 ∗ 101 8), el número a evaluar, y K el dı́gito que estamos
interesados en conocer, se garantiza que K siempre será menor o igual al número de dı́gitos de N.

Salida
La salida consta de dos números separados por un espacio, la cantidad de dı́gitos del número N, y el k-ésimo
dı́gito de este

Ejemplos de entrada Ejemplos de salida


18421 3 5 4
5.9 Ejercicios 111

5.9.11. JV-1436: El mayor posible


El pequeño Lua tenı́a anotado un número de la suerte en un papel, con el que podrı́a ganar la loterı́a; sin
embargo Mija el envidioso querı́a ganar la loterı́a ası́ que comenzaron a pelear jalando el papel y este se
partió en 3 (y Mija el envidioso se fue corriendo, temiendo ser golpeado por lo que hizo); al recoger las
3 partes Lua notó que no sabı́a cual era el número original; pero el sabe que de todas las opciones que se
pueden formar juntando los 3 trozos de papel el número más grande posible, es el número de la suerte... El
único problema es que el no sabe contar todavı́a ası́ que te pide ayuda para reconstruir el número original.
Por ejemplo supongamos que los 3 trozos de papel marcan 123, 817, 1001
El mayor número que puedes formar juntando estos 3 trozos es 8171231001.
Ayuda a Lua a rearmar el número de la suerte

Entrada
Se te dará 3 números enteros a,b,c cuya suma de dı́gitos es menor o igual a 15 dı́gitos, y son siempre mayores
o iguales a 0.

Salida
Imprimir el número de la suerte de Lua

Ejemplos de entrada Ejemplos de salida


123 817 1001 8171231001
112 Capı́tulo 5 Cadenas

5.9.12. JV-1348: Esperanto


Los números son mucho más fáciles de escribir en esperanto que en español. Los números del 1 al 10 se
detallan como sigue: unu, du, tri, kvar, Kvin, ses, Sep, OK, N au, dek. Números del 11 al 19 se escriben:
dek unu, dek du, ..., dek N au, un dek seguido de un solo espacio y el nombre del último dı́gito. Números
20 al 29 se escriben: dudek, dudek unu, dudek du, ..., dudek N au. Del mismo modo, 30 es tridek, ..., 90
es N audek. Sólo se unen el número de decena y dek. No hay excepciones como doce o quince en español.

Entrada
La entrada consiste en varios casos de prueba. Cada caso de prueba es un número entero n, 1 ≤ n ≤ 99 en
una lı́nea. La entrada termina cuando no hay más datos en la entrada.

Salida
Por cada caso de prueba en la entrada escriba una lı́nea con el nombre en esperanto.

Ejemplos de entrada Ejemplos de salida


1 unu
90 Naudek
11 dek unu
77 Sepdek Sep
5.9 Ejercicios 113

5.9.13. Tapices de Colores


A Taro le gustan las cosas de colores, especialmente los tapices.
El cuarto de Taro está dividido en L tapices cuadrados organizados en una lı́nea. Cada tapiz es de uno de los
siguientes colores: rojo, verde, azul o amarillo representados por R, G, B y Y respectivamente. A usted le
dan un cadena representando los tapices del cuarto. El carácter i del cuarto representa el color del tapiz.
Ha decidido cambiar el color de algunos tapices de tal forma que dos tapices adyacentes no tengan el mismo
color.
Le piden hallar el mı́nimo número de tapices que hay que cambiar.
Por ejemplo:
Si la entrada fuera RRRRRR cambiamos a RGRGRG y la respuesta es cambiar 3.
Si la entrada fuera BBBYYYYYY la respuesta es 4, porque podemos cambiar a BRBYRYRYR.

Entrada
La primera lı́nea indica cuantos casos de prueba hay. Las siguientes lı́neas tienen un caso de prueba por lı́nea.
Cada lı́nea es una cadena con un máximo de 10 caracteres representando los colores de los tapices.

Salida
La salida es el número de tapices que hay que cambiar. Se imprime por cada caso de prueba un número en
una lı́nea.

Ejemplos de entrada Ejemplos de salida


5 3
RRRRRR 3
GGGGGGG 4
BBBYYYYYY 0
BRYGYBGRYR 3
RGGBBBRYYB
114 Capı́tulo 5 Cadenas

5.9.14. JV-1363: Flecha más Larga


En este problema una flecha a la izquierda se define como el carácter < seguido inmediatamente de cero
o más caracteres −. Un flecha doble a la izquierda se define como un carácter < seguido de cero o más
caracteres consecutivos =.
Una flecha a la derecha se define como cero o más caracteres − seguidos del carácter >. Un flecha doble a
la derecha se define como cero o más caracteres = seguidos del carácter >.
Dada una cadena se quiere hallar la longitud de la flecha más larga.

Entrada
La entrada contiene varios casos de prueba. Cada caso de prueba es una cadena en una lı́nea. La cadena
contiene entre 1 y 50 caracteres. Cada carácter será <, >, −, =.
La entrada termina cuando no hay más datos.

Salida
Por cada caso de prueba escriba en una lı́nea la longitud de la cadena más larga. Si no existe una cadena
escriba −1.

Ejemplos de entrada Ejemplos de salida


<--->--==> 4
<<<<<<<<<< 1
----==- -1
<----=====> 6
5.9 Ejercicios 115

5.9.15. JV-1364: Fácil SQL


SQL (Structured Query Language, Lenguaje Estructurado de Consulta) es un lenguaje hecho para manipular
los datos contenidos dentro de una Base de Datos. Cada tabla en una Base de Datos tiene un DDL (Data
Definition Language, Lenguaje de Definición de Datos) y un DML (Data Management Language, Lenguaje
de Manipulación de Datos).
Para hacer la Manipulación de Datos más fácil, queremos crear la sentencia SELECT , usando la Definición
de Datos de cada tabla, que usa la sentencia CREAT E T ABLE.
Por ejemplo, tenemos la tabla: CREAT E T ABLE table1 (f ield1 type1, f ield2 type2) Y para obtener
su contenido usamos: SELECT f ield1, f ield2 F ROM table1
Tu tarea es dada una sentencia CREAT ET ABLE, construir la sentencia SELECT .

Entrada
La entrada tiene varios casos de prueba. Cada lı́nea contiene una sentencia CREAT E T ABLE. El fin de
la entrada es representada por un #.

Salida
Para cada sentencia CREAT E T ABLE, imprimir la sentencia SELECT

Ejemplos de entrada
CREATE TABLE table1 (field1 type1, field2 type2)
CREATE TABLE table2 (field3 type3, field4 type4, field5 type5, field6 type6)
#

Ejemplos de salida
SELECT field1, field2 FROM table1
SELECT field3, field4, field5, field6 FROM table2
116 Capı́tulo 5 Cadenas

5.9.16. JV-1445: Quitar puntuación


A algunos escritores les gusta super enfatizar ciertos puntos utilizando múltiples exclamaciones en lugar
de una. Por ejemplo que Bárbaro!!!!!. Otras veces expresan su sorpresa con múltiples exclamaciones e
interrogaciones, por ejemplo Realmente te gusta!?!?!?!?.
Usted está editando un documento para su publicación, y quiere eliminar la puntuación extra. Cuando ve
varios signos de exclamación seguidos los remplaza por uno solo. Si ve un conjunto de interrogaciones con
uno o ninguna exclamación remplazarlos por una interrogación simple. Si ve exclamaciones e interrogaciones
combinadas remplazar por una interrogación. Vea los datos de entrada y salida para mayores ejemplos

Entrada
La entrada consiste de varios casos de prueba. Cada caso de prueba viene en una lı́nea conteniendo el texto
a procesar. Tendrá entre 1 y 50 caracteres inclusive, letras mayúsculas, minúsculas, espacio y signos de
puntuación, exclamación e interrogación.
La entrada termina cuando no hay más datos.

Salida
Por cada lı́nea de entrada imprima la cadena modificada de acuerdo a la explicación.

Ejemplos de entrada
"Este queso esta muy bueno!!!!!"
"De verdad le gusta el queso!?!?!?!!!?"
" !!?X! ?? Es delicioso!!! ??!a!?!"

Ejemplos de salida
"Este queso esta muy bueno!"
"De verdad le gusta el queso?"
" ?X! ? Es delicioso! ?a?"
5.9 Ejercicios 117

5.9.17. JV-1446: Espejo del Reloj


Usted está sentado frente a un espejo y mira la imagen de un reloj situado detrás de usted. Quiere saber qué
hora es. El reloj es un reloj tradicional, que marca 12 horas (sin números escritos). Tiene un minutero y una
aguja que marca las horas (la manecilla de las horas es más corta, de modo que usted puede distinguirla ).
Dada una cadena que indica el tiempo como se ve en el espejo. se le pide devolver una cadena con la hora
real.
Por ejemplo si nos dan la cadena 10 : 00 como vemos en la imagen la respuesta será 02 : 00.

Entrada
La entrada consiste de varios casos de prueba. Cada caso de prueba es una cadena que viene en una lı́nea.
Indica la hora en el formato HH : M M , donde HH es la hora y tiene dos dı́gitos, y M M es el minuto
también con dos dı́gitos. La hora está entre 00 y 11, inclusive, donde 00 representa el 12 de la tarde. Los
minutos son un número entre 00 y 59, inclusive. La entrada termina cuando no hay más datos.

Salida
Por cada caso de prueba, devuelve una cadena en el mismo formato que con el tiempo real.

Ejemplos de entrada Ejemplos de salida


10:00 02:00
01:15 10:45
03:40 08:20
00:00 00:00
66.1.
Aritmética Básica
Introducción
Se hace muy importante revisar la teorı́a de números en el aprendizaje de la programación. Este conocimiento
permite comprender los aspectos que hacen al proceso computacional, tales como las capacidades de la
máquina, el tamaño máximo de números que se pueden tratar y como trabajar con números más grandes.
Aún cuando esta es una presentación elemental en el tratamiento de este capı́tulo se toman en cuenta los
aspectos relativos al tiempo de proceso para hacer eficiente los algoritmos expuestos.
Recordemos del capı́tulo 2 que lenguaje de programación Java tiene varios tipos de variables enteras que se
muestran en el cuadro 6.1.

tipo Nro. Bytes Nro. Bits Rango de números permitido


byte 1 bytes 8 bits desde − (27 + 1) hasta + 27
short 2 bytes 16 bits desde − (215 + 1) hasta + 215
int 4 bytes 32 bits desde − (231 + 1) hasta + 231
long 8 bytes 64 bits desde − (263 + 1) hasta + 263
Cuadro 6.1 Tipos de variables enteras en Java

Se hace muy necesario conocer como es el tiempo de proceso de las operaciones aritméticas de cada una de
ellas con la finalidad de escoger las más apropiadas para resolver un problema:

EL tiempo de proceso de las operaciones aritméticas en las variables de 64 bits demoran casi el doble
de las de 32 bits.
Las operaciones de división demoran más del doble de las operaciones de multiplicación
Las operaciones de multiplicación demoran casi el doble de las operaciones de suma y resta.
las operaciones de suma y resta demoran igual.

6.2. Sistema de numeración


Un sistema de numeración es el conjunto de sı́mbolos que permiten construir todos los números válidos. En
el sistema decimal utilizamos los sı́mbolos 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
En función de la cantidad de sı́mbolos podemos clasificar los sistemas de numeración como sigue:

Binario denominan los que tienen solo con dos sı́mbolos.


Octales los que tienen 8 sı́mbolos.
Decimal el sistema que utilizamos y que tiene 10 sı́mbolos
Hexadecimal los que tienen 16 sı́mbolos.
Vigesimal si tiene 20 sı́mbolos.

119
120 Capı́tulo 6 Aritmética Básica
Generalmente las bases de numeración las representamos con números del 0 al 9. Cuando no son suficientes
agregamos las letras del alfabeto. Por ejemplo en la base hexadecimal agregamos las letras a, b, c, d, e, f a
los 10 dı́gitos numéricos. De esta forma podemos representar números hasta la base 36, que corresponde a
las 26 letras del alfabeto más los 10 números. Esto no es una limitación, se puede representar un número en
cualquier base B siempre que se puedan escribir B sı́mbolos diferentes. Por ejemplo si queremos escribir un
número en base 2000, podemos utilizar como sı́mbolos el 0, 1, ....., 1998, 1999.

6.2.1. Teorema fundamental de la numeración

Teorema 1. Sea N un número valido en la base de numeración b que tiene k dı́gitos di el número N se
puede construir como sigue:

k−1
X
N= bi d i
i=0

Por ejemplo en la base de numeración decimal el número 123 se escribe como:

k=2
X
10i di = 1x102 + 2x101 + 3x100
i=0

En base binaria el número 1010 se escribe como:

1x23 + 0x22 + 1x21 + 0x20

Multiplicar un número por la base es equivalente a agregar un cero a la derecha del número. Utilizando el
ejemplo anterior: 1x23 = 1000, el siguiente término es 1x21 = 10 sumando ambos volvemos a obtener el
número original 1010.
Como ejemplo de numeración veamos la numeración en base 2 y base 3.

Numeración Binaria
0 0
1 1
2 10
3 11
4 100

Numeración Ternaria
0 0
1 1
2 2
3 10
4 11
6.2 Sistema de numeración 121

6.2.2. Composición y descomposición de números


6.2.2.1. Descomposición
Denominamos descomposición de número a separar el mismo en sus dı́gito y se llama composición a volver
a construir el número a partir de sus dı́gitos.
Para descomponer un número eb base B en sus dı́gitos, se utiliza el teorema 1, al dividir un número por la
base se quita el último dı́gito, por lo tanto el resto de la división nos proporcionara este. Veamos un ejemplos.
Si queremos descomponer el número 5263 en sus dı́gitos primero se halla el resto después de dividir por 10
tendremos el número 3. Segundo se divide el número por 10 para obtener 526. El proceso se repite hasta
obtener todos los dı́gitos.
El programa siguiente toma un número decimal, la base e imprime los dı́gitos de ese número.

p u b l i c c l a s s Descompone {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =9876 , b =10 , d ;
w h i l e ( n >0){
d=n %b ;
n /= b ;
System . o u t . p r i n t ( d +” ” ) ;
}
}
}

El resultado de la ejecución de este programa es:

6789

Como se muestra los dı́gitos se obtienen del menos significativo al más significativo.

6.2.2.2. Composición
Denominamos composición a formar el número a partir de sus dı́gitos. Esto coincide exactamente con el
teorema 1. Para simplificar el proceso de programación cambiaremos un poco la fórmula presentada.
k−1
X
N= bi di = dk−1 bk−1 + dk−2 bk−2 ... + d0
i=0

Factorizando b obtenemos:

b(dk−1 bk−2 + dk−2 bk−2 ... + d1 ) + d0

Volvemos a factorizar b

b(b(dk−1 bk−3 + dk−2 bk−4 ...d2 ) + d1 ) + d0

Haciendo esto repetidamente tenemos:

b(b(b......(bdk−1 ) + dk−2 )...... + d2 ) + d1 ) + d0


122 Capı́tulo 6 Aritmética Básica
A fin de implementar en un programa primero observamos que es conveniente comenzar del dı́gito más
significativo. Para incluir un segundo número multiplicamos por la base el resultado anterior y luego
sumamos el siguiente dı́gito. Si tenemos los dı́gitos del número 8764 se puede conformar como sigue:
(10(10(8 ∗ 10 + 7) + 6) + 4).
El siguiente programa descompone y compone el número 8764 en base 10;

p u b l i c c l a s s Compone {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =9764 , b =10 , d ;
i n t c = 0 ; / / componer e l numero
System . o u t . p r i n t l n ( ” D i g i t o s d e l n à o mero ” ) ;
w h i l e ( n >0){
d=n %b ; / / descomponer
n /= b ;
System . o u t . p r i n t ( d ) ; / / componer
c=c ∗10+ d ;
}
System . o u t . p r i n t l n ( ) ;
System . o u t . p r i n t l n ( ” Numero que s e r e c o m p u s o ” ) ;
System . o u t . p r i n t l n ( c ) ;
}
}

Si, usted codifica el programa de composición podrá apreciar que la composición crea el número en el orden
en que aparecen los dı́gitos, y en este ejemplo el número quedo invertido.

6.2.3. Cambio de base


El cambio de bases entre sistemas de numeración se hace muy fácil utilizando el teorema 1 y realizando las
operaciones aritméticas en base decimal. Veamos un ejemplo, si queremos convertir el número 3221 en base
4 a decimal tenemos:

3x43 + 2x42 + 2x41 + 1 = 233

Ahora para convertir el número 233 de base 4 hacemos el proceso inverso, primero hallamos el módulo 4
para obtener el último dı́gito luego dividimos el número por la base obteniendo la siguiente tabla:

Número Módulo
233 1
58 2
14 2
3 3

Recuperando los valores del ultimo al primero obtenemos el valor en base 4. De igual forma se procede
con cualquier base de numeración, sin embargo existen algunos algoritmos para casos especiales que se
utilizan con mucha frecuencia, tales como los números binarios, octales, y hexadecimales como se muestra
a continuación.
6.2 Sistema de numeración 123
Números binarios
Los números binarios son la forma básica en la que están representados los números en la computadora. y
consisten de unos y ceros. Para desplegar en pantalla un número hay que convertir este, primero a decimal y
es algo que el compilador hace automáticamente. Para obtener su representación binaria es suficiente mostrar
los bits individuales de una variable.
V
Para realizar operaciones con números binarios se utilizan las operaciones de bits: and (&) , or (|) y xor ( ).
Utilizar estas operaciones en lugar de las operaciones aritméticas hace el proceso más rápido. Por ejemplo si
un número es potencia de dos, es par o si un bit tiene un valor especifico.

Números Octales
Siendo que s3 = 8 para convertir de binario a base octal es suficiente con agrupar los bits en grupos de tres
comenzando desde la derecha y mostrar su sı́mbolo equivalente. Por ejemplo:

1 0 0 1 1 0 1 1 0 0

corresponde a 1154

1 1 5 4

Números Hexadecimales
De forma similar como 16 = 24 agrupamos los números de 4 en cuatro.

1 0 0 1 1 0 1 1 0 0

que corresponde a:

2 6 13

En la representación hexadecimal los números 10, 11, 12, 13, 14, 15 se representan con las letras A, B, C, D, E, F
respectivamente. Por este motivo el equivalente hexadecimal quedara representado por 16D. Este tipo re-
presentación hoy es muy utilizado en las computadoras y solo por citar un ejemplo, las tarjetas de red se
identifican por su número MAC, que se representa 12 dı́gitos hexadecimales.

6.2.4. Logaritmos

Definición
ab = c significa que loga c = b donde a se denomina la base de los logaritmos.
Para obtener el número de dı́gitos de un número N en base b la fórmula ⌊logb N ⌋ + 1. Por ejemplo cuantos
dı́gitos se requieren para representar el número 123456 aplicando la fórmula tenemos: ⌊log10 123456⌋ + 1 =
6.
Se procede de forma similar en cualquier base, por ejemplo el número 10 en binario requiere 4 dı́gitos,
log2 10 = 3, 3 + 1 = 4. El número decimal en binario es el 1010.
Algunas propiedades de los logaritmos se a continuación:
124 Capı́tulo 6 Aritmética Básica
1. log(ab) = log(a) + log(b)
2. log(a/b) = log(a) − log(b)
3. log(ab ) = b log(a)
4. logb (x) = loga (x)/ loga (b)
5. alog b = blog a

Para entender el concepto de logaritmo, podemos hallar cuantas veces podemos dividir un número por la
base del logaritmo. Por ejemplo log2 16 es equivalente a dividir 16 por dos 4 veces en este caso tenemos
16/2, 8/2, 4/2, 2/2 cuando llegamos a 1 terminamos las divisiones y vemos que son 4, por lo que log2 16 = 4

6.3. Series Simples


Supongamos que u(n) es una función definida para n valores. Si sumamos éstos valores obtenemos:

s(n) = u(1) + u(2) + u(3)........ + u(n).

Es muy común el cambiar la notación a la siguiente forma

sn = u1 + u2 + u3 ........ + un .

o simplemente
n
X
sn = ui .
i=1

6.3.1. Serie aritmética

Una serie aritmética puede expresarse como una secuencia en la que existe una diferencia constante, llamada
razón, entre dos elementos contiguos. La notación matemática para describirla es como sigue:

sn = a, a + d, a + 2d, ........, a + (n − 1)d.

La suma de los primeros n términos de una serie aritmética puede expresarse como
n
X n(a1 + an ) d
sn = ai = = (an + n(n − 1))
i=1
2 2

donde d es la razón ó sea la diferencia entre dos términos.

6.3.2. Serie geométrica

Una serie geométrica es una secuencia de términos donde los términos tiene la forma

a, ar, ar2 , ar3 ...., arn

la suma de ésta serie podemos expresarla como sigue:


n
X a(1 − rn )
sn = ari =
i=0
(1 − r)
6.4 Secuencias Importantes 125
en general la suma de una serie geométrica se denomina convergente cuando tiende a un lı́mite, y esto se da
si y solo si rn también tiende a un lı́mite. De esto podemos decir que una serie geométrica es convergente sı́
y solo sı́ −1 < r < 1.

6.3.3. Propiedades de la sumatorias


Propiedad distributiva
n
X n
X
ci = c i (6.3.1)
i=1 i=1

Propieadad asociativa
n
X n
X n
X
(ai + bi ) = ai + bi (6.3.2)
i=1 i=1 i=1

Propiedad conmutativa
X X
ai = ap (i) (6.3.3)
i∈K p(i)∈K

6.3.4. Series importantes

n
X
sn = 1=n (6.3.4)
i=1

n
X
sn = i = n(n + 1)/2 (6.3.5)
i=1

n
X
sn = i2 = n(n + 1)(2n + 1)/6 (6.3.6)
i=1

n
X
sn = ir = nr+1 /r + 1 + pr (n) (6.3.7)
i=1

donde r es un entero positivo y pr (n) es un polinomio de grado r.


n
X 1
Hn = (6.3.8)
i=1
i

ésta serie se denomina serie armónica y log(n + 1) < Hn ≤ 1 + log n.

6.4. Secuencias Importantes


Cuando se habla de una secuencia, uno se refiere a una lista de números o términos. Por ejemplo los números
múltiplos de 2 que son 2, 4, 6, 8, 10, . . .. Lo importante es que el orden debe ser lógico. En cambio cuando
nos referimos a una serie es necesario hallar la suma de los términos, por ejemplo la suma de los números
pares.
Existe una enciclopedia en lı́nea donde se pueden revisar múltiples secuencias, ésta disponible en
https://oeis.org/. Presentamos algunas secuencias importantes que tienen múltiples aplicaciones.
126 Capı́tulo 6 Aritmética Básica

6.4.1. Números Triangulares

Cada número triangular se define como sigue:


n(n − 1)
Tn =
2

Estos números se forman de la serie aritmética comenzando desde 1 y con razón de 1. Los primeros números
números son:

n Tn
1 1
2 3
3 6
4 10
5 15
6 21

Teorema 2. La suma Tn + Tn−1 es un cuadrado perfecto.

Demostración. La demostración se puede realizar sumando las expresiones de estos dos números:
(n + 1)n (n − 1 + 1)(n − 1) 1
+ = (n2 + n + n2 − 1) = n2
2 2 2

La suma de los números de los primeros n números triangulares se obtiene con la siguiente formula:
n(n + 1)(n + 2)
6

6.4.2. Números cuadrados o cuadrados perfectos

Se denominan cuadrados perfectos o números cuadrados a aquellos números cuya raı́z cuadrada es un número
natural. Los primeros números son: 1, 4, 9, 16, 25... que significa Tn = n2 .
Una propiedad interesante de estos números es que:

n
X
2i − 1 = n2
i=1

Veamos un ejemplo, sea n = 4:

4
X
2i − 1 = 1 + 3 + 5 + 7 = 16 = 42
i=1
6.5 Divisibilidad 127

6.4.3. Números de Fibonacci


La sucesión de números de Fibonacci es aquella que se forma por la fórmula Tn = Tn−1 + Tn−2 . Los
primeros dos números se definen como T0 = 0, T1 = 1. Esta serie produce la siguiente secuencia de
números. 0, 1, 2, 3, 5, 8, 13, 21.... En este capı́tulo solo se menciona pero se propone un capı́tulo dedicado
a la misma.

6.4.4. Números Primos


Se llaman números primos aquellos números cuyo únicos divisores son el 1 y el mismo número. La secuencia
de los primeros números primos es:

2, 3, 5, 7, 11, 13, 17, 23...

Esta secuencia no tiene una caracterı́stica que describa la generación, por lo que se hace muy interesante su
estudio, por lo que en el libro hay todo un capı́tulo dedicado a su estudio.
Hay que hacer notar que el número 1 no se considera número primo, porque el teorema fundamental de
la aritmética indica que todo número tiene una única descomposición en números (factores) primos. Por
ejemplo el número 12 tiene una única descomposición que es 22 ∗ 3. Si se incluye el 1 se puede escribir
1 ∗ 22 ∗ 3 con lo que se tiene 2 representaciones.

6.5. Divisibilidad
En la teorı́a de números la notación a|b se lee a divide b significa que b = ka para algún k > 0. También se
dice que b es múltiplo de a.
Si a|b decimos que a es un divisor de b por ejemplo los divisores de 20 son: 1, 2,4,5,10 y 20.
Todo entero a, es divisible por el divisor trivial 1. Los divisores no triviales se denominan factores.
Cuando un número tiene como único divisor el número 1 y a si mismo se denomina número primo.
Teorema 3. Para cualquier entero a y un número positivo n existen enteros k y r tal que 0 ≤ r < n y
a = qn + r

El valor q = ⌊a/n⌋ se denomina cociente de la división y r de denomina residuo o resto. El valor r también
se expresa como r = a mód n. De esto vemos que n|a si y solo si a mód n = 0
Si d|b y d|a entonces d es un común divisor de a y b. En este caso, se verifica

d|(a + b)
y en un caso más general d|(xa + yb)
si b|a y a|b significa que a = ±b
si b|a significa que b ≤ a o que a = 0

6.6. Aritmética modular


La aritmética modular es una de las aplicaciones más importantes de la teorı́a de números. Está representada
por la función mód . La función módulo representa el resto de la división. Por ejemplo a mód b significa
128 Capı́tulo 6 Aritmética Básica
que queremos hallar el resto de a/b que representamos matemáticamente como:
jak
a mód b = a − b (6.6.1)
b
La aritmética modular también define un sistema de numeración. Veamos por ejemplo la secuencia:

0 mód 3, 1 mód 3, 2 mód 3, 3 mód 3, 4 mód 3....

evaluando tenemos 0, 2, 1, 0, 1... que equivale a la numeración en base 3.

Propiedades

Presentamos algunas propiedades importantes de la aritmética modular.

Suma (x + y) mód m = (x mód m + y mód m) mód m.


Por ejemplo :

(8 + 7) mód 3 = 15 mód 3 = 0

y por la propiedad de la suma

(8 mód 3 + 7 mód 3) mód 3 = (2 + 1) mód 3 = 0

Resta La resta es solo la suma con valores negativos por los que (x − y) mód m = (x mód m − y
mód m) mód m.
Multiplicación La multiplicación xy mód m = (x mód m)(y mód m) mód m. Esto se da debido a
que la multiplación es simplemente una suma repetida.
División No existe el inverso de la multiplicación como la conocemos por ejemplo veamos: dx mód m =
dy mód m se puede pensar que podemos simplificar d obteniendo x mód m = y mód m, que no se
cumple en todos los casos. Veamos un ejemplo:

6·2 mód 3 = 6 · 1 mód 3 = 0

si simplificamos el 6 tenemos

2 mód 3 = 1

que es diferente de cero.


Solo es posible realizar estas simplificaciones cuando el único divisor común entre(d, m) es el 1.

Existen muchas aplicaciones de la aritmética modular, por ejemplo en el calendario los dı́as de la semana
correponden a una aritmética módulo 7, las horas, minutos y segundos corresponden al módulo 60. Hallar el
último dı́gito de un número decimal corresponde a una aritmética módulo 10.
El ejemplo que se presenta es extractado de [Miguel A. Revilla 2003]. Hallar el último dı́gito del número
2100 . Para ésto no es necesario hallar el valor del exponente y luego obtener el último dı́gito se puede resolver
como sigue:
6.7 Exponenciación Rápida 129

23 mód 10 = 8
26 mód 10 = (8 · 8) mód 10 = 4
212 mód 10 = (4 · 4) mód 10 = 6
224 mód 10 = (6 · 6) mód 10 = 6
248 mód 10 = (6 · 6) mód 10 = 6
296 mód 10 = (6 · 6) mód 10 = 6
2100 mód 10 = 296 · 23 · 21 mód 10 = 6 · 8 · 2 mód 10 = 6

6.7. Exponenciación Rápida


Cuando queremos hallar ab la primera idea que se tiene es el de multiplicar a todas las veces necesarias dadas
por el exponente b. Observando algunas propiedades de los exponentes es posible realizar esta operación en
forma mucho más eficiente. Tomando en cuenta la propiedad:
b b b
ab = a 2 a 2 = (a 2 )2

que se cumple cuando b es par, podemos reescribir la potencia como sigue:


b
1. Cuando b es par haga (a 2 )2 luego b = b/2
b
2. Cuando b es impar haga aa( 2 )2 luego b = b/2

Supongamos que queremos hallar p = 27 los pasos a seguir son;

a b p
2 7 1
4 3 2
16 1 8
256 0 128

El programa que implementa esto es :

public class Potencia {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t a =2 , b =7 , p = 1 ;
w h i l e ( b >0){
i f ( ( b &1)==1){ / / V e r i f i c a r s i es par
p∗= a ;
}
b=b>>1; / / d i v i d i r por 2
a ∗= a ;
}
System . o u t . p r i n t l n ( p ) ;
}
}
130 Capı́tulo 6 Aritmética Básica

6.8. Máximo común divisor


El máximo común divisor de dos números a, b ambos diferentes a cero, se denomina al divisor común más
grande de a, b. Se lo denomina como mcd(a, b). Se define mcd(0, 0) = 0.
Como ejemplo, tomemos los números 20 y 8. Los divisores del número 20 son: 20,10, 5,4 y 2. Los
divisores del número 8 son el 8, 4 y 2. De aquı́ vemos que el máximo común divisor es el número 4. Para
resolver adecuadamente este problema se construye un programa, inicialmente con la idea explicada, para
posteriormente buscar un solución más eficiente.
Para realizar una primera aproximación a la solución del problema probaremos todos los números hallando
el máximo número que divida a ambos.

Mcd ( i n t a , i n t b ) {
i n t mcd ( ) {
int i ;
f o r ( i =b ; i >1; i −−){
i f ( ( ( a %i ==0) && ( b %i = = 0 ) ) ) {
break ;
}
}
return ( i );
}
}

Análisis:
Se puede apreciar que primero a > b cuando hallamos el primer divisor este ya es máximo porque
comenzamos en b y vamos restando de uno en uno. En el peor caso cuando el máximo común divisor es
1 el algoritmo realiza el máximo número de operaciones, que son b divisiones en el peor caso, que se da
cuando no tienen ningún divisor en común.
Para mejorar este algoritmo se hace necesario analizar algunas propiedades matemáticas.

mcd(a, b) = mcd(−a, b)
mcd(a, b) = mcd(|a| , |b|)
mcd(a, ka) = a
mcd(a, 0) = |a|
mcd(a, a ∗ n + b) = mcd(a, b)
Propiedad asociativa.- mcd(a, b, c) = mcd(a, mcd(b, c))
Propiedad distributiva.- mcd(na, nb) = n mcd(a, b)
Propiedad idempotente.- mcd(a, a) = a)
Propiedad conmutativa.- mcd(a, b) = mcd(b, a)

Teorema 4. Si a, b son números enteros, diferentes de cero el mcd(a, b) es el elemento más pequeño del
conjunto {ax + by} donde x, y ∈ Z.
6.8 Máximo común divisor 131
Demostración. Sea q = ⌊a/s⌋ y sea s el entero más pequeño de la combinación lineal de a y b. Entonces

a mód s = a − qs
= a − q(ax + by)
= a(1 − qx) + b(qy)
Esto muestra que a mód s es una combinación lineal de a y b. El número más pequeño que puede dar a
mód s es 0 dado que a mód s < s. Podemos hacer un análisis similar y encontrar lo mismo para b. Esto
indica que s|a y que s|b por lo que s es un divisor común de a y b.
De acá vemos que mcd(a, b) ≥ s. Dado que el mcd(a, b) > 0 y que s divide a ambos a y b debe dividir
también a una combinación lineal de a y b por lo que mcd(a, b) ≤ s. Si mcd(a, b) ≥ s y mcd(a, b) ≤
entonces mcd(a, b) = s

De este teorema podemos deducir las siguientes propiedades:

Dados los números enteros positivos a, b, n


si n|ab y mcd(a, n) = 1 entonces b|n.
mcd(na, nb) = n mcd(a, b) (propiedad distributiva).
Si d|a y d|b entonces mcd(a, b)|d.
Teorema 5. (Teorema de Euclides) Para cualesquiera dos enteros no negativos a, b

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

Demostración. Se puede escribir a como tb + r y reemplazando se tiene

mcd(a, b) = mcd(tb + r, b)

cualquier divisor común de b y a debe dividir tb + r. Cualquier divisor de tb también es divisor de b que
implica que cualquier divisor de b debe dividir a r.

El algoritmo denominado de Euclides para hallar el máximo común divisor de dos números es más eficiente
porque en cada iteración se reduce la cantidad de iteraciones en función de b. Una implementación del
algoritmo es la siguiente:

public class Euclides {


int a , b;

Euclides ( int a , int b) {

int r = b;
while ( b > 0) {
r = a %b;
a = b;
b = r;
}
return (a );
}
132 Capı́tulo 6 Aritmética Básica
}

El algoritmo de Euclides es generalmente un algoritmo suficientemente eficiente para la mayorı́a de los casos,
sin embargo se puede utilizar una version mejorada que se denomina máximo común divisor binario que se
construye como sigue:

mcd (0, v) = v, porque todo divide a cero, y v es el número más grande que divide v.
mcd (u, 0) = u.
Si u y v son pares, entonces mcd(u, v) = 2 ∗ mcd(u/2, v/2), porque 2 es un divisor común.
Si u es par y v es impar, entonces mcd(u, v) = mcd(u/2, v), porque 2 no es un divisor común.
Si u es impar y v es par, entonces mcd(u, v) = mcd(u, v/2).
Si u y v son ambos impares, y u ≥ v, entonces mcd(u, v) = mcd((u − v)/2, v).
Si ambos son impares y u < v, entonces mcd(u, v) = mcd((v − u)/2, u).

Todos estos pasos se repiten hasta hallar el mcd. Este algoritmo se denomina binario porque las operaciones
de división por dos se pueden hacer con un desplazamiento de bits a la derecha.

6.9. Mı́nimo común múltiplo


El mı́nimo común múltiplo (mcm) de dos números es el número más pequeño que es divisible por ambos
números. Por ejemplo el mı́nimo común múltiplo entre 9 y 6 es 18. Para el cálculo del mı́nimo común
múltiplo no existe un algoritmo similar al de Euclides que facilite hallar este número. Este número se expresa
como sigue:

mcm(a, b) = min{k, k > 0 y a|k y b|k}

sin embargo es posible probar que

mcd(a, b)mcm(a, b) = ab

En el ejemplo podemos ver que el mcd(9, 6) es 3 y que 3x18 = 54 = 9x6

6.10. Ejemplos de aplicación


Se desea conocer si un número x con una cantidad de dı́gitos que no caben en una variable de 64 bits se desea
saber si es divisible por un número entero m.
En este problema no es posible hallar x %m porque x no puede representarse en una variable numérica. Para
resolver este problema hay que representar x como una cadena de texto, recomponer el número aplicando las
propiedades de la aritmética modular. La secuencia de pasos es como sigue:

1. Definir un variable r para el resultado


2. Tomar los caracteres di en orden del primero al ultimo y para cada uno hacer
3. r = ∗10 + di %m
6.11 Ejercicios 133
4. Para finalizar retornar r %m

El código es como sigue:

public class Divisibilidad {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S t r i n g x= ” 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0 1 2 ” ;
i n t m=1007 , r = 0 ;
f o r ( i n t i = 0 ; i <x . l e n g t h ( ) ; i ++) {
r = r ∗10+( x . c h a r A t ( i ) − ’ 0 ’ ) ; / / r e s t a m o s e l c e r o p a r a
/ / c o n v e r t i r de a s c i i a d e c i m a l
r = r %m;
}
System . o u t . p r i n t l n ( ” R e s p u e s t a : ” + ( r %m ) ) ;
}
}

6.11. Ejercicios
134 Capı́tulo 6 Aritmética Básica

6.11.1. JV-1091 Divisibilidad


Se le darán dos números naturales a y b. Su tarea es verificar si a es divisible por b, o b es divisible por a.

Entrada
Se le darán muchos casos de prueba. La entrada consta de dos números naturales a y b no mayores a 100.

Salida
Existen tres posibles respuestas.

Si a es divisible por b imprimir: "a es divisible por b".


Si b es divisible por a imprimir: "b es divisible por a".

Los valores de a y b dependen de la entrada.


Si ninguno de los casos se da imprimir: -1. Imprimir la respuesta en una sola linea.

Ejemplos de entrada Ejemplos de salida


5 25 25 es divisible por 5
52 2 52 es divisible por 2
5 13 -1
6.11 Ejercicios 135

6.11.2. JV-1269 Fracciones


Se dice que dos fracciones son iguales si el numerador y el denominador de las dos fracciones son iguales,
por ejemplo 33/15 es igual a 11/5 ya que simplificando 33/15 tenemos 11/5 que es igual a la segunda fracción.
Dado dos fracciones determinar si estos son iguales.

Entrada
La entrada viene dada por varios casos en una misma entrada. Cada lı́nea de la entrada representa un caso.
Cada caso consiste en cuatro números enteros los dos primeros n1 y d1 son los valores de la primera fracción
siendo n1 el numerador y d1 el denominador, siguen n2 y d2 siendo los valores de la segunda fracción
donde n2 es el numerador y d2 el denominador (1 ≤ n1, n2, d1, d2 ≤ 1000) la entrada termina cuando
n1 = d1 = n2 = d2 = 0.
Es decir, los últimos 4 números de la entrada serán 0s y este caso representa el fin de los datos de entrada y
no debe ser procesado.

Salida
Imprimir = si las fracciones son iguales caso contrario imprimir ! =.

Ejemplos de entrada Ejemplos de salida


33 15 11 5 =
7 8 11 2 !=
0 0 0 0
136 Capı́tulo 6 Aritmética Básica

6.11.3. JV-1275 Cuatro Dı́gitos


Buscar y listar todos los números de cuatro dı́gitos en la notación decimal que tienen la propiedad de que la
suma de sus cuatro dı́gitos es igual a la suma de sus dı́gitos cuando se representan en notación hexadecimal
(base 16) y también es igual a la suma de sus dı́gitos cuando se representan en notación duodecimal (12
base).
Por ejemplo, el número 2991 tiene la suma de dı́gitos (decimales) 2 + 9 + 9 + 1 = 21. Puesto que
2991 = 1 ∗ 1728 + 144 ∗ 8 + 9 ∗ 12 + 3, su representación es duodecimal 189312 , y estas cifras también
suman 21. Sin embargo, en hexadecimal, 2991 es BAF16 y 11 + 10 + 15 = 36, por lo que 2991 debe ser
descartado por su programa.
El número siguiente (2992), sin embargo, tiene cifras que suman 22 en las tres representaciones (incluyendo
BB016), por lo que 2992 deberı́a estar en la salida de la lista. (No queremos que los números decimales con
menos de cuatro dı́gitos, excluyendo ceros, de modo que conducen 2992 es la primera respuesta correcta.
La salida es como sigue:

2992
2993
2994
2995
2996
2997
2998
2999
...

Entrada
No hay datos de entrada

Salida
En la salida escriba un número en cada lı́nea de acuerdo a lo solicitado.
6.11 Ejercicios 137

6.11.4. JV-1281 Divisores en Orden


Escriba un programa para imprimir todos los divisores de un nÃo mero en orden especÃfico.Tome en cuenta
que todos los divisores son menores a la raÃz cuadrada de n.

Entrada
La entrada consiste de un numero natural n entre 1 y 109 .

Salida
Para cada nÃo mero natural n imprima todos los divisores de n en orden.

Ejemplos de entrada Ejemplos de salida


200 Divisores de 200: 1 2 4 5 8 10 20 25 40 50 100 200
6 Divisores de 6: 1 2 3 6
1 Divisores de 1: 1
100 Divisores de 100: 1 2 4 5 10 20 25 50 100
999999998 Divisores de 999999998: 1 2 691 1382 723589 1447178
999999937 Divisores de 999999937: 1 999999937
138 Capı́tulo 6 Aritmética Básica

6.11.5. JV-1389 Suma de Dı́gitos


Escriba un programa que lea un número e imprima la suma de sus dı́gitos.

Entrada
La entrada consiste de varios números, cada número tiene mı́nimo un dı́gito y máximo 50 dı́gitos. Cada
número viene en una lı́nea. La entrada termina cuando no hay más datos.

Salida
Para cada número imprima la suma de sus dı́gitos con el formato del ejemplo.

Ejemplos de entrada
29
1234567890123456789

Ejemplos de salida
La suma de los digitos de 29 es 11
La suma de los digitos de 1234567890123456789 es 90
6.11 Ejercicios 139

6.11.6. JV-1395 Factorial de N Otra Vez


El factorial de n se define como n(n − 1)(n − 2)...,1. Por ejemplo el factorial de 5 es 5x4x3x2x1 = 120.
Dado un número n te piden hallar el número de dı́gitos que tendrá n factorial. En el ejemplo, el factorial de 5
tiene 3 dı́gitos.

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba consiste en un número n menor a
10,000,001. La entrada termina cuando no hay más datos.

Salida
La salida consiste de un número en cada lı́nea con el número de dı́gitos que tiene el factorial de n.

Ejemplos de entrada Ejemplos de salida


5 3
7 4
10 7
140 Capı́tulo 6 Aritmética Básica

6.11.7. JV-1399 Inverso Factorial


Se define al factorial de un número N, como el producto de todos los números entre 1 a N: N ! =
1 ∗ 2 ∗ 3 ∗ 4 ∗ ... ∗ N con 0! = 1.
Por ejemplo 5! = 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 = 120
Lo que se pide en este problema, como el tı́tulo dice, es calcular el inverso del factorial, en otras palabras
encontrar un número tal que aplicando el factorial es igual al número de la entrada.
Por ejemplo: el inverso de 120 es 5, ya que 5!=120, el inverso de 6 es 3 ya que 3!=6

Entrada
La primera lı́nea indica el número de casos de prueba.
Consta de varios números que tienen entre 1 y 106 dı́gitos, estos son factoriales válidos. Los números son
mayores a 1.

Salida
Para cada número que recibas en la entrada le corresponde una lı́nea en la salida, que es el cálculo del inverso
del factorial.

Ejemplos de entrada Ejemplos de salida


2 5
120 3
6
6.11 Ejercicios 141

6.11.8. JV-1412 Divisibilidad -1


Te dan una secuencia de n dı́gitos decimales. La secuencia necesita ser particionada en una o mas secuencias
contiguas tal que cuando esta subsecuencia cuando es interpretada como un número decimal es divisible por
m.
Su tarea es listar estas particiones. Dos particiones son diferentes si las ubicaciones de la subsecuencia son
diferentes.
En el ejemplo nos piden las secuencias que son divisibles por 3 de la secuencia 12345.
Estas secuencias se obtienen dividiendo la secuencia en 12, 3 y 45.

Entrada
La entrada consiste de dos lı́neas. La primera lı́nea contiene un número entero 2 ≤ M ≤ 109. La segunda
lı́nea contiene una cadena de dı́gitos con 2 ≤ n ≤ 300000 caracteres.

Salida
En la salida escriba estas cadenas.

Ejemplos de entrada Ejemplos de salida


3 12
12345 3
45
142 Capı́tulo 6 Aritmética Básica

6.11.9. JV-1415 Pantalla


Te has comprado un nuevo celular, con una pantalla de n x m donde n denota la altura y m denota el ancho.
Quieres diseñar un programa para pintar la pantalla, con C colores. Se quiere pintar regiones rectangulares
con un color diferente. También se quiere usar todos los colores.
Lo que se quiere es encontrar el número de regiones rectangulares que se pueden pintar. En otras palabras,
encontrar todos los pares x,y de tal manera que se pueda pintar una sección x,y en la pantalla con c colores.
Solo se considera una sola orientación del teléfono. Esto significa que no lo puede rotar. Esto es que la altura
n y el ancho m mantienen su valor y nunca se intercambian.

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba contiene tres enteros separados por
un espacio que son n, m, c, (1 ≤ n, m, c ≤ 106).

Salida
Para cada caso de prueba imprima un entero con el número de diferentes tamaños de área rectangulares que
se pueden dibujar.

Ayuda
Caso de prueba 1. Todos los posibles pares donde se utilizan todos los colores son de dimensiones (2, 6), (3,
4) y (4, 3). Vea que el área rectangular de dimensión (1,12) no puede ser pintada porque 12 ¿6, dado que 12
excede el ancho de la pantalla.
Caso de prueba 2. No existe ningún área rectangular con 10 diferentes pixeles pintados.

Ejemplos de entrada Ejemplos de salida


4 6 12 3
3 3 10 0
6.11 Ejercicios 143

6.11.10. JV-1431 Casi son fibos


Todo el mundo conoce la serie de Fibonacci: 1, 1, 3, 5, 8, 13, 21, . . . En este caso estamos interesados
en series parecidas módulo M, pero que inician en términos distintos al Fibonacci original por ejemplo
2, 6, 8, 14, 22, 36, . . . o 9, 11, 20, 31, 51, . . . Debes imprimir el n-ésimo término módulo M de una de estás
series.

Entrada
Se te dará un número N (1 ≤ N ≤ 10000) el n-ésimo término que debes imprimir, M (a, b < M ≤ 1000) el
módulo y dos números a, b (1 ≤ a, b ≤ 1000) los términos iniciales de la serie.

Ejemplos de entrada Ejemplos de salida


4 10 1 7 5
144 Capı́tulo 6 Aritmética Básica

6.11.11. JV-1432: K-sumatoria


Se te pide que calcules la sumatoria de los primeros X múltiplos de K, módulo M.
Por ejemplo para X = 3, K = 6, M = 1000.
suma = 6 + 12 + 18 = 36(36 %1000 = 36)

Entrada
Se te darán 3 números X (1 ≤ X ≤ 10000)K(1 ≤ K ≤ 100) y M (1 ≤ M ≤ 1000).

Salida
Imprimir el resultado de la sumatoria módulo M.

Ejemplos de entrada Ejemplos de salida


3 6 1000 36
77.1.
Arreglos unidimensionales - vectores
Definición
Un vector o arreglo es una estructura de datos que permite almacenar valores secuencialmente en la memoria
de la computadora. Se utiliza como contenedor para almacenar datos, en lugar de definir una variable por
cada dato.
Todos los datos deben ser del mismo tipo. No se pueden mezclar tipos de datos.Los valores se colocan en
secuencia a partir de la posición cero.
La sintaxis para definir un vector es:

1. Se define el tipo de dato seguido de [] que indica que se trata de un vector.


2. Se define el nombre de la variable.
3. Se coloca el sı́mbolo igual seguido de new y el tipo de dato.
4. Finalmente se coloca [] con el tamaño definido entre ambos corchetes.

Veamos algunos ejemplos:

int [] vector = new int[8];

Esta definición crea una estructura que define 8 elementos enteros contiguos. El nombre vector es la dirección
donde se encuentran los valores de la estructura de datos.
La figura 7.1 muestra como ha sido definido el vector.

Vector
int [] vector = new int [n]
0
1
2
3
4
5
6
7
8

Figura 7.1 Definición de un vector

Si definimos un vector de cadenas, la sintaxis es:

145
146 Capı́tulo 7 Arreglos unidimensionales - vectores
String [] vector = new String[8];

En este caso, cada elemento del vector es la dirección de la memoria donde se encuentra la cadena.
La figura 7.2 muestra como ha sido definido el vector de cadenas. Para definir un vector con valores iniciales

Vector
String [] vector = new String [n] Cadena 0
0
1
2 Cadena 1
3
4
5
6
7
8
Cadena 8

Figura 7.2 Definición de un vector de cadenas

el procedimiento es como sigue:

1. Se define el tipo de dato seguido de [] que indica que se trata de un vector


2. Se define el nombre de la variable
3. Se coloca el sı́mbolo igual seguido de un {
4. Se colocan todos los valores separados por una coma
5. Se termina con }

Como ejemplo definamos un vector de enteros:

int [] vector = {1,10,5,15};

Cuando los valores de un vector se encuentra en una cadena se utiliza el método split de la clase String. Por
ejemplo:

String primos = "2, 3, 5, 7, 11";


int p= primos.split(",");

Acá el método split indica que hay múltiples valores delimitados por una coma.

7.2. Recorrido
Cada uno de los elementos de un vector se acceden por la posición donde está almacenado el valor que
queremos acceder. Consideremos la definición:

int [] v = {1,10,5,15};

Para acceder a cualquier valor se escribe el nombre de la variable seguido de [] con el número de elemento
que queremos acceder. Veamos v[0] = 1, v[3] = 15.
7.3 Valores iniciales 147
El tamaño del vector se almacena en la propiedad length. Note que no utilizamos los paréntesis en las
propiedades, en los vectores no es un método.

Para listar los elementos del vector definido, podemos utilizar un for

for (int i =0; i< v.length;i++)


System.out.println(v[i]);

Los ı́ndices del vector van desde 0 hasta length − 1. La cantidad de valores es length. No es posible acceder
a valores fuera de los lı́mites de un vector.
Una segunda forma de recorrer un vector es utilizando la instrucción for each. La sintaxis es como sigue:

for (tipo variable: vector) {


instrucciones
}

Lo que indica es que se quiere recorrer todos los elementos de un vector y cada uno de los elementos será
colocado en la variable, iteración por iteración. En código del ejemplo anterior es:

for (int i: v)
System.out.println(i);

Como ve no tuvimos que hacer una referencia individual a cada elemento del vector.

7.3. Valores iniciales


Cuando definimos un vector los valores iniciales que tiene son:

Cero cuando son valores numéricos


Si son referencias como el caso de las cadenas toma el valor null
Si son valores boolean toma el valor false

7.4. Ejemplos de aplicaciones


Para ejemplificar el uso de vectores vamos a hallar algunos indicadores estadı́sticos tı́picos: media, varianza,
moda, máximo.

1. Hallar la media. La fórmula de la media es:


Pn
i=1 xi
m=
n
Definiremos un vector para almacenar n valores, el recorrido del mismo será de 0 hasta n − a inclusive.
Para este los ejemplos siguientes colocaremos un vector con valores constantes. El código es como sigue

import java . u t i l . Scanner ;


p u b l i c c l a s s Media {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x= { 9 , 4 , 8 , 3 , 7 , 3 , 5 , 2 , 4 , 1 , 2 , 5 , 6 , 1 , 2 , 2 , 4 , 4 , 4 , 8 , } ;
148 Capı́tulo 7 Arreglos unidimensionales - vectores
d o u b l e m= 0 . 0 ;
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <x . l e n g t h ; i ++){
suma+=x [ i ] ;
}
m= ( d o u b l e ) suma / x . l e n g t h ;
System . o u t . p r i n t l n (m ) ;
}
}

2. Hallar la varianza. La varianza se calcula con la fórmula:


Pn
(m − xi )2
v 2 = i=1
n
Para resolver éste problema debemos hacer recorrer dos veces el vector una vez para hallar la media m
y la segunda para hallar la varianza. El programa es como sigue:

import java . u t i l . Scanner ;


p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x= { 9 , 4 , 8 , 3 , 7 , 3 , 5 , 2 , 4 , 1 , 2 , 5 , 6 , 1 , 2 , 2 , 4 , 4 , 4 , 8 , } ;
d o u b l e m= 0 . 0 ;
double v =0.0;
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <x . l e n g t h ; i ++){
suma+=x [ i ] ;
}
m= ( d o u b l e ) suma / x . l e n g t h ;
suma = 0 ;
System . o u t . p r i n t l n ( ” Media = ”+m ) ;
f o r ( i n t i = 0 ; i <x . l e n g t h ; i ++){
suma +=(m−x [ i ] ) ∗ ( m−x [ i ] ) ;
}
v=Math . s q r t ( suma ) / x . l e n g t h ;
System . o u t . p r i n t l n ( ” V a r i a n z a = ”+ v ) ;
}
}

3. Hallar el máximo. Para hallar el valor máximo, primero definimos el máximo con el valor más pequeño
que se puede almacenar. Luego debemos recorrer el vector y cada vez comparar con el máximo y
almacenar el valor mayor. El siguiente programa halla el máximo:

import java . u t i l . Scanner ;


p u b l i c c l a s s Maximo {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x= { 9 , 4 , 8 , 3 , 7 , 3 , 5 , 2 , 4 , 1 , 2 , 5 , 6 , 1 , 2 , 2 , 4 , 4 , 4 , 8 , } ;
i n t max= I n t e g e r . MIN VALUE ;
7.4 Ejemplos de aplicaciones 149

f o r ( i n t i = 0 ; i <x . l e n g t h ; i ++){
max=Math . max ( max , x [ i ] ) ;
}
System . o u t . p r i n t l n ( ” Máximo = ”+max ) ;
}
}

4. Hallar la moda. La moda se define como el valor que más se repite, para esto haremos dos procesos. El
primero para hallar las frecuencias de repetición de cada uno de los elementos de x. La segunda para
hallar el máximo de esta repeticiones.
Para hallar las frecuencia, es decir, cuantas veces ocurre cada elemento de x definimos un vector f donde
en f [0] servirá para contar cuantas veces aparece el 0, f [1] para el 1, ası́ sucesivamente. Hemos definido
f de tamaño 10 porque sabemos que ningún valor excede a 10 valores. La instrucción f [x[i]] + +
incrementa el vector de frecuencias en cada iteración.
La segunda parte es la que halla el máximo. Con el mismo algoritmo anterior, almacenando junto al
máximo la posición donde se encuentra. La posición representa la moda. En el segundo ciclo el recorrido
es sobre el tamaño de f . El programa resultante es:
import java . u t i l . Scanner ;
p u b l i c c l a s s Moda {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x= { 9 , 4 , 8 , 3 , 7 , 3 , 5 , 2 , 4 , 1 , 2 , 5 , 6 , 1 , 2 , 2 , 4 } ;
i n t [ ] f = new i n t [ 1 0 ] ;
f o r ( i n t i = 0 ; i <x . l e n g t h ; i ++){
f [x[ i ]]++;
}
i n t max= I n t e g e r . MIN VALUE ;
i n t moda = 0 ;
f o r ( i n t i = 0 ; i <10; i ++){
i f ( f [ i ]>max ) {
max= f [ i ] ;
moda= i ;
}
}
System . o u t . p r i n t l n ( ” Moda = ”+moda ) ;
}
}

En este ejemplo no es posible utilizar Math.max porque hay que almacenar dos valores.
5. La secuencia de Recaman. Los primeros términos de la secuencia de Recaman son:

S = {0, 1, 3, 6, 2, 7, 13, 20, 12, 21, 11, 22, 10, . . .}

La secuencia se construye como sigue:


a) Si n > 0 y el número no esta incluido en la secuencia entonces Sn = Sn−1 − n
b) Si n > 0 y el número ya esta incluido en la secuencia entonces Sn = Sn−1 + n
150 Capı́tulo 7 Arreglos unidimensionales - vectores
Para esto definimos un vector S, el primer valor n = 0 toma el valor de S0 = 0. Para el segundo valor
n = 1, no esta incluido en la secuencia por lo que Sn = Sn−1 − n, sin embargo S0 − 1 esta fuera del
vector por lo que tomamos el segundo caso Sn = Sn−1 + n de donde S1 = S0 + 1. Cuando n = 2,
vemos que el dos no esta en la secuencia por lo que tomamos S2 = S1 + 2 = 3 EL programa para
generar la secuencia es:

p u b l i c c l a s s Recaman {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
/ / arreglo para la secuencia
i n t [ ] s =new i n t [ 1 0 0 ] ;
/ / a r r e g l o p a r a s a b e r s i e s t a en l a s e c u e n c i a
b o o l e a n [ ] p=new b o o l e a n [ 1 0 0 ] ;
s [0]=0;
f o r ( i n t i = 1 ; i < 2 0 ; i ++) {
s [ i ] = s [ i − 1] − i ;
i f ( ( s [ i ] > 0 ) && ! p [ s [ i ] ] ) / / S i p o s i t i v o y no e s t a
p[ s [ i ]] = true ; / / e s t a en l a s e c u e n c i a
else {
s [ i ] = s [ i − 1] + i ;
p[ s [ i ]] = true ;
}
}
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( s ) ) ;
}
}

6. Veamos un ejemplo más complejo. Se trata de sumar los puntos de una partida de bolos (bowling). En
un juego de bolos un jugador tiene 10 oportunidades de anotar en el transcurso de un juego. Cuando
lanza la bola, puede voltear 10 palos si voltea todos. En este caso se contabiliza 10 más lo que haya
obtenido en sus siguientes dos jugadas. Cuando no voltea todos los palos, se anota la cantidad de palos,
luego se hace un segundo lanzamiento y se agrega la cantidad de palos que volteo. Si en ésta segunda
oportunidad completa los 10 palos volteados, se agrega lo que obtenga en su siguiente entrada.
Por ejemplo 5, 2, 3, 4 significa que primero volteo 5 palos, luego 2. En ésta entrada tiene 7 puntos en su
segunda entrada tiene 3 + 4 = 7 puntos. Veamos otro caso 10, 5, 2 en este caso en la primera entrada
volteo los 10 palos, por lo que el puntaje para esta entrada es 10 + 5 + 2 = 17. En la siguiente entrada
tiene 5 + 2 = 7 acumulando hasta este momento 24 puntos. Si los datos son 7, 3, 5, 2, significa que en su
primera entrada volteo los 10 palos en dos intentos, por lo que el puntajes que se asigna es 7+3+5 = 15,
y no acumula los últimos 2 puntos. Dada la lista de todos los palos volteados en un juego, calcular el
puntaje final.
Expliquemos el programa siguiente:

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;
p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x= { 5 , 2 , 1 0 , 9 , 0 , 8 , 1 , 5 , 5 , 1 0 , 6 , 4 , 4 , 2 , 9 , 1 , 1 0 , 1 0 , 1 0 } ;
7.4 Ejemplos de aplicaciones 151

Figura 7.3 Juego de bolos

// i n t [ ] x= { 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 , 1 0 } ;
i n t [ ] p u n t o s J u g a d a = new i n t [ 1 0 ] ;
i n t j u g a d a s =0 , i = 0 ;
while ( jugadas < 10){
i f ( x [ i ]==10){
p u n t o s J u g a d a [ j u g a d a s ]+= x [ i +2]+ x [ i + 1 ] + 1 0 ;
i ++;
j u g a d a s ++;
}
else
i f ( ( x [ i ] + x [ i +1]) <10){
p u n t o s J u g a d a [ j u g a d a s ]+= x [ i ] + x [ i + 1 ] ;
i = i +2;
j u g a d a s ++;
}
else {
p u n t o s J u g a d a [ j u g a d a s ]+= x [ i + 2 ] + 1 0 ;
i = i +2;
j u g a d a s ++;
}
}
i n t suma = 0 ;
for ( i n t j : puntosJugada )
suma+= j ;
System . o u t . p r i n t l n ( ” D a t o s i n i c i a l e s ”+ A r r a y s . t o S t r i n g ( x ) ) ;
System . o u t . p r i n t l n ( ” P u n t o s p o r j u g a d a ”+ A r r a y s . t o S t r i n g ( p u n t o s J u g a d a ) ) ;
System . o u t . p r i n t l n ( ” P u n t a j e f i n a l ”+ suma ) ;
152 Capı́tulo 7 Arreglos unidimensionales - vectores
}
}

Primero hemos definido un vector donde se establece el puntaje correspondiente a cada jugada, lo de-
nominamos puntosJugada, de tamaño 10 dado que solo se puede jugar 10 veces. También establecemos
un ı́ndice jugada que se incrementa en uno por cada jugada.
Se compara primero si derribó 10 palos, en cuyo caso se suma los dos valores siguientes y se incrementa
el ı́ndice jugada en uno, y el ı́ndice que recorre el vector en uno. Luego vemos si hizo 10 en dos
lanzamientos, en cuyo caso se incrementa el valor del próximo lanzamiento, y se incrementa el contador
que recorre el vector en dos. En otros casos se incrementa el número de palos derribados.
El resultado que se obtiene es:

Datos iniciales [5, 2, 10, 9, 0, 8, 1, 5, 5, 10, 6, 4,


4, 2, 9, 1, 10, 10, 10]
Puntos por jugada [7, 19, 9, 9, 20, 20, 14, 6, 20, 30]
Puntaje final 154

7.5. Métodos disponibles para vectores


Para el manejo de vectores existe la clase Arrays que tiene los siguientes métodos.

Metodo Descripción
Arrays.binarySearch(a, key) Busca el valor key en un vector a devuelve la posición donde se
encuentra o −1 si no existe.
Arrays.copyOf(original, longitud) Copia el vector original en función a la longitud a otro vector
Arrays.equals(a, a2) Devuelve verdadero si los dos vectores son iguales o falso
Arrays.fill(a, val) Llena el vector a con el val especificado
Arrays.sort(a) Ordena el vector a en forma ascendente
Arrays.toString(a) Convierte el vector a una cadena

7.6. Ejercicios
7.6 Ejercicios 153

7.6.1. JV-1397: Dos Unos


Dado un número positivo n imprima los primeros n enteros positivos con exactamente dos bits en uno, en su
representación binaria.
Ejemplo: Entrada: n = 3
Salida: 3 5 6
Los primeros 3 números con dos bits en uno son: 3 (0011), 5 (0101) y 6 (0110)
Entrada: n = 5 Salida: 3 5 6 9 10 12
Sugerencia: Para lograr la eficiencia NO convierta los números a binario para contar el número de unos.
Utilice potencias de dos para generar los números.

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba consiste de un número mayor a 0 y
menor 400 y termina cuando no hay mas casos de prueba.

Salida
Imprima los primeros n enteros positivos con exactamente dos bits en uno

Ejemplos de entrada Ejemplos de salida


3 3 5 6
5 3 5 6 9 10
154 Capı́tulo 7 Arreglos unidimensionales - vectores

7.6.2. JV-1214: Golf Club


Un campo de golf se compone de 18 canchas de césped conocido como hoyos. El objetivo del jugador
consiste en golpear una pelota con su palo de tal manera que vaya de un punto especificado en un extremo
de la cancha a un punto especificado denominado hoyo, y hacerlo con el menor número de golpes. Asociado
con cada hoyo (cancha) existe un número positivo denominado par, que es el número de golpes que se espera
que un golfista competente realice para completar el hoyo.
El desempeño de un jugador en un hoyo está descrita por una frase que depende del número de golpes que
tuvo en relación al par. Hacer un bogey, por ejemplo, significa que el jugador ha completado un hoyo en un
golpe más que el valor nominal, y un doble bogey es de dos golpes sobre par. Dos golpes bajo par, por el
contrario, es un eagle.
El siguiente es un diccionario completo de frases usadas en el golf para referirse al desempeño de un golfista:

triple bogey.- tres golpes sobre par


double bogey.- dos golpes sobre par
bogey.-un golpe sobre par
par.-exactamente par
birdie.- un golpe bajo par
eagle.- dos golpes bajo par
albatross.- tres golpes bajo par
hole in one.- exactamente un golpe

Los administradores del club de Golf le han contratado para poner en práctica, un sistema de puntuación que
traduzca la jerga de arriba para hallar total numérico.

Entrada
La entrada consiste de varios casos de prueba. En la primera lı́nea de cada caso de prueba se ingresa un vector
de 18 elementos con el valor numérico del par para cada uno de los 18 hoyos.
Las siguientes 18 lı́neas tienen los resultados para cada hoyo expresados utilizando el vocabulario anterior-
mente descrito.

Salida
Para cada caso de prueba escriba una lı́nea con el número total de golpes que el jugador tuvo que hacer.

Ejemplos de entrada
1 1 1 1 1 1 1 1 1 5 5 5 5 5 5 5 5 5
bogey
bogey
bogey
bogey
7.6 Ejercicios 155
bogey
bogey
bogey
bogey
bogey
eagle
eagle
eagle
eagle
eagle
eagle
eagle
eagle
eagle
3 2 4 2 2 1 1 1 3 2 4 4 4 2 3 1 3 2
eagle
birdie
albatross
birdie
birdie
par
hole in one
par
eagle
birdie
albatross
albatross
albatross
birdie
eagle
hole in one
eagle
birdie

Ejemplos de salida
45
18
156 Capı́tulo 7 Arreglos unidimensionales - vectores

7.6.3. JV-1294: Suma en rango


Se tiene una lista de números y te piden hallar la suma de todos los números que están entre las posiciones a
y b (incluidos a y b). Se sabe que la suma no excede un numero entero.

Entrada
La entrada consiste de múltiples casos de prueba. La primera lı́nea contiene un número N que indica el
número de casos de prueba. Cada caso de prueba consiste de dos lı́neas, la primera lı́nea tiene un numero M
que representa la cantidad de números que hay que leer y los números a,b que son menores a M. La segunda
lı́nea contiene los M números separados por un espacio.

Salida
Por cada caso de prueba escrita en una lı́nea la suma de los números que están entre a y b inclusive.

Ejemplos de entrada Ejemplos de salida


3 9
7 1 3 -4
1 2 3 4 5 6 7 -1
10 8 9
9 8 7 6 5 4 3 2 1 -5
5 0 0
-1 -3 5 3 1
7.6 Ejercicios 157

7.6.4. JV-1402: Cortando Postes


Un trabajador descuidado ha plantado varios postes en una fila para construir una cerca. Todos ellos deben
tener la misma altura pero fueron cortadas en diferentes tamaños. El propietario no quiere solamente que
todos estén a la misma altura también quiere que la altura sea la más alta posible. Nuestra tarea es cortar las
cimas más altas de los postes y pegarlas como tapas en la parte superior de las más cortas. Para ello, primero
ordenar los postes del más alto al más bajo, y proceder como sigue:

1. Cortar la punta del poste más alta, dejando a su altura igual a la altura media de los postes (por lo que el
poste no se corta más).
2. Pegar esta pieza en la parte superior de las más cortas del poste.
3. Reordenar los postes, y continuar desde el primer paso hasta que todos los postes sean de la misma
altura.

Escriba un programa que lea la altura de postes y devuelva el número de cortes necesarios para que todos los
postes estén a la misma altura mediante el algoritmo descrito.

Entrada
La entrada consiste en un lote de enteros entre [1 − 50] los mismos representan la altura de los postes de la
cerca, la altura está entre 1 ≤ alturaposte ≤ 1000, se garantiza que el promedio de la altura de los postes
es un valor entero.
La entrada termina cuando no hay más datos.

Salida
Para cada caso de entrada mostrar el número de cortes necesarios para dejar los postes de la cerca a una
misma altura.

Ejemplos de entrada Ejemplos de salida


2 1
1 3 0
3 2
10 10 10 7
4 5
1 1 3 3
8
10 10 10 10 10 10 10 18
10
10 1 9 2 8 3 7 4 6 10
158 Capı́tulo 7 Arreglos unidimensionales - vectores

7.6.5. JV-1405: Evaluando Promedios

Para el diploma de bachillerato internacional (IB), a los estudiantes se les asigna una nota n con 1 ≤ n ≤ 7,
basado en exámenes tomados al final de la secundaria. Desafortunadamente, estos exámenes nunca están a
tiempo para las admisiones en las universidades.
Para abordar este problema los profesores del IB tienen la necesidad de pronosticar las notas que obtendrán
los estudiantes en sus exámenes. Estas predicciones tienen un impacto muy fuerte en el futuro de los
estudiantes en sus posibilidades de ingresar a la universidad.
Se quiere que realice un programa para evaluar la eficiencia de las predicciones.
Por ejemplo:
Alumno Predicción Nota Diferencia
1 1 3 2
2 5 5 0
3 7 4 3
4 3 5 2
Para hacer esta evaluación se imprimirá 7 porcentajes como números enteros que representan el porcentaje
de notas que tuvieron diferencia de 0, de 1, de 2, sucesivamente hasta 6. En el ejemplo la respuesta es
25 0 50 25 0 0 0

Entrada

La entrada consiste de múltiples casos de prueba. La primera lı́nea de cada caso de prueba contiene el número
de notas n con 1 ≤ n ≤ 50. La segunda lı́nea contendrá las n notas pronosticadas separadas por un espacio.
La tercera lı́nea tendrá las n notas reales. La entrada termina cuando no hay mas datos en la entrada.

Salida

La salida son 7 números enteros en una sola lı́nea con los porcentajes mencionados.

Ejemplos de entrada
4
1 5 7 3
3 5 4 5
3
1 1 1
5 6 7
1
3
3
23
1 5 3 5 6 4 2 5 7 6 5 2 3 4 1 4 6 5 4 7 6 6 1
5 1 3 2 6 4 1 7 5 2 7 4 2 6 5 7 3 1 4 6 3 1 7
7.6 Ejercicios 159

Ejemplos de salida
25 0 50 25 0 0 0
0 0 0 0 33 33 33
100 0 0 0 0 0 0
17 13 21 17 21 4 4
160 Capı́tulo 7 Arreglos unidimensionales - vectores

7.6.6. JV-1406: Promedios


El municipio ha tomados exámenes a los estudiantes del colegios y ha obtenido los resultados de sus
habilidades verbales y numéricas. Le piden decir cuantos estudiantes están por debajo del promedio.
Para hallar el promedio se deben sumar los resultados de las habilidades numéricas y verbales de cada
estudiante.
Por ejemplo tenemos cuatro estudiantes con habilidades verbales 200,250,700,700 y habilidades numéricas
400,400,400,400, el promedio es (600+ 650+ 1100+ 1100)/4=862.5 por lo que dos estudiantes están por
debajo del promedio.

Entrada
La entrada consiste de varios casos de prueba. Cada caso de prueba consiste de tres lı́neas. La primera lı́nea
contiene el número de estudiantes. La segunda lı́nea contiene los resultados obtenidos en sus habilidades
verbales. La tercera lı́nea contiene los resultados obtenidos en sus habilidades numéricas. La entrada termina
cuando no hay más datos.

Salida
Por cada caso de prueba escriba el número de estudiantes que están por debajo del promedio.

Ejemplos de entrada Ejemplos de salida


4 2
200 250 700 700 0
400 400 400 400 0
2 6
500 400
300 400
1
293
799
7
400 400 400 400 400 400 401
400 400 400 400 400 400 400
7.6 Ejercicios 161

7.6.7. JV-1407: Cordillera


En Bolivia tenemos múltiples montañas. Dada los puntos de altura de una imagen simplificada, contar cuantas
picos existen en la imagen.
Dado que todos los números consecutivos son diferentes tenemos los siguientes casos:
a) 1 2 3 b) 3 2 1 c) 1 3 2 d) 2 1 3
En los casos a y b, tenemos una recta pendiente, ası́ que no podemos contar como un pico en la imagen. El
el caso c si tenemos un pico.

/\
/ \
/

En el caso d, no tenemos un pico, sino otra montaña que comienza, ası́ que no lo podemos contar como un
pico.

/
\ /
\/

Entrada
La entrada consiste de varios casos de prueba Q. Cada caso de prueba tiene: El número de puntos N(N≤
1000000), el primero y último tienen el valor 0. Siguen N alturas hi ≥ 0 ≤ 1000.
Dos alturas consecutivas son diferentes

Salida
Cuantos picos hay en el gráfico

Ejemplos de entrada Ejemplos de salida


1 3
9
0 10 0 8 0 3 4 5 0
162 Capı́tulo 7 Arreglos unidimensionales - vectores

7.6.8. JV-1500: Array Palı́ndrome?


Una cadena es palı́ndrome si puede ser leida igual de atras hacia adelante y de adelante hacia atras.
La definición de palı́ndrome también se puede extender hacia arreglos, para este problema te pedimos que
verifiques si la secuencia de valores del array puede ser leida de la misma forma de atrás hacia adelante y de
adelante hacia atrás.
Por ejemplo el array [1, 23, 1] es un array palı́ndrome, pero el array [123, 3, 21] no lo es.

Entrada
En la primera lı́nea de la entrada consiste en un número entero n (1 ≤ n ≤ 100)
En la segunda lı́nea vendrán los n elementos del array.

Salida
Imprime SI si es que el array es palı́ndrome y NO caso contrario.

Ejemplos de entrada Ejemplos de salida


5 SI
1 32 23 32 1
7.6 Ejercicios 163

7.6.9. JV-1520: Sucesion de Farey


La Sucesión de Farey es una de esas curiosidades matemáticas a la vez llenas de belleza y fáciles de entender
que casualmente descubrió un no-matemático, John Farey, en 1928.
La idea es tomar un número natural (ej. n = 3 ) y empezar a definir la serie Farey(3) como una serie de
fracciones que tienen como numerador y denominador los números entre 1 y n. En el caso de Farey(3)
escribiendo todas estas fracciones serı́an
11,12,13,21,22,23,31,32,33
Esta serie tiene propiedades muy interesantes, pero antes de ello. ¿Podras mostrar la sucesión descrita arriba?

Entrada
La entrada consiste en un número entero 1 ≤ n ≤ 100 que representa el número para el que quieres calcular
Farey(n).

Salida
La salida consiste en la serie descrita anteriormente, la impresión sera de un término por lı́nea.

Ejemplos de entrada Ejemplos de salida


3 9
7 1 3 -4
1 2 3 4 5 6 7 -1
10 8 9
9 8 7 6 5 4 3 2 1 -5
5 0 0
-1 -3 5 3 1
8
8.1.
Arreglos multidimensionales
Definición
Para introducir los arreglos multidimensionales, definamos un vector como sigue:

int [] v;

Al colocar dos corchetes hemos indicado que se trata del arreglo de una dimensión. Si escribimos

int [][] v;

dos veces los corchetes hemos definido un arreglo de dos dimensiones. Los arreglos bidimensionales también
se conoce como matrices. Definamos un arreglo bidimensional de 2x3,

int [][] v= new int[2][3];

Este arreglo tiene 2 filas por 3 columnas. Para definir valores constantes la sintaxis es como sigue:

int [][] x= {{5,2,10},{9,0,8}};

Bien ahora el resultado de System.out.println(x.length) es 2 por que tiene 2 filas. El resultado de Sys-
tem.out.println(x[0].length) es de 3 por que la fila 0 tiene tres elementos.
Para acceder a un elemento especı́fico se requieren 2 ı́ndices. El de la fila y el de la columna. Ası́ x[1][0] = 9,
esto es la fila 1 columna 0.
En arreglos multidimensionales no es posible utilizar Arrays.toString(x) para ver el arreglo como cadena. Esto
se debe a que cada elemento del vector es un puntero a otro vector. Para hacer esto tenemos dos opciones: la
primera recorrer con la instrucción for

for (int i=0; i<x.length;i++)


System.out.println(Arrays.toString(x[i]));

y la segunda utilizando la instrucción for each

for (int [] i: x)
System.out.println(Arrays.toString(i));

Los arreglos de dos dimensiones se denominan matrices. Supongamos que queremos mostrar matriz del
ejemplo anterior como sigue:

(0,0)=5 (0,1)=2 (0,2)=10


(1,0)=9 (1,1)=0 (1,2)=8

El código nos muestra el recorrido del vector y como podemos armar ésta salida.

165
166 Capı́tulo 8 Arreglos multidimensionales
p u b l i c c l a s s programa2 {
p u b l i c s t a t i c v o i d Mat1 ( S t r i n g [ ] a r g s ) {
i n t [ ] [ ] x= { { 5 , 2 , 1 0 } , { 9 , 0 , 8 } } ;
for ( int i = 0 ; i <2; i ++){
for ( int j = 0 ; j <3; j ++)
System . o u t . p r i n t ( ” ( ” + i + ” , ” + j +”)=”+ x [ i ] [ j ] + ” ” ) ;
System . o u t . p r i n t l n ( ) ;
}
}
}

Cada vez que terminamos de mostrar una fila bajamos imprimimos un cr y lf .

8.2. Ejercicios clásicos


1. Escriba un programa para llenar de ceros la matriz triangular superior de una matriz de dimensión N xN .
2. Escriba un programa para llenar de ceros la matriz triangular inferior de una matriz de dimensión N xN .
3. Escriba un programa para imprimir una matriz de dimensión N xM por filas
4. Escriba un programa para imprimir una matriz de dimensión N xM por columnas
5. Escriba un programa para sumar dos matrices de N xM Cada valor se calcula como sigue: (c(i, j) =
a(i, j) + b(i, j)).
6. Escriba un programa para multiplicar dos matrices A, B. Dados A = (aij )m.n B = (bij )n.p , el producto
se define como AB = (Cij )m.p donde
n
X
cij = air arj
r=1

7. Escriba un programa para hallar la matriz transpuesta de una matriz cuadrada de dimensión M xM .
8. Escriba un programa para generar una matriz caracol de unos y ceros
9. Escriba un programa que lea un número impar del teclado y genere una matriz cuadrada donde la suma
de las filas, columnas y diagonales da el mismo número. Por ejemplo en la matriz siguiente la suma es
15

6 1 8
7 5 3
2 9 4

Para ejemplificar resolvamos el ejercicio del cuadrado mágico. El truco para construir un cuadrado de
dimensión impar, esto es, el número de elementos de la fila es impar, es comenzar en el medio de la
primera fila.

0 1 0
0 0 0
0 0 0
8.2 Ejercicios clásicos 167
Ahora avanzamos un lugar hacia arriba, en diagonal, para no salirnos del cuadrado hacemos que el
movimiento sea cı́clico.
0 1 0
0 0 0
0 0 2

Repetimos el movimiento

0 1 0
3 0 0
0 0 2

Ahora no podemos continuar en diagonal, porque la casilla de destino está ocupada, o sea que bajamos
un fila.
0 1 0
3 0 0
4 0 2

Continuamos
0 1 6
3 5 0
4 0 2

Luego se baja un lugar

0 1 6
3 5 7
4 0 2

Y termina en:
8 1 6
3 5 7
4 9 2

El programa siguiente construye el cuadrado mágico.

import java . u t i l . Scanner ;


p u b l i c c l a s s Cuadrado {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e =new S c a n n e r ( System . i n ) ;
int N = lee . nextInt ( ) ;

i n t [ ] [ ] magico = new i n t [N ] [ N ] ;

/ / Comenzamos en f i l a 0 y a l medio con 1


i n t f i l a = 0;
i n t col = N/ 2 ;
168 Capı́tulo 8 Arreglos multidimensionales
magico [ f i l a ] [ c o l ] = 1 ;

f o r ( i n t i = 2 ; i <= N∗N ; i ++) {


/ / s i l a d i a g o n a l e s 0 r e c o r r e r un l u g a r
i f ( magico [ ( f i l a − 1+N) % N ] [ ( c o l + 1 ) % N] == 0 ) {
f i l a = ( f i l a − 1+N) % N ;
c o l = ( c o l + 1) % N;
}
else {
f i l a = ( f i l a + 1 ) % N ; / / b a j a r una f i l a
}
magico [ f i l a ] [ c o l ] = i ;
}

// print results
f o r ( i n t i = 0 ; i < N ; i ++) {
f o r ( i n t j = 0 ; j < N ; j ++) {
System . o u t . p r i n t ( magico [ i ] [ j ] + ”\ t ” ) ;
}
System . o u t . p r i n t l n ( ) ;
}

}
}

En el programa la función módulo, hace que recorramos en forma cı́clica ya sea por filas o columnas.

8.3. Dimensión de tamaño variable


Hasta este momento hemos visto solo como definir arreglo donde el tamaño es fijo. Consideremos la
definición siguiente:

int [][] v;

Esto especifica que v es un arreglo bidimensional, pero, no se ha especificado el tamaño. El valor inicial en
éste caso es null. Con el siguiente código podemos especificar el tamaño del vector:

v=new int [m][n] ;

Para hacer que cada fila tenga un tamaño diferente: Primero definimos un vector sin especificar la dimensión
de las columnas, solo especificamos la dimensión de las filas, digamos 5:

int [][] v= new int[5][];

Luego para cada fila especificamos su tamaño:

v[0]=new int [i];


8.4 Arreglos dinámicos 169
Esto nos puede ahorrar mucho espacio de memoria. Por ejemplo para definir una matriz triangular inferior,
cada fila la definirı́amos como sigue:

for (int i=0; i<v.length;i++)


v[0]=new int [i];

8.4. Arreglos dinámicos


Los arreglos que pueden cambiar su tamaño manteniendo sus valores se denominan dinámicos. Los arreglos
o vectores dinámicos son dos: ArrayList y Vector. Estas dos estructuras son similares con la diferencia que
Vector cada vez que se incrementa de tamaño duplica su capacidad. Comienza en 10 luego duplica a 20,
después a 40 y ası́ sucesivamente. Veamos un ejemplo
p u b l i c c l a s s VectorDinamico {

{
private static final String letras [] =
{ ”a ” , ”b ” , ”c ” , ”d ” ,” e ” , ” f ” };

p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
V e c t o r <S t r i n g > v e c t o r = new V e c t o r <S t r i n g > ( ) ;
System . o u t . p r i n t l n ( v e c t o r ) ;
System . o u t . p r i n t f ( ” \ nTama ño : %d\ n C a p a c i d a d : %d \n ” ,
vector . size () , vector . capacity ( ) ) ;
/ / a ñ a d i r e l e m e n t o s
for ( String s : let ra s )
v e c t o r . add ( s ) ;
System . o u t . p r i n t l n ( v e c t o r ) ;

System . o u t . p r i n t f ( ” \ nTama ño : %d\ n C a p a c i d a d : %d \n ” ,


vector . size () , vector . capacity ( ) ) ;

/ / a ñ a d i r m ás e l e m e n t o s
for ( String s : let ra s )
v e c t o r . add ( s ) ;
System . o u t . p r i n t l n ( v e c t o r ) ;
System . o u t . p r i n t f ( ” \ nTama ño : %d\ n C a p a c i d a d : %d \n ” ,
vector . size () , vector . capacity ( ) ) ;
}
}

El resultado de la ejecución es como sigue:

[]

Tama~
no: 0
Capacidad: 10
170 Capı́tulo 8 Arreglos multidimensionales
[a, b, c, d, e, f]

Tama~
no: 6
Capacidad: 10
[a, b, c, d, e, f, a, b, c, d, e, f]

Tama~
no: 12
Capacidad: 20

Antes de insertar valores el vector está vacı́o. Se insertan 6 valores y tiene una capacidad de 10. Cuando se
inserta 6 valores adicionales el tamaño crece a 12 pero la capacidad se ha duplicado a 20.
Los métodos de Java para la colección Vector se describen en el cuadro

Método Descripción
add(E elemento) Agrega un elemento al vector.
clear() Borra la lista.
capacity() Muestra el espacio reservado para el vector.
addAll(int i,coleccion) Agrega toda una colección.
addAll(int i,coleccion) Agrega toda una colección en la
posición i.
remove(int i) Quita el elemento de la
posición i.
set(int i, E elemento) Cambia el elemento de la posición i.
isEmpty() Devuelve true si el vector esta vacı́o.
contains(E elemento) Devuelve true si el vector contiene el
elemento.
size() Devuelve el número de elementos.
firstElement() Devuelve el primer elemento.
lastElement() Devuelve el último elemento.

Los ArrayList administran la memoria como una lista y crecen dinámicamente. La capacidad es igual al
tamaño. Veamos un ejemplo de como puede crecer un ArrayList de tamaño.

import java . u t i l . ArrayList ;


p u b l i c c l a s s Crece {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
A r r a y L i s t v = new A r r a y L i s t ( 5 ) ;
f o r ( i n t i = 0 ; i < 1 0 ; i ++) {
v . add ( i ) ;
}
System . o u t . p r i n t l n ( v ) ;
}
}
8.4 Arreglos dinámicos 171
En este ejemplo hemos definido un vector del tipo ArrayList con tamaño 5. Porteriormente se insertan 10
valores. Cuando se llega al lı́mite de elementos el vector cambia dinámicamente de tamaño incrementando
el mismo en uno. Cabe notar que no es posible acceder a valores que exceden el tamaño del mismo.
Los métodos de Java para la colección ArrayList se describen en el cuadro

Método Descripción
add(E elemento) Agrega un elemento al vector.
clear() Borra la lista.
addAll(int i,coleccion) Agrega toda una colección.
addAll(int i,coleccion) Agrega toda una colección en la
posición i.
remove(int i) Quita el elemento de la
posición i.
set(int i, E elemento) Cambia el elemento de la posición i.
isEmpty() Devuelve true si el vector está vacı́o.
contains(E elemento) Devuelve true si el vector contiene el
elemento.
size() Devuelve el número de elementos.

Como ejemplo vamos a crear una lista con 5 números enteros, hacer crecer la lista, modificar y listar los
elementos de la misma.
import java . u t i l . ArrayList ;
public class EjemploArrayList {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
A r r a y L i s t v = new A r r a y L i s t ( 5 ) ;
f o r ( i n t i = 0 ; i < 1 0 ; i ++) {
v . add ( i ) ;
}
/ / imprimir los elementos
System . o u t . p r i n t l n ( v ) ;
/ / imprimir el elemento 5
System . o u t . p r i n t l n ( v . g e t ( 5 ) ) ;
/ / c a m b i a r e l e l e m e n t o 3 p o r 100
v . set (3 , 100);
/ / Eliminar el elemento 5
v . remove ( 5 ) ;
/ / i m p r i m i r e l tama ño
System . o u t . p r i n t l n ( v . s i z e ( ) ) ;
/ / recorrer la l i s t a
f o r ( i n t i = 0 ; i < v . s i z e ( ) ; i ++) {
System . o u t . p r i n t ( ” E l e m e n t o ” + i
+ ”=” + v . g e t ( i ) + ” ” ) ;
}

}
172 Capı́tulo 8 Arreglos multidimensionales
}

8.5. Arreglos de más de dos dimensiones


Para definir arreglos de cualquier dimensión se ponen más corchetes en c la definición. Por ejemplo para
definir un arreglo de tres dimensiones:

int [][][] v;

Para acceder a los valores del arreglo requeriremos tres ı́ndices. Por ejemplo para definir un cubo con caras
de tres por tres, hay que especificar 6 caras y el tamaño de cada cara:

int [6][3][3] v;

8.6. Ejemplo de aplicación


Resolvamos el ejemplo Buscando Pares de letras cuyo enunciado dice:
Juan ha decido estudiar las frecuencias de aparición de dos letras en un texto. Para esto dispone una cantidad
de texto. Para entender el problema realicemos un ejemplo corto . Su pongamos que el texto es casa cosa
amor roma saca en este texto podemos los siguientes pares de letras ca, as, sa, co, os, sa, am, mo, or, ro,
om, ma, sa, ac, ca. Analizando las frecuencias de cada par de letras es: ca = 2, sa = 3 el resto tiene una sola
aparición.

Entrada
Hay varios casos de prueba, cada uno en una lı́nea. Cada lı́nea es una cadena de longitud arbitraria. Y termina
cuando no hay más datos en la entrada.

Salida
La salida consiste del par de letras y la frecuencia separados por un espacio. Solo se listarán los pares de
letras que tengan una frecuencia mayor a 3. La salida estará ordenada en orden alfabético, primero por la
primera letra luego por la segunda. Después de cada caso de prueba imprima − − − − − − − − −−.
El ejemplo de datos de entrada es:

casa cosa amor roma saca aca


cocacola cocacola todoas a la cola
coco coco coco coco toma cocacola

El ejemplo de datos de salida es:

----------
co 5
la 4
----------
co 10
oc 5
----------
8.6 Ejemplo de aplicación 173
Para contar las combinaciones de letras primero recordamos que cada carácter es un número entre 0 y 255.
Inicialmente leemos una lı́nea con texto = en.nextLine().trim(). Usamos el método trim() para eliminar
los espacios al final de la lı́nea.
Para contar los pares de caracteres definimos una matriz de 255x255. La elección del tamaño es debido
a que existen 255 caracteres. Para contar los pares de caracteres los usamos como ı́ndice. El código
matriz[texto.charAt(i)][texto.charAt(i + 1)] + + incrementa en uno la ocurrencia de cada par de
caracteres.
Cuando recorremos la matriz el ı́ndice representa el caracter por esto cuando se imprimen colocamos (char)i.
EL programa resultante es:

import java . u t i l . Scanner ;


p u b l i c c l a s s BuscaPar {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r en =new S c a n n e r ( System . i n ) ;
i n t [ ] [ ] m a t r i z = new i n t [ 2 5 5 ] [ 2 5 5 ] ;
String texto =””;
w h i l e ( en . h a s N e x t L i n e ( ) ) {
t e x t o = en . n e x t L i n e ( ) . t r i m ( ) ;
m a t r i z = new i n t [ 2 5 5 ] [ 2 5 5 ] ;
f o r ( i n t i = 0 ; i <t e x t o . l e n g t h ( ) − 1 ; i ++)
i f ( t e x t o . c h a r A t ( i ) = = ’ ’ | | t e x t o . c h a r A t ( i +1)== ’ ’ )
continue ;
else
matriz [ t e x t o . charAt ( i ) ] [ t e x t o . charAt ( i +1)]++;
f o r ( i n t i = 0 ; i <255; i ++)
f o r ( i n t j = 0 ; j <255; j ++)
i f ( m a t r i z [ i ] [ j ] >3)
System . o u t . p r i n t l n ( ” ” + ( c h a r ) i + ( c h a r ) j +” ”+ m a t r i z [ i ] [ j ] ) ;
System . o u t . p r i n t l n (”−−−−−−−−−−”);
}
}
}
174 Capı́tulo 8 Arreglos multidimensionales

8.7. Ejercicios
8.7.1. JV-1322 Tablero de Ajedrez
Un Tablero de ajedrez de 8x8 en la notación del juego de ajedrez se etiqueta como sigue: Las filas se cuentan
de 1 a 8, y las columnas son identificadas por letras desde la a a la h. Una casilla se define con el código de
la columna y luego el de la fila, por ejemplo e4. El cuadro siguiente muestra la denominación de todas las
casillas del ajedrez:

Usted ha decidido numerar las casillas del tablero fila por fila desde el 1 al 64. La figura muestra la
numeración de las celdas:

Por ejemplo la casilla a1 ha sido numerada con 1 y la h8 con 64.


Su problema consiste en cambiar la notación, si es una notación de ajedrez que describe una casilla cambie
esta notación al numero correspondiente. Si es una casilla representada correspondiente cambie a la notación
del ajedrez.

Entrda
La entrada consiste de varios casos de prueba. La primera lı́nea contiene un número entero indicando la
cantidad de casos de prueba. Las siguientes lı́neas son los casos de prueba, con el número de celda, en una
de las dos notaciones.
8.7 Ejercicios 175

Salida
Para cada caso de prueba escriba una lúnea con la identificación de la casilla en la notación opuesta a la leı́da.

Ejemplos de entrada Ejemplos de salida


6 a1
1 b1
2 b4
26 3
c1 29
e4 64
h8
176 Capı́tulo 8 Arreglos multidimensionales

8.7.2. JV-1323 Matrices de Suma Máxima

Supongamos que tenemos la matriz cuadrada definida como:

2 3 -9 6
3 4 4 -5
5 5 6 3
-1 -1 -1 10

Eliminando la primera fila y la primera columna obtenemos la matriz

4 4 -5
5 6 3
-1 -1 10

Repitiendo el proceso obtenemos

6 3
-1 10

Este proceso termina cuando obtenemos una matriz de 2x2. Definimos la suma de la matriz como la suma
de todos sus elementos.

Entrada

La entrada consiste de varios casos de prueba, la primera lı́nea contiene el número de casos de prueba
n < 1000. Para cada caso de prueba la primera lı́nea contiene la dimensión de la matriz (m < 100), seguidos
de m2 elementos.

Salida

Su programa debe calcular cual de estas sub matrices tiene la suma máxima. La suma máxima debe
imprimirse en una lı́nea por cada caso de prueba.
8.7 Ejercicios 177

Ejemplos de entrada Ejemplos de salida


3 21
4 16
-2 3 -9 6 8
3 -4 4 -6
5 5 6 3
-1 -1 -1 10
2
1 4
5 6
3
-1 -1 -1
-1 2 2
-1 2 2
178 Capı́tulo 8 Arreglos multidimensionales

8.7.3. JV-1324 Matrices Simétricas


Se define una matriz cuadrada como simétrica si luego de transponer las filas y columnas obtenemos la misma
matriz, por ejemplo la matriz, siguiente es simétrica:

2 3
3 4

Entrada
La entrada consiste de varios casos de prueba, la primera lı́nea contiene el número de casos de prueba
n < 1000. Para cada caso de prueba la primera lı́nea contiene la dimensión de la matriz (2 ≤ m ≤ 100),
seguidos de m2 elementos.

Salida
Su programa debe determinar, para cada caso de prueba si la matriz es simétrica, mostrando la frase Simétrica
o No simétrica según sea el caso.

Ejemplos de entrada Ejemplos de salida


3 Simetrica
4 No simetrica
-2 3 -9 6 No simetrica
3 -4 4 -6
-9 4 6 3
6 -6 3 6
2
1 4
5 6
3
1 2 3
1 1 4
3 4 1
8.7 Ejercicios 179

8.7.4. JV-1327 Desproporción Diagonal


Se le ha encargado a usted calcular la desproporción diagonal de una matriz cuadrada. La desproporción
diagonal de una matriz cuadrada es la suma de los elementos de su diagonal principal menos la suma de los
elementos de la diagonal secundaria o colateral.
La figura muestra la diagonal principal.

La figura muestra la diagonal secundaria.

Entrada
La entrada de datos consiste de varios casos de prueba. El primer número tiene el número de casos de prueba.
Cada caso de prueba comienza con un número entero que tiene el número de filas de la matriz. Seguidamente
vienen N lı́neas, con la misma cantidad de caracteres, que contienen caracteres entre 0 y 9.

Salida
La salida es una lı́nea por caso de prueba que tiene diferencia de las dos diagonales.
180 Capı́tulo 8 Arreglos multidimensionales

Ejemplos de entrada Ejemplos de salida


4 1
3 -1
190 0
828 -24
373
4
9000
0120
0000
9000
1
6
10
7748297018
8395414567
7006199788
5446757413
2972498628
0508396790
9986085827
2386063041
5687189519
7729785238
8.7 Ejercicios 181

8.7.5. JV-1362 Rotar Cuadrados

Si tenemos un cuadrado de números enteros de tamaño n, el contorno conforma un cuadrado de n*n, al


interior existe un segundo contorno y ası́ sucesivamente. Por ejemplo si tenemos el arreglo bidimensional de
5*5

1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25

El contorno que llamaremos nivel 0 esta formado por el cuadrado

1 2 3 4 5
6 10
11 15
16 20
21 22 23 24 25

El contorno de nivel 1 es el cuadrado interior

7 8 9
12 14
17 18 19

y finalmente el cuadrado de nivel 2 está formado por un solo número el 13.


Rotar un contorno de nivel r significa rotar todos los elementos una posesión en sentido a las manecillas del
reloj. La rotación del cuadrado de nivel 1 es:

12 7 8
17 9
18 19 14

Entrada
La primera lı́nea de la entrada consiste de un número n que tiene la dimensión del arreglo. Luego siguen n
lı́neas con n valores enteros. Finalmente viene un número r indicando el cuadro de que nivel rotar.

Salida
Escriba en la salida la matriz con el nivel solicitado rotado.
182 Capı́tulo 8 Arreglos multidimensionales

Ejemplos de entrada Ejemplos de salida


5 1 2 3 4 5
1 2 3 4 5 6 12 7 8 10
6 7 8 9 10 11 17 13 9 15
11 12 13 14 15 16 18 19 14 20
16 17 18 19 20 21 22 23 24 25
21 22 23 24 25
1
8.7 Ejercicios 183

8.7.6. JV-1453 Invertir Diagonales


Dada una matriz cuadrada se le pide invertir (dar vuelta) la diagonal principal y la diagonal secundaria.
Por ejemplo dada la matriz de 3x3:

1 2 3
4 5 6
7 8 9

El resultado de invertir las dos diagonales es:

9 2 7
4 5 6
3 8 1

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea es el número de casos de prueba. Cada caso
de prueba consiste de una lı́nea con el numero n ≤ 30 que representa el tamaño de la matriz. Luego siguen
n lı́neas cada una con n números enteros separados por un espacio.

Salida
En la salida escriba en una lı́nea la palabra Çaso: el numero de caso de prueba. Luego escriba la matriz
2

resultante fila por fila, cada número separado por un espacio.

Ejemplos de entrada Ejemplos de salida


2 Caso: 0
3 9 2 7
1 2 3 4 5 6
4 5 6 3 8 1
7 8 9 Caso: 1
4 2 2 3 5
1 2 3 4 5 7 8 8
5 6 7 8 9 7 6 6
9 8 7 6 4 4 3 1
5 4 3 2
184 Capı́tulo 8 Arreglos multidimensionales

8.7.7. JV-1525 Matriz Flip


La entrada consiste de matrices cuadradas y el propósito es conocer que transformación sobre la matriz
uno, produjo la segunda matriz. Las transformaciones posibles son, flip horizontal, flip vertical o las
combinaciones.
Dada la matriz:
 
1 2 3
4 5 6
 
7 8 9

Flip vertical es:

 
7 8 9
4 5 6
 
1 2 3

Flip horizontal es:

 
3 2 1
6 5 4
 
9 8 7

Entrada
Los datos consisten de múltiples casos de prueba. La primera lı́nea de un caso de prueba consiste en el tamaño
N ≤ 30 de las dos matrices. Luego siguen N filas con N números enteros separados por un espacio, para
la primera matriz. A continuación siguen N filas con N números enteros separados por un espacio, para la
segunda matriz
La entrada termina cuando no hay más datos.

Salida
En la salida escriba según corresponda las transformaciones realizadas a la primera matriz para obtener la
segunda.

flip vertical
flip horizontal
flip vertical y flip horizontal
sin transformaciones
respuesta desconocida

”sin transformaciones”primera opción ”flip horizontal”segunda opción ”flip vertical”tercera opción ”flip
vertical y flip horizontalçuarta opción respuesta desconocida”última opción
8.7 Ejercicios 185

Ejemplos de entrada Ejemplos de salida


3 flip vertical
1 2 3
4 5 6
7 8 9
7 8 9
4 5 6
1 2 3
99.1.
Métodos, funciones, procedimientos y
clases
Definición
Volvamos a los conceptos presentados el capı́tulo 1 cuando se construyó el primer programa.

Figura 9.1 Estructura de un programa Java

9.2. Herramientas de desarrollo


En la imagen 9.1. vemos que se ha definido un método en el interior de una clase.
En java una clase es un archivo que tiene un nombre y la instrucción public class Nombre donde el nombre
del archivo debe igualar al nombre de la clase. Por ejemplo:

public class P1 {
instrucciones de la clase
}

Se pueden definir dos tipos de clases: La que puede ejecutarse en forma independiente y la que requiere que
otra clase la invoque. Para que una clase pueda ejecutarse se requiere que tenga la instrucción:

public static void main(String[] args) {


instrucciones
}

Estas instrucciones definen un método denominado main. El nombre main indica que este método se ejecutará
cuando se inicie el programa. Entre paréntesis están los argumentos que puede aceptar. En este caso un vector

187
188 Capı́tulo 9 Métodos, funciones, procedimientos y clases
de tipo cadena que se llama args. Estos parámetros son los que se pueden incluir en la lı́nea de comando al
ejecutar el programa. Por ejemplo:

c:> java P1 los parámetros deseados

En este caso recibirá un vector de tipo cadena con tres valores args[0]=los,args[1]=parámetros,args[2]=deseados.

Denominamos funciones a los métodos que devuelven un resultado o valor.


Denominamos procedimientos a los métodos que devuelven un valor

El método main es un procedimiento porque no devuelve un valor. En java solo nos referimos como métodos.
¿Donde se pueden escribir los métodos? Es posible codificar métodos en el programa principal, es decir, el
que incluye el método main, o en un archivo separado. Inicialmente veamos como se trabaja en programa
principal.

p u b l i c c l a s s Hola {

/ / a n t e s de un m étodo
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t ( ” h o l a ” ) ;
}
/ / d e s p u e s de un metodo
}

En el ejemplo vemos que se escribe antes o después de un método.

9.2.1. Sintaxis para escribir funciones

Para escribir una función se sigue la siguiente sintaxis:

tipoAdevolver NombreDeLaFunción (tipo NombeDelParametro, ....){


intrucciones
return valor
}

Por ejemplo se deseamos realizar una función que nos devuelva true cuando un número es par y f alse cuan-
do es impar, primero le damos un nombre adecuado, por ejemplo esPar. Luego decidimos que parámetros va
a recibir. En nuestro caso decidamos que pasaremos un número entero. El código será simplemente verificar
el último bit si es cero devolvemos true en otros casos false. Con estos datos el programa queda como:

boolean esPar(int n){


if ((n&1)==0)
return true;
else
return false;
}
9.3 Variables locales y globales 189
Para utilizar la función es suficiente con insertar el nombre definido como si fuera una instrucción parte del
lenguaje. Para ejemplificar este método construyamos un programa para sumar todos los números pares entre
entre uno y quince.
p u b l i c c l a s s F1 {

s t a t i c boolean esPar ( i n t n ){
i f ( ( n &1)==0)
return true ;
else
return false ;
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <16; i ++)
i f ( esPar ( i ))
suma+= i ;
System . o u t . p r i n t l n ( suma ) ;
}
}

Es necesario aclarar que el método main es de tipo static, por esto es necesario incluir la palabra static antes
de la definición de la función esPar. Esto significa que la dirección de memoria donde se cargará para su
ejecución es fija. No cambia de una ejecución a otra.

9.2.2. Sintaxis para escribir procedimientos


Para escribir procedimientos seguimos los mismos pasos que para escribir funciones. Lo que no se hace es
incluir un valor a retornar por los que no tiene un tipo.

void NombreDelProceimiento (tipo NombeDelOarametro, ....){


intrucciones
}

9.3. Variables locales y globales


Las variables pueden ser globales o locales. Las variables globales son visibles en todo el programa, las
variables locales solo en el método que se definió. Veamos el siguiente programa que contiene una función
para sumar los elementos de un vector:
p u b l i c c l a s s F2 {
s t a t i c i n t Sumar ( i n t [ ] v ) {
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <v . l e n g t h ; i ++)
suma+=v [ i ] ;
r e t u r n suma ;
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
190 Capı́tulo 9 Métodos, funciones, procedimientos y clases
i n t suma = 0 ;
in t [] v = {1 ,2 ,3 ,4 ,5 ,6};
suma=Sumar ( v ) ;
System . o u t . p r i n t l n ( suma ) ;
}
}

Se definieron dos variables con el nombre suma una en el método y otra en el programa principal. Cada una
se considera diferente y local, porque su visibilidad está dada solo en el método que se definió.
Para ejemplificar las variables globales definiremos una variable fuera de los métodos, esto hasta que su
visibilidad sea en todos. El programa siguiente muestra como hacer esto. Otra vez tuvimos que agregar la
palabra static, porque el programa principal es de este tipo.
p u b l i c c l a s s F3 {

s t a t i c i n t suma = 0 ;
s t a t i c v o i d Sumar ( i n t [ ] v ) {
f o r ( i n t i = 0 ; i <v . l e n g t h ; i ++)
suma+=v [ i ] ;
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
in t [] v = {1 ,2 ,3 ,4 ,5 ,6};
Sumar ( v ) ;
System . o u t . p r i n t l n ( suma ) ;
}
}

El problema que tienen las variables globales se puede resumir en la falta de control. Cualquier método puede
modificar su valor, y esto puede llevar a errores difı́ciles de detectar.

9.4. Definición de métodos en archivos separados


Para definir métodos en archivos separados del programa principal, primero se crea una clase de tipo public
y en el interior de la clase colocamos los métodos. Para ejemplificar esto hagamos que el método para sumar
los elementos de un vector sea una clase separada. El programa queda como sigue:
p u b l i c c l a s s Suma {
i n t Sumar ( i n t [ ] v ) {
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <v . l e n g t h ; i ++)
suma+=v [ i ] ;
r e t u r n suma ;
}
}

Para utilizar este programa debemos crear una instancia del mismo. En esta instancia serán accesibles los
métodos definidos. El código que debemos escribir es el siguiente:
9.4 Definición de métodos en archivos separados 191

p u b l i c c l a s s P2 {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Suma a1 =new Suma ( ) ;
in t [] v = {1 ,2 ,3 ,4 ,5 ,6};
i n t suma = a1 . Sumar ( v ) ;
System . o u t . p r i n t l n ( suma ) ;
}
}

En este código no fue necesario especificar static. Esto debido a que la dirección donde se encuentran las
instrucciones ejecutables se definió con la instrucción: Suma a1=new Suma(). Ahora se tiene la instancia a1
los métodos son accesibles por su nombre. En el ejemplo para acceder al método que suma los elementos del
vector escribimos a1.Sumar(v), como el resultado es un entero que lo asignamos a una variable para luego
mostrar en pantalla.
Para asignar valores iniciales cuando se define una clase, se debe crear un constructor. Un constructor se
define como un método que tiene el mismo nombre que el nombre de la clase.
Cambiemos el programa para hallar la suma de elementos de un vector. Creamos un constructor donde se
pasa el vector. Luego en los métodos ya no le pasamos el vector porque ya lo definimos con anterioridad. El
programa queda como sigue:

public class vect {


int [] v;
public vect ( int [] v) {
this .v = v;
}

i n t Sumar ( ) {
i n t suma = 0 ;
f o r ( i n t i = 0 ; i <v . l e n g t h ; i ++)
suma+=v [ i ] ;
r e t u r n suma ;
}
}

Es necesario explicar la palabra this. En la definición del constructor una alternativa es:

public vect(int[] v2) {


v = v2;
}

En el ejemplo pusimos el mismo nombre a la variable del parámetro v2 que a la variable de la clase v para
diferenciar a cual variable nos referimos se utiliza this, que indica que nos referimos a la variable definida en
la clase y no a la de los parámetros.
192 Capı́tulo 9 Métodos, funciones, procedimientos y clases
Es posible definir múltiples constructores con el mismo nombre, siempre y cuando los parámetros con los que
se invoque el método sean diferentes. Esto se llama sobrecarga de métodos. ¿Cuál constructor se utilizará?
El que iguale en nombre y parámetros en la llamada y definición del mismo.

9.5. Ejemplos de aplicación


Un mecanismo para contar los números primos Contando primos es el algoritmo denominado criba de
Eratóstenes. Este algoritmos indica que una criba de números primos se construye como sigue:

1. Se marcan los múltiplos de 2.


2. Luego los de 3, 5, 7, etc. sucesivamente.
3. Una vez completado el marcado los no marcados son los números primos.

Este algoritmo esta descrito en el capı́tulo de números primos con mayor detalle. Queremos contar todos los
números primos entre a y b. El programa inicial puede ser el siguiente:

p u b l i c c l a s s P1 {

public s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
int n = 10000000;
int [ ] c r i b a = new i n t [ n + 1 ] ;
// c o n s t r u c c i o n de l a c r i b a
for ( i n t i = 2 ; i <= n ; i ++)
/ / s i no s e marco e s numero p r i m o
i f ( c r i b a [ i ] == 0 ) {
/ / m a r c a r l o s m u l t i p l o s de i
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
i n t c o n t a r = 0 , a =0 , b = 1 0 0 0 ;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( c r i b a [ i ]==0)
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}

Los objetivos que queremos alcanzar son los siguientes:

1. Escribir una clase que pueda utilizarse en múltiples programas.


2. Construir métodos para contar los primos,
3. Construir métodos para construir la criba
4. Especificar el tamaño de la criba.
9.5 Ejemplos de aplicación 193
Estos objetivos los iremos desarrollando paso a paso. Primero creamos una clase que la denominaremos
Criba. Llevamos todo el código a esta clase.

public c l a s s P2 {
public P2 ( ) {
int n = 10000000;
int [ ] c r i b a = new i n t [ n + 1 ] ;
// c o n s t r u c c i o n de l a c r i b a
for ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
i n t c o n t a r = 0 , a =0 , b = 1 0 0 0 ;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( c r i b a [ i ]==0)
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}

Para invocar al mismo el programa principal usamos la instrucción Criba a=new Criba()
Este programa tiene el problema que solo sirve para un caso particular. Dividiremos el mismo en constructor
y un método para contar que devuelva el número de primos. Para hacer esto el vector Criba cambiamos para
que sea una variable global, para que este disponible en los diferentes métodos.

public c l a s s Criba {

int [] criba ;
public Criba ( ) {
i n t n = 10000000;
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
}
public i n t Contar ( i n t a , i n t b ) {
i n t contar = 0;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( c r i b a [ i ]==0)
194 Capı́tulo 9 Métodos, funciones, procedimientos y clases
c o n t a r ++;
}
return contar ;
}
}

Adicionalmente se han agregado dos parámetros para que la cuenta sea en el rango solicitado.
Para hacer más entendible el programa, crearemos un método esPrimo que devuelva verdadero o falso.

public c l a s s Criba {

int [] criba ;
public Criba ( ) {
i n t n = 10000000;
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
}
p u b l i c boolean esPrimo ( i n t i ) {
i f ( c r i b a [ i ]==0)
return true ;
else
return false ;
}
public i n t Contar ( i n t a , i n t b ) {
i n t contar = 0;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( esPrimo ( i ) )
c o n t a r ++;
}
return contar ;
}
}

Es más fácil de entender porque no es necesario conocer la implementación criba[i] == 0 para probar la
primalidad.
El programa desarrollado crea una criba de tamaño fijo de n = 10000000 y es deseable crear una criba
variable para reducir el tamaño de memoria utilizado y el tiempo de crear la criba. Para esto creamos un
constructor que reciba el parámetro n. Con esto tendremos dos constructores, uno cuando se especifica el
tamaño y otro para el valor máximo cuando no se especifica el tamaño:
9.5 Ejemplos de aplicación 195

public c l a s s Criba {

int [] criba ;
public Criba ( ) {
i n t n = 10000000;
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
}

public Criba ( i n t n ) {
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
}
p u b l i c boolean esPrimo ( i n t i ) {
i f ( c r i b a [ i ]==0)
return true ;
else
return false ;
}
public i n t Contar ( i n t a , i n t b ) {
i n t contar = 0;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( esPrimo ( i ) )
c o n t a r ++;
}
return contar ;
}
}

Hemos creado un nuevo problema, código repetido. Ahora crearemos un método crearCriba donde se
hallarán los valores y eliminará el código repetido.

public c l a s s Criba {
196 Capı́tulo 9 Métodos, funciones, procedimientos y clases

int [] criba ;
public Criba ( ) {
crearCriba (10000000);
}

public Criba ( i n t n ) {
crearCriba (n );
}
public void cr ear Cr i ba ( i n t n ) {
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( c r i b a [ i ] == 0 ) {
f o r ( i n t j = i + i ; j <= n ; j = j + i ) {
criba [ j ] = 1;
}
}
}
p u b l i c boolean esPrimo ( i n t i ) {
i f ( c r i b a [ i ]==0)
return true ;
else
return false ;
}
public i n t Contar ( i n t a , i n t b ) {
i n t contar = 0;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( esPrimo ( i ) )
c o n t a r ++;
}
return contar ;
}
}

Ahora trabajemos en el código del método crearCriba. El código criba[i] == 0 se cambia por esP rimo(i).
Luego creamos un método marcarMultiplos que marque los múltiplos de i. Con esto llegamos a la versión
final:

public c l a s s Criba {

int [] criba ;
public Criba ( ) {
crearCriba (10000000);
}
9.6 Cuando hay que usar métodos y clases 197

public Criba ( i n t n ) {
crearCriba (n );
}
public void cr ear Cr i ba ( i n t n ) {
c r i b a = new i n t [ n + 1 ] ;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( esPrimo ( i ) )
marcarMultiplos ( i ) ;
}
public void marcarMultiplos ( i n t i ) {
f o r ( i n t j = i + i ; j <= c r i b a . l e n g t h ; j = j + i ) {
criba [ j ] = 1;
}
}
p u b l i c boolean esPrimo ( i n t i ) {
i f ( c r i b a [ i ]==0)
return true ;
else
return false ;
}
public i n t Contar ( i n t a , i n t b ) {
i n t contar = 0;
f o r ( i n t i = a ; i <= b ; i ++) {
i f ( esPrimo ( i ) )
c o n t a r ++;
}
return contar ;
}
}

9.6. Cuando hay que usar métodos y clases


Es de interés dividir el programa en métodos para:

1. Hacerlo más fácil de modificar. Cada vez que se realizan cambios en los programas se hace más difı́ciles
de entender.
2. Hacerlo más fácil de entender. Es más fácil de entender un parte de un programa que todo el conjunto,
esto facilita el mantenimiento.
3. Mejorar su diseño.
4. Eliminar el código duplicado. Eliminar la redundancia, las correcciones se hacen en un solo lugar.
5. Encontrar errores. Al reorganizar un programa se pueden apreciar todas las suposiciones que se hicieron.
198 Capı́tulo 9 Métodos, funciones, procedimientos y clases
6. Para programar más rápido. Utilizando métodos ya desarrollados, se desarrollará más rápidamente un
nuevo programa.
7. Distribuir un programa. Cuando distribuimos un programa para que pueda ser incluido en otras aplica-
ciones, se distribuyen las clases con las instrucciones de uso.

Estos cambios se hacen creando métodos, funciones, procedimientos y clases.

9.7. Ejercicios
Codificar los siguientes ejercicios tomando en cuenta todos los aspectos mencionados en el capı́tulo. Eliminar
todo el código repetido. Crear una clase matriz que implemente los siguientes métodos:

1. Crear una clase matriz que implemente los siguientes métodos:


a) Definir una matriz bidimensional
b) Transponer la matriz
c) Sumar con otra matriz pasada como parámetro.
d) Multiplicar con otra matriz pasada como parámetro.
e) Imprimir la matriz resultante por filas
f ) Copiar la matriz a otra pasada como parámetro.
2. Crear una clase que permita ordenar una matriz bidimensional. Se deben implementar dos algoritmos
de ordenación, los métodos a implementar son:
a) Definir una matriz bidimensional
b) Ordenar por la primera columna.
c) Ordenar por una columna especificada.
10
10.1.
Números Primos
Introducción
La teorı́a de los números primos ha tenido un desarrollo muy intenso con las aplicaciones de criptografı́a. En
esta sección le dedicamos un especial interés debido a las complejidades que lleva éste cuando se trabaja con
números grandes.

10.2. Definición
Se define como primo el número entero positivo que es divisible solamente por 1 o por si mismo. Los
primeros números primos son 2, 3, 5, 7, 11, 19... Como se ve el único número primo par es el número 2.
El número 1 cumple la definición sin embargo no es un número primo. Es que cada muy claro con el
teorema fundamental de la aritmética que indica que todo número puede representarse en forma única como
el producto de un conjunto de factores que son números primos.
Tomando un ejemplo, consideremos el número 20, su descomposición en factores primos seria 20 = 22 ∗ 5.
La descomposición 20 = 4 ∗ 5 no es válida porque 4 no es un número primo. Si consideramos el número 1
como primo se puede escribir múltiples descomposiciones con todas las potencias de 1 dado que 1 elevado a
cualquier potencia es 1.
El número 1 no es parte de los números primos, sin embargo, algunos autores lo incluyen en ciertos problemas
especı́ficos.

10.3. Test de primalidad


Para muchas aplicaciones es necesario realizar un test de primalidad que significa verificar si el número es
primo o no. Esto se puede realizar por divisiones sucesivas sin embargo no es un método muy adecuado
cuando se trata de números grandes.

Es posible probar la primalidad de un número n en un tiempo proporcional a O( n) con el siguiente código
de divisiones sucesivas:

j = 2;
while (j * j <= n) {
if ((n%j)==0)
break; //no primo
j++;
}

Ahora escribamos un programa que cuente los números primos hasta 10,000,000 y midamos el tiempo de
proceso.
/∗∗
∗ S o l u c i o n a l problema Contando primos

199
200 Capı́tulo 10 Números Primos
∗ por D i v i s i o n e s Sucesivas

∗∗/
import java . u t i l . Scanner ;

public class DivisionesSucesivas {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

i n t contar = 0;
i n t n = 100000000;
f o r ( i n t i = 2 ; i <= n ; i ++) {
i f ( EsPrimo ( i ) )
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}

s t a t i c b o o l e a n EsPrimo ( i n t numero ) {
f o r ( i n t j = 2 ; j ∗ j <= numero ; j ++) {
i f ( ( numero %j ) = = 0 )
r e t u r n f a l s e ; / / no p r i m o
}
r e t u r n t r u e ; / / primo
}
}

Como habrá podido probar este proceso es muy lento y por lo tanto es necesario mejorarlo. Analizando los
números primos vemos que todos los números que terminan en 2, 4, 6, 8, 0 son divisibles por 2 y por lo tanto
no son primos. Como ya sabe que el 2 es primo solo es necesario recorrer los números impares reduciendo la
prueba a la mitad de los números. También podemos probar primeramente los divisibles por 3, y los divisibles
por 5.
El programa corregido es el siguiente:

/∗∗
∗ S o l u c i o n a l problema Contando primos
∗ p o r D i v i s i o n e s S u c e s i v a s Mmejorado

∗ @author J o r g e T e r a n
∗∗/
import java . u t i l . Scanner ;

public class DivisionesSucesivasMejorado {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t contar = 4;
i n t n = 100000000;
10.4 Generación de primos 201

Múltiplo de 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2 x x x x x x
3 x x x x
5 x x
Marcados x x x x x x x x
Cuadro 10.1 Ejemplo de una criba de Eratóstenes

f o r ( i n t i = 1 1 ; i <= n ; i +=2) {
i f ( EsPrimo ( i ) )
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}

s t a t i c b o o l e a n EsPrimo ( i n t numero ) {
i f ( numero %3==0)
return false ;
i f ( numero %5==0)
return false ;
f o r ( i n t j = 7 ; j ∗ j <= numero ; j = j + 2 ) {
i f ( ( numero %j ) = = 0 )
r e t u r n f a l s e ; / / no p r i m o
}
r e t u r n t r u e ; / / primo
}
}

10.4. Generación de primos


El problema que representa el método presentado de divisiones sucesivas es la cantidad de divisiones que hay
que realizar. Como ya vimos el tiempo que toma una división es mucho mayor al de la suma. Para convertir
éstas divisiones en suma de números, la forma más fácil es a través del método denominado la criba de
Eratóstenes.
Una criba es un tamiz que permite discriminar alguna propiedad, en nuestro caso la propiedad de primalidad.
Eratóstenes de Ciernes inventó este algoritmo 200 años ac., y hasta ahora es uno de los algoritmos más
eficiente para generar números primos.
La criba de Eratóstenes se construye a partir de los múltiplos de los números como sigue

1. Se marcan los múltiplos de 2.


2. Luego los de 3, 5, 7, etc. sucesivamente.
3. Una vez completado el marcado los no marcados son los números primos.
202 Capı́tulo 10 Números Primos
Como se observa en el cuadro 10.1 se marcan en la primera pasada los múltiplos de 2, la segunda pasada los
múltiplos de 3 y ası́ sucesivamente. Al terminar el proceso, los dı́gitos no marcados son los números primos.
Codificando este programa el tiempo de proceso se reduce al eliminar las divisiones.

/∗∗
∗ S o l u c i o n a l problema Contando primos
∗ u t i l i z a n d o una c r i b a

∗ @author J o r g e T e r a n
∗∗/
import java . u t i l . Scanner ;

public c l a s s Criba {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n = 100000000;
B o o l e a n [ ] c r i b a = new B o o l e a n [ n + 1 ] ;
Criba ( criba , n ) ;
i n t contar = 0;
f o r ( i n t i = 2 ; i <= n ; i ++) {
i f ( criba [ i ]!= true )
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}

static void Criba ( s h o r t [ ] p , i n t n ){


int i = 2, j = 0, a , b;
// c o n s t r u c c i o n de l a c r i b a
for ( i = 2 ; i <= n ; i ++)
i f ( p [ i ] == 0 ) {
f o r ( j = i + i ; j <= n ; j = j + i ) {
p[ j ] = true ;
}
}
}
}

Los números primos que pueden calcularse a través de este método se consideran números pequeños. El
número máximo depende de la memoria de la máquina, la implementación realizada y el lenguaje de
programación. Vea que si almacena solo los números impares puede almacenar el doble de números en
la criba.
10.5 Criba de Atkin 203

10.5. Criba de Atkin


Conjuntamente Arthur Oliver Lonsdale Atkin conjuntamente con Daniel J. Bernstein han desarrollado
la criba de Atkin. Este trabajo se basa en la teorı́a: cribas utilizando formas cuadráticas binarias, que
publicaron el año 2003. Este algoritmo es más complejo que la criba de Eratóstenes y puede considerarse
una optimización del mismo.
El algoritmo no se explicará en éste texto pero se basa en las formas cuadráticas 4x2 + y 2 y 3x2 + y 2 . Se
deja para el lector el análisis del mismo. El programa java que usa el la criba de Atkin es:

import java . u t i l . ArrayList ;


p u b l i c c l a s s Atkin {
/∗∗
∗ S o l u c i o n a l problema Contando primos
∗ u t i l i z a n d o una c r i b a de A t k i n

∗ @author J o r g e T e r a n
∗∗/
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

i n t l i m i t e =100000000;

b o o l e a n [ ] e s P r i m o = new b o o l e a n [ l i m i t e + 1 ] ;
i n t s q r t = ( i n t ) Math . s q r t ( l i m i t e ) ;

f o r ( i n t x = 1 ; x <= s q r t ; x ++)
f o r ( i n t y = 1 ; y <= s q r t ; y ++)
{
int n = 4 ∗ x ∗ x + y ∗ y;
i f ( n <= l i m i t e && ( n % 12 == 1 | | n % 12 == 5 ) )
esPrimo [ ( i n t ) n ] ˆ= t r u e ;

n = 3 ∗ x ∗ x + y ∗ y;
i f ( n <= l i m i t e && n % 12 == 7 )
esPrimo [ n ] ˆ= t r u e ;

n = 3 ∗ x ∗ x − y ∗ y;
i f ( x > y && n <= l i m i t e && n % 12 == 1 1 )
esPrimo [ n ] ˆ= t r u e ;
}

f o r ( i n t n = 5 ; n <= s q r t ; n ++)
i f ( esPrimo [ n ] )
{
int s = n ∗ n;
f o r ( i n t k = s ; k <= l i m i t e ; k += s )
204 Capı́tulo 10 Números Primos
esPrimo [ k ] = f a l s e ;
}

i n t c o n t a r =2;
f o r ( i n t n = 5 ; n <= l i m i t e ; n +=2)
i f ( esPrimo [ n ] )
c o n t a r ++;
System . o u t . p r i n t l n ( c o n t a r ) ;

}
}

10.6. Criba Lineal


Se han escrito algoritmos que se denominan lineales para construir todos los números primos menores a un
n. Estos se llaman lineales porque no marcan más de una ves un número para determinar si es primo.
Un de los textos más conocidos es el de David Gries y Jayadev Misrra publicado en 1978. Posteriormente se
han escrito varios algoritmos que también son lineales como es el caso de Jonathan P. Sorenson, Knuth, entre
otros.
Presentamos un algoritmo que ejemplifica esto. Sin embargo las implementaciones toman un tiempo que no
es tan práctico.

import java . u t i l . Arrays ;


import java . u t i l . Vector ;

public c l a s s Pruebas {
// criba lineal
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t N= 1 0 0 0 0 0 0 0 ;
i n t l p [ ] = new i n t [N+ 1 ] ;
V e c t o r <I n t e g e r > p r = new V e c t o r <I n t e g e r > ( ) ;
f o r ( i n t i = 2 ; i < N ; i ++) {
i f ( lp [ i ]==0){
l p [ i ]= i ;
p r . add ( i ) ;
}
f o r ( i n t j = 0 ; ( j <p r . s i z e ( ) ) && ( p r . g e t ( j )<= l p [ i ])&&( i ∗ p r . g e t ( j )<=N ) ; j ++)
lp [ i ∗ pr . get ( j )]= pr . get ( j ) ;
}
/ / System . o u t . p r i n t l n ( p r ) ;
/ / System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( l p ) ) ;
10.7 Aplicaciones de la Criba 205

Hasta Cantidad de Primos


10 4
100 25
1.000 168
10.000 1.229
100.000 9.592
1.000.000 78.498
10.000.000 664.579
100.000.000 5.761.455
1.000.000.000 50.847.534
10.000.000.000 455.052.511
100.000.000.000 4.118.054.813
1.000.000.000.000 37.607.912.018
10.000.000.000.000 346.065.536.839
100.000.000.000.000 3.204.941.750.802
1.000.000.000.000.000 29.844.570.422.669
10.000.000.000.000.000 279.238.341.033.925
100.000.000.000.000.000 2.623.557.157.654.233
1.000.000.000.000.000.000 24.739.954.287.740.860
10.000.000.000.000.000.000 234.057.667.276.344.607
100.000.000.000.000.000.000 2.220.819.602.560.918.840
1.000.000.000.000.000.000.000 21.127.269.486.018.731.928
10.000.000.000.000.000.000.000 201.467.286.689.315.906.290
100,000,000,000,000,000,000,000 1,925,320,391,606,803,968,923
1,000,000,000,000,000,000,000,000 18,435,599,767,349,200,867,866
Cuadro 10.2 Cantidad de primos por rango conocidos a la fecha

}
}

.
La página http://primes.utm.edu/ proporciona un acceso importante a la teorı́a de números primos y publica
la cantidad de primos por rango conocidos a la fecha, están detallados en el cuadro 10.2.

10.7. Aplicaciones de la Criba


La criba no solo es útil para generar los números primos, también con pequeños cambios obtener más
información para otros procesos.
Si queremos contar cuantos factores primos tiene un número es suficiente cambiar el vector booleano por un
entero que cuente los cuales números tienen como múltiplo este número. EL código muestra esto:

s t a t i c void Criba ( s h o r t [ ] p , i n t n ){
206 Capı́tulo 10 Números Primos
int i = 2, j = 0, a , b;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i = 2 ; i <= n ; i ++)
i f ( p [ i ] == 0 ) {
f o r ( j = i + i ; j <= n ; j = j + i ) {
p [ j ] = ++;
}
}
}
}

Como verá no cuenta los factores repetidos, pero usted puede realizar algunos cambios para incluir estos
factores en caso necesario.
Otra variante es guardar por ejemplo el último factor, esto con la finalidad posterior de hallar los divisores de
un número. Vea la variante en el código siguiente:

s t a t i c void Criba ( s h o r t [ ] p , i n t n ){
int i = 2, j = 0, a , b;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i = 2 ; i <= n ; i ++)
i f ( p [ i ] == 0 ) {
f o r ( j = i + i ; j <= n ; j = j + i ) {
p[ j ] = i ;
}
}
}
}

10.8. Factorización
La factorización de un número consiste en descomponer el mismo en sus divisores. Se demuestra que todos
los factores son números primos. Analice que si uno de sus factores es un número compuesto también podrı́a
descomponerse en factores. Para una demostración más formal vean [Thomas H. Cormen and Rivest 1990].
La forma de expresar éstos factores es como sigue: an1 1 an2 2 an3 3 .... Los factores primos de 168 son 2, 2, 2, 3, 7
que se expresa como 23 · 3 · 7 = 168. El siguiente código muestra el proceso de factorización de números.
Hay que indicar que este proceso es aplicable a números pequeños.

/∗∗
∗ C a l c u l a l o s f a c t o r e s p r i m o s de N
∗∗/

public class Factores {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
l o n g N = Long . p a r s e L o n g ( a r g s [ 0 ] ) ;
10.9 Prueba de la primalidad 207

/ / long N = 168;
System . o u t
. p r i n t ( ” Los f a c t o r e s p r i m o s de : ”
+ N + ” son : ” ) ;

f o r ( l o n g i = 2 ; i <= N / i ; i ++) {

w h i l e (N % i == 0 ) {
System . o u t . p r i n t ( i + ” ” ) ;
N = N / i;
}
}

/ / S i e l p r i m e r f a c t o r o c u r r e una s o l a v e z
i f (N > 1 )
System . o u t . p r i n t l n (N ) ;
else
System . o u t . p r i n t l n ( ) ;
}
}

Utilizando la variante que se desarrolló para hallar el último factor podemos mejorar el tiempo de proceso de
la factorización dividiendo solo entre los que son factores del número. El código es como sigue:

/ / n numero a f a c t o r i z a r
w h i l e ( n >0) {
i f ( c r i b a [ n ]== n ) {
System . o u t . p r i n t l n ( n ) ;
break ;
} / / es primo
else {
System . o u t . p r i n t l n ( n ) ;
n=n / c r i b a [ n ] ;
}
}

10.9. Prueba de la primalidad


La prueba de primalidad es simple para números primos pequeños. Se puede hacer por divisiones sucesivas,
aún cuando, es mejor utilizar una criba con números precalculados. Las dificultades en el cálculo de números
primos radica cuando los números a tratar son grandes. Esto hace necesario buscar otros métodos para
determinar la primalidad de los mismos. Analizamos algunos métodos sin ahondar en las demostraciones
que están ampliamente desarrolladas en la teorı́a de números y el área de criptografı́a.
208 Capı́tulo 10 Números Primos

10.10. Teorema de Fermat


Este teorema indica que todo número primo cumple la relación:

an−1 ≡ 1 mód n ∀a ≤ n (10.10.1)

por lo que parece suficiente realizar este cálculo para determinar la primalidad.
Desafortunadamente solo sirve para conocer si un número es compuesto dado que para algunos números
primos no se cumple. Estos números se denominan números de Carmichael . Veamos por ejemplo el número
compuesto 561:

2561−1 mód 561 = 1

Algunos números que no cumplen este teorema son el 561, 1105, 1729. Estos números se denominan pseudo
primos.
Para determinar si un número es compuesto podemos comenzar un algoritmo con a = 2 y el momento que
no se cumpla el teorema podemos decir que el número es compuesto. En otros casos se indica que el número
es probablemente primo.
La utilidad de este método se ve en el tratamiento de números grandes donde la facilidad de la exponenciación
módulo es mucho más eficiente que la división.

10.11. Prueba de Miller - Rabin


Este algoritmo tiene su nombre por los autores del mismo. Esta prueba provee un algoritmo probabı́listico
eficiente aprovechando algunas caracterı́sticas de las congruencias.
Dado un entero n impar, hagamos n = 2r s + 1 con s impar. Escogemos un número entero aleatoriamente
j
y sea éste 1 ≤ a ≤ n. Si as ≡ 1 mód (n) o a2 s ≡ −1 mód (n) para algún j, que esté en el rango
0 ≤ j ≤ r − 1, se dice que n pasa la prueba. Un número primo pasa la prueba para todo a.
Para utilizar este concepto se escoge un número aleatorio a. Si pasa la prueba, probamos con otro número
aleatorio. Si no pasa la prueba decimos que el número es compuesto. Se ha probado que la probabilidad de
que, un número a pase la prueba, es de 14 por lo que hay que realizar varias pruebas para tener más certeza.
El propósito de comentar este algoritmo es el hecho que la implementación de Java ya lo incluye en sus
métodos para trabajar con números grandes por lo que, no desarrollaremos el algoritmo en extenso.
Con la finalidad de comparar con los otros algoritmos se incluye el programa.
/∗∗
∗ E j e m p l o de una i m p l e m e n t a c i ó n de a l g o r i t m o
∗ de M i l l e r − R a b i n

∗ @author J o r g e T e r a n
∗∗/
import java . u t i l . Scanner ;

public class MillerRabin {

p u b l i c s t a t i c i n t n = 100000000;
10.11 Prueba de Miller - Rabin 209

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t contar = 0;
f o r ( i n t i = 2 ; i <= n ; i ++)
i f ( M i l l e r R a b i n ( i ) == t r u e )
c o n t a r ++;
System . o u t . p r i n t l n ( c o n t a r ) ;
}

public s t a t i c boolean Miller Rabin ( i n t n ) {


i f ( n <= 1 )
return false ;
e l s e i f ( n == 2 )
return true ;
else i f ( iteracion Miller Rabin (2 , n)
&& ( n <= 7 | | i t e r a c i o n M i l l e r R a b i n ( 7 , n ) )
&& ( n <= 61 | | i t e r a c i o n M i l l e r R a b i n ( 6 1 , n ) ) )
return true ;
else
return false ;
}

p r i v a t e s t a t i c boolean i t e r a c i o n M i l l e r R a b i n ( i n t a , i n t n ) {
i n t d = n − 1;
i n t s = Integer . numberOfTrailingZeros ( d ) ;
d >>= s ;
i n t a elevado a = exponente modulo ( a , d , n ) ;
i f ( a e l e v a d o a == 1 )
return true ;
f o r ( i n t i = 0 ; i < s − 1 ; i ++) {
i f ( a e l e v a d o a == n − 1 )
return true ;
a elevado a = exponente modulo ( a elevado a , 2 , n ) ;
}
i f ( a e l e v a d o a == n − 1 )
return true ;
return false ;
}

p r i v a t e s t a t i c i n t e x p o n e n t e m o d u l o ( i n t b a s e , i n t p o t e n c i a , i n t modulo ) {
long r e s u l t a d o = 1;
f o r ( i n t i = 3 1 ; i >= 0 ; i −−) {
r e s u l t a d o = ( r e s u l t a d o ∗ r e s u l t a d o ) % modulo ;
i f ( ( p o t e n c i a & ( 1 << i ) ) ! = 0 ) {
210 Capı́tulo 10 Números Primos
r e s u l t a d o = ( r e s u l t a d o ∗ b a s e ) % modulo ;
}
}
return ( int ) resultado ;
}

10.12. Números Grandes


La aritmética de números grandes es esencial en varios campos tales como la criptografı́a. Las bibliotecas de
Java incluyen una variedad de funciones para el tratamiento de éstos números. Cabe hacer notar que cuando
se trata con números pequeños estas librerı́as son más lentas que los métodos descritos con anterioridad.

Constructores
BigInteger(String val) Permite construir un número grande a partir de una cadena
BigInteger(int largo, int certeza, Random r) Permite construir
un número probablemente primo de la longitud de bits especificada, la certeza especificada y
random es un generador de números aleatorios.
Métodos
add(BigInteger val) Devuelve this + val
compareTo(BigInteger val) Compara this con val y devuelve
−1, 0, 1 para los casos que sean <, =, > respectivamente
intValue() Devuelve el valor entero de this
gcd(BigInteger val) Halla el máximo común divisor entre this y val
isProbablePrime(int certeza) .- Prueba si el número es probablemente primo con el grado de certeza
especificado. Devuelve falso si el número es compuesto.
mod(BigInteger m) Devuelve el valor this mód m
modInverse(BigInteger m) Retorna el valor
this−1 mód m
modPow(BigInteger exponente, BigInteger m) Devuelve
thisexponente mód m
multiply(BigInteger val) Devuelve this ∗ val
pow(int exponente) Devuelve thisexponente
probablePrime(int longitud, Random rnd) Devuelve un número probablemente primo. El grado de
certeza es 100. Y rnd es un generador de números aleatorios.
remainder(BigInteger val) Devuelve this %val
subtract(BigInteger val) Devuelve this − val
toString(int radix) Devuelve una cadena en la base especificada.
10.12 Números Grandes 211

10.12.1. Ejemplo
Hallar un número primo aleatorio de 512 Bits. La función probablePrime utiliza para ésto las pruebas de
Miller-Rabin y Lucas-Lehmer.

/∗∗
∗ P r o g r a m a p a r a h a l l a r un numero
∗ p r o b a b l e p r i m o de 512 b i t s
∗ @author J o r g e T e r a n

∗/

i m p o r t j a v a . math . B i g I n t e g e r ;
i m p o r t j a v a . u t i l . Random ;

p u b l i c c l a s s PrimoGrande {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
Random r n d = new Random ( 0 ) ;
/ / g e n e r a r un p r o b a b l e p r i m o de 512 b i t s
/ / con una p r o b a b i l i d a d de que s e a c o m p u e s t o
/ / de 1 en 2 e l e v a d o a l 0 0 .
B i g I n t e g e r prime =
B i g I n t e g e r . probablePrime (512 , rnd ) ;

System . o u t . p r i n t l n ( p r i m e ) ;
}
}

Un ejemplo de ejecución del programa es:

11768683740856488545342184179370880934722814668913767795511
29768394354858651998578795085800129797731017311099676317309
3591148833987835653326488053279071057

Veamos otro ejemplo. Supongamos que queremos desarrollar el algoritmo de encriptación de llave pública
RSA . El algoritmo consiste en hallar dos números d, e que cumplan la propiedades siguientes: c =
me mod n y m = cd mod n. Para resolver esto procedemos como sigue:

1. Escoger dos números primos p y q con p 6= q

BigInteger p = new BigInteger(longitud/2, 100, r);


BigInteger q = new BigInteger(longitud/2, 100, r);

2. Calcular n = pq

n = p.multiply(q);

3. Escoger e que sea relativamete primo a (p-1)(q-1)


212 Capı́tulo 10 Números Primos
BigInteger m = (p.subtract(BigInteger.ONE))
.multiply(q.subtract(BigInteger.ONE));
e = new BigInteger("3");
while(m.gcd(e).intValue() > 1)
e = e.add(new BigInteger("2"));

4. Calcular el multiplicativo inverso de e

d = e.modInverse(m);

5. Publicar la clave pública como el par p = (e, n)


6. Publicar la clave privada como el par s = (d, n)

Veamos el código completo. Aquı́ hay que hacer notar que en Java existen dos clases para hallar números
aleatoreos, Random y SecureRandom. La diferencia entre ambas está en que la clase SecureRandom hace la
semilla inicial de generación de números menos predecibles. Esto significa que de una ejecución a otra la
secuencia aleatoria no va a ser la misma.
/∗∗
∗ Programa p a r a p r o b a r e l c i f r a d o
∗ de c l a v e p u b l i c a
∗ @author J o r g e T e r a n
∗ @param a r g s n i n g u n o
∗/

i m p o r t j a v a . math . B i g I n t e g e r ;
i m p o r t j a v a . s e c u r i t y . SecureRandom ;

c l a s s Rsa
{
private BigInteger n , d , e ;
p u b l i c Rsa ( i n t l o n g i t u d ) {
SecureRandom r = new SecureRandom ( ) ;
B i g I n t e g e r p = new B i g I n t e g e r ( l o n g i t u d / 2 , 1 0 0 , r ) ;
B i g I n t e g e r q = new B i g I n t e g e r ( l o n g i t u d / 2 , 1 0 0 , r ) ;
n = p . multiply (q );
B i g I n t e g e r m = ( p . s u b t r a c t ( B i g I n t e g e r . ONE ) )
. m u l t i p l y ( q . s u b t r a c t ( B i g I n t e g e r . ONE ) ) ;
e = new B i g I n t e g e r ( ” 3 ” ) ;
w h i l e (m. gcd ( e ) . i n t V a l u e ( ) > 1 )
e = e . add ( new B i g I n t e g e r ( ” 2 ” ) ) ;
d = e . m o d I n v e r s e (m ) ;
}
p u b l i c B i g I n t e g e r c i f r a r ( B i g I n t e g e r mensaje )
{
r e t u r n m e n s a j e . modPow ( e , n ) ;
}
10.13 Lecturas para Profundizar 213

p u b l i c B i g I n t e g e r d e c i f r a r ( B i g I n t e g e r mensaje )
{
r e t u r n m e n s a j e . modPow ( d , n ) ;
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Rsa a = new Rsa ( 1 0 0 ) ; / / H a l l a r d y e de 100 B i t s
B i g I n t e g e r m e n s a j e =new B i g I n t e g e r ( ” 6 4 6 5 6 6 6 7 6 8 6 9 7 0 ” ) ;
B i g I n t e g e r c i f r a d o = a . c i f r a r ( mensaje ) ;
BigInteger limpio= a . d e c i f r a r ( cifrado ) ;
System . o u t . p r i n t l n ( m e n s a j e ) ;
System . o u t . p r i n t l n ( c i f r a d o ) ;
System . o u t . p r i n t l n ( l i m p i o ) ;
}
}

10.13. Lecturas para Profundizar


Para profundizar el tratamiento de los números primos se puede leer Factorization and Primality Testing
[Bressoud 1988], Primes and Programming [Giblin 1993], y un texto con muchas curiosidades Prime
Numbers - The Most Mysterious Figures in Math [Wells 2005].
En la (http://mathworld.wolfram.com/search/) enciclopedia de matemáticas Mathworld se pueden encontrar
otras pruebas de primalidad tanto probabilı́sticas, como determinı́sticas de las que mencionamos: Curva
elı́ptica, test de primalidad de Adleman-Pomerance-Rumely, AKS de Agragual, Lucas-Lehmer, Ward’s y
otros.
Muchos de éstos métodos no se han visto efectivos para la prueba de primalidad cuando se trata de números
grandes.

10.14. Ejemplos de aplicación


Algunos problemas se pueden resolver directamente con los algoritmos mostrados, sin embargo, otros
requieren una técnica un poco diferente. En algunos casos es mejor construir los posibles resultados que
probar todos los números.
Analicemos el problema denominado primos redondos. Este problema se describe en la parte de ejercicios.
La definición indica que un número primo es redondos cuando al quitar sucesivamente los dı́gitos de
derecha a izquierda los resultados que obtenemos también son números primos. Por ejemplo del número
719, quitamos el 9 y tenemos 71 que es primo. Luego quitamos el 1 obteniendo 7 que también es primo.
Para resolver este problema tenemos algunas dificultades, primero que se pide que se evalúen todos los
números hasta 101 0 y en nuestra criba de Eratostenes solo se puede evaluar hasta 107 .
En este caso utilizaremos la técnica de construir los números que se piden, en lugar de, partir de un número
primo y verificar si cumple la propiedad pedida.
Los números primos terminan en 1, 3, 7, 9 excepto el número 2. Con esta propiedad podemos generar
números de la siguiente forma:
214 Capı́tulo 10 Números Primos
Inicialmente comenzamos con los primos de un dı́gito. Tomamos el número 2 y agregamos las posibles
terminaciones, para obtener 21, 23, 27, 29. Verificamos para el primer valor si es primo o no lo es. En este
caso el 23 es primo, por lo tanto repetimos el proceso para obtener 231, 233, 237, 239. Verificamos cada uno
de ellos para saber si es primo, en cuyo caso repetimos el proceso para los que sean primos.
Posteriormente seguimos con los número 2, 3, 5, 7, 9 hasta terminar de procesar todos los números.

/∗∗
∗ S o l u c i o n a l p r o b l e m a numeros r e d o n d o s
∗ s o n l o s que t r u n c a n d o e l u l t i m o d i g i t o t a m b i e n
∗ son primos
∗ @author J o r g e T e r a n
∗∗/
import java . u t i l . Scanner ;

p u b l i c c l a s s NumerosRedondos {
s t a t i c boolean esPrimo ( long p ){
/ / System . o u t . p r i n t l n ( p ) ;

f o r ( i n t i = 2 ; ( l o n g ) ( i ∗ i )<=p ; i ++)
i f ( p %i ==0)
return false ;
return true ;
}

p u b l i c s t a t i c i n t n = 1000;
p u b l i c s t a t i c l o n g [ ] p = new l o n g [ n + 1 ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t i = 2 , j = 0 , k =0 , a =0 , b =0 , a1 , b1 , c o n t a r ;
/ / g e n e r a r l o s p o s i b l e s numeros pimos T e r m i n a n
/ / en 1 , 3 , 7 , 9
/ / l o s p r i m o s de un d i g i t o e l uno f u e i n c l u i d o p o r d e f .
p [0]=1;
p [1]=3;
p [2]=7;
p [3]=9;
p [4]=2;
a =0; b =4; c o n t a r =4;
f o r ( i = 0 ; i <9; i ++){
f o r ( j =a ; j <=b ; j ++){
f o r ( k = 0 ; k <4; k ++){
i f ( e s P r i m o ( p [ j ] ∗10+ p [ k ] ) ) {
c o n t a r ++;
p [ c o n t a r ] = p [ j ] ∗10+ p [ k ] ;
}
10.14 Ejemplos de aplicación 215

}
}
a=b + 1 ;
b= c o n t a r ;
/ / System . o u t . p r i n t l n ( a +” ”+ b ) ;
}
System . o u t . p r i n t l n ( p [ c o n t a r ] ) ;
c o n t a r =0;
for ( long z : p )
i f ( z >0){
/ / System . o u t . p r i n t ( z +” ” ) ;
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}

Analicemos otro ejercicio, esta vez veamos el ejercicio casi primos descritos en la sección de ejercicios.
Un número se denomina casi primo si no es primo y tiene un solo factor. Una primera aproximación puede
ser tomar todos los números no primos y hallar sus factores. Contamos cuantos factores se tienen y si es uno
hacemos la cuenta. Con este algoritmo encontramos una solución muy lenta para los valores extremos.

/∗∗
∗ Solucion l e n t a a l problema CasiPrimo

∗∗/
import java . u t i l . Scanner ;

p u b l i c c l a s s CasiPrimoSlow {

p u b l i c s t a t i c i n t n = 10000000;
p u b l i c s t a t i c i n t [ ] p = new i n t [ n + 1 ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t contar = 0;
int i = 2, j = 0, a , b;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i = 2 ; i ∗ i <= n ; i = i + 1 )
/ / f u n c i o n a en c r i b a
i f ( p [ i ] == 0 ) {
f o r ( j = i + i ; j <= n ; j = j + i ) {
p [ j ] = 1;
}
}
S c a n n e r i n = new S c a n n e r ( System . i n ) ;
216 Capı́tulo 10 Números Primos
while ( in . hasNext ( ) ) {
a = in . nextInt ( ) ;
b = in . nextInt ( ) ;
contar = 0;
f o r ( i = a ; i <= b ; i ++) {
i f ( p [ i ] != 0) {
if ( Factores ( i )) {
c o n t a r ++;
}
}
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}

static b o o l e a n F a c t o r e s ( i n t N) {
int s = 0;
for ( i n t i = 2 ; i <= N / i ; i ++) {
if ( p [ i ] == 0 ) {
i f (N % i == 0 ) {
s ++;
}
w h i l e (N % i == 0 ) {
N = N / i;
}
}
}
i f (N ! = 1 ) {
s ++;
}
r e t u r n ( s <2);
}
}

Para resolver este ejercicio eficientemente recurriremos a una modificación en la criba de Eratostenes.
Recordemos que lo que hacemos es marcar los números compuestos. Cada vez que marcamos un número es
porque este tiene un factor, entonces cambiemos la lógica. Sumemos 1 en lugar de colocar en 1. Ahora la
criba almacena el número de factores, con lo que la cuenta se hace trivial. El código muestra la solución.

/∗∗
∗ Solucion f i n a l a l problema Casi primos

∗∗/
import java . u t i l . Scanner ;
10.14 Ejemplos de aplicación 217

p u b l i c c l a s s CasiPrimo {

p u b l i c s t a t i c i n t n = 10000000;
p u b l i c s t a t i c i n t [ ] p = new i n t [ n + 1 ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t contar = 0;
int i = 2, j = 0, a , b;
/ / c o n s t r u c c i o n de l a c r i b a
f o r ( i = 2 ; i <= n ; i ++)
i f ( p [ i ] == 0 ) {
f o r ( j = i + i ; j <= n ; j = j + i ) {
p [ j ] ++;
}
}
S c a n n e r i n = new S c a n n e r ( System . i n ) ;
while ( in . hasNext ( ) ) {
contar = 0;
a = in . nextInt ( ) ;
b = in . nextInt ( ) ;
f o r ( i = a ; i <= b ; i ++) {
i f ( p [ i ] == 1 ) {
c o n t a r ++;
}
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}
}

Como ve una pequeña variación a la criba puede proporcionar resultados adicionales muy interesantes.
218 Capı́tulo 10 Números Primos

10.15. Ejercicios
10.15.1. JV-1033: Contando Primos
Los números primos son aquellos que son solo divisibles por si mismo o el uno. La lista de los primeros
números primos es 2, 3, 5, 7, 9, 11, 13, 17, 19
Dados 0 ≤ a, b ≤ 107 contar cuantos primos hay en ese rango.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea contiene el número de casos de prueba.
Las lı́neas siguientes corresponden a los casos de prueba, cada caso de prueba contiene dos enteros a, b, La
entrada termina cuando no hay más datos.

Salida
Para cada caso de prueba su programa debe mostrar, en la salida, una lı́nea con el número de primos existente
entre a y b inclusive.

Ejemplos de entrada Ejemplos de salida


4 168
2 1000 68906
100000 1000000 586081
1000000 10000000 664579
2 10000000
10.15 Ejercicios 219

10.15.2. JV-1080: Raı́z digital Prima

La raı́z digital de un número se halla adicionando todos lo dı́gitos en un número. Si el número resultante
tiene más de un dı́gito, el proceso es repetido hasta tener un dı́gito.
Tu trabajo en este problema es calcular una variación de la raı́z digital? una raı́z digital prima. El proceso
de adición descrito arriba para cuando solo queda un dı́gito, pero podemos parar en el número original, o en
cualquier número intermedio (formado por la adición) que sea número primo.
Si el proceso continúa y el resultado es un dı́gito que no es primo, entonces el número original no tiene raı́z
digital prima.
Un entero mayor que uno es llamado número primo si tiene solo dos divisores, el uno y si mismo. Por
ejemplo:

Los primeros seis primos son 2, 3, 5, 7, 11, y 13.


El número 6 tiene cuatro divisores: 6, 3, 2, y 1. Por eso 6 no es primo.
Advertencia: en número 1 no es primo.

Ejemplos:

1 Este no es un número primo, ası́ que 1 no tiene raı́z digital prima.


3 Este es un número primo, ası́ que la raı́z digital prima de 3 es 3.
4 Este no es un número primo, ası́ que 4 no tiene raı́z digital prima.
11 Este es un número primo, ası́ que la raı́z digital prima de 11 es 11.
642 Este no es un número primo, ası́ que sumando 6 + 4 + 2 = 12. Este no es un número primo, ası́ que
sumando 1 + 2 = 3. Este si es un número primo, ası́ que la raı́z digital prima de 642 es 3. 128 Este no
es un número primo, ası́ que sumando 1 + 2 + 8 = 11. Este es un número primo, ası́ que la raı́z digital
prima de 128 es 11.
886 Este no es un número primo, ası́ que sumando 8 + 8 + 6 = 22. Este no es un número primo, ası́ que
sumando 2 + 2 = 4. Este no es un número primo, ası́ que 886 no tiene raı́z digital prima.

Entrada

La primera lı́nea de la entrada contendrá el número de casos de prueba. Cada caso de prueba viene en una
lı́nea y contendrá un entero en cada lı́nea en el rango de 0 a 999999 inclusive.

Salida

Si el número ingresado tiene raı́z digital prima, entonces se debe desplegar el valor original y el valor de la
raı́z digital prima, caso contrario se despliega el valor original seguido por la palabra none, los valores deben
estar alineados con una justificación derecha de 7 espacios, como se muestra en el ejemplo de salida.
220 Capı́tulo 10 Números Primos

Ejemplos de entrada Ejemplos de salida


5 134 none
134 11 11
11 642567 3
642567 912367 912367
912367 9234567 none
9234567
10.15 Ejercicios 221

10.15.3. JV-1301: Números Feos


Los números feos son números cuyos únicos factores primos son 2, 3, o 5.
La secuencia:
1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ....
Muestra los primeros 11 números feos. Por convención se incluye el 1.
Esto equivale a encontrar los números que se pueden formar por 2a 3b 5c . Escriba un programa que encuentre
e imprima el número 1500.

Entrada
No existen datos de entrada en este problema.

Salida
La salida debe consistir de una lı́nea reemplazando número con el número calculado.

Ejemplos de entrada
No existen datos de entrada en este problema.

Ejemplos de salida
El numero feo 1500 es ......
222 Capı́tulo 10 Números Primos

10.15.4. JV-1366: Factorizar un Número Grande


Se tiene un número compuesto 2 ≤ n ≤ 231 . Hallar sus factores primos. Como ejemplo los factores primos
de 36 son 2, 2, 3, 3.
Si el número es primo por ejemplo el número 7 se imprimirá un solo factor el 7.

Entrada
La entrada consiste de varios casos de prueba. En cada lı́nea se tiene un número entero positivo compuesto.
La entrada termina cuando no hay más datos.

Salida
Para cada caso de prueba escriba los factores primos del número en una sola lı́nea separados por un espacio.

Ejemplos de entrada Ejemplos de salida


36 2 2 3 3
1504703107 1504703107
600851475143 71 839 1471 6857
60085147514356 2 2 83 179 683 1480319
9223372036854775805 5 23 53301701 1504703107
9223372036854775807 7 7 73 127 337 92737 649657
10.15 Ejercicios 223

10.15.5. JV-1337: Primos Redondos


El número n = 719 tiene una propiedad muy interesante. Siendo n un número primo es posible quitar
continuamente sus dı́gitos de derecha a izquierda en forma continua y cada uno de los números remanentes
también es primo. En cada etapa : 719, 71, y 7 también son números primos.
Tome en cuenta que los números 1, 2, 3, 5 y el 7 se consideran redondos. Note que el número 1 no es primo,
pero en este ejercicio se lo considerar como tal. Por este motivo el 11 se considera un número redondo.
El tiempo de proceso para este programa es de 1 segundo.
Para resolver el problema en un tiempo adecuado tome en cuenta que los números primos terminan en 1,3,7
y 9. Con esta idea construya todos los números que cumplan la definición dada.

Entrada
No hay datos de entrada

Salida
Su programa de contar cuanto números primos redondos existen entre 1 y 1010

Ejemplos de entrada Ejemplos de salida


140
224 Capı́tulo 10 Números Primos

10.15.6. JV-1347: Pares de Ruth-Aaron


Este nombre fue dado por Carl Pomerance en honor a Babe Ruth y Hank Aron estrellas de las ligas del
baisballl estadounidense. El récord que tenı́a Ruth era de 714 entradas, que superada por Aron el 8 de abril
de 1974, con un total de 715 entradas. Uno de los estudiantes de Pomerance advirtió que la suma de los
factores primos de 714 y 715 eran iguales. Veamos:
Si factorizamos ambos números obtenemos las siguientes descomposiciones:

714 = 2 × 3 × 7 × 17

715 = 5 × 11 × 13

Si nos fijamos en las sumas de ambas factorizaciones tenemos que:

2 + 3 + 7 + 17 = 5 + 11 + 13 = 29

A los números que satisfacen esta propiedad, es decir, a los pares consecutivos cuya descomposición
en factores primos tienen la misma suma, Pomerance les llamó pares de Ruth-Aaron. Y claro está, los
ordenadores son fantásticos. Pomerance descubrió que entre los números menores a 20,000 hay 26 pares
de Ruth-Aaron. El mayor en éste rango lo forman el 18,490 y el 18,491.
Si solo consideramos los factores primos diferentes los primeros pares de Ruth-Aaron son: (5, 6), (24, 25),
(49, 50), (77, 78), (104, 105), (153, 154), (369, 370), (492, 493), (714, 715), (1682, 1683) y (2107, 2108)
Si contamos los factores primos repetidos, por ejemplo 8 = 2 × 2 × 2 y 9 = 3 × 3 con 2 + 2 + 2+ = 3 + 3
los primeros pares de Ruth-Aaron son: (5, 6), (8, 9), (15, 16), (77, 78), (125, 126), (714, 715), (948, 949) y
(1330, 1331).
La intersección de las dos listas es: (5, 6), (77, 78), (714, 715).

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea de un caso de prueba contiene un entero N
representando el número de casos de prueba (1 ≤ N ≤ 100). En las siguientes lı́neas de cada caso de prueba
contienen dos enteros a, b (2 ≤ a, b ≤ 1000000),

Salida
Para cada caso de prueba su programa debe mostrar en la salida los pares de Ruth-Aron, entre a y b, que
pertenecen a la intersección de ambas listas, la que incluye factores primos repetidos y la que no incluye
factores primos repetidos.

Ejemplos de entrada Ejemplos de salida


1 5 6
2 1331 77 78
714 715
10.15 Ejercicios 225

10.15.7. JV-1636: Casi Primos


Los números casi primos son números no-primos que son divisibles por solo un número primo. En este
problema tu trabajo es escribir un programa que encuentre la cantidad de números casi primos dentro de
cierto rango.
No se consideran casi primos los números primos.
Veamos un ejemplo, si tomamos el rango entre 2 y 10 solo hay 3 números Casi primos:

El 4 solo divisible por el 2, por lo que es casi primo


El 6 es divisible por 2 y 3, por lo que no es casi primo
El 8 solo divisible por el 2, por lo que es casi primo
El 9 solo divisible por el 3, por lo que es casi primo
El 10 es divisible por 2 y 5, por lo que no es casi primo
Los números 2, 3, 5, 7 no son casi primo son primos.

Entrada
La primera lı́nea de la entrada contiene un entero N (N ≤ 600) que indica cuantos conjuntos de datos siguen.
Cada una de las siguientes N lı́neas son los conjuntos de entrada. Cada conjunto contiene dos número enteros
low y high (0 ≤ low ≤ high ≤ 107 ).

Salida
Por cada lı́nea de entrada excepto la primera usted debe producir una lı́nea de salida. Esta lı́nea contendrá un
entero, que indica cuantos números casi primos hay dentro del rango(incluyendo) low...high.

Ejemplos de entrada Ejemplos de salida


6 3
2 10 1
10 20 10
2 100 236
2 1000000 241
500000 5000000 555
2 10000000
11
11.1.
Números de Fibonacci
Introducción
Fibonacci fue uno del los más reconocidos matemáticos de la edad media. Su nombre completo fue Leonardo
de Pisa. Nació en Pisa, Italia, un importante pueblo comercial en esa época en 1175. Su padre se llamaba
Guglielmo Bonacci, y como se utilizaba fi para decir hijo de, quedo el nombre de Fibonacci.
Leonardo viajó extensivamente por la costa del mediterráneo donde aprendió la forma en la que los comer-
ciantes hindúes y árabes utilizaban la aritmética. Popularizó los conocimientos de los árabes introduciendo
los números arábicos al mundo occidental.
La sucesión 1, 1, 2, 3, 5, 8.... lo hizo famoso y lleva su nombre.
Esta sucesión se arma sumando los dos números anteriores, para obtener el siguiente número. Matemática-
mente fn = fn−1 + fn−2 . Estas ecuaciones se denominan ecuaciones recurrentes o de recurrencia.

11.2. Programando la secuencia


Para hallar esta secuencia inicialmente la programafmos utilizando la fórmula, sumar un valor con los
dos anteriores, Primero mostramos el primer valor de la serie, luego iteramos hasta hallar los valores que
deseamos. El programa resultante:

import java . u t i l . ∗ ;
p u b l i c c l a s s Fib {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t y =1;
i n t n =0;
i n t x =0;
i n t f i b =0;
w h i l e ( n <6){
f i b =x+y ;
System . o u t . p r i n t ( f i b + ” ” ) ;
x=y ;
y= f i b ;
n ++;
}
}
}

227
228 Capı́tulo 11 Números de Fibonacci
Como hallar la solución de la ecuación fn = fn−1 + fn−2 puede ver en [Pommier 1993]. No es intención
de este texto trabajar en matemática discreta, por lo que solo presentamos la solución:
√ √
1 1+ 5 n 1− 5 n
f (n) = √ [( ) −( ) ]
5 2 2
El programa
import java . u t i l . ∗ ;
p u b l i c c l a s s Fib2 {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
double n =6;
d o u b l e r 5 = Math . s q r t ( 5 . 0 ) ;
d o u b l e f i b = ( Math . pow ( ( 1 + r 5 ) / 2 , n)−Math . pow((1 − r 5 ) / 2 , n ) ) / r 5 ;
System . o u t . p r i n t l n ( f i b ) ;
}
}

Resuelve el problema de hallar la secuencia, claro está, que con un error de precisión. Para el ejemplo del
programa el Fibonacci 6 da 8,000000000000002.
Si dividimos los valores consecutivos de la serie, cada uno con el elemento anterior, obtenemos

1/1 1
2/1 2
3/2 1.5
5/3 1.66
8/5 1.6
13/8 1.625
21/13 1.618

Si continuamos son la serie, veremos que el cociente converge a 1,6180 este valor se denomina número
aúreo o de oro porque aparece en muchos aspectos de la naturaleza. Este número es:

1+ 5
phi = )
2
Otra forma más eficiente de hallar los números de la secuencia de Fibonacci es utilizando matrices.
Escribamos el parte del programa que hicimos para hallar la secuencia

(1) while (n<6){


(2) fib=x+y;
(3) System.out.print(fib+ " ");
(4) x=y;
(5) y=fib;
(6) n++;

En este código se ve en (2) que f ib = x + y y en (5) y = f ib, remplazando f ib queda;

x=y;
11.2 Programando la secuencia 229
y=x+y;

Estas ecuaciones las podemos escribir en forma matricial:


! ! !
x 0 1 x
= (11.2.1)
y 1 1 y

Denominando estas matrices A, B tenemos

!
x
A= (11.2.2)
y

!
0 1
B= (11.2.3)
1 1

!2 ! ! !
0 1 0 1 0 1 1 1
= = (11.2.4)
1 1 1 1 1 1 1 2

!4 ! ! !
1 1 1 1 1 1 2 3
= = (11.2.5)
1 2 1 2 1 2 3 5

!
a b
(11.2.6)
b a+b

Calculando B · B se tiene que:

a = a · a + b · by

b = b · a + a · b + b · b.

Calculando A · B se tiene que:

x=a·x+b·y

y = b · x + a · y + b · y.

Programando esta solución con el algoritmo de multiplicación rápida tenemos:


import java . u t i l . ∗ ;
p u b l i c c l a s s Fib {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t n =12 , a =0 , b =1 , x =0 , y =1 , temp = 0 ;
while ( n !=0){
i f ( n %2==0){
temp = a ∗ a + b ∗b ;
b = b∗ a + a ∗b + b∗b ;
230 Capı́tulo 11 Números de Fibonacci
a=temp ;
n=n / 2 ;
}
else {
temp = a ∗ x + b ∗y ;
y = b∗x + a ∗y + b∗y ;
x=temp ;
n=n −1;
}
}
System . o u t . p r i n t l n ( ” x= ”+ x ) ;
}
}

11.3. Fibonacci y el triángulo de Pascal


El triángulo de Pascal se construye colocando un 1 arriba y dos números por debajo formando un triángulo.
Cada número es la suma de los dos números que tiene arriba.

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

Este triángulo tiene varias propiedades interesantes, en la primera de las diagonales los valores son todos 1.
En la segunda diagonal los valores son la secuencia 1, 2, 3, 4, 5.... Una caracterı́stica es que es simétrico con
relación al centro.
La suma de las filas tiene la propiedad de ser las potencias de 2.

1 suma = 20 = 1
1 1 suma = 21 = 2
1 2 1 suma = 22 = 4
1 3 3 1 suma = 23 = 8
1 4 6 4 1 suma = 24 = 16
1 5 10 10 5 1 suma = 25 = 32

Para mostrar la relación que tiene el triángulo de Pascal con los números de Fibonacci escribamos el mismo
triángulo con todos los números alineados a la izquierda:
11.4 Propiedades 231

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

Si sumamos la primera diagonal tenemos 1. La segunda diagonal también es 1. La tercera diagonal da


1 + 1 = 2. La cuarta 1 + 3 + 1 = 5. Otra vez la secuencia de Fibonacci. Esto se puede ver que se da
debido a que cada número es la suma de dos números anteriores.

11.4. Propiedades
Presentamos algunas propiedades de esta serie:

1. Hay muchas formas en las que aparecen los números de Fibonacci, por ejemplo en combinatoria, sea N
tomado de K:
!
n n!
=
k (n − k)!k!

!
0
=1
0
!
1
=1
0
! !
2 2
+ =2
0 1
! !
3 3
+ =3
0 1
! ! !
4 4 3
+ + =5
0 1 2
! ! !
5 5 4
+ + =8
0 1 2
! ! ! !
6 6 5 4
+ + + = 13
0 1 2 3
! ! ! !
7 7 6 5
+ + + = 21
0 1 2 3
2. Suma de los términos f1 + f2 + f3 + f4 ...... + fn = fn+2 + 1
3. Suma de los términos impares f1 + f3 + f3 + f5 ...... + f2n−1 = f2n .
4. Suma de los términos pares f2 + f4 + f6 + f6 ...... + f2n = f2n−1 + 1
5. Suma de los cuadrados f12 + f22 + f32 + f42 ...... + fn2 = fn fn+1
2 2
6. Diferencia de cuadrados fn+1 − fn−1 = f2n
232 Capı́tulo 11 Números de Fibonacci
7. Dados dos números Fibonacci fa , fb el máximo común divisor de ambos números es el número Fibo-
nacci fmcd(fa ,fb ) . Por ejemplo f8 = 21, f12 = 144 entonces el mcd(8, 12) = 4. El mcd(21, 144) = 3
y el f3 = 4
8. Si a es divisible por b entonces fa es divisible por fb .
9. El número fa es par si y solo si a es múltiplo de 3.

Otras variantes de la serie de Fibonacci se obtienen cambiando los valores iniciales, la cantidad de términos
a sumar, etc. Para comprender mejor esta serie se recomienda hacer los siguientes programas:

1. Escribir un programa para verificar las propiedades mostradas.


2. Escribir un programa para generar la serie 2, 5, 7, 12, 19
3. Escribir un programa para generar la serie 1, 3, 4, 7, 11, 18
4. Escribir un programa para generar la serie −1, −5, −6, −11, −17

11.5. Números de Pisano


En teorı́a de números, el perı́odo Pisano n, que se escribe como π(n), es el perı́odo con el cual la secuencia
de los números de Fibonacci tomada módulo n se repite. Existen diferentes perı́odos, dependiendo del valor
n que se tome para hallar el módulo. El cuadro 11.1 muestra los resultados de aplicar el módulo n a la
secuencia de Fibonacci.

n π(n) periodos
1 1 0
2 3 011
3 8 0112 0221
4 6 011231
5 20 01123 03314 04432 02241
6 24 011235213415 055431453251
7 16 01123516 06654261
8 12 011235 055271
9 24 011235843718 088764156281
10 60 011235831459437 077415617853819
099875279651673 033695493257291
Cuadro 11.1 Los primeros 10 números de Pisano

De donde sabemos que la propiedad de los números Fibonacci es que el último dı́gito se repite cada 60
números, cuando aplicamos el módulo 10.
Con esto es suficiente hallar los primeros 60 últimos dı́gitos de los números de Fibonacci, usando un
algoritmo lineal y luego hallando el módulo 60 sabemos que dı́gito será el último.
Como podemos hallar el perı́odo π(n). Para esto simplemente hallamos los restos después de hallar el módulo
n. Luego verificamos donde comienza a repetirse la secuencia.
Para más información sobre esta secuencia puede leer el libro [Fisher 2010] y la página web [Knott 2010].
11.6 Los números de Lucas 233

11.6. Los números de Lucas


Los números de Lucas es una secuencia de enteros nombrada en honor al matemático François Édouard
Anatole Lucas (1842?91) que estudio los números de Fibonacci y otras secuencias complementarias.
Los números de Lucas se obtienen comenzando con 2, 1 en lugar de 0, 1 como se forma sumando los dos
números anteriores al igual que los números de Fibonacci. Los primeros términos de la serie son:

2, 1, 3, 4, 7, 11, . . .

Una propiedad interesante es que el número de oro



1+ 5
phi = ) = 1, 6180339887498948
2
permite hallar todos los números de la secuencia en forma directa. Tomando las potencias de número phi
phi1 ? 1.618
phi2 2.618
obtenemos directamente: phi3 4,23 Como ve redondeando se obtienen directamente los números de
phi4 6.85
phi5 11.09
Lucas.
234 Capı́tulo 11 Números de Fibonacci

11.7. Ejercicios
11.7.1. JV-1142: Fibonacci Modificado
Una serie puede ser definida de la siguiente manera:
Dado los elementos n y (n + 1), el (n + 2) elemento puede ser calculado por la siguiente relación:

Tn+2 = (Tn+1 )2 + Tn

Entonces, si los dos primeros elementos de la serie fueran 0 y 1:

el tercer elemento es 12 + 0 = 1
el cuarto elemento es 12 + 1 = 2
el quinto elemento es 22 + 1 = 5
. . . , etc.

Dado tres enteros A, B y N, tal que los primeros dos elementos de la serie son A y B respectivamente, calcule
el elemento número N de la serie.

Entrada
Una única lı́nea conteniendo tres enteros A, B ( 0 ≤ A, B ≤ 2) y N (3 ≤ N ≤ 20).

Salida
Un entero. Este entero es el elemento Número N de la serie.

Ejemplos de entrada Ejemplos de salida


0 1 5 5
11.7 Ejercicios 235

11.7.2. JV-1342: Fibonacci Modular


Los números Fibonacci son: ( 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, . . .) y se define con la siguiente recurencia:

 0
 amp; si n = 0,
F (n) = 1 amp; si n = 1,

 F (n − 1) + F (n − 2) amp; en otros casos.

Escriba un programa que calcule F (n) mód m.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea contiene el número de casos de prueba. Cada
caso de prueba viene en una una lı́nea y contiene dos números (2 ≤ m ≤ 10000), (0 ≤ n ≤ 90).

Salida
Por cada caso de prueba imprime F (n) mód m.

Ejemplos de entrada Ejemplos de salida


2 5
11 7 5
11 6
236 Capı́tulo 11 Números de Fibonacci

11.7.3. JV-1343: Patrones de Fibonacci


Veamos el último dı́gito de cada número de Fibonacci.

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, . . .

La pregunta que nos hacemos es si hay un patrón para estos dı́gitos

0, 1, 1, 2, 3, 5, 8, 3, 1, 4, 5, 9, 4, 3, 7, 0, 7, . . .

La respuesta es si, después de 60 números de vuelven a repetir, en forma cı́clica.


En un caso más general podemos hallar cada uno de los números módulo 2, 3, 4, . . .
Por ejemplo si hallamos los valores después de hallar al módulo 2 vemos que los restos son 0, 1, 1, 0, 1, 1, 0, . . ..
La respuesta es que cada 3 números se vuelve a repetir la serie.

Entrada
La entrada consiste de varias lı́neas. En cada lı́nea hay un número 1 ≤ a ≤ 10000, que utilizaremos para
hallar el módulo. La entrada termina cuando no hay más datos.

Salida
Por cada lı́nea de entrada en la salida se imprimirá un número entero que indica cada cuantos números se
repite la secuencia resultante de hallar el módulo a sobre los números de Fibonacci.

Ejemplos de entrada Ejemplos de salida


2 3
3 8
4 6
5 20
10 60
1000 1500
11.7 Ejercicios 237

11.7.4. JV-1457: Base Fibonacci


La forma de interpretar un número decimal en binario es ver el número como potencias de 2. Por ejemplo el
número 10=1010 es decir 23 + 21 .
También puede interpretarse como una suma de números de Fibonacci. Recuerde que los números de
Fibonacci se obtienen sumando los dos números anteriores. El 13 es la suma de 8 y 5. Los primeros números
de ésta secuencia son:

1, 2, 3, 5, 8, 13, 21, . . . ,

En la base Fibonacci representamos un número como la suma de números de Fibonacci. El número 10 se


representa como 8 + 2. La representación en esta base es 10010. La tabla siguiente muestra como se obtiene
este número:
Número Fibonacci 8 5 3 2 1
Dı́gito base Fibonacci 1 0 0 1 0
Existe otra representación que es utilizar los números 5,3,2 que también suman 10.
La representación correcta es comenzar con el Fibonacci, más grande menor al número buscado. Escoger
5,3,2 es incorrecto. La impresión se hace en el mismo orden.

Entrada
La entrada consiste de un número entero positivo, menor a 1012 , termina cuando no hay más datos.

Salida
Para cada caso de prueba, en la salida escriba el número en su representación en base Fibonacci.

Ejemplos de entrada Ejemplos de salida


10 10010
12 10101
15 100010
238 Capı́tulo 11 Números de Fibonacci

11.7.5. JV-1549: Fibonacci y Pitágoras


El famoso teorema de Pitágoras indica que la hipotenusa c de un triángulo rectángulo de lados a, b se puede
calcular con la formula c2 = a2 + b2 . Este teorema se ha hecho tan famoso que muchos números se han
denominados pitagóricos si pueden hallarse como la suma de dos números enteros elevados al cuadrado.
Que tiene que ver Fibonacci con Pitágoras?. Bien primero recordemos que la sucesión de Fibonacci se define
matemáticamente con la ecuación f (n) = f (n − 1) + f (n − 2). Los primeros números de la serie son:
0,1,1,2,3,5,8,13..etc.
Tratando de ver si se pueden encontrar números pitagóricos formados por exclusivamente números de
Fibonacci, se ve lo siguiente: El primer número pitagórico que se puede hallar es el número 1 = 02 + 12 ,
el segundo es el 2 = 12+ 12 , ası́ se forma una secuencia de números que son de Fibonacci y pitagóricos,
simultáneamente. Los primeros elementos de esta serie son: 1,2,5,13.... todos formados exclusivamente con
números de Fibonacci.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea indica cuantos casos de prueba existen. Cada
caso de prueba contiene un número menor o igual a 43, que indica la posición en la secuencia de los números
Fibonacci pitagóricos queremos hallar.

Salida
Imprima en la salida el número Fibonacci - Pitagorico indicado.

Ejemplos de entrada Ejemplos de salida


0 1 5 5
12
12.1.
Algoritmos de búsqueda y clasificación
Introducción
La búsqueda y la clasificación tienen una serie de aplicaciones que serán descritas en las siguientes secciones.
Se procederá, de métodos generales a casos particulares que reducen el tiempo de proceso. El proceso
de especificación se realiza como se mostró en el capı́tulo anterior especificando las invariantes, pre y
post condiciones sin entrar en la demostración de éstas. Los libros de Horstman [Cay S. Horstman 2005]
proporcionan una buena descripción de las librerı́as de Java. Parte de este texto proviene del libro del mismo
autor [Pommier 1993] donde se desarrolla ésta temática con una profundidad diferente.
Para comprender como se derivan los algoritmos presentado se hace necesario describir los siguientes
conceptos:

Precondición Son lo valores iniciales que toman las variables del programa, o también los pre requisitos
necesarios para su correcto funcionamiento. Por ejemplo puede ser que una variable no sea nula, que
los datos esten ordenados, la existencia de una clase, etc.
Post condición Son los valores finales que toman las variables del programa

Todo programa consiste de tres partes:

1. Inicialización
2. Preservación de propiedades
3. Terminación

La preservación de propiedades se denomina invariante y es lo que nos permite verificar y desarrollar el


código.
No es intención de este texto profundizar este tema, sin embargo, es una forma en la que introduciremos los
algoritmos para ordenar y buscar.
Cada ciclo en un programa tiene una propiedad invariante, esto significa que la propiedad que definamos al
principio del bucle se mantendrá al final del mismo. Una propiedad puede ser una expresión matemática,
una relación, una forma ú otra propiedad que se mantiene constante. Por ejemplo para ordenar usaremos la
propiedad es mayor a los elementos ya ordenados. La idea quedará más clara a medida que se expongan los
algoritmos que se muestran en el capı́tulo.

12.2. Algoritmos de búsqueda


La búsqueda de elementos ha sido analizada en diferentes entornos, memoria, bases de datos, texto plano
y otros. Cada algoritmo de búsqueda da como resultado una eficiencia diferente en función de como se
ha organizado la información. La búsqueda en forma genérica se presenta como, el mecanismo de hallar
un elemento en un vector del cual solo conocemos, el número de elementos que contiene. Este algoritmo,
denominado búsqueda secuencial, viene especificado como sigue:

239
240 Capı́tulo 12 Algoritmos de búsqueda y clasificación
p u b l i c c l a s s Sec {
/ ∗ P r o g r a m a de b u s q u e d a s e c u e n c i a l
∗@ a u t h o r J o r g e T e r a n
∗/
s t a t i c i n t buscar ( i n t [] v , i n t t ) {
f o r ( i n t i = 0 ; i < v . l e n g t h ; i ++)
i f ( v [ i ] == t ) {
hallo = i ;
break ;
}
return ( hallo );
}

Este algoritmo realiza como máximo n comparaciones por lo que se denomina algoritmo lineal, y es muy
lento.
Es necesario conocer más sobre el conjunto de datos para mejorar el tiempo de búsqueda. Este concepto
hace que se puedan realizar búsquedas especı́ficas para cada conjunto de datos a fin de mejorar el tiempo de
proceso.
Supóngase que se tiene una lista de números enteros entre 0 y n. Estos números corresponden al código
de producto en un almacén. Dado un número se quiere conocer si este pertenece al almacén. Para esto
podemos organizar los números de varia formas. Se puede colocar la información en un vector y proceder
con el esquema de búsqueda anterior realizando n comparaciones como máximo. ¿Será posible decidir si el
producto existe en el almacén con una comparación ?.
La respuesta es sı́. Consideremos un código n pequeño entonces podemos armar un vector de bits en
memoria, que indique que números existen y cuales no. Esto reduce el número de comparaciones a 1.
Se quiere cambiar la búsqueda secuencial aprovechando algunas caracterı́sticas particulares al problema,
suponiendo que los datos están previamente ordenados.
Esta solución debe funcionar tanto para enteros, cadenas y reales siempre y cuando todos los elementos sean
del mismo tipo. La respuesta se almacena en un entero p que representa la posición en el arreglo donde se
encuentra el elemento t que estamos buscando, −1 indica que el elemento buscado no existe en el arreglo.
Esta búsqueda denominada búsqueda binaria, resuelve el problema recordando permanentemente el rango en
el cual el arreglo almacena t. Inicialmente el rango es todo el arreglo y luego es reducido comparando t con
el valor del medio y descartando una mitad. El proceso termina cuando se halla el valor de t o el rango es
vacı́o. En una tabla de n elementos en el peor caso realiza log2 n comparaciones.
La idea principal es que t debe estar en el rango del vector. Se utiliza la descripción para construir el
programa.

precondición el vector esta ordenado


inicializar el rango entre $0..n-1$
loop
{invariante: el valor a buscar debe estar en el rango}
si el rango es vacio
terminar y avisar que t no esta en el arreglo
12.2 Algoritmos de búsqueda 241
calcular m que es el medio del rango
use m con una prueba para reducir el rango
si se encuentra t en el proceso de reducción
terminar y comunicar su posición

La parte esencial del programa es la invariante que al principio y final de cada iteración nos permite tener el
estado del programa y formalizar la noción intuitiva que tenı́amos.
Se construye el programa utilizando refinamiento sucesivo haciendo que todos los pasos respeten la invariante
del mismo.
Primero buscaremos una representación del rango digamos l..u entonces la invariante es el rango debe estar
entre l..u. El próximo paso es la inicialización y debemos estar seguros que respeten la invariante, la elección
obvia es l = 0, u = n − 1.
Codificando un programa con los conceptos detallados previamente tenemos:

precondición x debe estar ordenado


post condición p especifica la posición
o p es -1 cuando no existe
l=0; u=n-1
loop
{invariante: debe estar en el rango l,u}
si el rango es vacio
terminar y avisar que t no esta en el arreglo
calcular m que es el medio del rango
use m con una prueba para reducir el rango
si se encuentra t en el proceso de reducción
terminar y comunicar su posición

Continuando con el programa vemos que el rango es vacı́o cuando l > u. en esta situación terminamos y
devolvemos p = −1. Llevando esto al programa tenemos:

precondición x debe estar ordenado


post condición p especifica la posición
o p es -1 cuando no existe
l=0; u=n-1
loop
{invariante: debe estar en el rango l,u}
if l > u
p=-1; break;
calcular m que es el medio del rango
use m con una prueba para reducir el rango
si se encuentra t en el proceso de reducción
terminar y comunicar su posición
242 Capı́tulo 12 Algoritmos de búsqueda y clasificación
Ahora calculamos m con m = (l + u)/2 donde / implementa la división entera. Las siguientes lı́neas
implican el comparar t con x[m] en la que hay tres posibilidades, por igual, menor y mayor. Con lo que el
programa queda como sigue:

precondición x debe estar ordenado


post condición p especifica la posición
o p es -1 cuando no existe
l=0; u=n-1
loop
{invariante: debe estar en el rango l,u}
if l > u
p=-1; break;
m=(l+u)/2;
case
x[m]< t : l=m+1;
x[m]= t : p=m: break;
x[m]> t : u=m-1

Este algoritmo se encuentra implementado en el método binarySearch de arrays .

12.3. Clasificación
La clasificación o procedimiento de ordenar es uno de los problemas más importantes en la computación y
existen un sin número de problemas de programación que requieren de ella. A continuación se detallan una
serie de aplicaciones sin pretender que sean todas.

Cómo podemos probar que en un grupo de elementos no existen elementos duplicados? Bien para esto
ordenamos el conjunto y posteriormente verificamos que no existen valores tales que x[i] = x[i + 1]
recorriendo el conjunto para todos los valores de i permitidos.
Cómo podemos eliminar los duplicados? Ordenamos el conjunto de datos y luego utilizamos dos ı́ndices
haciendo x[j] = x[i]. Se incrementa el valor de i y j en cada iteración. Cuando se halla un duplicado
solo incrementa i. Al finalizar ajustamos el tamaño del conjunto al valor de j.
Supongamos que tenemos una lista de trabajos que tenemos que realizar con una prioridad especı́fica.
Puede ser por ejemplo una cola de impresión donde se ordenan los trabajos en función de alguna
prioridad. Para esto ordenamos los trabajos según la prioridad y luego se los procesa en este orden.
Por supuesto que hay que tomar recaudos al agregar nuevos elementos y mantener la lista ordenada.
Una técnica muy utilizada para agrupar ı́tems es la denominada cortes de control. Por ejemplo, si se
desea obtener los totales de sueldo por departamento en una organización, ordenamos los datos por el
departamento que, se convierte en el elemento de control y luego cada vez que cambia imprimimos un
total. Esto se denomina un corte de control. Pueden existir varios cortes de control, por ejemplo sección,
departamento, etc.
Si deseamos hallar la mediana de un conjunto de datos podemos indicar que la mediana está en el medio.
Por ejemplo para hallar el elemento k ésimo mayor bastarı́a en acceder a x[k].
12.4 Clasificación en Java 243
Cuando queremos contar frecuencias y desconocemos el número de elementos existentes o este es muy
grande, ordenando y aplicando el concepto de control, con un solo barrido podemos listar todos las
frecuencias.
Para realizar unión de conjuntos se pueden ordenar los conjuntos, realizar un proceso de intercalación.
Cuando hay dos elementos iguales solo insertamos uno eliminando los duplicados.
En el caso de la intersección intercalamos los dos conjuntos ordenados y dejamos uno solo de los que
existan en ambos conjuntos.
Para reconstruir el orden original es necessario crear un campo adicional que mantenga el original para
realizar una nueva ordenación para, reconstruir el orden original.
Como vimos también se pueden ordenar los datos para realizar búsquedas rápidas.

12.4. Clasificación en Java


Los métodos propios del Java proveen rutinas para ordenar objetos que facilitan el desarrollo de aplicaciones.
Para ordenar vectores que pueden ser de tipos int, long, short, char, byte, float o double es suficiente utilizar
la clase Arrays con el método sort, el siguiente ejemplo genera 10 números al azar y los ordena.
import java . u t i l . ∗ ;
/∗∗
∗ Programa o r d e n a r e n t e r o s
∗ u t i l i z a n d o e l metodo de j a v a
∗ @author J o r g e T e r a n
∗/
public class SortVect {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x = new i n t [ 1 0 ] ;
Random gen = new Random ( ) ;
/ / G e n e r a r 10 n úmeros e n t e r o s e n t r e 0 y 100
f o r ( i n t i = 0 ; i < 1 0 ; i ++)
x [ i ] = gen . n e x t I n t ( 1 0 0 ) ;
/ / O r d e n a r en f o r m a a s c e n d e n t e
Arrays . s o r t ( x ) ;
/ / m o s t r a r l o s n úmeros o r d e n a d o s
f o r ( i n t i = 0 ; i < 1 0 ; i ++)
System . o u t . p r i n t l n ( x [ i ] ) ;
}
}

Cuando se requiere ordenar un objeto se hace necesario crear un mecanismo para comparar los elementos
del objeto. Construyamos una clase P ersona que almacene nombre, apellido y nota.

import java.util.*;

class Persona {
private String nombre;
244 Capı́tulo 12 Algoritmos de búsqueda y clasificación
private String apellido;
private int nota;
public Persona(String no, String ap, int n) {
nombre = no;
apellido = ap;
nota = n;
}

public String getNombre() {


return nombre;
}
public String getApellido() {
return apellido;
}

public int getNota() {


return nota;
}
}

Para crear diferentes instancias de P ersona se procede como sigue:

Persona[] alumnos = new Persona[3];


alumnos[0] = new Persona("Jose","Meriles", 70);
alumnos[1] = new Persona("Maria","Choque",55);
alumnos[2] = new Persona("Laura","Laura", 85);

Si se requiere ordenar éstos alumnos por nota no es posible utilizar directamente Arrays.sort(alumnos) dado
que el método de clasificación no conoce cuáles y qué campos comparar para realizar el ordenamiento.
Por esto es necesario primeramente escribir un método que resuelva el problema. En nuestro ejemplo creamos
el método compareTo. Este nombre es un nombre reservado de Java y lo que realiza es una sobrecarga de
método reemplazando el método de Java con el nuestro.
Los valores que debe devolver son −1 cuando es menor, 0 cuando es igual y 1 cuando es mayor. Lo que hace
este método es comparar el valor presente con otro pasado a la rutina. Esta rutina utiliza el método qsort para
ordenar los valores. Esta rutina queda como sigue:

public int compareTo(Persona otro){


if (nota < otro.nota) return -1;
if (nota > otro.nota) return 1;
return 0;
}

Además es necesario especificar que la clase persona incluye el método Comparable y se especifica ası́:

class Persona implements Comparable<Persona>


12.4 Clasificación en Java 245
El programa final queda implementado en el siguiente código:

import java . u t i l . ∗ ;
/∗∗
∗ Programa o r d e n a r o b j e t o s
∗ u t i l i z a n d o e l metodo de j a v a
∗ @author J o r g e T e r a n
∗/

p u b l i c c l a s s OrdObjeto {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
P e r s o n a [ ] a l u m n o s = new P e r s o n a [ 3 ] ;

a l u m n o s [ 0 ] = new P e r s o n a ( ” J o s e ” , ” M e r i l e s ” , 7 0 ) ;
a l u m n o s [ 1 ] = new P e r s o n a ( ” M a r i a ” , ” Choque ” , 5 5 ) ;
a l u m n o s [ 2 ] = new P e r s o n a ( ” L a u r a ” , ” L a u r a ” , 8 5 ) ;
Arrays . s o r t ( alumnos ) ;

f o r ( Persona e : alumnos )
System . o u t . p r i n t l n ( ” Nombre =” + e . getNombre ( )
+ ” , Apellido= ” + e . getApellido ()
+ ” , n o t a =” + e . g e t N o t a ( ) ) ;
}
}

c l a s s P e r s o n a i m p l e m e n t s Comparable<P e r s o n a > {

p r i v a t e S t r i n g nombre ;

private String apellido ;

p r i v a t e i n t nota ;

p u b l i c P e r s o n a ( S t r i n g no , S t r i n g ap , i n t n ) {
nombre = no ;
a p e l l i d o = ap ;
nota = n ;
}

p u b l i c S t r i n g getNombre ( ) {
r e t u r n nombre ;
}

public String getApellido () {


return apellido ;
246 Capı́tulo 12 Algoritmos de búsqueda y clasificación
}

public i n t getNota ( ) {
return nota ;
}

p u b l i c i n t compareTo ( P e r s o n a o t r o ) {
i f ( nota < otro . nota )
r e t u r n −1;
i f ( nota > otro . nota )
return 1;
return 0;
}
}

12.5. Algoritmos de clasificación


Dado que el lenguaje ya incluye un soporte para realizar clasificaciones con un algoritmo parece que no es
necesario revisar algoritmos de clasificación. En la realidad existen problemas que pueden ser resueltos más
eficientemente con un algoritmo especı́fico. Esto se logra conociendo como están los datos que pretendemos
procesar.
Los algoritmos para ordenar se clasifican en algoritmos generales y particulares. Los algoritmos generales se
pueden aplicar sin importarnos como son los datos, en cambio los algoritmos particulares son aplicables a
casos especiales para obtener un mejor tiempo de proceso.
Entre los algoritmos generales se explican los métodos de clasificación por inserción, selección, qsort, y el
de burbuja.
Para entender los diferentes algoritmos de ordenar es necesario comprender como establecer la invariante
en cada uno de los métodos. Para esto consideraremos que los datos a ordenar corresponden al eje y de un
gráfico bidimencional y la posición del vector al eje x. Ver la figura 12.1.

Figura 12.1 Representación de una secuencia de datos


12.5 Algoritmos de clasificación 247
Este gráfico representa los datos iniciales, es decir, la precondición que viene dada por la figura 12.2:

Figura 12.2 Precondición de una secuencia de datos

Una vez ordenados todos los elementos, la figura 12.3 que representa a la postcondición es la siguiente:

Figura 12.3 Postcondición de una secuencia de datos

12.5.1. Método de la burbuja


El método de la burbuja es el más simple y el más antiguo. También hay que mencionar que es el método
más lento para ordenar.
El método consiste en comparar todos los elementos de la lista con el elemento de su lado. Si se requiere
se realiza un intercambio para que estén en el orden previsto. Este proceso se repite hasta que todos los
elementos estén ordenados. Vale decir que, en alguna pasada no se realiza ningún intercambio.
El código para este algoritmo es el siguiente:

void Burbuja ( i n t [ ] x ) {
248 Capı́tulo 12 Algoritmos de búsqueda y clasificación

Figura 12.4 Invariante de clasificación por inserción

i n t i , j , temp ;
i n t n=x . l e n g t h ;
f o r ( i = ( n − 1 ) ; i >= 0 ; i −−) {
f o r ( j = 1 ; j <= i ; j ++) {
i f ( x [ j − 1] > x [ j ] ) {
temp = x [ j − 1 ] ;
x [ j −1] = x [ j ] ;
x [ j ] = temp ; }
}
}

Como se ve en el programa en el primer ciclo se recorren todos los elementos. El segundo ciclo se encarga
de que el último elemento sea el mayor de todos.
Analizando el programa se puede determinar que, el tiempo de proceso en el caso general es proporcional al
cuadrado de la cantidad de elementos. Podemos mencionar como una ventaja la simplicidad de la codificación
y como desventaja la ineficiencia del algoritmo.
Existen otros métodos que también tienen un tiempo similar pero que son mucho más eficientes que
mencionamos a continuación.

12.5.2. Clasificación por inserción


Cuando se tiene un conjunto de cartas en la mano lo que hacemos es tomar una carta y buscar su ubicación e
insertarla en su sitio hasta ordenar todas. Para ésto podemos suponer que el primer elemento está en su lugar
y proceder a colocar los siguientes en función del primer elemento.
Representando gráficamente el método obtenemos (ver figura 12.4): La invariante indica que los valores hasta
i están ordenados y los datos posteriores pueden ser menores o mayores que el último dato ya ordenado.

void I n s e r c i o n ( i n t [ ] x ) {
i n t i , j , temp ;
int n = x . length ;
f o r ( i = 1 ; i < n ; i ++) {
/ / los datos estan ordenados hasta i
12.5 Algoritmos de clasificación 249

for ( j = i ; j > 0 && x [ j − 1 ] > x [ j ] ; j −−) {


temp = x [ j −1];
x [ j −1] = x[ j ];
x[ j ] = temp ;
}
}
}

Para explicar el funcionamiento del algoritmo supongamos que inicialmente tenemos la siguiente secuencia
de números

62 46 97 0 30

Inicialmente suponemos que el primer valor es el menor y lo insertamos en la primera posición que, es el
lugar donde estaba inicialmente, luego se toma el siguiente valor y se ve si le corresponde estar antes o
después, en el ejemplo se produce un intercambio

46 62 97 0 30

En la siguiente iteración tomamos el 97 y se ve que, no debe recorrerse hacia adelante por lo que no se
hace ningún intercambio. Luego se realiza lo mismo con el siguiente elemento recorriendo todo el vector e
insertando el elemento en el lugar apropiado, obteniendo

0 46 62 97 30

esto se repite hasta obtener el vector ordenado.


La ventaja de este algoritmo es que es más simple que, el anterior y aún cuando su tiempo de proceso es
también proporcional a n2 es más eficiente y puede afinarse para ser más eficiente. El código siguiente
muestra el algoritmo mejorado para ser más eficiente. El tiempo de proceso sigue siendo proporcional a
O(n2 )
\ begin { center }
\ begin { verbatim }
void I ns er ci on2 ( i n t [ ] x ) {
i n t i , j , temp ;
int n = x . length ;
f o r ( i = 1 ; i < n ; i ++) {
/ / los datos estanordenados hasta i
temp=x [ i ] ;
f o r ( j = i ; j > 0 && x [ j − 1 ] > temp ; j −−) {
x [ j ] = x [ j −1];
}
x [ j ] = temp ;
/ / con e s t a a s i g n a c i o n r e s t a b l e c e m o s l a i n v a r i a n t e
}
}
\ end { v e r b a t i m }
\ end { c e n t e r }
250 Capı́tulo 12 Algoritmos de búsqueda y clasificación

Figura 12.5 Invariante de clasificación por selección

Como se observa se ha mejorado la parte que corresponde a construir el espacio para introducir el elemento
siguiente reduciendo el número de intercambios.

12.5.3. Ordenación por selección

En el algoritmo de inserción la invariante indica que los valores posteriores al ı́ndice i pueden ser mayores
o menores que el último valor ordenado. Podemos cambiar esta invariante haciendo que el último valor
ordenado sea menor que todos los elementos por ordenar. Esto se representa gráficamente como sigue (ver
figura 12.5):
Para implementar el concepto expresado en la invariante se escoge el elemento más pequeño y se reemplaza
por el primer elemento, luego repetir el proceso para el segundo, tercero y ası́ sucesivamente. El resultado es
el siguiente programa

void Seleccion ( i n t [ ] x ) {
int i , j ;
i n t min , temp ;
int n = x . length ;
f o r ( i = 0 ; i < n − 1 ; i ++) {
min = i ;
f o r ( j = i + 1 ; j < n ; j ++) {
i f ( x [ j ] < x [ min ] )
min = j ;
}
temp = x [ i ] ;
x [ i ] = x [ min ] ;
x [ min ] = temp ;
}
}

Como se observa este algoritmo es similar al algoritmo de inserción con tiempo similar y proporcional a
O(n2 )
12.5 Algoritmos de clasificación 251

Figura 12.6 Invariante de clasificación rápida

12.5.4. Algoritmo de clasificación rápida

El qsort cuyo nombre en inglés es QuickSort o también denominado algoritmo de clasificación rápida ya fue
publicada por C.A.R. Hoare en 1962 y se basa en dividir el vector en dos mitades utilizando un elemento
denominado pivote de tal forma que todos los elementos mayores al pivote estén en un vector y los restantes
en el segundo vector. Como pivote inicial se puede tomar el primer elemento o uno al azar.
El siguiente la figura 12.6 muestra la invariante, vemos que para cada punto los valores de la derecha siempre
son mayores.
Este proceso se repite en forma recursiva hasta tener el vector ordenado. A partir de este enfoque podemos
realizar una primera aproximación a lo que viene ser el algoritmo:

Ordenar (l,u){
if (u >= l) {
hay como maximo un elemento
por lo tanto no hacer nada
}
hallar un pivote p para dividir en vector en dos
Ordenar (l,p-1)
Ordenar (p+1,u)
}

Aquı́ como dijimos el problema es encontrar el punto de partición denominado pivote. Utilizando el concepto
que la invariante es que: los valores entre x[m] y x[i] son menores al pivote x[l] podemos construir el siguiente
código para hallar el pivote.

int m = l;
//invariante los valores entre x[m] y x[i]
//son menores al pivote x[l]
252 Capı́tulo 12 Algoritmos de búsqueda y clasificación
for (int i = l + 1; i <= u; i++) {
if (x[i] < x[l])
{
temp = x[++m];
x[m] = x[i];
x[i] = temp;
}
}
temp = x[l];
x[l] = x[m];
x[m] = temp;

Supongamos que tenemos los siguientes datos:

27 2 81 81 11 87 80 7

Iniciamos un contador m en el primer valor y tomando x[l] = 27 como pivote y se recorre el vector
comparando cada uno de los valores con el pivote haciendo un intercambio entre (m+1) y la posición del
vector, obteniendo en el primer intercambio entre el número 2 que es menor que el pivote y el número de la
posición m que es casualmente el 2, obteniendo:

27 2 81 81 11 87 80 7

El segundo intercambio es entre el 11 que es menor que el pivote y el 81 obteniento:

27 2 11 81 81 87 80 7

El tercer intercambio se produce con el 7 y el 81 obteniendo

27 2 11 7 81 87 80 81

Para finalizar intercambiamos el pivote con la posición del último número menor al pivote, obteniendo un
vector donde todos los elementos menores al pivote están a la izquierda y los mayores a la derecha.

7 2 11 27 81 87 80 81

Este proceso se puede repetir recursivamente para el vector a la izquierda del pivote y para el que está a la
derecha obteniendo finalmente:

private void qsort(int[] x, int l, int u) {


if (l >= u)
return;
int m = l, temp;
//invariante los valores entre x[m] y x[i]
// son menores al pivote x[l]
for (int i = l + 1; i <= u; i++) {
if (x[i] < x[l])
{
temp = x[++m];
x[m] = x[i];
12.5 Algoritmos de clasificación 253
x[i] = temp;
}
}
temp = x[l];
x[l] = x[m];
x[m] = temp;
qsort(x, l, m - 1);
qsort(x, m + 1, u);
}

La clasificación rápida es mucho más eficiente con un tiempo proporcional n log(n), que no es propósito de
este texto probar.
Aunque no se divide exactamente a la mitad se ha tomado este valor para mostrar como mejora la eficiencia
. En la realidad se puede mejorar en función de como está el conjunto de datos y como se elige al pivote.
En el caso de que el vector esté previamente ordenado el tiempo de proceso será mayor. El proceso de elegir
un pivote aleatoriamente produce un algoritmo más estable. Es posible optimizar el método de ordenación
rápida pero no se tratará en el texto. Cuando uno requiere un algoritmo de estas caracterı́sticas normalmente
se recurre a las rutinas ya implementadas en las librerı́as del lenguaje.
Se pueden mejorar aún más los algoritmos de clasificación? En casos particulares se puede obtener algo-
ritmos proporcionales a O(n) en el caso más general, el mejor algoritmo es proporcional a n log(n). Una
demostración se puede leer en el texto de [Gilles Brassard 1996]

12.5.5. Algoritmos lineales


Considere las siguientes condiciones para los datos de entrada, se dispone de memoria, suficiente, los datos
son enteros positivos sin repetición en el rango de 0 a N, donde N es el número máximo existente. Para
ordenar estos números se puede utilizar cualquiera de los algoritmos descritos para ordenar.
Para mejorar el proceso de ordenar consideremos la siguiente estructura

BitSet a = new BitSet(max + 1);

Si cada posición del vector representa a uno de los números del vector se utiliza un bit para almacenar cada
uno de los números. En este caso es posible leer los números de entrada y encender el bit que le corresponde.
Recorriendo el vector se pueden imprimir los números a los que corresponden los bits encendidos en forma
ordenada.
El resultado proporciona el siguiente algoritmo.

public void b i t S o r t ( i n t [ ] x ) {
i n t max =0 , j = 0 ;
f o r ( i n t i = 0 ; i < x . l e n g t h ; i ++){
i f ( x [ i ] > max )
max=x [ i ] ;
}
B i t S e t a = new B i t S e t ( max + 1 ) ;
/ / H a s t a a q u i t o d o s l o s v a l o r e s de
254 Capı́tulo 12 Algoritmos de búsqueda y clasificación
/ / a e s t a n en c e r o
f o r ( i n t i = 0 ; i < x . l e n g t h ; i ++)
a . set (x[ i ]);
f o r ( i n t i = 0 ; i <= max ; i ++){
i f ( a . get ( i ))
x [ j ++]= i ;
}
}

Analizando el algoritmo se ve que se tienen tres ciclos que recorren todos los datos dando un tiempo de
ejecución proporcional a O(n).
Como se ve en este método antes de aplicar el algoritmo ya conocemos la posición que cada elemento debe
tomar. En estos casos se puede desarrollar algoritmos de tiempo lineal.
Otro ejemplo de clasificación en tiempo lineal es el problema de ordenar la bandera de Bolivia. Supongamos
que tenemos una bandera que tiene los colores desordenados (precondición), tal como muestra la imagen:

Una vez ordenado (postcondición) el resultado a obtener serı́a

Para ordenar utilizaremos la siguiente invariante:

Para resolver este problema ya conocemos el lugar exacto de cada uno de los colores de la bandera. Rojos
a la izquierda, amarillos al centro, y verdes a la derecha. El proceso que se sigue, manteniendo la invariante
que nos dice que desde el último amarillo hasta el primer verde no está ordenado.
Llevamos sucesivamente los rojos a la izquierda y los verdes a la derecha. Lo amarillos quedan al centro por
lo que queda ordenado. El programa java ejemplifica el proceso:
/∗∗
∗ P r o g r a m a p a r a o r d e n a r l a b a n d e r a de B o l i v i a

∗ @author J o r g e T e r a n

∗/
p u b l i c c l a s s Bandera {
p u b l i c s t a t i c f i n a l v o i d main ( S t r i n g [ ] a r g s )
12.5 Algoritmos de clasificación 255

throws Exception {
c h a r [ ] b a n d e r a = { ’R ’ , ’A’ , ’A’ , ’V’ ,
’A’ , ’V’ , ’V’ , ’R ’ , ’A’ , ’R’ } ;
f o r ( i n t i = 0 ; i < 1 0 ; i ++)
System . o u t . p r i n t ( b a n d e r a [ i ] + ” ” ) ;
System . o u t . p r i n t l n ( ” ” ) ;
i n t r = 0 , a = 0 , v = 10;
char t ;
while ( a != v ) {
i f ( b a n d e r a [ a ] == ’R ’ ) {
t = bandera [ r ] ;
bandera [ r ] = bandera [ a ] ;
bandera [ a ] = t ;
r ++;
a ++;
} e l s e i f ( b a n d e r a [ a ] == ’A’ ) {
a ++;
} e l s e i f ( b a n d e r a [ a ] == ’V’ ) {
t = bandera [ a ] ;
bandera [ a ] = bandera [ v − 1 ] ;
bandera [ v − 1] = t ;
v−−;
}
}

f o r ( i n t i = 0 ; i < 1 0 ; i ++)
System . o u t . p r i n t ( b a n d e r a [ i ] + ” ” ) ;
System . o u t . p r i n t l n ( ” ” ) ;
}
}

Está probado que cualquier algoritmo basado en comparaciones toma un tiempo O(n log n). ¿Cómo es
posible que ordenar la bandera tome un tiempo lineal? La respuesta es que en el proceso conocemos la
posición de los elementos de la bandera.

12.5.6. Laboratorio

Con la finalidad de probar experimentalmente los algoritmos de clasificación implemente los algoritmos y
llene el cuadro siguiente:
256 Capı́tulo 12 Algoritmos de búsqueda y clasificación

Tamaño 10 102 103 104 105 106


Burbuja
Inserción
Inserción2
Selección
Quicksort
Del Java
Bitsort

12.6. Ejemplo de aplicación


La unidad de contabilidad tiene una cantidad de comprobantes c, que están numerados desde 0 ≤ c ≤ 1000.
Le han pedido leer todos los números de comprobantes 0 ≤ n ≤ 101 0 y contar cuantos números de
comprobantes están duplicados. La entrada de datos consiste de varios casos de prueba. Cada caso de prueba
comienza con un número n entero que indica la cantidad de comprobantes. Luego vienen n números de
comprobantes. Los casos de prueba terminan cuando n es cero.

Ejemplo de entrada
10
12 88 77 3 50 12 86 77 3 55
5
1 2 3 4 5
6
1 1 1 1 1 1
0

Ejemplo de salida
3
0
5

Para resolver el problema primero debemos leer en un vector los números de comprobantes. Seguidamente
ordenamos el vector. Como ahora están ordenados es suficiente comparar un valor con el anterior. Si son
iguales están duplicados. Después de recorrer el vector se imprime la cuenta. El programa siguiente resuelve
el problema.

import java . u t i l . ∗ ;
p u b l i c c l a s s programa2 {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e = new S c a n n e r ( System . i n ) ;
f o r ( i n t i = l e e . n e x t I n t ( ) ; i >0; i = l e e . n e x t I n t ( ) ) {
i n t [ ] n = new i n t [ i ] ;
f o r ( i n t j = 0 ; j <i ; j ++)
12.6 Ejemplo de aplicación 257

n [ j ]= l e e . n e x t I n t ( ) ;
Arrays . s o r t ( n ) ; / / ordenar
/ / c o m p a r a r un e l e m e n t o con e l a n t e r i o r
i n t c o n t a r =0;
f o r ( i n t j = 1 ; j <i ; j ++){
i f ( n [ j ]== n [ j −1])
c o n t a r ++;
}
System . o u t . p r i n t l n ( c o n t a r ) ;
}
}
}
258 Capı́tulo 12 Algoritmos de búsqueda y clasificación

12.7. Ejercicios
12.7.1. JV-1029: Escoger Equipos
Se tiene una lista de jugadores con los que conformaremos equipos. Se tiene de cada jugador un número que
califica su capacidad de juego. El número más grande representa un capacidad mayor
Para escoger dos equipos se seleccionan dos capitanes, el primero naturalmente escoge al que juega mejor.
Luego el segundo capitán escoge de los que quedan el que juega mejor y ası́ sucesivamente.
Veamos un ejemplo: los jugadores vienen como sigue: 5,7,8,4,2, el primer capitán escogerı́a el 8, el segundo
el 7, ası́ hasta que no queden jugadores. Con este proceso el equipo uno tendrı́a a los jugadores con capacidad
8+5+2=15 y el segundo equipo 7+4=11. Se quiere mostrar en pantalla la diferencia en valor absoluto de
ambas sumas 15-11=4

Entrada
La entrada consiste en múltiples casos de prueba. Cada caso de prueba contiene entre 1 y 50 números
separados por un espacio en una lı́nea. La entrada termina cuando no hay mas datos

Salida
Por cada caso de prueba escriba en la salida una lı́nea con la diferencia en valor absoluto de la suma de la
capacidad de juego.

Ejemplos de entrada Ejemplos de salida


5 7 8 4 2 4
100 100
1000 1000 0
9 8 7 6 2
1 5 10 1 5 10 0
12.7 Ejercicios 259

12.7.2. JV-1031: Mediana


En un conjunto de números distintos la mediana es un elemento M de tal forma que el número de elementos
mayores a M son exactamente igual al número de elemento menores a M.
Por ejemplo dato el conjunto 1, 4, 2, 5, 7 la mediana es 4 porque hay dos elementos el 7 y el 5 que son
mayores a 4 y dos elementos el 1 y el 2 menores a 4.
El conjunto 1, 5, 8, 3 no tiene mediana porque ningún elemento satisface la definición anterior.

Entrada
La entrada consiste de varios casos de prueba. Cada caso de prueba vienen en dos lı́neas. La primera lı́nea
contiene el número n de elementos del conjunto. La segunda lı́nea contiene n números enteros separados por
un espacio. La entrada termina cuando no hay más datos.

Salida
Por cada caso de prueba escriba en una lı́nea la mediana del conjunto si existe o -1 si no existe.

Ejemplos de entrada Ejemplos de salida


5 4
1 4 2 5 7 -1
4 7
1 5 8 3 -1
1
7
2
7 12
260 Capı́tulo 12 Algoritmos de búsqueda y clasificación

12.7.3. JV-1058: Ordenando números


Te diré varios números, y quiero que los imprimas ordenados ascendentemente. (el menor se imprime
primero, y el mayor se imprime último).

Entrada
En una lı́nea, se te dará un número entero 1 ≤ n ≤ 50 indicando la cantidad de casos de prueba.
En la siguiente lı́nea, se te dará un número entero 1 ≤ a ≤ 50, indicando cuántos números existen en la lista
de números que debes ordenar.
En la siguiente lı́nea, y separados entre sı́ por un espacio, se te darán a números enteros dentro del rango
[−103, 103]

Salida
Por cada caso de entrada, debes imprimir una lı́nea con los números enteros que se te dieron de entrada
ordenados ascendentemente, imprime un espacio después de cada número y un salto de lı́nea después de
imprimir todos los números con su respectivo espacio.

Ejemplos de entrada Ejemplos de salida


3 0 1 2 3 4 5 6 7 8 9
10 1 2 3 4 5
1 9 4 8 7 6 3 2 5 0 1 100
5
5 4 3 2 1
2
100 1
12.7 Ejercicios 261

12.7.4. JV-1093: Intercambio de Trenes


En una antigua estación de ferrocarril, usted todavı́a puede encontrar uno de los últimos intercambiadores de
trenes. Un intercambiador de tren es un empleado del ferrocarril, cuya única función es la de reorganizar los
vagones de los trenes.
Una vez que los carros están organizados en el orden óptimo, el conductor del tren todo lo que tiene que
hacer, es dejar los vagones, uno por uno, en las estaciones para las que corresponde la carga.
El tı́tulo intercambiadores de trenes se deriva de la primera persona que llevó a cabo esta tarea, en una
estación cerca de un puente del ferrocarril. En lugar de abrir verticalmente, el puente girar, sobre un pilar
en el centro del rı́o. Después de girar el puente de 90 grados, los barcos podı́an pasar a la izquierda o a la
derecha.
El primer intercambiador de trenes habı́a descubierto que el puente puede ser operado con un máximo de
dos vagones en el. Al girar el puente de 180 grados, cambiado el lugar de los vagones, lo que le permite
reorganizar los mismos (como efecto secundario, los vagones quedan en la dirección opuesta pero los vagones
de tren se puede mover en cualquier dirección, ası́ que le importa).
Ahora que casi todos los intercambiadores de vagones han fallecido la compañı́a desea automatizar su
operación. Parte del programa a ser desarrollado consiste en una rutina que decide, para¡un tren dado, el
número de intercambios de dos vagones adyacentes, de tal forma de ordenar el tren. Su trabajo es crear esta
rutina.

Entrada
La primera lı́nea de la entrada es el número de casos de prueba N. Cada caso de prueba consiste de dos lı́neas
de entrada. La primera contiene un número entero L que especifica la longitud del tren. La segunda lı́nea
consiste de una permutación de números entre 1 y L, indicando el orden actual de los vagones. Los vagones
deben ser ordenados de tal forma que el 1 viene antes del 2 y ası́ sucesivamente, con el vagón L al final.

Salida
Para cada caso de prueba escriba un número en una lı́nea que representa el número óptimo de intercambio.

Ejemplos de entrada Ejemplos de salida


3 1
3 6
1 3 2 1
4
4 3 2 1
2
2 1
262 Capı́tulo 12 Algoritmos de búsqueda y clasificación

12.7.5. 1333: Easy Suffix Array

En Ciencias de la Computación un arreglo de sufijos es un arreglo ordenado de todos los sufijos de una cadena
dada. Esta estructura de datos es muy simple, pero sin embargo es muy poderosa y es usada en algoritmos de
compresión de datos y dentro del campo de la bioinformática , indización de textos completos, entre otros.
Por ejemplo dada la cadena ”banana”los sufijos de esta cadena son:

banana
anana
nana
ana
na
a

Y si los ordenamos lexicograficamente se tiene:

a
ana
anana
banana
na
nana

El array {”a”, ”ana”, ”anana”, ”banana”, ”na”, ”nana”} sera el suffix array de la cadena ”banana”.
Se te pede obtener el suffix array de una cadena dada en la entrada, existen algoritmos muy eficientes para
obtener el suffix array de una cadena pero en esta ocasión no estamos interesados en ellos.

Entrada
Una sola cadena S sin espacios de longitud N (1 ≤ N ≤ 100). La cadena solo consta de letras minúsculas.

Salida
N cadenas, una cadena en cada lı́nea, las cuales representan el suffix array de la cadena de la entrada.
12.7 Ejercicios 263

Ejemplos de entrada Ejemplos de salida


banana a
ana
anana
banana
na
nana
264 Capı́tulo 12 Algoritmos de búsqueda y clasificación

12.7.6. 1191: Suma Exacta


Te dan una lista de precios de juguetes, y un cupón de compra que te regalaron.
Como es época de regalos debes comprar dos regalos y quieres utilizar el cupón.
Obviamente quieres utilizar el cupón completamente por que la diferencia que no se gasta se pierde.
Se quiere que el primer juguete sea el de menos valor posible. y el segundo el de mayor valor.

Entrada
Cada entrada comienza con 2 ≤ N ≤ 10000, el n[umero de juguetes disponibles. Las siguiente linea tendrá
N enteros representando el valor de un juguete. Cada juguete cuesta menos de 1000001. Luego sigue una
lı́nea con el monto M del cupón. La entrada termina cuando no hay más datos.

Salida
Para cada caso de prueba debe imprimir I y J que representan los precios de los juguetes cuya suma I+J =M.
Si no existe una solución imprima -1.

Ejemplos de entrada Ejemplos de salida


2 40 40
40 40 2 8
80
5
10 2 6 8 4
10
12.7 Ejercicios 265

12.7.7. JV-1379: Juego de Niños


Existen muchos jugos para niños. Estos juegos muy simples de jugar pero no tan simples de construir. Este
juego consiste en dar N números positivos a un jugador. El jugador debe un n[umero grande concatenando
los números uno detrás del otro. Por ejemplo hay 4 números tales como 123, 124, 56, 90 entonces se
puede construir los siguientes enteros 1231245690, 1241235690, 5612312490, 9012312456, 9056124123.
De hecho se pueden construir 24 de estos enteros. El entero más grande que se puede construir es 905612412.
Para este Problema considere el orden lexicogr[afico de los números cuando son tomados como cadenas.

Entrada
La entrada comienza con un numero positivo N y en las siguientes N lineas hay N números positivos que
terminan cuando N=0, que no se procesa.

Salida
Por caso de prueba usted debe imprimir el entero más grande que se pueda construir utilizando los N enteros.

Ejemplos de entrada Ejemplos de salida


4 9056124123
123 124 56 90 90956124123
5 99999
123 124 56 90 9
5
9 9 9 9 9
0
13
13.1.
Recursividad
Estructura de datos pila
Para entender como funcionan los procesos recursivo explicaremos brevemente en que consiste una estructura
de datos que se denomina pila.
Una pila es un arreglo que permite dos operaciones básicas. Insertar un valor a la pila y remover un valor de
la pila. La caracterı́stica de estas operaciones es que cada vez que insertamos un valor a la pila el elemento
insertado se coloca encima de los demás. Por ejemplo si tenemos los elementos a, b, c, d que queremos
insertar a una pila la forma en la que esto ocurre es:

c
b b
a a a
Estado de la Estado de la Estado de la Estado de la
pila pila después de pila después de pila después de
insertar a insertar b insertar c

Cada vez que removemos la pila decrece y se quita el elemento de encima.

c
b b
a a a
Estado de la Estado de la Estado de la Estado de la
pila pila después de pila después de pila después de
remover remover remover

Como se puede apreciar sin ingresamos a la pila a, b, c, d al momento de extraer todos los valores tendremos
d, c, b, a los valores invertidos.
Esta estructura se utiliza en todas las llamada recursivas. Las variables son guardadas en una estructura de
datos de pila y recuperadas cuando termina la recursión.
En los procesos recursivos denominamos pila de recursión a todos los valores que se almacenan en una pila
en cada vez que un procedimiento se llama ası́ mismo.

13.2. Introducción
Las técnicas de programación basadas en ciclos se denominan programas iterativos.
Los programas que describen acciones repetitivas basadas en llamarse a si mismo, se denominan programas
recursivos.
Los problemas recursivos los representamos con ecuaciones de recurrencia, y de la misma forma en la que
se presentan estas ecuaciones los programas recursivos deben tener dos partes:

267
268 Capı́tulo 13 Recursividad
Caso base El caso base debe ser tan simple que puede ser resuelto sin una llamada recursiva.
Caso recursivo El caso recursivo involucra convertir el problema a otro más simple que puede ser
resuelto por una llamada recursiva, es decir llamandose a si mismo.

El caso base es el que termina el proceso recursivo. Algunos problemas involucran múltiples casos base y
recursivos, sin embargo, debe existir al menos uno de cada caso. Para escribir programas recursivos existe un
modelo general, que se muestra en el algoritmo 1.

Algoritmo 1: Modelo para construir problemas recursivos


Problema(parametros)
if ((parametros = algun valor))
// caso base que termina la recursión ;
......... ;
else
// caso recursivo ;
......... ;

Las ecuaciones de recurrencia son similares a la inducción matemática. Primero necesita conocer como
resolver un caso base, para n = 0, n = 1. Luego asumiendo que conoce como construir un problema de
tamaño n − 1, se busca un modo de obtener la solución de tamaño n a partir de la solución de tamaño n − 1.
Para resolver un problema recursivo se sugiere:

Definir un problema en base a un problema similar y más pequeño.


Describir como cada recursión reduce el tamaño del problema.
Definir la instancia del problema que puede servir como caso base.
Verificar que a medida que el problema disminuye se puede llegar al caso base.

13.3. Cálculo del factorial


Utilizando un ejemplo sencillo se muestra como se construye un programa recursivo. Consideremos el
problema de hallar el factorial. Matemáticamente se lo escribe con una ecuación de recurrencia como sigue:
(
1 si n = 0
f actorial(n) = (13.3.1)
n ∗ f actorial(n − 1) si n > 0

Como se ve en la definición existe un caso base, que es cuando n = 0. y el caso recursivo simplifica el
problema reduciendo el tamaño, esto es, reducir n en uno.
Utilizando la plantilla primero codificamos el caso base obteniendo el algoritmo 2
Como ve el caso base es una transcripción directa del enunciado. Luego continuamos con la parte recursiva,
y codificamos el caso recursivo como se ve en el algoritmo 3 :
Para probar el programa podemos escribir un programa que nos permitirán verificar las soluciones tal como
se muestra en el algoritmo 4
13.3 Cálculo del factorial 269

Algoritmo 2: Cálculo del factorial recursivamente


Factorial(n)
if (n = 0)
// caso base que termina la recursión ;
return 1 ;
else
// caso recursivo ;
......... ;

Algoritmo 3: Completando el caso recursivo


Factorial(n)
if (n = 0)
// caso base que termina la recursión ;
return 1 ;
else
// caso recursivo ;
return n*factorial(n-1) ;

Algoritmo 4: Prueba de programa para calcular factorial del algoritmo

imprimir(Factorial(2)) ;
imprimir(Factorial(3)) ;
imprimir(Factorial(4)) ;
imprimir(Factorial(5)) ;

Analizando el proceso recursivo vemos en la primera llamada se tiene el valor n. La segunda llamada a si
mismo haciendo n = n − 1. El primer valor de n es almacenado en una estructura de pila que es inherente a
la recursión. Finalmente cada llamada recursiva llama a la función factorial, hasta que llegue a un caso base,
que establece la terminación de las llamadas recursivas.
Cuando se llega a este punto los valores intermedios se extraen de la pila de recursión para ir produciendo el
resultado deseado.
El proceso recursivo requiere una memoria adicional para almacenar los resultados intermedios, por lo que es
deseable eliminar la recursión y convertir el programa a un programa iterativo, vale decir, basado en ciclos.
En el ejemplo del cálculo del factorial el programa iterativo es el algoritmo 5:
270 Capı́tulo 13 Recursividad

Algoritmo 5: Cálculo del factorial iterativamente

long r=1;
for (i=(1,n])
r*=i;
return r;

13.4. Invertir los datos de entrada


Veamos un problema recursivo, que nos mostrará el mecanismo con el que funciona la recursión. Suponga-
mos que tenemos un archivo de texto que esta formado por palabras cada una en una lı́nea. Se quiere imprimir
estas palabras en orden inverso. Para resolver esto podemos guardar las palabra en una estructura de datos
que haga el trabajo. Por ejemplo en una estructura de pila. Sin explı́citamente utilizar ninguna estructura de
datos podemos resolver el problema utilizando la recursión.
Definimos la estructura básica de un problema recursivo (Algoritmo 6):

Algoritmo 6: Estructura básica para comenzar


Invertir(parametros)
if ((parametros = algun valor))
// caso base que termina la recursión ;
......... ;
else
// caso recursivo ;
......... ;

Primero los parámetros, en este caso pondremos el flujo del cual vamos a leer los datos.

Algoritmo 7: Estructura básica con flujo de lectura


Invertir(Scanner leer)
if ((parametros = algun valor))
// caso base que termina la recursión ;
......... ;
else
// caso recursivo ;
......... ;

Como este programa no devuelve nada dejamos el tipo de retorno como void. Luego definimos el caso base
que es cuando se llega el fin de los datos obteniendo el algoritmo 8:
Ahora procedemos con el proceso recursivo. Definimos una cadena donde leer la lı́nea de texto. Luego
llamamos a la función recursiva. Cuando termina, procedemos a desplegar en la pantalla los datos (algoritmo
9). Los datos para probar están en el archivo de entrada que se muestra y el resultado se detalla en el archivo
de salida.
13.4 Invertir los datos de entrada 271

Algoritmo 8: Estructura con caso base


Invertir(Scanner leer)
if (es fin de archivo)
return ;
else
// caso recursivo ;
......... ;

Algoritmo 9: Programa recursivo para imprimir una cadena en orden inverso


Invertir(Scanner leer)
if (es fin de archivo)
return ;
else
// caso recursivo ;
leer (l) ;
invertir(leer);
imprimir(l);

Archivo: de entrada
esta
es
la
clase
de
programacion

Archivo: de Salida
programacion
de
clase
la
es
esta
En este ejemplo en cada llamada recursiva reducimos el tamaño del problema en uno y se termina cuando
llegamos al final del archivo.
¿Donde se guardaron los valores leı́dos para que el programa efectivamente haga lo pedido?
Respuesta: No se crean múltiples copias del programa. Los valores intermedios se guardan en una estructura
de datos pila que luego los devuelve a medida que se termina la recursión.
272 Capı́tulo 13 Recursividad

13.5. Máximo común divisor


Uno de los algoritmos más antiguos proviene de Euclides. Este algoritmo es para calcular el máximo común
divisor de dos números positivos, y esta definido por la ecuación de recurrencia 13.5.1:

(
a si b = 0
M CD(a, b) = (13.5.1)
M CD(b, a % b) Otros casos

Por ejemplo, el máximo común divisor entre 22 y 8 que escribimos como MCD(22,8) es MCD(22,8) =
MCD(8,6) = MCD(6,2) = MCD(2,0)=2.
Este proceso el el caso de que b sea mayor que 0 puede escribirse recursivamente como mostramos en el
algoritmo 10, En el caso de que a = 0, el máximo comun divisor es 0.

Algoritmo 10: Cálculo recursivo del máximo común divisor


MCD(a,b)
if ((b=0))
return a ;
else
MCD(b,a % b) ;

13.6. La secuencia de Fibonacci


Cuando un problema se puede resolver expresando la solución con una ecuación de recurrencia, es muy
probable que se pueda mejorar su eficiencia usando técnicas comúnmente utilizadas en la Programación
Dinámica.
Veamos un ejemplo. La secuencia de Fibonacci se construye mediante la suma de los dos términos anteriores.
Los primeros números de la secuencia son: 0, 1, 1, 2, 3, 5, 8, 13, 21, ... Formalmente la expresamos con la
ecuación de recurrencia:

 0
 si n = 0
f (n) = 1 si n = 1 (13.6.1)

 f (n − 1) + f (n − 2) en otros casos

Como se ve en la definición los casos base (casos que terminan la recursión) se dan cuando n = 0 y cuando
n = 1. Codificando esta solución se obtiene el algoritmo 11:
Al probar la recursión llamamos a F ib(n) y se ve que los resultados son correctos. A medida que crece n, se
observa que el tiempo de ejecución es demasiado largo. La figura 13.1 muestra el proceso recursivo tomando
n = 5.
Como se ve en la figura 13.1 f (3) se calcula en ambos lados, f (2) se calcula 3 veces, ası́ podemos ver
que muchos valores se vuelven a calcular muchas veces. Es aquı́ donde los principios de la programación
dinámica ayudan a resolver este problema de tiempo de proceso.
¿Cuánto tiempo toma hallar f (n) ? Como se ve en el árbol de recursión en cada nodo existen dos caminos
que se abren, lo que nos hace pensar que la complejidad es proporcional a 2n .
13.6 La secuencia de Fibonacci 273

Algoritmo 11: Números de Fibonacci solución recursiva

Fib(n) ;
if (n = 0)
return 0 ;
if (n = 1)
return 1;
return (Fib(n - 1) + Fib(n - 2));

Cálculo del Fibonacci 3

3 4

Cálculo del Fibonacci 3

2 1 3 2

1 0 2 1 1 0

1 0

Figura 13.1 Arbol de la recursion para el Fibonacci 5

Para hallar el tiempo de proceso observemos la solución de la ecuación de recurrencia [Gilles Brassard 1996].

√ √
1 1+ 5 n 1− 5 n
f (n) = √ [( ) −( ) ]
5 2 2

dividiendo f (n) con f (n − 1) se obtiene:



fn (1 + 5)
≈ = 1,61803
fn+1 2
274 Capı́tulo 13 Recursividad
Esto significa que fn > 1,61n , por lo que se tiene 1,61n llamadas a procedimientos, lo que nos lleva a
concluir que es un algoritmo exponencial.

13.6.1. Recordar los valores calculados anteriormente


Caching es una técnica para guardar y recordar los valores calculados con anterioridad y utilizarlos posterior-
mente. Para evitar recalcular los mismos valores repetidamente creamos un vector para almacenar los valores
de f (n) ya calculados (algoritmo 12).

Algoritmo 12: Recordando los valores calculados

Fib(n) ;
if (f [n] = 0 && n > 0)
f[n] = Fib(n - 1) + Fib(n - 2) ;
return f[n]);

El vector que guarda los valores calculados se denomina cache.


En este problema hemos creado un vector f donde guardamos los valores anteriores. Cuando un valor
f (n) = 0 tenemos un n que no ha sido hallado con anterioridad. Los casos base f (1) = 0, f (1) = 1 se
los inicializa el momento de definir el vector.
¿Cuál es la complejidad de este algoritmo? Si hacemos un árbol para ver como se evalúa la recursión veremos
que una vez que se tiene un valor anterior termina eliminando todas las ramas de este valor. Esto nos da un
tiempo proporcional a n. Esto equivale a decir un tiempo lineal.
Hemos convertido el tiempo exponencial O(1,61n ) a un tiempo lineal, O(n).
El método general de explı́citamente guardar los resultados de llamadas recursivas para evitar el recálculo es
una técnica de la programación dinámica. Sin embargo en algoritmos como quicksort no tiene sentido porque
en las llamadas se tienen diferentes parámetros.
Utilizar caching tiene sentido cuando el espacio que ocupan diferentes parámetros es pequeño y podemos
disponer de espacio de almacenamiento extra. En nuestro caso solo almacenamos un entero entre 0 y n por
los que hay solo O(n) valores que guardar en el cache.
Un gasto lineal de memoria es una buena compensación para reducir el tiempo exponencial.

13.6.2. Eliminando la recursión


Si analizamos el problema anterior vemos que solo es necesario sumar dos valores anteriores con lo que
podemos simplemente hacer un programa iterativo como se muestra en el algoritmo 13:

Algoritmo 13: Recordando los valores calculados

f[0]=0;
f[1]=1;
for (i=[2,n))
f[i] = f[i - 1] + f[i - 2];

En este código se ve más claramente que O(n) = n que muestra que el tiempo de proceso es lineal.
13.7 Coeficientes binomiales 275

13.6.3. Eliminando el almacenamiento de los cálculos intermedios


Un análisis detallado nos muestra que no es necesario guardar todos los valores intermedios. Esto porque la
recurrencia solo depende de los dos valores anteriores. Para reducir la memoria utilizada solo es necesario
definir dos variables temporales para almacenar los valores anteriores permitiendo escribir un programa
iterativo simple (algoritmo 14).

Algoritmo 14: Solución iterativa

anterior1 = 1, anterior2 = 0, proximo = 0;


for (i=(2,n])
proximo = anterior1 + anterior2;
anterior2 = anterior1;
anterior1 = proximo;
imprimir ( return (anterior2 + anterior1));

En el algoritmo 14 es más visible la complejidad lineal. Es un solo ciclo que va desde 1 hasta n que claramente
tiene O(n) = n.

13.7. Coeficientes binomiales


Los coeficientes binomiales [Ronald L. Graham 1994] son otro ejemplo de eliminar la recursión especifican-
do el orden de evaluación. Los coeficientes binomiales son una forma de hallar las combinaciones e indicar
el número de maneras de escoger k elementos de n posibilidades. Y se escriben como:
!
n
k

Por ejemplo si tenemos 3 elementos abc las formas diferentes de escoger 2 elementos son ab, ac, bc.
matemáticamente se calcula con la fórmula:
!
n n!
= (13.7.1)
k k!(n − k)!

Calcular el factorial implica realizar n multiplicaciones. Recordemos que n! = n(n − 1)(n − 2) . . . 1.


Para eliminar el proceso de multiplicar, que generará números muy grandes y reducir el tiempo en la
multiplicación dado que es mucho mayor que el de la suma, recurriremos a la ecuación 13.7.2:
! ! !
n n−1 n−1
= + (13.7.2)
k k−1 k

Es necesario demostrar que la ecuación 13.7.2 es equivalente a la definición. Demostración:


(n − 1)! (n − 1)!
= +
(k − 1)!(n − k)! k!(n − 1 − k)!

k(n − 1)! (n − k)(n − 1)!


= +
k(k − 1)!(n − k)! k!(n − 1 − k)!(n − k)
276 Capı́tulo 13 Recursividad
k(n − 1)! (n − k)(n − 1)! n!
= + =
k!(n − k)! k!(n − k)! k!(n − k)!

13.7.1. Solución recursiva

Como ya hemos convertido el problema inicial a una recursión podemos aplicar los principios aprendidos.
Ninguna recursión está completa si no conocemos el caso base. ¿Cuáles coeficientes
! binomiales conocemos
n−1
sin calcularlos? Eventualmente el lado derecho nos llevará a . Cuántas formas existen para
0
escoger 0 elementos de un conjunto? Exactamente 1 el conjunto vacı́o.
!
n−1
Si no es convincente también podemos tomar = n − 1 como caso base. El término de la derecha
1
!
k
llegará hasta = 1.
k
Para escribir un programa que resuelva el requerimiento de hallar los coeficientes binomiales, observamos
que hay dos valores que cambian. Esto lleva a deducir que se requiere una matriz de dos dimensiones para
almacenar y recordar los valores anteriores.
El algoritmo 15 que mostramos tiene una matriz x[][] para almacenar los cálculos anteriores.

Algoritmo 15: Cálculo de factores binomiales

CoefBin(n,k) ;
if (x[n][k] > 0)
return x[n][k] ;
if (k = 0)
x[n][k] = 1 ;
return 1 ;
if (k = 1)
x[n][k] = n ;
return n ;
if (k = n)
x[n][k] = 1 ;
return 1 ;
x[n][k] = CoefBin(n - 1, k - 1) + CoefBin(n - 1, k) ;
return x[n][k] ;

13.7.2. Eliminando la recursión

Ahora podemos eliminar la recursión. Para comenzar se inicializa una matriz con los casos base.
¿Qué muestran las condiciones de los casos base?
13.8 Recursiones que requieren tablas 277
!
n
0 1 2 3 4 5
m
0 A
1 B G
2 C - H
3 D - - I
4 E - - - J
5 F - - - - K
Los valores iniciales de la A, B, C.., K son uno, y corresponden a uno de los casos base, el resto de los
valores se los llena con la ecuación 13.7.2.
!
n
0 1 2 3 4 5
m
0 1
1 1 1
2 1 2 1
3 1 3 3 1
4 1 4 6 4 1
5 1 5 10 10 5 1
El algoritmo 16 permite hallar todos los coeficientes binomiales en forma iterativa.

Algoritmo 16: Calculo de factores binomiales iterativamente

// Invariante cada lı́nea debe comenzar y terminar con uno ;


for (i=(0,x.length))
x[i][0]=1 ;
x[i][i]=1 ;
// Invariante cada lı́nea debe sumar 2i ;
for (i=(1,x.length))
for (j=(1,x.length))
x[i][j]=x[i-1][j-1]+x[i-1][j] ;

Una vez que se tiene calculada la matriz x para hallar n tomados de k es suficiente tomar el valor x[n][k].

13.8. Recursiones que requieren tablas


Dada la matriz:

 
1 2 3 4 5
8 7 6 4 5
 
c=1 10 2 3 5
 
 
10 20 30 2 33
2 3 4 1 9
278 Capı́tulo 13 Recursividad
Analice el problema de hallar en forma recursiva una sub matriz de dos por dos cuya suma de elementos sea
mı́nima.
Si se está en una posición i, j la suma de los elementos de la sub matriz se halla con la fórmula: q =
c[i][j] + c[i][j + 1] + c[i + 1][j] + c[i + 1][j + 1]) y la ecuación de recurrencia asociada es:



 0 si i > 3

 0 si j > 3
f (i, j) =
 min(q, f (i + 1, j), f (i, j + 1))
 para 0 ≤ i ≤ 4

para 0 ≤ j ≤ 4

La solución recursiva de esta ecuación de recurrencia requiere que se recorra todas las filas y columnas. Para
esto llamaremos recursivamente a la función incrementando la fila y la columna. El algoritmo 17 muestra la
solución.

Algoritmo 17: Solución recursiva


f(i,j)
if (i > 5)
return 0 ;
if (j > 4)
return 0 ;
q=c[i][j]+c[i][j+1]+c[i+1][j]+c[i+1][j+1] ;
minimo=Math.min(minimo,q,f(i+1,j),f(i,j+1)) ;
return minimo ;

El resultado queda en la variable mı́nimo y es 16, que es la matriz de dos por dos cuya suma de elementos es
más pequeña. Está formada por elementos que comienzan en las filas i = 1 y j = donde 6 + 5 + 2 + 3 = 16.
En cuanto a la utilización de memoria podemos decir que el programa recursivo tiene que almacenar todos
los valores de i, j en la pila de recursión. Por lo que en relación al espacio de memoria utilizado es claro que
los programas iterativos utilizan menos memoria.

13.9. Puntos a tomar en cuenta


La recursión es una técnica en la cual los métodos se llaman a si mismos.
Los métodos recursivos tienen dos casos: caso base, y caso recursivo donde se reduce el tamaño de la
instancia.Esta información se almacena en la parte superior de una pila y estos datos son retornados
cuando se termina la recursión.
La recursión es muy útil para representar mucha funciones matemáticas. Las soluciones recursivas
generalmente igualan con las definiciones matemáticas.
13.10 Generación de objetos combinatorios 279

13.10. Generación de objetos combinatorios


13.10.1. Generación de secuencias

La recursión es una herramienta conveniente para escribir programas que generan elementos de un conjunto
finito. Por ejemplo escribir todas las secuencias de longitud n, que se componen de números 1..k. Cada
secuencia debe imprimirse una sola vez, ası́ que se imprimirán k n secuencias. Por ejemplo si k=2 y n= 3 se
generaran las siguientes secuencias:

1 1 1
1 1 2
1 2 1
1 2 2
2 1 1
2 1 2
2 2 1
2 2 2

Para construir una solución definimos un arreglo de tamaño n y el valor de k y una variable t que controla que
lleguemos a formar una secuencia. En cada recursión verificamos si se ha llegado a encontrar una solución.
Esto se da cuando t = n − 1. Es conveniente mantener una función separada para incorporar todos los
procesos necesarios para procesar una solución.
El procedimiento se muestra en el algoritmo 18

Algoritmo 18: Generar secuencias


GenerarSecuencias()
int j;
if (esSolucion(n))
procesarSolucion() ;
return ;
else
for (j=[1,k])
t=t+1;
a[t]=j;
GenerarSecuencias() ;
t=t-1 ;

Este procedimiento puede generar secuencias de un tamaño arbitrario. La solución se da cuando el valor de
n toma el valor correspondiente al tamaño de la secuencia buscada. Sin embargo para generar secuencias
de un tamaño pequeño es más simple codificar varios ciclos anidados. Por ejemplo si queremos calcular las
secuencias de tamaño 3, cuyos valores están en un vector a, se construye 3 ciclos como mostramos en el
algoritmo 19.
280 Capı́tulo 13 Recursividad

Algoritmo 19: Generar secuencias iterativamente

a={a,b,c,d} ;
n=4 ;
for (i=[0,n])
for (j=[0,n])
for (k=[0,n])
imprimir a[i],a[j],a[k] ;

13.10.2. Generación de las permutaciones de una secuencia

Las permutaciones de una secuencia son n!, por ejemplo si tenemos la secuencia 123 todas sus permutaciones
son 1 · 2 · 3 = 6 y son: 231, 321, 312, 132, 213, 123
Si tenemos un arreglo a = {1, 2, 3} de longitud n definidos como variables globales, con el algoritmo 20 se
hallan todas las permutaciones. La condición esSolucion() ocurre cuando n = 1. El proceso de la solución
solo consiste en mostrar el vector. En este algoritmo la función swap intercambia dos elementos del vector.

Algoritmo 20: Generar las permutaciones de una secuencia


Permutar(n)
if (esSolucion(n))
procesarSolucion() ;
return ;
for (i = [0,n))
swap(i, n-1) ;
Permutar( n-1) ;
swap(i, n-1) ;

En el programa de ejemplo que se muestra, se ha generado un vector, en forma automática para probar el
algoritmo 20.
En muchas ocasiones se quiere simplificar la programación. Para un vector de tres elementos es necesarios
realizar tres ciclos tal como se mostró al generar todas las secuencias. La condición para generar todas las
permutaciones es que no existan elementos repetido en la secuencia. Para verificar que no estamos incluyendo
elementos repetidos, es decir que un mismo elemento no se puso dos veces en la secuencia, simplemente
verificamos que los ı́ndices sean diferentes. Esto nos da el algoritmo 21.

13.10.3. Generación de todos los subconjuntos

Escribir un programa para hallar todos los subconjuntos. Por ejemplo tomemos el conjunto a = {1, 2, 3}.
Todos los subconjunto que podemos construir son:

{1 2 3}, {1 2}, {1 3}, {2 3}, {1}, {2}, {3}, {}


13.10 Generación de objetos combinatorios 281

Algoritmo 21: Generar todas las permutaciones

a={a,b,c} ;
n=3 ;
for (i=[0,n))
for (j=[0,n))
for (k=[0,n))
if (i!=j AND i!=k AND j!=k)
//las tres variables deben ser distintas ;
imprimir a[i],a[j],a[k] ;

Para generar todo los subconjuntos comenzamos definiendo un vector booleano del tamaño del conjunto, este
vector lo hemos denominados conjuntos. Luego se llama al algoritmo 22 para generar todos los subconjuntos.
Cuando se llama a la función esSolucion se verifica que hemos llegado al final del vector booleano. En esta
función mostramos la solución considerando solamente los valores que en conjuntos tengan verdadero (true).
Todos los subconjuntos de un conjunto de cardinalidad n son 2n . El algoritmo 22 genera todos los sub-
conjuntos creando un vector booleano, en el cual vamos marcando todos los elementos que estarán en el
subconjunto. El parámetro posicionActual debe comenzar en cero.

Algoritmo 22: Generar todos los subconjuntos


imprimeSubconjuntos(conjuntos, posicionActual)
if (esSolucion(conjuntos, posicionActual))
procesarSolucion(conjuntos) ;
else
conjuntos[posicionActual] = true ;
imprimeSubconjuntos(conjuntos, posicionActual + 1) ;
conjuntos[posicionActual] = false ;
imprimeSubconjuntos(conjuntos, posicionActual + 1) ;

Una forma simple de generar todas los subconjuntos es creando un contador donde cada dı́gito binario
representa un elemento del subconjunto. Si generamos todos los números de tres dı́gitos binarios con un
contador se tiene:

{111}, {110}, {101}, {100}, {011}, {010}, {001}, {000}

Si la posición 0 corresponde al primer elemento, la posición 1 al segundo elemento, y la posición 3 al tercer


elemento; al mostrar todos los elementos del conjunto inicial que corresponde a los bits del contador que
están en 1 se obtienen los subconjuntos buscados. El resultado que corresponde a los contadores anteriores
es:

{123}, {12}, {13}, {1}, {23}, {2}, {3}, {}


282 Capı́tulo 13 Recursividad
Para esto es suficiente crear un contador y verificar cuales bits están en 1. La posición nos indica cual
elemento pertenece al subconjunto. Esta solución iterativa se ve en el algoritmo 23.

Algoritmo 23: Generar todos los subconjuntos iterativamente

conjunto = {1, 2, 3} ;
nbits = conjunto.length ;
max = 1 << conjunto.length ;
for (int i=[0,max))
for ( j=[0,nbits))
if ( ((i&(1 << j)) > 0))
imprimir(conjunto[j]) ;

13.10.4. Hallar todos los subconjuntos que sumen un valor

En este problema nos piden hallar todos los subconjuntos que sumen un valor dado. Por ejemplo sea
a = {11, 13, 24, 7, 3, 4} y nos piden hallar todos los subconjuntos que sumen m = 31. Estos subconjuntos
son {11, 13, 7}, {11, 13, 3, 4}, {24, 7}, {24, 3, 4}. Una posible solución es generar todos los subconjuntos y
al encontrar uno que sume el valor buscado presentar la solución. Este proceso serı́a tal como se explicó en
el proceso de generar todos los subconjuntos.
Sin embargo, en muchas soluciones ya podemos saber que este subconjunto no producirá una solución, dado
que ya excedió la suma máxima, por lo que no es necesario continuar con el proceso. Esto se hace controlando
que antes de hacer una llamada recursiva el valor de la suma no exceda el valor buscado. El algoritmo
24 muestra esta estrategia. Esto puede reducir significativamente el tiempo de proceso. Este problema se
denomina subset sum y se presentará luego como un ejercicio de programación dinámica que provee una
solución aún más eficiente. En este algoritmo hay que hacer notar que se debe comenzar con cero y imprimir.

Algoritmo 24: Hallar los subconjuntos que suman un valor


imprimeSubconjuntos(conjuntos, posicionActual)
if (esSolucion(conjuntos, posicionActual))
procesarSolucion(conjuntos) ;
else
conjuntos[posicionActual] = true ;
s+=a[posicionActual] ;
if (s <= m)
imprimeSubconjuntos(conjuntos, posicionActual + 1) ;
conjuntos[posicionActual] = false ;
s-=a[posicionActual];
imprimeSubconjuntos(conjuntos, posicionActual + 1) ;
13.11 Ejercicios 283

13.11. Ejercicios
Para cada una de las recurrencias presentadas: (a) Escriba un programa que resuelva recursivamente la
ecuación. (b) Escriba un programa que resuelva iterativamente la ecuación. (c) Pruebe el tamaño máximo
de conjunto que puede resolver.

1. Escriba un programa que calcule an en un tiempo de proceso proporcional a O(log n). Considere las
siguientes propiedades:


 1 si n = 0
P ot(a, n) = an/2 an/2 cuando n es par
an−1 a

 cuando n es impar

2. Escriba un programa que escriba la representación decimal de un número. Para la descomposición debe
utilizar recursión.
3. Escriba un programa que escriba la representación binaria de un número de 16 bits. Para la descompo-
sición debe utilizar recursión.
4. Escriba un programa recursivo que imprima la suma de los dı́gitos de un número.
5. Escriba un programa para hallar f(n)
(
0 si n ≤ 0
f (n) =
2 ∗ f (n − 1) + f (n − 2) + f (n − 3) + 1 otros casos

6. Sea

p = {70, 55, 13, 2, 99, 2, 80, 80, 80, 80, 100, 19, 7, 5, 5, 5}

y la siguiente ecuación recursiva:

(
0 si n = 0
f (n) =
max(p(n − 1) + f (n − 1), 1 + p(n)) otros casos

Halle el valor máximo.


7. Sea

p = {70, 55, 13, 2, 99, 2, 80, 80, 80, 80, 100, 19, 7, 5, 5, 5}

y la siguiente ecuación recursiva:

(
0 si n = 0
f (n) =
max(p(i) + f (n − 1), 1 + p(i)) para 1 ≤ i ≤ n

Halle el valor máximo.


8. Sea
284 Capı́tulo 13 Recursividad

p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30}

y la siguiente ecuación recursiva:

(
0 si n = 0
f (n) =
max(p(i) + f (n − 1)) para 1 ≤ i ≤ n

Halle el valor máximo.


9. Consideremos un conjunto A con n elementos, el número total de subconjuntos que podemos formar es
2n . Nos preguntamos, si construimos conjuntos con grupos de k subconjuntos de A, si la unión de los
conjuntos construidos, nos da el conjunto original, excluyendo el subconjunto vacı́o. Adicionalmente se
quiere conocer cuántos de estos conjuntos se pueden formar.
Para comprender el problema considere el conjunto p = {1, 2, 3, 4} todos los conjuntos de 2 elementos
que podemos formar talque la unión de los mismos de el conjunto original, estos son:

1 {(1),(2,3,4)}
2 {(2),(1,3,4)}
3 {(3),(2,1,4)}
4 {(4),(2,3,1)}
5 {(1,2),(3,4)}
6 {(1,3),(2,4)}
7 {(1,4),(2,3)}

La unión de los conjuntos (1, 2, 3) y (3, 2) no es una solución valida porque la unión no reproduce el
conjunto original.
Estos números se denominan números de Stirling [Ronald L. Graham 1994] de segundo orden. Para
calcular S(n, k) donde k es el número de elementos o partes, se utiliza la siguiente recurrencia:

S(n, k) = S(n − 1, k) + kS(n − 1, k)

Hay que tomar en cuenta que los lı́mites son: S(n, 1) = 1, y S(n, n) = 1.
10. Hallar Cn que es el número de formas distintas de agrupar n + 1 factores mediante paréntesis. Para n =
3 por ejemplo, tenemos las siguientes cinco formas distintas de agrupar los cuatro factores:
((ab)c)d (a(bc))d (ab)(cd) a((bc)d) a(b(cd))
Los números Catalanes nos permiten hallar este resultado y son los números que están asociados a la
fórmula
n−1
!
X 1 2n
Cn = Ck Cn−1−k =
n+1 n
k=0

Los primeros números Catalanes son: 1,1,2,5,14,42,132,429


13.12 Programas mencionados en el texto 285

13.12. Programas mencionados en el texto


13.12.1. Cálculo del factorial

import java . u t i l . Scanner ;


/∗
∗ Autor Jorge Teran
∗/

public class CalculoFactorial {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( F a c t o r i a l ( 5 ) ) ;
}

p r i v a t e s t a t i c long F a c t o r i a l ( i n t n ) {
i f ( n ==0) / / caso base
return 1;
r e t u r n n∗ F a c t o r i a l ( n −1);
}
}

13.12.2. Invertir un archivo de entrada

import java . u t i l . ∗ ;

/∗
∗ Autor Jorge Teran
∗/

public c l a s s RecursTexto {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r l e e r = new S c a n n e r ( System . i n ) ;
invertir ( leer );
}
s t a t i c void i n v e r t i r ( Scanner l e e r ){

i f ( l e e r . hasNext ( ) ) {
String l = l e e r . next ( ) ;
invertir ( leer );
System . o u t . p r i n t l n ( l ) ;
}
286 Capı́tulo 13 Recursividad
}
}

13.12.3. Fibonacci solución recursiva

/∗
∗ S o l u c i o n r e c u r s i v a a l p r o b l e m a de F i b o n a c c i
∗ Autor Jorge Teran
∗/

public class FibRecursivo {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( F i b r ( 4 5 ) ) ;

s t a t i c long F i b r ( i n t n ) {
i f ( n == 0 )
return 0;
i f ( n == 1 )
return 1;
r e t u r n ( F i b r ( n − 1) + F i b r ( n − 2 ) ) ;
}
}

13.12.4. Fibonacci solución con caching

/∗
∗ S o l u c i o n con c a c h i n g a l p r o b l e m a de F i b o n a c c i
∗ Autor Jorge Teran
∗/

publ i c c l a s s FibCaching {
s t a t i c l o n g [ ] f = new l o n g [ 1 0 0 ] ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
f [0] = 0;
f [1] = 1;
System . o u t . p r i n t l n ( F i b c ( 4 5 ) ) ;
}
s t a t i c long Fib c ( i n t n ) {
13.12 Programas mencionados en el texto 287

i f ( f [ n ] == 0 && n > 0 )
f [ n ] = Fib c ( n − 1) + Fib c ( n − 2 ) ;
return f [n ];
}
}

13.12.5. Fibonacci solución no recursiva

/∗
∗ S o l u c i o n no r e c u r s i v a , a l p r o b l e m a de F i b o n a c c i
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s FiBNoRecursivo {
s t a t i c l o n g [ ] f = new l o n g [ 1 0 0 ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

System . o u t . p r i n t l n ( F i b n r ( 4 5 ) ) ;

s t a t i c long Fib nr ( i n t n ) {
f [0] = 0;
f [1] = 1;
f o r ( i n t i = 2 ; i <= n ; i ++)
f [ i ] = f [ i − 1] + f [ i − 2 ] ;
return f [n ];
}
}

13.12.6. Fibonacci solución utilizando solo dos variables

/∗
∗ Autor Jorge Teran
∗/
p u b l i c c l a s s FibGuardaDos {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

System . o u t . p r i n t l n ( F i b d v ( 4 5 ) ) ;
288 Capı́tulo 13 Recursividad
}

s t a t i c long Fib dv ( i n t n ) {
long back1 = 1 , back2 = 0 , n e x t = 0 ;
f o r ( i n t i = 2 ; i <= n ; i ++) {
n e x t = back1 + back2 ;
back2 = back1 ;
back1 = n e x t ;
}
r e t u r n ( back2 + back1 ) ;
}
}

13.12.7. Coeficientes binomiales solución recursiva

import java . u t i l . Arrays ;

/∗
∗ Autor Jorge Teran
∗/

public c l a s s CoefBinomialRecursivo {
s t a t i c i n t n = 22;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( C o e f B i n ( 2 0 , 1 5 ) ) ;
}

p u b l i c s t a t i c long CoefBin ( i n t n , i n t k ) {
i f ( k == 0 ) {
return 1;
}
i f ( k == 1 ) {
return n;
}
i f ( k == n ) {
return 1;
}
r e t u r n CoefBin ( n − 1 , k − 1) + CoefBin ( n − 1 , k ) ;
}
}
13.12 Programas mencionados en el texto 289

13.12.8. Coeficientes binomiales con caching

import java . u t i l . Arrays ;

/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s CoefBinomialCaching {
s t a t i c i n t n = 22;
s t a t i c l o n g [ ] [ ] x = new l o n g [ n ] [ n ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( C o e f B i n ( 2 0 , 1 5 ) ) ;
for ( long [ ] i : x )
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( i ) ) ;
}

p u b l i c s t a t i c long CoefBin ( i n t n , i n t k ) {
i f ( x [ n ] [ k ] > 0)
return x[n ][ k ];
i f ( k == 0 ) {
x [ n ][ k ] = 1;
return 1;
}
i f ( k == 1 ) {
x[n][k] = n;
return n;
}
i f ( k == n ) {
x [ n ][ k ] = 1;
return 1;
}
x [ n ] [ k ] = CoefBin ( n − 1 , k − 1) +
CoefBin ( n − 1 , k ) ;
return x[n ][ k ];
}
}

13.12.9. Coeficientes binomiales solución iterativa


290 Capı́tulo 13 Recursividad
import java . u t i l . Arrays ;

/∗

∗ Autor Jorge Teran


∗/

p u b l i c c l a s s CoefBinomial {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] [ ] x = new i n t [ 6 ] [ 6 ] ;
CoefBin ( x ) ;
for ( int [] i : x)
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( i ) ) ;
}

p u b l i c s t a t i c void CoefBin ( i n t [ ] [ ] x ) {
/ / I n v a r i a n t e c a d a l i n e a d e b e c o m e n z a r con uno
f o r ( i n t i = 0 ; i < x . l e n g t h ; i ++)
x [ i ][0] = 1;
/ / I n v a r i a n t e c a d a l i n e a d e b e t e r m i n a r con uno
f o r ( i n t i = 0 ; i < x . l e n g t h ; i ++)
x [ i ][ i ] = 1;
f o r ( i n t i = 1 ; i < x . l e n g t h ; i ++)
/ / I n v a r i a n t e c a d a l i n e a d e b e sumar 2∗∗ i
f o r ( i n t j = 1 ; j <= i ; j ++)
x [ i ] [ j ] = x [ i − 1] [ j − 1] + x [ i − 1] [ j ] ;
}
}

13.12.10. Recursión utilizando una tabla

public class RecursionTabla {

/∗
∗ Autor Jorge Teran
∗/

public s t a t i c i n t c [ ] [ ] = {{1 ,2 ,3 ,4 ,5} ,{8 ,7 ,6 ,5 ,4} ,


{1 ,10 ,2 ,3 ,5} , {10 ,20 ,30 ,2 ,33} ,{2 ,3 ,4 ,1 ,9}};
p u b l i c s t a t i c i n t minimo= I n t e g e r . MAX VALUE;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

System . o u t . p r i n t l n ( f ( 0 , 0 ) ) ;
13.12 Programas mencionados en el texto 291

}
private static int f ( int i , int j ) {
i f ( i >3) { r e t u r n 0 ; }
i f ( j >3) { r e t u r n 0 ; }
i n t q=c [ i ] [ j ] + c [ i ] [ j +1]+ c [ i + 1 ] [ j ] + c [ i + 1 ] [ j + 1 ] ;
minimo = Math . min ( q , minimo ) ;
/ / System . o u t . p r i n t l n ( ” i =”+ i +” ”+” j =”+ j +” ”+ q ) ;
f ( i , j +1);
f ( i +1 , j ) ;
r e t u r n minimo ;
}
}

13.12.11. Generar todas las secuencias de 1..k

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;

/∗
∗ Autor Jorge Teran
∗/

public class GenerarSecuencias {

s t a t i c i n t n =3;
s t a t i c i n t t =0 , k = 2 ;
static i n t [ ] a=new i n t [ n ] ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
t =−1;
generar ( t ) ;
}

p r i v a t e s t a t i c void generar ( i n t t ) {
int i , j ;
i f ( esSolucion ( t )) {
procesarSolucion ( ) ;
return ;
}
else
{
f o r ( j = 1 ; j <=k ; j ++){
t = t +1;
a [ t ]= j ;
292 Capı́tulo 13 Recursividad
generar ( t ) ;
t = t −1;
}

}
}
p r i v a t e s t a t i c void procesarSolucion ( ) {
f o r ( i n t i = 0 ; i < n ; i ++) {
System . o u t . p r i n t ( a [ i ] ) ;
}
System . o u t . p r i n t l n ( ” ” ) ;
}

public s t a t i c boolean esSolucion ( i n t posicionActual ) {


r e t u r n ( p o s i c i o n A c t u a l == n−1 ) ;
}
}

13.12.12. Hallar todas las permutaciones

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;

/∗
∗ Autor Jorge Teran
∗/

public c l a s s Permutar {
s t a t i c i n t n =3;
s t a t i c i n t t =0;
static i n t [ ] a=new i n t [ n ] ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
f o r ( i n t i = 0 ; i <n ; i ++)
a [ i ]= i +1;
t =0;
permutar ( n ) ;
}
public s t a t i c boolean esSolucion ( i n t posicionActual ) {
r e t u r n ( p o s i c i o n A c t u a l == 1 ) ;
}
p r i v a t e s t a t i c void permutar ( i n t n ) {
i f ( esSolucion (n )) {
procesarSolucion ( ) ;
13.12 Programas mencionados en el texto 293

return ;
}
f o r ( i n t i = 0 ; i < n ; i ++) {
swap ( i , n − 1 ) ;
per mutar ( n −1);
swap ( i , n − 1 ) ;
}
}
p r i v a t e s t a t i c v o i d swap ( i n t i , i n t j ) {
i n t temp=a [ i ] ;
a [ i ]= a [ j ] ;
a [ j ] = temp ;

}
p r i v a t e s t a t i c void procesarSolucion ( ) {
f o r ( i n t i = 0 ; i < n ; i ++) {
System . o u t . p r i n t ( a [ i ] ) ;
}
System . o u t . p r i n t l n ( ” ” ) ;
}
}

13.12.13. Hallar todos los subconjuntos recursivamente

public c l a s s Subconjuntos {

/ ∗ Programa p a r a h a l l a r t o d o s
∗ los subconjuntos
∗ @author J o r g e T e r a n
∗/

p r i v a t e s t a t i c void procesarSolucion ( boolean [ ] conjuntos ) {


System . o u t . p r i n t ( ” { ” ) ;
f o r ( i n t i = 0 ; i < c o n j u n t o s . l e n g t h ; i ++)
i f ( conjuntos [ i ])
System . o u t . p r i n t ( ( i + 1 ) + ” ” ) ;
System . o u t . p r i n t l n ( ” } ” ) ;
}

public s t a t i c boolean esSolucion ( boolean [ ] conjuntos ,


int posicionActual ) {
r e t u r n ( c o n j u n t o s . l e n g t h == p o s i c i o n A c t u a l ) ;
}
294 Capı́tulo 13 Recursividad

public s t a t i c void imprimeSubconjuntos ( boolean [ ] conjuntos ,


int posicionActual ) {

i f ( esSolucion ( conjuntos , posicionActual ) ) {


procesarSolucion ( conjuntos ) ;
} else {
conjuntos [ posicionActual ] = true ;
imprimeSubconjuntos ( conjuntos , posicionActual + 1 ) ;
conjuntos [ posicionActual ] = false ;
imprimeSubconjuntos ( conjuntos , posicionActual + 1 ) ;
}
}

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
b o o l e a n [ ] c o n j u n t o s = new b o o l e a n [ 3 ] ;
imprimeSubconjuntos ( conjuntos , 0 ) ;
}

13.12.14. Hallar todos los subconjuntos iterativamente

p u b l i c c l a s s SubConj {

/ ∗ Programa p a r a h a l l a r t o d o s
∗ los subconjuntos
∗ @author J o r g e T e r a n
∗/
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
char [ ] conjunto = { ’1 ’ , ’2 ’ , ’3 ’};
int nbits = conjunto . length ;
i n t max = 1 << c o n j u n t o . l e n g t h ;
f o r ( i n t i = 0 ; i < max ; i ++) {
f o r ( i n t j = 0 ; j <n b i t s ; j ++){
i f ( ( i & (1<< j ) ) >0)
System . o u t . p r i n t ( c o n j u n t o [ j ] + ” ” ) ;
}
System . o u t . p r i n t l n ( ) ;
}
}
}
13.12 Programas mencionados en el texto 295

13.12.15. Hallar todos los subconjuntos que suman un valor

public c l a s s Subconjuntos {

/ ∗ Programa p a r a h a l l a r t o d o s
∗ los subconjuntos
∗ que suman un v a l o r
∗ @author J o r g e T e r a n
∗/

s t a t i c i n t [ ] a ={11 ,13 ,24 ,7 ,3 ,4};


s t a t i c i n t s =0 ,m= 3 1 ;
p r i v a t e s t a t i c void procesarSolucion (
boolean [ ] conjuntos ) {
i f ( s ! =m) {
return ;
}
System . o u t . p r i n t ( ” { ” ) ;
f o r ( i n t i = 0 ; i < c o n j u n t o s . l e n g t h ; i ++)
i f ( conjuntos [ i ])
System . o u t . p r i n t ( a [ i ] + ” ” ) ;
System . o u t . p r i n t l n ( ” } ” ) ;
}

public s t a t i c boolean esSolucion ( boolean [ ] conjuntos ,


int posicionActual ) {
r e t u r n ( c o n j u n t o s . l e n g t h == p o s i c i o n A c t u a l ) ;
}
public s t a t i c void imprimeSubconjuntos (
boolean [ ] conjuntos , i n t posicionActual ) {
i f ( esSolucion ( conjuntos , posicionActual ) ) {
procesarSolucion ( conjuntos ) ;
} else {
s += a [ p o s i c i o n A c t u a l ] ;
conjuntos [ posicionActual ] = true ;
i f ( s<=m)
imprimeSubconjuntos ( conjuntos , posicionActual +1);
conjuntos [ posicionActual ] = false ;
s−=a [ p o s i c i o n A c t u a l ] ;
imprimeSubconjuntos ( conjuntos , posicionActual +1);
}
}
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
296 Capı́tulo 13 Recursividad
b o o l e a n [ ] c o n j u n t o s = new b o o l e a n [ a . l e n g t h ] ;
imprimeSubconjuntos ( conjuntos , 0 ) ;
}
}

13.13. Ejercicios Resueltos


13.13.1. Cálculo de potencia

/∗
∗ Autor Jorge Teran
∗/

public class Potencia {


p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( P o t ( 2 , 8 ) ) ;
}
p r i v a t e s t a t i c i n t Pot ( i n t a , i n t n ) {
i f ( n == 0 ) r e t u r n 1 ;
i f ( n %2 == 0 ) r e t u r n P o t ( a ∗ a , n / 2 ) ;
e l s e r e t u r n P o t ( a , n −1)∗ a ;
}
}

13.13.2. Descomponer un número

/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s Descomponer {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Imprimir (1234);
}
p r i v a t e s t a t i c void Imprimir ( i n t n ) {
i f ( n < 1 0 ) System . o u t . p r i n t l n ( n ) ;
else {
Imprimir ( n / 1 0 ) ;
System . o u t . p r i n t l n ( n %10);

}
13.13 Ejercicios Resueltos 297

}
}

13.13.3. Representación binaria de un número

public c l a s s RepBinaria {

/∗
∗ Autor Jorge Teran
∗/

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Imprimir (255 ,16);
}
p r i v a t e s t a t i c void Imprimir ( i n t n , i n t l ) {
i f ( l == 0 ) r e t u r n ;
else {
I m p r i m i r ( n>>1, l − 1 ) ;
System . o u t . p r i n t ( n %2);

}
}
}
14 Backtracking
Podemos resolver muchos problemas utilizando técnicas de búsqueda completa para llegar a la solución
óptima, sin embargo la complejidad en tiempo puede ser muy grande. Para algunas aplicaciones puede
convenir tomar un tiempo extra para hallar la solución óptima. Un ejemplo es la prueba de programas, donde
se puede probar la correctitud probando todas las posibles entradas.
Es muy importante entender cuán grande es un millón. Un millón de permutaciones equivale a construir
todos los arreglos de apenas 10 u 11 objetos. También es equivalente a construir todos los subconjuntos de
20 elementos. Para resolver problemas con mayor número de elementos se requiere que cuidadosamente
reduzcamos el espacio de búsqueda a los elementos que realmente interesan.
Backtracking también llamada método de retroceso o vuelta atrás es una técnica para listar todas las posibles
soluciones para un problema combinatorio.
Generalmente se escoge una opción, y esto genera una serie de nuevas opciones. Para entender el proceso de
retroceso consideremos el siguiente árbol en el cual buscamos una solución.

B C

D E F G

Buscamos una solución, y comenzamos por la raı́z A. En el caso que A no sea una solución vamos a B. Si
no obtenemos una solución pasamos a D. En caso de no encontrar una solución, como no es posible seguir
avanzando, retrocedemos a B. Esto se denomina backtrack. Luego bajamos a E, si no llegamos a la solución
retrocedemos hasta A. Y ası́ sucesivamente continuamos hasta obtener una solución o terminar de recorrer
todos los nodos.
Este método se ha generalizado para resolver problemas de búsqueda completa, reduciendo el espacio de
búsqueda.

299
300 Capı́tulo 14 Backtracking

14.1. Problema Laberinto


Dado un laberinto en la forma de una matriz de tamaño n x n, con todos sus elementos en 0 o en 1, decimos
que el 0 representa un lugar seguro y el 1 un lugar inseguro [csgeek 2016]. Esto significa, si estamos en una
celda no podremos seguir a otra celda que tenga un uno.
Comenzando en la celda (0,0), imprima un camino seguro, si existe, para llegar a la última celda (n-1,n-1).
La restricción es que solo se puede mover una celda a la derecha o hacia abajo.
Dada la matriz de siguiente:

0 0 1 0
1 0 0 0
0 1 0 1
1 0 0 0

Si D es ir a la derecha y A hacia abajo, un camino seguro es: (DADAAD).

14.1.1. Solución
Se construye la solución en un arreglo bidimensional que denominamos camino en el que se coloca un 1 en
las celdas que constituyen la solución.
Existen dos opciones:

1. Suponga que se escoge ir a la derecha y se puede ir a ese lugar porque la ubicación es segura. En
este punto verificamos recursivamente si existe un camino a partir de esa celda. Si existe un camino
marcamos esta celda con 1 y otra vez escogemos una de las dos soluciones.
2. Si no existe un camino entonces volvemos a la celda anterior, que fue el recorrido a la derecha para
intentar ir hacia abajo, se continúa recursivamente probar a la derecha o ir hacia abajo.

14.1.2. Implementación
Sea m la matriz del laberinto y N el tamaño, construimos el método validarCelda para verificar si podemos
ir a una celda.

Algoritmo 25: Verificar si es una celda válida


validaCelda(m, i,j)
if (i < 0ki ≥ N )
return false ;
if (j < 0kj ≥ N )
return false ;
if (laberinto[i][j] = 1)
return false ;
return true ;

Este algoritmo verifica que si la celda a la que se llega está dentro del laberinto y si el lugar de destino es
válido. La implementación del algoritmo (26) con retroceso es:
14.2 Problema de las 8 reinas 301

Algoritmo 26: Buscar la solución


hallaCamino(laberinto, i,j, camino)
if (i = N − 1&j = N − 1)
camino[i][j] = 1 ;
return true;
if (validaCelda(laberinto,i,j))
camino[i][j] = 1;
if (hallaCamino(laberinto,i+1,j,camino))
return true;
if (hallaCamino(laberinto,i,j+1,camino))
return true;
camino[i][j] = 0;
return false;
return false;

14.2. Problema de las 8 reinas


El problema de las 8 reinas es un problema clásico backtracking [Gilles Brassard 1996]. Dado un tablero
de ajedrez con 8 filas y 8 columnas, colocamos una reina en una posición, y queremos colocar las 7 reinas
restantes de tal forma que ninguna de ellas se amenace.
Una reina en ajedrez puede amenazar a cualquier pieza de ajedrez que se ubique en las diagonales, en la
misma columna y fila, como se muestra a continuación:

1 2 3 4 5 6 7 8
1
2
3
4 տ ↑ ր
5 ← ♣ →
6 ւ ↓ ց
7
8

Inicialmente podemos plantear la soluciones como 8 tuplas x1 , x2 , x3 , x4 , x5 , x6 , x7 , x8 donde cada elemento


xi es la columna donde esta la reina xi . Ese planteamiento nos da un espacio de búsqueda de 16, 777, 216(88 )
posibles posiciones para las reinas.
Como cada reina puede amenazar a todas las reinas que están en la misma fila y misma diagonal, cada una
ha de situarse en una fila diferente. Esto reduce el tamaño de la búsqueda a todas las permutaciones de 8 que
es 8!.
302 Capı́tulo 14 Backtracking

14.2.1. Solución

Para plantear una solución se representa las 8 reinas mediante un vector[1-8], teniendo en cuenta que cada
ı́ndice del vector representa una fila y el valor una columna.
Para resolver los problemas de bactracking una técnica es la de construir dos métodos: uno para realizar el
backtracking, un segundo para conocer si la solución hallada es una posible solución válida además de la
llamada principal. En el ejemplo de las 8 reinas creamos primero un método (algoritmo 27) que verifica si la
posible solución es válida.

Algoritmo 27: Verificar si es una posible solucion

VerSiSolucion(col, probarFila) { ;
for (prev = [1,col))
if (f ila[prev] = probarF ilak(abs(f ila[prev] − probarF ila) = abs(prev − col)))
return false
return true ;
}

La condición f ila[prev] == probarF ila verifica que no estemos en la misma fila. La condición
M ath.abs(f ila[prev] − probarF ila) == M ath.abs(prev − col) verifica que no estemos en la misma
diagonal. Se recorre todas las filas hasta la columna en la que estamos para determinar si no hace conflicto
con las reinas ya colocadas. Se devuelve verdadero si es una posible solución en otros casos falso. Esto es
el proceso que reduce el espacio de búsqueda dado que cuando se encuentra una posición incorrecta ya no
continua con la búsqueda.
El método de backtraking consistirá en probar todas las filas y para todas las columnas si es una posición
posible para colocar una reina. Cuando no encuentra una solución posible para una fila, busca en la siguiente
columna. Al retornar devuelve los valores a una posición anterior.
Se pide que se impriman todas las soluciones para el caso que una reina este en una fila y columna especifica,
solo se imprimen las soluciones que contengan una reina en esta fila y columna. Para esto definimos a y b
con los valores iniciales donde colocaremos una reina y llamamos a la función de backtrak que va a generar
todas las soluciones candidatas, como se muestra en el algoritmo 28.

Algoritmo 28: Llamada a la función de retroceso

a=0 ;
b=0 ;
backtrack(1) ;

El método para el backtracking se muestra en el algoritmo 29. Se recorren todas las filas buscando una
columna donde colocar la reina. Cuando se llegó a la fila 8 y es una posible solución se imprime cuando la
solución contiene una reina en la fila y columna que buscamos.
Como ejemplo si se comienza con una reina en la posición 3,3 las soluciones son:
14.2 Problema de las 8 reinas 303

Algoritmo 29: Verificar si es una posible solucion

backtrack(col) { ;
for (probarFila = [1,8])
if (VerSiSolucion(col, probarFila))
fila[col] = probarFila ;
if (col = 8&f ila[b] == a)
imprimir solución ;
else
backtrack(col + 1)

Solucón 1 2 3 4 5 6 7 8
1 2 7 3 6 8 5 1 4
2 4 7 3 8 2 5 1 6
3 7 1 3 8 6 4 2 5
4 7 5 3 1 6 8 2 4

La ubicación de las reinas en el tablero que corresponde a la primera solución es:

1 2 3 4 5 6 7 8
1 ♣
2 ♣
3 ♣
4 ♣
5 ♣
6 ♣
7 ♣
8 ♣
304 Capı́tulo 14 Backtracking

14.3. Programas mencionados en el texto


14.3.1. Hallar el camino

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;
/∗
∗ Autor Jorge Teran
∗/

public class Laberinto {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t l a b e r i n t o [ ] [ ] = {{ 0 , 0 , 1 , 0 } ,
{ 1 ,0 ,0 ,0 } ,
{ 0 ,1 ,0 ,1 } ,
{ 1 ,0 ,0 ,0 }};
caminoEnLaberinto ( l a b e r i n t o ) ;

}
s t a t i c void caminoEnLaberinto ( i n t [ ] [ ] l a b e r i n t o ) {
i n t camino [ ] [ ] = new
int [ laberinto . length ][ laberinto . length ];
int i , j ;
f o r ( i = 0 ; i <l a b e r i n t o . l e n g t h ; i ++) {
f o r ( j = 0 ; j <l a b e r i n t o . l e n g t h ; j ++) {
camino [ i ] [ j ] = 0 ;
}
}

i f ( h a l l a C a m i n o ( l a b e r i n t o , 0 , 0 , camino ) ) {
System . o u t . p r i n t l n ( ” E x i s t e un Camino ” ) ;
i m p r i m i r C a m i n o ( camino ) ;
}
else {
System . o u t . p r i n t l n ( ” No e x i s t e un camino ” ) ;
}
}
s t a t i c boolean validarCelda ( i n t [ ] [ ] l a b e r i n t o , i n t i ,
int j ) {
i f ( i < 0 | | i >= l a b e r i n t o . l e n g t h ) {
return false ;
}
14.3 Programas mencionados en el texto 305

i f ( j < 0 | | j >= l a b e r i n t o . l e n g t h ) {
return false ;
}
i f ( l a b e r i n t o [ i ] [ j ] != 0) {
return false ;
}
return true ;
}

s t a t i c boolean hallaCamino ( i n t [ ] [ ] l a b e r i n t o , i n t i ,
i n t j , i n t [ ] [ ] camino ) {
i f ( ( i == l a b e r i n t o . l e n g t h −1) &&
( j == l a b e r i n t o . l e n g t h −1))
i f ( l a b e r i n t o [ i ] [ j ]==1) r e t u r n f a l s e ; e l s e {
camino [ i ] [ j ] = 1 ;
return true ;
}
if ( validarCelda ( laberinto , i , j )) {
camino [ i ] [ j ] = 1 ;
i f ( h a l l a C a m i n o ( l a b e r i n t o , i +1 , j , camino ) ) {
return true ;
}

i f ( h a l l a C a m i n o ( l a b e r i n t o , i , j +1 , camino ) ) {
return true ;
}
camino [ i ] [ j ] = 0 ;
return false ;
}
return false ;
}
s t a t i c v o i d i m p r i m i r C a m i n o ( i n t [ ] [ ] camino ) {
f o r ( i n t i = 0 ; i <camino . l e n g t h ; i ++) {
f o r ( i n t j = 0 ; j <camino . l e n g t h ; j ++) {
System . o u t . p r i n t ( camino [ i ] [ j ] + ” ” ) ;
}
System . o u t . p r i n t l n ( ) ;
}
}
}

14.3.2. Problema de las reinas


306 Capı́tulo 14 Backtracking
import java . u t i l . ∗ ;

/∗
∗ Autor Jorge Teran
∗ 8 R e i n a s P r o b l e m a de A j e d r e z
∗/

c l a s s Reinas {
p r i v a t e s t a t i c i n t [ ] f i l a = new i n t [ 9 ] ;
p r i v a t e s t a t i c i n t TC , a , b , C o n t a d o r ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
S c a n n e r s c = new S c a n n e r ( System . i n ) ;
a = sc . n e x t I n t ( ) ; / / f i l a
b = s c . n e x t I n t ( ) ; / / columna
f o r ( i n t i = 0 ; i < 9 ; i ++)
f i l a [ i ] = 0;
Contador = 0;
System . o u t . p r i n t f ( ”SOLN COLUMNAS\n ” ) ;
System . o u t . p r i n t f ( ” # 1 2 3 4 5 6 7 8\ n\n ” ) ;
backtrack ( 1 ) ; / / generar 8! s o l u c i o n e s c a n d i d a t a s
i f ( TC > 0 )
System . o u t . p r i n t f ( ” \ n ” ) ;
}

p r i v a t e s t a t i c boolean v e r P o s i b l e s o l u c i o n ( i n t col ,
int probarFila ) {
f o r ( i n t p r e v = 1 ; p r e v < c o l ; p r e v ++) {
/ / ver l a s r e i n a s previamente colocadas
i f ( f i l a [ p r e v ] == p r o b a r F i l a
| | ( Math . a b s ( f i l a [ p r e v ] − p r o b a r F i l a ) ==
Math . a b s ( p r e v − c o l ) ) )
r e t u r n f a l s e ; / / e s s o l . no v á l i d a s i c o m p a r t e n l a misma
/ / f i l a o l a misma d i a g o n a l
}
return true ;
}

p r i v a t e s t a t i c void backtrack ( i n t col ) {


f o r ( i n t p r o b a r F i l a = 1 ; p r o b a r F i l a <= 8 ; p r o b a r F i l a ++)
/ / probamos t o d a s l a s p o s i b l e s f i l a s
i f ( v e r P o s i b l e s o l u c i o n ( col , p r o b a r F i l a ) ) {
/ / s i podemos c o l o c a r
/ / una r e i n a en e s t a
14.3 Programas mencionados en el texto 307

/ / f i l a y columna
f i l a [ c o l ] = p r o b a r F i l a ; / / C o l o c a r l a r e i n a en e s t a
/ / f i l a y columna
i f ( c o l == 8 && f i l a [ b ] == a ) { / / Es una s o l u c i o n
/ / candidata & (a , b) tienen 1 reina
System . o u t . p r i n t f (” %2d %d ” , ++ C o n t a d o r , f i l a [ 1 ] ) ;
f o r ( i n t j = 2 ; j <= 8 ; j ++)
System . o u t . p r i n t f ( ” %d ” , f i l a [ j ] ) ;
System . o u t . p r i n t f ( ” \ n ” ) ;
} else
b a c k t r a c k ( c o l + 1 ) ; / / p r o b a r r e c u r s i v a m e n t e l a proxima
/ / columna
}
}
}
15
15.1.
Un problema sencillo
El sub vector máximo
Para mostrar como un algoritmo se puede ir optimizando sucesivamente hasta obtener una solución eficiente
usaremos el problema del vector contiguo más largo denominado algoritmo de Kadane en honor a su autor
Jay Kadane de la universidad Carnegie Mellon [Bentley 2000].
Dado un vector de números enteros encuentre la máxima suma de cualquier sub vector contiguo que se
encuentre en el vector dado. Por ejemplo si el vector x contiene 10 elementos en la entrada (ver cuadro 15.1):

31 -41 59 26 -53 58 97 -93 -23 82


↑ ↑
2 6
Cuadro 15.1

El programa da como resultado la suma desde x[2]..x[6] 0 sea 187. Si todos fueran números positivos la
solución es muy simple: halle la suma de todos los elementos del vector. La dificultad viene cuando hay
números positivos y negativos. Un número positivo puede compensar varios números negativos que están
con anterioridad.

15.2. Solución trivial


La solución trivial toma todos los posibles subconjuntos y halla la suma y toma el máximo de estas sumas.
Esto es iterar sobre todos los pares de enteros 0 ≤ i, j ≤ b y para cada par halla la suma desde x[i]..x[j] y va
comparando con el máximo cada vez. El algoritmo 30 ejemplifica esto.

Algoritmo 30: Hallar el subconjunto solución trivial

maximo=0 ;
for (i=0,n)
for (j=i,n)
suma=0 ;
for (k=i,j)
suma+=x[k] ;
maximo = max(maximo,suma) ;

Como ve este algoritmo tiene una complejidad O(n3 ). Esto significa que si tenemos un vector de 10000(104 )
3
elementos tendrá que iterar (104 ) veces. Un computadora moderna hace aproximadamente (108 ) operacio-
nes en un segundo. Lo que probablemente va a demorar esta cerca de 104 segundos. Este tiempo es claramente
inaceptable. Sin embargo, para un vector pequeño que podrı́a ser 30 elementos es una solución viable.

309
310 Capı́tulo 15 Un problema sencillo

15.3. Solución cuadrática


Analizando la solución de fuerza bruta vemos que el subconjunto del cual hallamos la suma crece en un
elemento en cada iteración. Por esto no es necesario volver a sumar todos los elementos. Si guardamos
la suma podemos simplemente sumar el próximo elemento, sin necesidad de volver a recorrer todos los
elementos. El algoritmo 31 muestra esta mejora.

Algoritmo 31: Solución cuadrática

maximo=0 ;
for (i=0,n)
suma=0 ;
for (j=i,n)
suma+=x[k] ;
maximo = max(maximo,suma) ;

Este algoritmo tiene un tiempo de proceso proporcional a O(n2 ). Con esta mejora se podrá resolver el caso
de un vector de 100 elementos, porque 1002 significa aproximadamente 104 operaciones que es bastante
menor a la cantidad de operaciones que un procesador puede hacer en un segundo.
Otra forma de resolver el problema puede ser utilizando un vector acumulativo. Vea el algoritmo 32:

Algoritmo 32: Solución cuadrática con vector acumulado

acumulado[n] ;
acumulado[0]=x[0] ;
for i=1,n do
acumulado[i]=acumulado[i-1]+x[i] ;
maximo=0 ;
for i=0,n do
for j=i,n do
suma=acumulado[j]-acumulado[i] ;
maximo = max(maximo,suma) ;

Este algoritmo tiene una complejidad proporcional a n+n2 . Tomando la notación de O grande para el análisis
de complejidad vemos que es un algoritmo que sigue siendo cuadrático O(n2 ).

15.4. Divide y vencerás


Es posible mejorar esta algoritmo mucho más. Se conoce que la mayorı́a de los algoritmos pueden convertirse
de n2 a n log n utilizando la técnica divide y vencerás. Este método indica que hay que dividir el vector en
mitades y recursivamente aplicar la solución a cada mitad.

a b

Ahora resolvemos recursivamente el problema en el vector a y en el b obteniendo un máximo para a y b


15.5 Solución eficiente 311

ma mb

La respuesta es correcta si el máximo esta en el vector a o en el vector b. Hay algunos casos donde la solución
puede estar en la intersección de a y b.

mc

Esta solución comienza dividiendo los datos, en dos vectores, luego hallar el máximo de los vectores y de la
intersección, luego la respuesta será el máximo de los tres valores. Esto lleva a escribir el algoritmo 33:

Algoritmo 33: Solución logaritmica


void maxsum (l,u)
if (l > u)
return 0 //hay cero elementos ;
if (l = u)
return max(0, x[l]) // hay un solo elemento ;
m=(l+u)/2) // el valor del medio ;
// hallar el maximo que cruza a la izquierda ;
lmax=sum=0;
for (i=m;i ≥ 1;i–)
sum+=x[i] ;
lmax=max(lmax,sum) ;
// hallar el maximo que cruza a la derecha ;
rmax=sum=0 ;
for (i=m+1;i < l;i++)
sum+=x[i] ;
rmax=max(rmax,sum) ;
return max(lmax+rmax, maxsum(l,m),maxsum(m+1,u) ;

La llamada para hacer correr este programa seria maxsum(0, n − 1). Este problema claramente es del
tipo 2T (n/2) + n que da una solución en tiempo proporcional a n log n. Obviamente mejor a la solución
cuadrática anterior. Es claro que si el tamaño del vector es del orden de 108 tampoco solucionará el ejercicio
en tiempo razonable.

15.5. Solución eficiente


Ahora se muestra una solución que utiliza el algoritmo más simple que hay en problemas con vectores. Esto
es recorrer el vector de principio a fin, una vez.
Suponga que ha resuelto el problema para x[0...i − 1]. ¿Como se puede extender la solución para que incluya
x[i]? Utilizando un razonamiento similar al de divide y vencerás. El máximo en la posición i será la suma de
los i − 1 elementos (denominaremos maximoHastaAqui) o los elementos hasta la posición i (denominaremos
maximoFinal).

maximoHastaAqui maximoFinal
312 Capı́tulo 15 Un problema sencillo
Utilizando la técnica de guardar la suma de los elementos anteriores llegamos al algoritmo 34.

Algoritmo 34: Solución lineal

maximoHastaAqui=maximoFinal=0 ;
for (i=0,n)
// invariante maximoHastaAqui, maximoFinal son correctos para x[0..i-1] ;
maximoFinal=max(maximoFinal+x[i],0) ;
maximoHastaAqui=max(maximoHastaAqui,maximoFinal) ;

Esta solución es claramente mucho mejor, el tiempo es proporcional a O(n).


Analizando el proceso realizado, destacamos los siguientes principios:

Guardar un estado para evitar el recálculo de los valores. Esta es una forma simple de
programación dinámica. Se utilizo en la solución para guardar valores y no tener que sumarlos
otra vez.
Utilizar las estructuras de datos apropiadas. Se utilizó un vector con el acumulado de la suma
para hallar la suma de un sub vector muy rápidamente.
Algoritmos de recorrido. Algunos problemas pueden resolverse extendiendo la solución de
x[0...i − 1] a x[0...i].
Acumulados. Se escribió una solución que guarda la suma acumulada en una variable.
Lı́mites inferiores de proceso. El programador debe conocer cuales son los lı́mites inferiores
en el tiempo de proceso para un problema dado.

15.6. Extendiendo el problema a dos dimensiones


Ahora consideremos el problema de hallar la sub matriz que tenga la suma máxima. Por ejemplo veamos la
matriz siguiente [Halim and Halim 2013]:

 
0 −2 7 0
9 2 −6 2 
a=
 
−4 1 −4 1 

−1 8 0 −2

La solución trivial para este problema es hallar todas las sub matrices, luego la suma de cada una de ellas
para finalmente escoger la de menor suma. Esto podemos hacer con seis ciclos, los primeros dos para indicar
donde comienza la fila y columna de la sub matriz. Los siguientes dos para indicar donde finaliza. Luego dos
bucles para hallar la suma de la sub matriz. Claramente este algoritmo produce una solución O(n6 ). En este
ejemplo la solución se forma por el rectángulo que comienza en la posición (1, 0) y termina en la posición
(3, 1) con una suma de 15.
Ahora, utilizando los principios del ejemplo anterior, es necesario crear una matriz que acumule los valores
para que luego se puedan utilizar los mismos valores para hallar los valores buscados. Supongamos que
15.6 Extendiendo el problema a dos dimensiones 313
tenemos una tabla inicial y una tabla con valores acumulados con la propiedad que una celda (k, l) contiene
la suma de los valores del rectángulo formado por vértices (0, 0)y (k, l). Veamos un ejemplo, sea la tabla:

a b c d

e f g h

i j k l

m n o p

Ahora suponga que tenemos una sub matriz de la cual queremos hallar la suma de sus elementos:

(i,j)
(k,l)

La sub matriz en la celda (k, l) contendrá la suma a + b + c + e + f + g + i + j + k. Para hallar la suma de


los elementos del área sombreada, hay que restar a este valor los elementos del rectángulo (0, 0), (i − 1, l)
que son los elementos a, b, c cuya suma está en la posición (i − 1, l).
También es necesario restar el rectángulo formado por los vértices (0, 0), (k, j − 1), que comprende los
elementos a, e, i y su suma está en la posición (k, j − 1). Como se ve la posición (0, 0) ha sido restada dos
veces. El valor calculado para el área será:

a + b + c + e + f + g + i + j + k − a − b − c − a − e − i = −a + f + g + j + k

Para eliminar el termino −a sumamos el rectángulo formado desde (0, 0) hasta (i − 1, j − 1) que en el
ejemplo es a. Con esta información podemos escribir el código para hallar la suma de un rectángulo que se
encuentra entre (i, j) y (k, l):

suma = matriz[k][l];
if (i>0) suma-= matriz[i-1][l];
if (j>0) suma-= matriz[k][j-1];
if (i>0 && j > 0) suma+= matriz[i-1][j-1];

Lo que toca hacer es calcular esta matriz de valores acumulados:

if (i>0) suma+= matriz[i-1][j];


if (j>0) suma+= matriz[i][j-1];
if (i>0 && j > 0) suma-= matriz[i-1][j-1];
314 Capı́tulo 15 Un problema sencillo
Cada celda lleva la suma de las celdas anteriores, tanto de la parte superior como la izquierda.
Dado que, los rectángulos tiene una intersección en una celda es necesario quitar un elemento para evitar la
doble suma. Veamos el caso de la celda (1, 1). En el ejemplo la celda (0, 1) tendrá la suma de a + b y la celda
(1, 0) tendrá la suma de a + e. Como a se sumó dos veces es necesario restar esta celda para poder eliminar
esta suma duplicada.
Como se mostró es posible hallar el valor máximo con cuatro ciclos que representa un optimización
significativa de (O(n6 ) a O(n4 ).
Tomando como entrada la matriz del ejemplo el algoritmo 35 halla la suma de la sub matriz que da suma
máxima. El algoritmo está dividido en dos partes hallar la suma acumulada, y luego hallar suma máxima.

Algoritmo 35: Hallar la matriz acumulada

//Primero acumular ;
for (0,n)
for (0,n)
if (i > 0)
a[i][j]+=a[i-1][j] ;
if (j > 0)
a[i][j]+=a[i][j-1] ;
//Aca restamos para evitar que se sume dos veces como se explico. ;
if (i > 0&&j > 0)
a[i][j]-=a[i-1][j-1] ;

// hallar la suma máxima ;


maximo=-10000 //el número más pequeño que esperamos;
maxmat=0 ;
for (i,n)
for (j,n)
for (k,n)
for (l,n)
maxmat=a[k][l];
if (i > 0)
maxmat-=a[i-1][l];
if (j > 0)
maxmat-=a[k][j-1];
if (i > 0&&j > 0)
maxmat+=a[i-1][j-1];
maximo = Math.max(maximo, maxmat) ;

imprimir(maximo) ;
15.7 Ejercicios 315

15.7. Ejercicios
15.7.1. La mejor empresa

Problema número 1288 en https://jv.umsa.bo

Un analista está estudiando los comportamientos mensuales de diversas empresas desde un año base. Desea
averiguar los meses cuando a las empresas les fue mejor, pero para ello debe averiguar el perı́odo de tiempo
(desde que mes hasta que mes) cuando les fue mejor.
Las ganancias que obtuvo están expresadas como números enteros. Se desea conocer cual fue el perı́odo (mes
de inicio y mes de finalización) de ganancia máxima, y el número de meses que representa este perı́odo.
Por ejemplo:

Mes 0 1 2 3 4 5 6 7 8 9
Utilidad 5 -15 7 18 -10 9 10 -25 -15 20

En este ejemplo el perı́odo que obtuvo la máxima ganancia es: del mes 2 al mes 6 con una ganancia de 34
Dados M datos, numerados entre el mes 0 y el mes M − 1.

Entrada
La entrada consiste de varios datos de prueba y termina cuando no hay más datos. La primera lı́nea de un
caso de prueba es la cantidad de meses (1 ≤ M ≤ 106 ) a evaluar. La siguiente lı́nea tiene M números enteros
separados por un espacio con valores entre -100 y 100.
La entrada termina cuando M = 0.

Salida
En la salida escriba la ganancia máxima, el mes de inicio y finalización del perı́odo de ganancia máxima.
Es posible de que existan múltiples soluciones óptimas, si este fuera el caso, imprimir la que tenga el mes de
finalización mayor y si aún existiera empate, imprimir la que tenga el perı́odo más corto.
Si la ganancia máxima es un número no positivo, imprimir 0 -1 -1.

Ejemplos de entrada Ejemplos de salida


10 34 2 6
5 -15 7 18 -10 9 10 -25 -15 20 10 0 0
1 0 -1 -1
10
2
-15 -5
0
316 Capı́tulo 15 Un problema sencillo

15.7.2. Concurso de televisión


Problema número 1401 en https://jv.umsa.bo

Con la finalidad de atraer a mayor audiencia el canal de TV de su localidad ha decidido crear un concurso en
el que va regalar dinero a los participantes.
El concurso consiste de una caja rectangular de bolas, donde cada bola tiene un número entero que está en
el rango (−100 ≤ n 100). El concursante debe escoger un rango de bolas de la base y obtiene un premio en
dinero equivalente a la suma de todas las bolas del rango que escogió y de todas las que están por encima de
estas bolas.
Por ejemplo, si tenemos una caja con las bolas como se muestra en la figura:

Si escoge de las 6 columnas las que corresponden al rango de 1 a 5 se lleva el dinero que corresponde a las
columnas 1,2,3,4,5 haciendo un total de 16 pesos.
Para no perder dinero en el concurso te piden hallar el máximo de dinero que un concursante puede obtener.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea de cada caso de prueba tiene dos números que
son las dimensiones de la matriz n,m, (1 ≤ n, m ≤ 100000). Luego siguen n filas cada una con m números
separados por espacio.

Salida
Escriba en la salida el máximo número de pesos que un concursante puede ganar.

Ejemplos de entrada Ejemplos de salida


3 6 16
5 -1 -10 4 -1 3
1 -1 17 -2 3 1
7 -1 -8 4 -1 -8

15.7.3. La submatriz más larga


Problema número 1307: disponible en https://jv.umsa.bo/
15.7 Ejercicios 317
Sea A una matriz N xN matriz de ceros u unos. Una Sub matriz S es un conjunto de entradas contiguas que
forman un cuadrado o rectángulo.
Escriba un programa que determine el numero de elementos que tiene la sub matriz más larga formada de
números unos. La más grande significa el área más grande.

Entrada
La entrada comienza con un número positivo en una lı́nea indicando el número de casos de prueba. Luego
siguen un número de filas de la matriz.
La matriz se da lı́nea por lı́nea. Cada lı́nea consiste de ceros y unos separados por un espacio. El orden de la
matriz también es igual al número de lı́neas de entrada. 1 ≤ N ≤ 25.

Salida
Para cada caso de prueba, escriba en la salida el máximo número de elementos de la matriz más larga que se
encontró. La salida de dos casos de prueba consecutivos estará separada por una lı́nea en blanco.

Ejemplos de entrada Ejemplos de salida


1 16
8
1 0 1 1 1 0 0 0
0 0 0 1 0 1 0 0
0 0 1 1 1 0 0 0
0 0 1 1 1 0 1 0
0 0 1 1 1 1 1 1
0 1 0 1 1 1 1 0
0 1 0 1 1 1 1 0
0 0 0 1 1 1 1 0

15.7.4. Limpiar Bits


Problema número 1451, disponible en https://jv.umsa.bo/

Se tiene una imagen en blanco y negro representada como una matriz de bits. donde los puntos negros están
representados por un uno y los blancos por un cero.
En nuestra imagen hay una serie de impurezas que representan puntos negros que no deberán estar. El
programa que debes realizar es uno que permita eliminar las impurezas.
Para esto tomaremos áreas rectangulares y las que tengan menos de una cantidad K de números unos se
deben poner en cero. Vale decir cambiar de negro a blanco.
Por ejemplo, sea la imagen siguiente con K = 4:

[0, 0, 1, 1, 0, 1]
[1, 0, 1, 1, 0, 0]
[1, 1, 1, 1, 1, 1]
318 Capı́tulo 15 Un problema sencillo
[0, 0, 1, 1, 0, 0]
[0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1]

Dado que K = 4 hay que poner en cero todas las sub matrices con 4 o menos unos. Este proceso nos da la
matriz.

[0, 0, 1, 1, 0, 0]
[0, 0, 1, 1, 0, 0]
[1, 1, 1, 1, 1, 1]
[0, 0, 1, 1, 0, 0]
[0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1]

Tu tarea es hacer dicho programa.

Entrada
La primera lı́nea contiene el número de casos de la imagen y el valor de K ≤ N xM . Cada caso de prueba
comienza con una lı́nea que tiene el tamaño (2 ≤ N, M ≤ 100) de la imagen. Las siguientes N filas contiene
M valores de unos y ceros separados por un espacio.

Salida
Escriba en la salida N, M filas que representan la matriz obtenida. Escriba las filas en una lı́nea, separando
los uno y ceros por un espacio.

Ejemplos de entrada Ejemplos de salida


6 6 4 0 0 1 1 0 0
0 0 1 1 0 1 0 0 1 1 0 0
1 0 1 1 0 0 1 1 1 1 1 1
1 1 1 1 1 1 0 0 1 1 0 0
0 0 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 1 1 1 1
1 1 1 1 1 1

15.7.5. Campos de Footbal

Problema número 1400 disponible en https://jv.umsa.bo

Se ha decidido construir muchos campos de deportivos se buscan áreas planas para construir canchas.
Se disponen de fotografı́as tomadas desde el aire. Cada fotografı́a consiste de una matriz cuadrada de 25 x
25 bits donde el número 1 representa un espacio plano y 0 una depresión o montaña.
15.7 Ejercicios 319
Como no se tiene suficientes recursos económicos para este proyecto solo se considerarán los espacios planos.
Para determinar si se puede construir un campo de football en este espacio, se le pide hallar el área plano
más grande de cada fotografı́a.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea tiene el número de casos de prueba. Cada
lı́nea consiste de 24 números enteros que representan la fotografı́a tomada.

Salida
Para cada caso de prueba imprima el área plana más grande que se encuentra en cada fotografı́a.

Ejemplos de entrada
2
33554431 33554431 33554431 33554431 33554431
33554431 33554431 33554431 33554431 33554431
33554431 33554431 33554431 33554431 33554431
33554431 33554431 33554431 33554431 33554431
33554431 33554431 33554431 33554431 33554431
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 184 20 56
58 63 94 94 30

Ejemplos de salida
625
16
320 Capı́tulo 15 Un problema sencillo

15.8. Programas mencionados en el texto


15.8.0.1. Sub suma máxima en un vector
import java . u t i l . Arrays ;
import java . u t i l . Scanner ;

/∗
∗ Autor Jorge Teran
∗/

public class x {

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t [ ] x = { 3 1 , −41 , 5 9 , 2 6 , −53 , 5 8 , 9 7 ,
−93 , −23 , 82 } ;
System . o u t . p r i n t l n ( seqMax ( x ) ) ;
System . o u t . p r i n t l n ( seqMaxMejor ( x ) ) ;
System . o u t . p r i n t l n ( seqMaxAcum ( x ) ) ;
System . o u t . p r i n t l n ( seqMaxLog ( x , 0 , x . l e n g t h − 1 ) ) ;
System . o u t . p r i n t l n ( s e q M a x L i n e a l ( x ) ) ;
}

p r i v a t e s t a t i c i n t seqMaxLineal ( i n t [ ] x ) {
int n = x . length ;
i n t maximoHastaAqui = 0 , m a x i m o F i n a l = 0 ;
f o r ( i n t i = 0 ; i < n ; i ++){
/ / i n v a r i a n t e maximoHastaAqui , m a x i m o F i n a l
/ / s o n c o r r e c t o s p a r a x [ 0 . . i −1]
m a x i m o F i n a l = Math . max ( m a x i m o F i n a l + x [ i ] , 0 ) ;
maximoHastaAqui = Math . max ( maximoHastaAqui ,
maximoFinal ) ;
}
r e t u r n maximoHastaAqui ;
}

p r i v a t e s t a t i c i n t seqMaxLog ( i n t [ ] x , i n t l , i n t u ) {
{ i n t i , m;
i n t lmax , rmax , sum ;
if ( l > u) / / hay c e r o e l e m e n t o s
return 0;
i f ( l == u ) / / hay un s o l o e l e m e n t o
r e t u r n Math . max ( 0 , x [ l ] ) ;
m = ( l +u ) / 2 ; / / e l v a l o r d e l medio
/ / h a l l a r e l maximo que c r u z a a l a i z q u i e r d a
lmax = sum = 0 ;
15.8 Programas mencionados en el texto 321

f o r ( i = m; i >= l ; i −−) {
sum += x [ i ] ;
i f ( sum > lmax )
lmax = sum ;
}
/ / h a l l a r e l maximo que c r u z a a l a d e r e c h a
rmax = sum = 0 ;
f o r ( i = m+ 1 ; i <= u ; i ++) {
sum += x [ i ] ;
i f ( sum > rmax )
rmax = sum ;
}
r e t u r n Math . max ( lmax + rmax ,
Math . max ( seqMaxLog ( x , l , m) ,
seqMaxLog ( x ,m+1 , u ) ) ) ;
}
}
p r i v a t e s t a t i c i n t seqMaxAcum ( i n t [ ] x ) {
int n = x . length ;
i n t [ ] a c u m u l a d o = new i n t [ n ] ;
acumulado [ 0 ] = x [ 0 ] ;
f o r ( i n t i = 1 ; i < n ; i ++)
acumulado [ i ] = acumulado [ i − 1] + x [ i ] ;
i n t maximo = 0 ;
f o r ( i n t i = 1 ; i < n ; i ++) {
f o r ( i n t j = i ; j < n ; j ++) {
i n t suma = a c u m u l a d o [ j ] − a c u m u l a d o [ i − 1 ] ;
maximo = Math . max ( maximo , suma ) ;
}
}
r e t u r n maximo ;
}

p r i v a t e s t a t i c i n t seqMaxMejor ( i n t [ ] x ) {
int n = x . length ;
i n t maximo = 0 ;
f o r ( i n t i = 0 ; i < n ; i ++) {
i n t suma = 0 ;
f o r ( i n t j = i ; j < n ; j ++) {
suma += x [ j ] ;
maximo = Math . max ( maximo , suma ) ;
}
}
r e t u r n maximo ;
322 Capı́tulo 15 Un problema sencillo
}

p r i v a t e s t a t i c i n t seqMax ( i n t [ ] x ) {
int n = x . length ;
i n t maximo = 0 ;
f o r ( i n t i = 0 ; i < n ; i ++)
f o r ( i n t j = i ; j < n ; j ++) {
i n t suma = 0 ;
f o r ( i n t k = i ; k <= j ; k ++)
suma += x [ k ] ;
maximo = Math . max ( maximo , suma ) ;
}
r e t u r n maximo ;
}
}

15.8.0.2. Extension a dos dimensiones

import java . u t i l . Arrays ;


/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s E2d {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t a [][]={{0 , −2 , −7 ,0} ,{9 ,2 , −6 ,2} ,{ −4 ,1 , −4 ,1} ,
{ −1 ,8 ,0 , −2}};
f o r ( i n t i = 0 ; i < a . l e n g t h ; i ++) {
f o r ( i n t j = 0 ; j < a . l e n g t h ; j ++) {
i f ( i >0) a [ i ] [ j ]+= a [ i −1][ j ] ;
i f ( j >0) a [ i ] [ j ]+= a [ i ] [ j − 1 ] ;
i f ( i >0 && j >0) a [ i ] [ j ]−= a [ i −1][ j − 1 ] ;
}
}
i n t maximo= I n t e g e r . MIN VALUE ;
i n t maxmat = 0 ;
f o r ( i n t i = 0 ; i < a . l e n g t h ; i ++) {
f o r ( i n t j = 0 ; j < a . l e n g t h ; j ++) {
f o r ( i n t k = i ; k < a . l e n g t h ; k ++) {
f o r ( i n t l = j ; l < a . l e n g t h ; l ++) {
maxmat=a [ k ] [ l ] ;
i f ( i >0) maxmat−=a [ i −1][ l ] ;
i f ( j >0) maxmat−=a [ k ] [ j − 1 ] ;
i f ( i >0 && j >0) maxmat+= a [ i −1][ j − 1 ] ;
maximo = Math . max ( maximo , maxmat ) ;
15.8 Programas mencionados en el texto 323

}
}
}
}
System . o u t . p r i n t l n ( maximo ) ;
}
}
16
16.1.
Programación Dinámica
Definición
El término programación dinámica viene de la teorı́a general desarrollado por El matemático Richard
Bellman en 1953, para resolver problemas en sistemas de control [Aho and Ullman 1994]. Se utiliza
para optimizar problemas complejos que pueden ser discretizados y secuencializados. En el campo de la
informática se utiliza generalmente en la optimización de problemas combinatorios. La definicion que da la
wikipedia [Wikipedia 2016c] dice:
En el campo de la informática la programación dinámica es un método para reducir el tiempo de ejecución
de un algoritmo mediante la utilización de subproblemas superpuestos y subestructuras óptimas. Al referirse
a optimalidad queremos decir: a buscar alguna de las mejores soluciones entre varias alternativas posibles.
Este proceso se ve como una secuencia de decisiones que proporciona la solución correcta.
El principio de optimalidad de Bellman dice que dada una secuencia óptima de decisiones, toda subsecuencia
de ella es, a su vez, óptima. Las soluciones de muchos problemas pueden ser expresadas recursivamente en
términos matemáticos. La formas más directa de resolverlos es programar recursivamente la ecuación. El
tiempo de ejecución de la solución recursiva, es generalmente de orden exponencial y por tanto impracticable.
La programación dinámica se define como una técnica que usualmente se basa en una ecuación de recurrencia
y algunos estados iniciales. Una sub solución se construye de las sub soluciones halladas previamente. Las
soluciones de programación dinámica tienen una solución con complejidad polinomial que asegura un tiempo
mucho más rápido que otras técnicas tales como backtracking, fuerza bruta, búsqueda completa, etc.
La programación dinámica se parece a la técnica de divide y vencerás, sin embargo, la programación
dinámica crea un arreglo relacionado a soluciones de sub problemas (no necesariamente disjuntos) más
simples, que se almacenan en el arreglo y luego calcula la solución del problema más complicado. La técnica
divide y vencerás particiona el problema en partes que no se superponen buscando cual es la que contiene la
solución.
Las soluciones a los sub problemas previamente calculados se guardan en un arreglo. Se denomina recordar
el pasado, en el inglés se denomina memoization. En el texto se refiere solo como recordar. Por este motivo,
las soluciones de programación dinámica también ha sido denominado como algoritmo de llenado de tablas
por Alfred V. Aho y Jeffrey D. Ullman [Aho and Ullman 1994].
Existen tres pasos [Soltys 2012] que se siguen para hallar una solución de programación dinámica:

Defina la clase de subproblemas


Escriba una ecuación de recurrencia basada en resolver cada sub problema en términos de un
problema más simple.
Construya el algoritmo para calcular la recurrencia.

325
326 Capı́tulo 16 Programación Dinámica
El estudio de la Programación Dinámica se lo hace presentando problemas y la forma de resolución. En
el texto presentaremos problemas clásicos su solución y ejercicios similares para practicar los principios
presentados.

16.2. La Programación Dinámica y la Teorı́a de Grafos


16.3. Grafos
Un grafo G = (V, E) consiste de conjunto de vértices, (V ) también llamados nodos y un conjunto aristas
(E) que se denominan arcos, donde cada arco corresponde a un par de vértices (i, v) [Dasgupta et al. 2006].
Cuando los arcos entre vértices son ordenados, vale decir que el arco que une los vértices (v, w) es diferente
al arco (w, v), el grafo se denomina grafo dirigido. En otros caso se denomina no dirigido. Si (v, w) es un
arco en un grafo dirigido, decimos que (v, w) deja el nodo v y entra en el nodo w.
Dado el grafo G = (V, E) se llama trayectoria a una sucesión de vértices v1 , v2 , .., vn tales que el arco
(vi , vi+1 ) pertenece a E. Decimos que tal trayectoria conecta el vértice v1 con el vértice vn . Denominamos
esta trayectoria un camino cuando en el trayecto no se pasa por el mismo vértice dos veces.
Se dice que el grafo es dirigido y acı́clico o DAG (del inglés Directed Acyclic Graph), si es un grafo dirigido
que no tiene ciclos; esto significa que para cada vértice v, no hay un camino directo que empiece y termine
en v.
Un grafo de problemas de programación dinámica tiene un arco dirigido, desde el vértice para el subproblema
x, al vértice para el subproblema y, si determinar una solución óptima para el subproblema x implica
considerar directamente una solución óptima para el subproblema y [Thomas H. Cormen and Rivest 1990]. El
grafo que se construye es un grafo dirigido acı́clico. Es dirigido porque los arcos vienen de los subproblemas
previamente resueltos. Si el grafo no fuera acı́clico no existe solución.
Cuando analizamos los problemas de programación dinámica se ve como un sub problema es parte de la
solución de un problema más grande, dado que, cada problema depende uno del otro. Se puede dibujar un
grafo de los sub problemas y mostrar como lleva a obtener la solución buscada.

16.4. Grafo dirigido de la Secuencia de Fibonacci


Considere los números de Fibonacci cuya secuencia se forma por la recurrencia fn = fn−1 + fn−2 .
Asociando ésta recurrencia a un grafo podemos dibujar un grafo dirigido, de la siguiente forma: nombraremos
los vértices del grafo 1, 2, 3, 4... representando el número de Fibonacci que buscamos. El tercer Fibonacci se
forma sumando el segundo con el primero. Esto se representará con dos arcos, uno que va del Fibonacci 3 al
Fibonacci 2 y otro que va del Fibonacci 3 al 1.
Se puede demostrar que este grafo es acı́clico observando que todos los arcos van desde el último vértice
hacia el origen como se ve en la imagen 16.1. El orden topológico de un DAG (V, E) es el orden de los
vétices en V tal que para cada arco (vi , vj ) que pertenece a E se cumple que vi < vj . Para las soluciones de
programación dinámica se utiliza el orden topológico reverso que es equivalente al orden topológico del grafo
transpuesto. Esto asegura que ningún problema es considerado hasta que todos subproblemas que requiere
han sido resueltos.
La noción de grafo de Fibonacci se puede ver en el trabajo de Korenblit [Korenblit and Levit 2013]. Un grafo
de Fbonacci tiene vértices {1, 2, 3, ..., n} y arcos (v, v + 1) para v = 1, 2, ..., n − 1 unión (v, v + 2) para
v = 1, 2, ..., n − 2.
16.4 Grafo dirigido de la Secuencia de Fibonacci 327

1 2 3 4 5

Figura 16.1 Grafo de Fibonacci

El grafo de Fibonacci tiene la representación que se muestra en la imagen 16.1 y se ve que está ordenado
topológicamente. Para resolver un problema de programación a partir de un DAG, comenzamos del vértice
del que se quiere hallar el resultado y se recorre el grafo hasta llegar al origen.
En el caso de los números de Fibonacci contamos todos los posibles caminos que existen para llegar a un
nodo desde el vértice 1. Si consideramos que cada vértice es un subproblema, se aprecia que para hallar el
número Finonacci 5 es necesario primero calcular los números predecesores el 4 y el 3.
Los caminos que existen para llegar del vértice 5 al vértice 1 son: del vértice 1 al vértice 2 solo existe
un camino. Del vértice 1 podemos llegar al vértice 3 por dos caminos: el camino 1, 2, 3 y el camino 1, 3.
Del vértice 4 existen 3 caminos: los caminos (1, 2, 3, 4), (1, 2, 4) y (1, 3, 4), al vértice 5 llega un camino
del vértice 3 a donde llegan 2 caminos uno del vértice 4 de donde llegan 3 caminos haciendo un total de
5 caminos. Como se aprecia el contar el número de caminos refleja la secuencia de Fibonacci. Para hallar
todos los caminos de entre dos puntos de un DAG, solo es necesario modificar el algoritmo de recorrido de
grafo siguiendo todos los caminos. El programa siguiente muestra como codificar esto con los datos de los
ejemplos mostrados:

import java . u t i l . ArrayList ;


import java . u t i l . Arrays ;
/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s Sol {
s t a t i c A r r a y L i s t <I n t e g e r >[] l i s t a ;
s t a t i c l o n g dp [ ] ;
static int n ,v , e;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
i n t arcos [][]={{1 ,0} ,{2 , 0} ,{2 ,1} ,{3 ,1} ,{3 ,2} ,
{4 ,2} ,{4 ,3} ,{5 ,3} ,{5 ,4} ,{6 ,5} ,{6 ,4}};
v =6;
e= a r c o s . l e n g t h ;
n=v ;
l i s t a = new A r r a y L i s t [ v ] ;
dp = new l o n g [ v + 1 ] ;
A r r a y s . f i l l ( dp , −1);
f o r ( i n t j = 0 ; j < v ; j ++)
l i s t a [ j ] = new A r r a y L i s t <I n t e g e r > ( ) ;
int x ,y;
328 Capı́tulo 16 Programación Dinámica
f o r ( i n t i = 0 ; i <e ; i ++){
x= a r c o s [ i ] [ 0 ] ;
y= a r c o s [ i ] [ 1 ] ;
l i s t a [ y ] . add ( x ) ;
}
for ( ArrayList i : l i s t a )
System . o u t . p r i n t l n ( i ) ;
System . o u t . p r i n t l n ( ) ;
System . o u t . p r i n t l n ( s o l v e ( 0 ) ) ;
}

s t a t i c long solve ( i n t u ) {
i f ( u == n )
return 1;
i f ( dp [ u ] ! = −1)
r e t u r n dp [ u ] ;
long ans = 0;
f o r ( i n t i = 0 ; i <( i n t ) l i s t a [ u ] . s i z e ( ) ; i ++) {
a n s += s o l v e ( l i s t a [ u ] . g e t ( i ) ) ;
}
r e t u r n dp [ u ] = a n s ;
}

16.5. Pagar con Monedas


16.5.1. Enunciado

Este problema clásico se ha presentado en varios libros de algoritmos [Halim and Halim 2013] [Gilles Bras-
sard 1996]. Inicialmente veremos un ejemplo que tiene una solución utilizando un algoritmo voraz (greedy)
y luego presentaremos la solución general.
Suponga que vivimos en un paı́s donde las siguientes monedas están disponibles: 1 peso (100 centavos), 25
centavos, 10 centavos, 5 centavos y 1 centavo.
El problema radica en pagar a un cliente en monedas utilizando el menor número de monedas. Por ejemplo
si queremos pagar 289 centavos la mejor solución es pagar con 2 monedas de 1 peso, 3 monedas de 25, una
de 10 y 4 de un centavo.
Esta solución que se denomina greedy que considera primero la moneda de mayor valor para luego repetir
esto hasta completar el cambio. En el ejemplo primero escogemos dos monedas de 100 centavos, quedando
89 para dar cambio. En la segunda iteración se tomas las monedas de 25, calculando 3 monedas. En la tercera
iteración escogemos una de 10 y finalmente 4 de 1.
16.5 Pagar con Monedas 329
Que pasará si las monedas que tenemos disponibles son 6, 4, 1 y debemos pagar un valor de 8? Con la idea
greedy o voraz primero escogemos una moneda de 6 y luego dos monedas de 1. con un total de 3 monedas.
Claramente se ve que hay una solución menor. Se requieren solo dos de 4.
Las denominaciones de las monedas afectan al tipo de solución que podemos implementar. A continuación
se presentan algunas posibles denominaciones de monedas. Indique si la solución greedy (voraz) obtiene el
mı́nimo número de monedas para dar cambio. En caso que no halle la solución correcta de un ejemplo.

{5,4,3,2,1}
{13, 11, 7, 5, 3, 2, 1}
{8,6,4,2,1}

16.5.2. Solución General


Para construir una solución al problema consideremos el siguiente ejemplo:
Sean las denominaciones de las monedas {1, 3, 4, 5} y el monto que queremos pagar con el mı́nimo de
monedas es 7. A fin de hallar el mı́nimo número de monedas, la solución voraz consiste en seleccionar
inicialmente la moneda denominación 5. Luego seleccionar 2 monedas de 1 haciendo un total de 3 monedas.
Sin embargo vemos que hay una solución que usa solo dos monedas, las monedas de 4 y 3.
Para plantear una solución vemos que si tenemos 7 podemos usar una moneda de 5 con lo que nos quedan 2
por pagar, una de 4 restando 3 o una de 1 quedando 6 por pagar. Dibujando esto en un grafo el ejemplo de
pagar 7 obtenemos la figura 16.2.

7 6 4 3 2

Figura 16.2 Grafo que representa los vértices después de utilizar una de las denominaciones.

Lo que el grafo de la figura 16.2 representa es lo que queda después de utilizar una moneda de una
denominación especı́fica. Este proceso podemos repetir para cada un de los vértices obteniendo la figura
16.3.
La solución buscada es el camino mı́nimo del grafo y se ve en la figura 16.3 que se obtiene de dos formas,
escogiendo primero la moneda de valor 4 y luego la de valor 3. La segunda forma es escoger las monedas en
orden inverso. Esta solución no indica cuales son las monedas que se utilizarán. Para conocer los valores se
resta los vértices que son parte del camino crı́tico.
Otra alternativa es etiquetar las aristas del grafo con los valores de las monedas escogidas, y el momento de
recorrer el grafo recuperar las etiquetas de los arcos. La figura 16.4 muestra esa alternativa, donde los trazos
oscuros indican cual es el camino mı́nimo.

16.5.3. Solución basada en un arreglo unidimensional


Después de comprender el problema se plantean dos soluciones de programación dinámica para resolver
este problema. Una solución consiste en construir un arreglo bidimensional con las filas para almacenar las
330 Capı́tulo 16 Programación Dinámica

4
7 0

2 1

6 5

Figura 16.3 Grafo que representa los vértices después de utilizar cada una de las denominaciones.

3 4
1
3
4
7 3 3
5 1 0
1 1
1
3 1
2 1
4

6
1 3

5 4

Figura 16.4 Grafo que representa los vértices después de utilizar cada una de las denominaciones.

denominaciones de monedas y las columnas para las los montos de los que podemos dar cambio. La segunda
se basa en un vector para almacenar los importes.

16.5.4. Solución basada en un arreglo

Problema: se tienen monedas con denominaciones

d= \{ d_1 < d_2 < ... < d_k\}

Se tiene un monto n que hay que dar con el número mı́nimo de monedas.

1. Sea el vector c donde c[n] el mı́nimo número de monedas para dar cambio de un monto n.
2. Sea x el valor de la primera moneda utilizada en la solución óptima.
3. Entonces c[n] = 1 + c(n − x)
16.5 Pagar con Monedas 331
El punto 3 lo podemos deducir viendo el grafo construido, si se está en c[n] y x pertenece a la solución
óptima de c[n − x] hasta c[n] existe un camino. El problema es que no conocemos el valor de x. La solución
se construye probando todos los valores de x.


 0
 si n = 0
c[j] = min(c[n − di ] + 1) ∀di ∈ d ∧ p <= di (16.5.1)

 ∀j <= n

Para construir un algoritmo que resuelva el ejemplo d = {1, 3, 4, 5} y n = 7, requerimos construir un vector
de tamaño n = 7, con valor inicial en 0.
Siguiendo la ecuación de recurrencia 16.5.1 debemos probar para cada elemento que pertenece a d, con el
cual se halla el elemento mı́nimo. Comenzamos con el elemento de la posición 1, que representa un valor de
n = 1 el valor mı́nimo se obtiene con d0 = 1. El resultado de c[n − di ] + 1 = c[1 − 1] + 1 = 1. Valor que
se almacena en la posición c[1]. Se pasa a probar para n = 2, el valor mı́nimo de (c[n − di ] + 1) solo se da
con d0 = 1 y tiene como resultado 2. No es posible probar valores mayores de d, dado que el resultado es
negativo. El resultado parcial del proceso se muestra:

0 1 2 0 0 0 0 0

Continuando con el proceso el resultado final al que se arriba es:

0 1 2 1 1 1 2 2

El valor óptimo buscado es el que se encuentra en la posición c[7] que es dos, es el mismo resultado que el
hallado cuando se calculó el camino mı́nimo del grafo que representó el problema.
Aun cuando el resultado es correcto no sabemos que monedas se debe usar, para esto es necesario guardar
los valores con los cuales se obtuvo el mı́nimo. El resultado del proceso es el siguiente:

0 1 1 3 4 5 1 3

Para hallar las monedas que se requieren comenzamos en la posición 7 y vemos que se usó una moneda de
valor 3. Luego recorremos tres posiciones a la izquierda y hallamos el valor de la segunda moneda que es 4.
Finalmente se recorre a la izquierda 4 lugares para llegar a cero y terminar el proceso.
Para resolver el problema implementamos el algoritmo 36.
Para determinar la complejidad del algoritmo 36, se observa que en cada llamada debe recorrer todo el arreglo
d, de los cortes de monedas y la cantidad de llamadas es igual al monto n. Esto da una complejidad de O(xn),
donde x es el tamaño del vector d, es decir, la cantidad de monedas diferentes, y n el monto que queremos
hallar con el menor número de monedas.

16.5.5. Solución basada en arreglo bidimensional


Una forma natural de pensar la programación dinámica es crear una tabla con los elementos que participan
en el problema. En este caso como tenemos monedas e importe, se construye una matriz c[n][p] con una fila
por cada denominación y una columna por cada monto a pagar desde de 0 hasta p. En esta matriz c(i, j) se
332 Capı́tulo 16 Programación Dinámica

Algoritmo 36: Hallar el número mı́nimo de monedas, usando un arreglo unidimensional


void Cambio (p)
l=0 ;
if (p > n)
return ;
if (p=0)
c[p]=0 ;
return ;
min=numeroGrande ;
for (i=0,d.length)
if (d[i] < p)
if ((c[p − d[i]] + 1) < min)
min=c[p-d[i]]+1 ;
l=i ;
c[p]=min;
s[p]=d[l];

Cambio(p+1) ;

representará el número mı́nimo de monedas requerido para pagar un monto (0 ≤ j ≤ p) con denominaciones
(1 ≤ i ≤ n).
Veamos los casos base:

Para todo valor de i, si requerimos pagar 0 la respuesta es 0


Para todo valor de j si hay una moneda la respuesta es j

No incluir ninguna moneda del tipo D(i), supone que el valor de c(i, j) va a coincidir con el de c(i − 1, j),
y por tanto c(i, j) = c(i − 1, j).
De incluirla, entonces, al incluir la moneda del tipo t(i), el número de monedas global coincide con el número
óptimo de monedas para una cantidad (j − t(i)) más esta moneda t(i), es decir podemos expresar c(i, j), en
este caso, como c(i, j) = 1 + c(i, j − d[i]).
Dado que tenemos que minimizar el número de monedas debemos escoger

min(c(i − 1, j), 1 + c(i, j − d[i]))

La ecuación 16.5.2 describe la recurrencia para este problema.


16.5 Pagar con Monedas 333



 j si i = 0

 0 si j = 0
c[i][j] = (16.5.2)

 c[i − 1][j] si j < d[i]

min(c[i − 1][j], 1 + c[i][j − d[i]]); en otros casos

Para resolver la recurrencia desarrollamos el algoritmo , que es una solución iterativa al problema.

Algoritmo 37: Hallar el número mı́nimo de monedas, usando un arreglo bidimensional

int c[t.length][valor + 1]; for (i=0,t.length)


c[i][0] = 0 ;
for (i=0,t.length)
c[0][j] = j ;
for (i=0,d.length)
for ((i=0,valor])
if (j < d[i])
c[i][j] = c[i - 1][j] ;
else
c[i][j] = min(c[i - 1][j], 1 + c[i][j - d[i]]);

La respuesta quedará en el último valor. Con este análisis se obtiene una solución eficiente. La solución por
fuerza bruta requerirı́a hallar todas las posibles combinaciones de monedas que sumen el monto solicitado.
Este problema claramente exponencial ha sido reducido a una solución polinomial, con una complejidad
igual a la anterior solución pero con un mayor almacenamiento de memoria.
En el texto Competive Programming 3, [Halim and Halim 2013] en la página 116 se presentan varios
problemas similares que sugerimos que el lector trate de resolver,
334 Capı́tulo 16 Programación Dinámica

16.5.6. Programas mencionados en el texto


16.5.6.1. Programa para hallar el número mı́nimo de monedas, usando un arreglo unidimensional

import java . u t i l . Arrays ;

/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s Monedas {
s t a t i c i n t d [] = {1 ,3 ,4 ,5};
s t a t i c i n t n =7;
s t a t i c i n t c [ ] = new i n t [ n + 1 ] ;
s t a t i c i n t s [ ] = new i n t [ n + 1 ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Change ( 1 ) ;
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( c ) ) ;
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( s ) ) ;
}

p r i v a t e s t a t i c v o i d Change ( i n t p ) {
i n t l =0;
i f ( p>n ) r e t u r n ;
i f ( p ==0){ c [ p ] = 0 ; r e t u r n ; }
i n t min= I n t e g e r . MAX VALUE;
f o r ( i n t i = 0 ; i <d . l e n g t h ; i ++){
i f ( d [ i ]>p ) b r e a k ; e l s e
i f ( ( c [ p−d [ i ]]+1) < min ) {
min=c [ p−d [ i ] ] + 1 ;
l=i ;
}
}
c [ p ] = min ;
s [ p ]= d [ l ] ;
/ / System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( s ) ) ;
Change ( p + 1 ) ;
}
}

16.5.6.2. Programa para hallar el número mı́nimo de monedas, usando un arreglo bidimensional
16.6 Corte de troncos 335

import java . u t i l . Arrays ;


/∗
∗ Autor Jorge Teran
∗/

p u b l i c c l a s s Monedas {

public s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
int d [ ] = { 1 ,3 ,4 ,5 };
int valor = 7;
int c [ ] [ ] = new i n t [ d . l e n g t h ] [ v a l o r + 1 ] ;
for ( i n t i = 0 ; i < d . l e n g t h ; i ++)
c [ i ][0] = 0;
f o r ( i n t j = 0 ; j <= v a l o r ; j ++)
c [0][ j ] = j ;
f o r ( i n t i = 1 ; i < d . l e n g t h ; i ++) {
f o r ( i n t j = 1 ; j <= v a l o r ; j ++) {
if ( j < d[ i ])
c [ i ][ j ] = c [ i − 1][ j ];
else
c [ i ] [ j ] = Math . min ( c [ i − 1 ] [ j ] ,
1 + c[ i ][ j ] − d[ i ]]);
}
}
System . o u t . p r i n t l n ( c [ d . l e n g t h − 1 ] [ v a l o r ] ) ;
/ / for ( int [] i : c )
/ / System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( i ) ) ;
}
}

16.6. Corte de troncos


16.6.1. Enunciado
Supongamos que una empresa maderera quiere cortar troncos, en tamaños de longitud diferente [Thomas
H. Cormen and Rivest 1990]. Supongamos además que conocemos el precio pi para cada corte de una
longitud dada. Por ejemplo:

longitud i 1 2 3 4 5 6 7 8 9 10
precio pi 1 5 8 9 10 17 17 20 24 30

Para comprender mejor este problema construyamos un grafo para representar el mismo. Consideremos el
mismo caso tomando como ejemplo un tronco de tamaño 4 que se tiene que cortar. Comenzando del nodo 4,
se pueden hacer un corte de tamaño 4, otro de tamaño 3, de 2 y finalmente de tamaño 1. Se construye un arco
del vértice 4 al vértice 0 para indicar que se ha realizado un corte de tamaño 4. Para indicar que realizamos
un corte de tamaño 3, colocamos un arco del nodo 4 al nodo 1. Como ve la diferencia entre dos nodos nos da
336 Capı́tulo 16 Programación Dinámica
el tamaño del corte. A cada arco asociamos el precio que se obtiene por el corte. Esto da como resultado el
grafo que se muestra en la figura 16.5.

4 8
1 5 1
1
1
3 1 5 0
2
5
8

Figura 16.5 Grafo que representa el corte de un tronco de tamaño 4

Si se analiza el grafo se ve que el camino más largo es el que da la respuesta correcta. En este caso haciendo
dos cortes de tamaño 2 con un valor de 5 cada uno.
De este análisis, se detallan todos los cortes posibles para determinar cuales dan la máxima ganancia posible:

número de Cortes Tamaño de los cortes Precio


1 4 9
2 2+2 5 + 5 = 10
3 1+3 1+8=9
4 3+1 9+1=9
5 1+1+1+1 1+1+1+1=4
6 1+1+2 1+1+5=7
7 1+2+1 1+5+1=7
8 2+1+1 5+1+1=7

Como ve el mejor precio que podemos obtener es de 10 que se obtiene cortando los troncos por la mitad
haciendo dos troncos de 2. Respuesta que coincidente con el grafo desarrollado.
Para cortar un tronco se tienen todas las posibles sumas (de números entre 0 y n) cuyo resultado sea igual a
n. En general un tronco de tamaño n se puede cortar de 2n−1 formas distintas. Si realizamos un programa
que halle estas 2n−1 diferentes particiones el tiempo es proporcional a 2n . Este tiempo es inaceptable para la
mayor parte de los problemas por lo que buscaremos una solución más eficiente.
Se puede pedir que los troncos se corten en un orden determinado. Esto puede reducir el número de formas
distintas. Sin embargo sigue siendo una solución poco eficiente. Estas formas de dividir, se denominan
funciones de partición.
Para i = 1, 2, 3, ..., n − 1 la descomposición en partes la haremos utilizando la notación de la suma. Por
ejemplo un corte de longitud 7 = 2 + 2 + 3 nos indica que un tronco de longitud 7 se corto en tres troncos
16.6 Corte de troncos 337
dos de longitud 2 y uno de longitud 3. Si la solución óptima corta el tronco en k partes, para algún 1 ≤ k ≤ n
entonces la descomposición óptima del tronco es:

n = i1 + i2 + i3 ......ik

después de cortar el tronco en partes de longitud i1 , i2 , ...., ik . Es corte proveerá las utilidades

rn = pi1 + pi2 + pi3 ......, pik

En forma general podemos para n ≥ 1 podemos decir que los valores rn en términos de los cortes más
pequeños es:

rn = max(pn , r1 + rn−1 , r2 + rn−2 , ....., rn−1 , r1 )

En forma más simple se puede expresar como

rn = max(pi + rn−i )

El primer argumento pn corresponde al caso de no hacer cortes y vender el tronco de longitud n en el tamaño
actual. Los otros elementos corresponden al máximo rédito, al cortar en piezas de tamaño i y n − i partes,
para i = 1, 2, 3..., n − 1. Como no conocemos cual i optimiza el problema debemos considerar todos los
valores de i y escoger el que maximice las ganancias.
Vea que el problema original es de tamaño n, resolveremos problemas del mismo tipo pero de tamaño
menor. Una vez que hacemos la primera partición podemos considerar a las dos partes como dos instancias
independientes del mismo problema. La solución óptima será el máximo de las dos soluciones.
Se dice que el problema de cortar troncos muestra una subestructura óptima: soluciones óptimas al problema
incorporan soluciones óptimas a los subproblemas relacionados, que se pueden resolver en forma indepen-
diente. Expresando la solución en forma recursiva podemos ver que consiste en cortar una pieza de longitud
i a la izquierda y n − 1 al lado derecho. El caso base se da cuando n = 0 el resultado es 0.
(
0 si n = 0
rn = (16.6.1)
max(pi + rn−1 ) para 1 ≤ i ≤ n

Esto indica que solo lo que queda, exceptuando la primera parte, puede ser subdivida. Esto nos da una versión
más simple de las ecuaciones anteriores. La implementación de la solución se muestra en el algoritmo 38.
Analicemos la complejidad de esta solución
El programa hace n iteraciones donde se realiza la llamada recursiva.

n−1
X
T (n) = 1 + T (i)
i=0
338 Capı́tulo 16 Programación Dinámica

Algoritmo 38: Implementación directa de la ecuacion de recurrencia de 16.6.1


int Resolver (n)
int q = 0 ;
if (n = 0)
return 0 ;
q=númeroPequeño ;
for (i = [1, n])
q= max(q, precios[i] + Resolver(n - i)) ;
return q ;

Resolviendo la recurrencia podemos hallar que el tiempo asintótico es 2n . El problema es que esta solución
está hallando todas las 2n−1 formas de cortar para escoger la que proporciona el valor máximo.

16.6.2. Solución utilizando Programación Dinámica

Ahora mostraremos como convertir este problema en un algoritmo eficiente utilizando los principios que se
vió sobre la programación dinámica.
El método de la programación dinámica funciona como sigue. Habiendo observado que una solución
recursiva es ineficiente porque resuelve un mismo subproblema repetidamente, arreglamos para que se
resuelva una sola vez, guardando su solución. Si nos referimos a este subproblema posteriormente, solo
tomamos la solución en lugar de recalcularla. La programación dinámica utiliza memoria adicional para
ahorrar tiempo de recálculo. Con esta técnica una solución con tiempo de ejecución exponencial puede
convertirse en tiempo de ejecución polinomial. El tiempo de ejecución es polinomial cuando cada uno de
los subproblemas es polinomial en la entrada y puede resolverse en tiempo polinomial.
Existen dos métodos de programación dinámica, que se explicarán con este ejemplo.
El primer método es del tipo top-down recordando las soluciones anteriores. En este método escribimos
la solución en forma recursiva y luego la modificamos para guardar los resultados de cada subproblema. El
procedimiento revisa si tiene un resultado para este subproblema en cuyo caso da la respuesta inmediatamente
ahorrando cálculos futuros. Si no conoce el resultado entonces procede de la forma normal. Decimos que el
procedimiento recursivo recuerda que resultados ha calculado previamente.
El segundo método el botton-up. Este método depende de alguna noción natural del tamaño de un subpro-
blema. Esto es, resolver un subproblema en base de resolver los subproblemas más pequeños. Se ordenan los
subproblemas por tamaño y se comienza a resolver desde el más pequeño. Cuando encontramos un subpro-
blema se resuelve una sola vez, dado que ya se resolvieron todos los problemas que son prerequisito.
Los dos métodos se ejecutan en tiempos similares, excepto que el recursivo tiene una constante de tiempo
mayor debido a la recursión. A continuación se muestra como se construyen las soluciones utilizando estos
métodos.
16.6 Corte de troncos 339

16.6.3. Solución top-down recordando soluciones anteriores

Analicemos el programa recursivo. Se ve que cuando n = 0 el resultado es cero. Este es el caso que hace que
la recursión termine. Para almacenar los valores anteriores se define un vector que denominamos memoria
en el que vamos almacenando todos los valores ya calculados.
La recursión termina cuando existe un valor ya calculado esto es memoria[n] ≥ 0. En otros casos se continua
como en el programa recursivo. Antes de devolver el resultado se lo guarda en el vector de resultados ya
calculados. La implementación de la solución da el algoritmo 39.

Algoritmo 39: Implementación recordando soluciones anteriores de la ecuacion de recurrencia de


16.6.1
int Resolver (n)
if (memoria[n] ≥ 0)
return memoria[n];
if (n = 0)
q=0 ;
else
q=númeroPequeño ;
for (i = [1, n])
q = max(q, precios[i] + Resolver(n - i)) ;
memoria[n] = q ;
return memoria[n] ;

16.6.4. Solución bottom-up

Para esta solución es necesario primero resolver un subproblema de tamaño i más pequeño que un problema
de tamaño j. En esta solución se utiliza el arreglo memoria para guardar los resultados de subproblemas
anteriores. Comienza inicializando el valor r[0] en cero, dado que este corte no da ninguna utilidad. Luego
resuelve el problema para tamaños 1, , 2, 3, ..., n, incrementando el tamaño cada vez.
La complejidad es proporcional a n2 , y es fácil de ver dado que tiene dos ciclos anidados. En la solución
recursiva no se ve claramente que su complejidad es proporcional a n2 debido a que retornan los valores que
ya están calculados.
La implementación de la solución bottom-up es el algoritmo 40.

16.6.5. Reconstruyendo la solución

No solo es suficiente conocer cual es la utilidad máxima sino también queremos conocer el detalle de los
cortes. Para esto se hace necesario guardar los subı́ndices de las sub soluciones óptimas.
Para esto creamos un vector s en el cual guardamos el ı́ndice de cada solución óptima. Luego en un proceso
de que parte del último valor podemos ir hallando los valores que nos llevaron al resultado.
Volvamos al ejemplo, los valores del vector memoria y el vector s para n = 10 son:
340 Capı́tulo 16 Programación Dinámica

Algoritmo 40: Solución bottom up a la ecuación recurrencia 16.5


int Resolver (p)
for (j = [1, n])
min=númeroPequeño ;
for (1 = [1, j])
q = Math.max(q, precios[i] + memoria[j - i])
memoria[j] = q ;
return memoria[n] ;

longitud i 0 1 2 3 4 5 6 7 8 9 10
precio pi 0 1 5 8 9 10 17 17 20 24 30
memoria[i] 0 1 5 8 10 13 17 18 22 25 30
s[i] 0 1 2 3 2 2 6 1 2 3 10

Para reconstruir la secuencia de cortes comenzamos del último valor que está en la solución de cortes óptimos.
Se imprime este corte y se resta el tamaño cortado para obtener el siguiente corte óptimo.
Considerando el ejemplo de cortar un tronco de tamaño 4, vemos en el vector memoria[4] se tiene el valor
de 10 que es el valor máximo. En la reconstrucción tomamos s[4] que tiene el valor de 2 indicando que se
usó un corte de tamaño 2. Restando este tamaño de corte llegamos a S[2] = 2 que indica que se cortó un
tronco de tamaño 2. Luego se llega a cero y termina el proceso.
La implementación de la solución es el algoritmo 41.

Algoritmo 41: Recostruyendo la solución de la ecuación recurrencia 16.5


int Resolver (p)
while (n > 0)
print(”Tamaño -s[n] + ”de - precios[s[n]]+”, ”) ;
n=n-s[n] ;
16.6 Corte de troncos 341

16.6.6. Programas mencionados en el texto


16.6.6.1. Solución recursiva al problema cortar troncos

/∗
∗ S o l u c i o n r e c u r s i v a a l p r o b l e m a de c o r t a r t r o n c o s
∗ Autor Jorge Teran
∗/

public c l a s s TroncosRecursivo {
/ / s t a t i c int [] longitud =
// { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
stati c int [] precios =
{ 0 , 1 , 5 , 8 , 9 , 1 0 , 1 7 , 1 7 , 2 0 , 2 4 , 30 } ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {

System . o u t . p r i n t l n ( R e s o l v e r ( 4 ) ) ; / / d e b e s e r 10
System . o u t . p r i n t l n ( R e s o l v e r ( 8 ) ) ; / / d e b e s e r 22
}

s t a t i c i n t Resolver ( i n t n ) {
i f ( n == 0 )
return 0;
i n t q = I n t e g e r . MIN VALUE ;
f o r ( i n t i = 1 ; i <=n ; i ++) {
q = Math . max ( q , p r e c i o s [ i ] + R e s o l v e r ( n − i ) ) ;
}
return q;
}
}

16.6.6.2. Solución con caching al problema cortar troncos

import java . u t i l . Arrays ;


/∗
∗ S o l u c i o n con c a c h i n g a l p r o b l e m a de c o r t a r t r o n c o s
∗ Autor Jorge Teran
∗/
p u b l i c c l a s s TroncosMemoria {
342 Capı́tulo 16 Programación Dinámica
/∗
∗ Autor Jorge Teran
∗/

/ / s t a t i c int [] longitud =
// { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
stati c int [] precios =
{ 0 , 1 , 5 , 8 , 9 , 1 0 , 1 7 , 1 7 , 2 0 , 2 4 , 30 } ;
s t a t i c int l t = precios . length ;
s t a t i c i n t [ ] memoria = new i n t [ l t ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
f o r ( i n t i = 0 ; i < l t ; i ++)
memoria [ i ] = I n t e g e r . MIN VALUE ;
System . o u t . p r i n t l n ( R e s o l v e r ( 4 ) ) ; / / d e b e s e r 10
System . o u t . p r i n t l n ( R e s o l v e r ( 8 ) ) ; / / d e b e s e r 22
}

s t a t i c i n t Resolver ( i n t n ) {
i f ( memoria [ n ] >= 0 )
r e t u r n memoria [ n ] ;
i n t q = 0;
i f ( n == 0 )
q = 0;
else {
q = I n t e g e r . MIN VALUE ;
f o r ( i n t i = 1 ; i <= n ; i ++) {
q = Math . max ( q , p r e c i o s [ i ] + R e s o l v e r ( n − i ) ) ;
}
}
memoria [ n ] = q ;
/ / System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( memoria ) ) ;
r e t u r n memoria [ n ] ;
}
}

16.6.6.3. Solución no recursiva al problema cortar troncos

import java . u t i l . Arrays ;

/∗
∗ S o l u c i o n no r e c u r s i v a a l p r o b l e m a de c o r t a r t r o n c o s
∗ Autor Jorge Teran
16.6 Corte de troncos 343

∗/

p u b l i c c l a s s TroncosBottomUp {
/ / s t a t i c int [] longitud =
// { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
static int [] precios =
{ 0 , 1 , 5 , 8 , 9 , 1 0 , 1 7 , 1 7 , 2 0 , 2 4 , 30 } ;
s t a t i c int l t = precios . length ;
s t a t i c i n t [ ] memoria = new i n t [ l t ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t l n ( R e s o l v e r ( 4 ) ) ; / / d e b e s e r 10
memoria = new i n t [ l t ] ;
System . o u t . p r i n t l n ( R e s o l v e r ( 8 ) ) ; / / d e b e s e r 22
}

s t a t i c i n t Resolver ( i n t n ) {
f o r ( i n t j = 1 ; j <= n ; j ++) {
i n t q = I n t e g e r . MIN VALUE ;
f o r ( i n t i = 1 ; i <= j ; i ++) {
q = Math . max ( q , p r e c i o s [ i ] + memoria [ j − i ] ) ;
}
memoria [ j ] = q ;
}
/ / System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( memoria ) ) ;
r e t u r n memoria [ n ] ;
}
}

16.6.6.4. Reconstrucción del problema cortar troncos

import java . u t i l . Arrays ;

/∗
∗ R e c o n t r u y e n d o l a s o l u c i o n p r o b l e m a de c o r t a r t r o n c o s
∗ Autor Jorge Teran
∗/

public class TroncosReconstruir {


/ / s t a t i c int [] longitud =
// { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
static int [] precios =
{ 0 , 1 , 5 , 8 , 9 , 1 0 , 1 7 , 1 7 , 2 0 , 2 4 , 30 } ;
344 Capı́tulo 16 Programación Dinámica
s t a t i c int l t = precios . length ;
s t a t i c i n t [ ] memoria = new i n t [ l t ] ;
s t a t i c i n t [ ] s = new i n t [ l t ] ;

p u b l i c s t a t i c v o i d main ( S t r i n g [] args ) {
System . o u t . p r i n t l n ( R e s o l v e r ( 4 ) ) ; / / d e b e s e r 10
Reconstruir (4);
memoria = new i n t [ l t ] ;
s = new i n t [ l t ] ;
System . o u t . p r i n t l n ( R e s o l v e r (8)); / / d e b e s e r 22
Reconstruir (8);
memoria = new i n t [ l t ] ;
s = new i n t [ l t ] ;
System . o u t . p r i n t l n ( R e s o l v e r (10)); / / d e b e s e r 30
Reconstruir (10);
}

s t a t i c i n t Resolver ( i n t n ) {
i n t ind =0;
f o r ( i n t j = 1 ; j <= n ; j ++) {
i n t q = I n t e g e r . MIN VALUE ;
f o r ( i n t i = 1 ; i <= j ; i ++) {
i f ( q < p r e c i o s [ i ] + memoria [ j − i ] ) {
q = p r e c i o s [ i ] + memoria [ j − i ] ;
ind = i ;
}
}
memoria [ j ] = q ;
s [ j ]= ind ;
}
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( p r e c i o s ) ) ;
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( memoria ) ) ;
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( s ) ) ;
r e t u r n memoria [ n ] ;
}
s t a t i c void Reconstruir ( i n t n ) {
System . o u t . p r i n t l n ( ” R e c o n s t r u y e n d o ”+ n ) ;
while ( n > 0) {
System . o u t . p r i n t ( 1 + ” de ”+ p r e c i o s [ s [ n ] ] + ” , ” ) ;
n = n − s[n ];
}
System . o u t . p r i n t l n ( ) ;
}
16.7 Distancia de edición 345

16.7. Distancia de edición


16.7.1. Enunciado

Se llama Distancia de Levenshtein o Distancia de edición al número mı́nimo de operaciones requeridas


para transformar una cadena en otra. Se entiende por operación una inserción, eliminación o substitución
de un caracter [Wikipedia 2016a]. Esta distancia recibe ese nombre en honor al cientı́fico ruso Vladimir
Levenshtein, quien se ocupara de esta distancia en 1965. Es útil en programas relacionados al genoma
humano, plagio, corrección de ortografı́a, etc.. Este tema se ha tratado en múltiples textos de [Miguel
A. Revilla 2003] [Dasgupta et al. 2006][Thomas H. Cormen and Rivest 1990] para mostrar diferentes formas
de afrontar problemas de programación dinámica.
Veamos algunos ejemplos de transformaciones:

llamas --> llama (eliminar la ’s’ al final)


vasta --> basta (remplazar de ’v’ por ’b’)
echo --> hecho (insertar la ’h’ al principio)

En el desarrollo se utiliza el ejemplo de cambiar aprecio − − > auspicio donde la cadena t = aprecio y la
cadena s = auspicio.

aprecio --> auspicio


aprecio --> auspicio (remplazar a por a)
aprecio --> auprecio (insertar u)
aprecio --> ausprecio (insertar s)
ausprecio --> auspecio (eliminar la r)
auspecio --> auspicio ( cambiar la e por i)
auspecio --> auspicio ( cambiar la c por c)
auspecio --> auspicio ( cambiar la i por i)
auspecio --> auspicio ( cambiar la o por o)

Para encontrar coincidencias aproximadas en una cadena hay que, primero definir una función de costo que
nos diga la distancia entre dos cadenas. Minimizar la distancia, también minimiza el costo de los cambios
para convertir una cadena en otra.
Definamos las siguientes operaciones:

Remplazo consiste en cambiar un único caracter del patrón s a un caracter diferente en el texto t.
Inserción consiste en insertar un único carácter en el patrón s para ayudarlo a coincidir con el texto t.
Eliminación consiste en eliminar un único caracter en el patrón s para ayudarlo a coincidir con el texto
t.

Para conseguir que la semejanza de cadenas sea una pregunta con sentido, es necesario que fijemos el costo
de cada una de estas operaciones de transformación. El costo de insertar o borrar sera 1, podemos darnos
cuenta que el costo de remplazar es 0 cuando son iguales porque no es necesario hacer nada, 1 en otros casos
porque remplazamos un caracter.
346 Capı́tulo 16 Programación Dinámica
Los casos base (triviales) ocurren en los extremos. Cuanto tenemos 0 caracteres en una de las dos cadenas se
tiene que insertar todos los caracteres de la otra. Y en el otro sentido será exactamente igual.
Sean las cadenas s, t donde i, j nos permiten recorrer los caracteres de las cadenas s, t respectivamente.
Cuando dos caracteres igualan hay que recorrer el caracter siguiente en cada una de las cadenas, esto
representa sumar uno a los ı́ndices i y j. En el caso de insertar un caracter en la cadena s implica que
en la cadena hay que recorrer un caracter a la derecha, vale decir sumar 1 al ı́ndice j. En el caso de borrar
un caracter en la cadena t, se debe considerar solo el siguiente caracter de la cadena t, que es equivalente a
incrementar el ı́ndice i.
Con esta información podemos plantear la ecuación de recurrencia:


 j si i=0
 i si j=0




D(i, j) =  (D(i − 1, j) + costo borrar
 (16.7.1)




 minimo D(i, j − 1) + costo insertar
 
 D(i − 1, j − 1) + costo remplazar

El algoritmo 42 es una implementación directa de la ecuación de recurrencia, donde los costos han sido
remplazados por un valor constante. En un caso más general se puede escribir un método que calcule cada
uno de estos costos.

Algoritmo 42: Algoritmo que implemente de la ecuacion de recurrencia de 16.7.1


int DistEd (n)
remplazo=0,insertar=0,borrar=0 ;
if (i == 0)
return j ;
if (j == 0)
return i ;
if (s(i) == t(j))
remplazo = DistEd(i - 1, j - 1) +0 ;
else
remplazo = DistEd(i - 1, j - 1) +1 ;
insertar = DistEd( i, j - 1) + 1 ;
borrar = DistEd( i - 1, j) + 1 ;
return min(empareja, min(insertar,borrar)); ;

Como se aprecia en el algoritmo 42 presentado existen tres ramificaciones recursivas para obtener un valor.
Esto representa un tiempo de proceso proporcional a 3n , que como se dijo es un tiempo inaceptable. Además
que los valores se calculan múltiples veces. Siendo un algoritmo con un tiempo de proceso claramente
exponencial ya tenemos la respuesta. Hay que aplicar los principios de la programación dinámica para
obtener una solución en tiempo eficiente.
Aplicando los conceptos de programación dinámica reduciremos la complejidad a O(n2 ). La solución
recursiva presentada es claramente una estrategia top down, dado que el algoritmo recorre las cadenas del
último caracter al primero.
16.7 Distancia de edición 347
Se presenta una solución bottom up comenzado del principio de la cadena para llegar al final. Para esto se
requiere una matriz que almacena los valores dejando la respuesta en el último valor.

16.7.2. Solución con Programación Dinámica


Para implementar una solución hay que plantear el principio de optimalidad, esto está descrito en la ecuación
de recurrencia 16.7.1.
Las condiciones iniciales de este problema son:

Las filas y columnas de la matriz representan cada una, una cadena.


Insertar i caracteres cuesta i
borrar i caracteres cuesta i

Ahora solo hay que recorrer la matriz de la programación dinámica hallando los costos intermedios, para
obtener el algoritmo 43.

Algoritmo 43: Algoritmo que implemente de la ecuacion de recurrencia de 16.7.1 iterativamente


int DistEd (n)
remplazo=0,insertar=0,borrar=0 ;
for ([i = 0, s.length()))
m[0][i] = i ;
for ([i = 0, t.length()))
m[i][0] = i ;
for ([i = 0, s.length()))
for ([j = 0, t.length()))
if (s(i) == t(j))
remplazo = m[i - 1][j - 1] +0 ;
else
remplazo = m[i - 1][j - 1] +1 ;
insertar = m[i][j - 1] + 1 ;
borrar = m[i - 1][j] + 1 ;
m[i][j] = min(empareja, min(insertar,borrar));
return (m[s.length()-1][t.length()-1]);

¿Dónde está la solución?. Como el proceso se hizo comenzando de la primera posición la solución está en la
última posición que es m[s.length() − 1][t.length() − 1].
Analizando el algoritmo vemos dos ciclos anidados, por lo que, queda claro que el algoritmo 43 toma un
tiempo proporcional a O(n2 ).
Luego de la ejecución del algoritmo 43 la matriz de programación dinámica resultante se muestra a
continuación:

a u s p i c i o
a 0 1 2 3 4 5 6 7
348 Capı́tulo 16 Programación Dinámica
p 1 1 2 2 3 4 5 6
r 2 2 2 3 3 4 5 6
e 3 3 3 3 4 4 5 6
c 4 4 4 4 4 4 5 6
i 5 5 5 5 4 5 4 5
o 6 6 6 6 5 5 5 4

La última casilla muestra que el mı́nimo número de operaciones de edición para transformar aprecio a
auspicio es de 4. Como puede apreciar en cada una de las celdas se ha almacenado el valor más pequeño que
puede obtenerse al realizar una transformación, después analizar las tres celtas anteriores.

16.7.3. El grafo que representa la distancia de edición


Con la finalidad de construir un DAG, se introduce un arco entre cada dos configuraciones. Si una configura-
ción se calcula de otra configuración colocamos un precio adicional representa moverse de una configuración
a otra. De esta forma tenemos en la distancia de edición tenemos arcos que van desde el vértice (i, j) al vérti-
ce (i, j − 1) y al vértice (i − 1, j) ambos con un costo de 1. Además existe otro arco del vértice (i, j) al
vértice (i − 1, j − 1) con un costo de 0 si s(i) = t(j) y 1 en otros casos.
En el grafo resultante hay que hallar el camino más corto entre (0, 0) y (m, m). Este grafo es un DAG (grafo
dirigido acı́clico), que en este problema tiene un tamaño de n ∗ m donde n y m son los tamaños de las dos
cadenas. La matriz de adyacencia que que se construye con los datos del ejemplo es:

a u s p i c i o
a 0→ 1→ 1ց 1 1 1 1 0
p 1 1 1 0ց 1 1 1 1
r 1 1 1 1 1↓ 1 1 1
e 1 1 1 1 1ց 1 1 1
c 1 1 1 1 1 0ց 1 1
i 1 1 1 1 1 1 0ց 1
o 1 1 1 1 1 1 1 0

Las flechas a la derecha representan una inserción, hacia abajo borrar un caracter, y en diagonal un remplazo.
Como se ve éste recorrido proporciona el camino mı́nimo.
El tiempo de proceso para estas soluciones es proporcional a O(nm), donde m y n son las longitudes de
las cadenas. La matriz de programación dinámica también requiere el mismo espacio de almacenamiento al
igual que la solución utilizando un DAG.

16.7.4. Reconstruir las acciones realizadas


No solo queremos hallar el valor mı́nimo, también queremos conocer todas las operaciones necesarias para
hallar éste valor. Para reconstruir el camino recorrido es necesario recordar de que operación llegamos a la
que estamos buscando. Es posible crear otro arreglo donde guardemos estos valores o con un método más
general, como describe Skenia [Miguel A. Revilla 2003], que puede aplicarse a diversos tipos de problemas,
en el cual se crea una clase par para almacenar el costo y el padre de cada una de las celdas seleccionadas.
Denominamos padre al solución del subproblema anterior.
16.7 Distancia de edición 349
static class Par {
int costo, padre;
}

Corregimos el programa para que almacene en el atributo costo los valores del costo, y el en el atributo
padre un identificador para saber cual fue su antecesor. A éste identificador daremos los siguientes valores
EM P AREJA = 0, IN SERT AR = 1, BORRAR = 2 EL momento de realizar el proceso guardaremos
en el atributo padre si el proceso para llegar a esta celda fue de emparejar, insertar o borrar.
Ahora podemos reconstruir la distancia de edición recursivamente. El caso base está en la posición (0, 0) que
tiene el valor −1. Comenzamos la reconstrucción desde el último valor, volviendo a ver si empareja, si hay
que borrar, o insertar.

Algoritmo 44: Algoritmo para reconstruir la secuencia de cambios en la distancia de edición


reconstruirCamino (i,j)
remplazo=0,insertar=0,borrar=0 ;
if (j == −1)
return ;
if (i == −1)
return ;
if (m[i][j].padre == −1)
return ;
if (m[i][j].padre == EM P AREJA)
reconstruirCamino(i - 1, j - 1) ;
if (s(i) == t(j))
print(”Sin Cambios”) ;
else
print(Remplazo”) ;
return ;
if (m[i][j].padre == IN SERT AR)
reconstruirCamino(i, j - 1);
print(Ïnsertar”) ;
return;
if (m[i][j].padre == BORRAR)
reconstruirCamino(i - 1, j) ;
print(”Borrar”) ;
return;

Para el ejemplo la repuesta que obtenemos del algoritmo 44 nos muestra los pasos que se realizaron para
obtener la mı́nima distancia de edición. La salida del programa se muestra a continuación:

Sin Cambios
Insertar
350 Capı́tulo 16 Programación Dinámica
Insertar
Sin Cambios
Remplazo
Borrar
Sin Cambios
Sin Cambios
Sin Cambios
16.7 Distancia de edición 351

16.7.5. Programas mencionados en el texto

16.7.6. Solución recursiva al problema distancia de edición

import java . u t i l . Scanner ;


/∗
∗ S o l u c i o n r e c u r s i v a a l p r o b . de d i s t a n c i a de e d i c i o n
∗ Autor Jorge Teran
∗/

public class EditBrute {


s t a t i c S t r i n g s = new S t r i n g ( ” a u s p i c i o ” ) ;
s t a t i c S t r i n g t = new S t r i n g ( ” a p r e c i o ” ) ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t f ( ” Mejor C o s t o = %d \ n ” ,
Distancia Edicion ( s . length () − 1 , t . length () − 1));
}
static int Distancia Edicion ( int i , int j ) {
i n t e m p a r e j a =0 , i n s e r t a r =0 , b o r r a r = 0 ;
i f ( i == 0 )
return j ;
i f ( j == 0 )
return i ;
i f ( s . c h a r A t ( i ) == t . c h a r A t ( j ) )
e m p a r e j a = D i s t a n c i a E d i c i o n ( i −1 , j −1) + 0 ;
else
e m p a r e j a = D i s t a n c i a E d i c i o n ( i −1 , j −1) + 1 ;
i n s e r t a r = D i s t a n c i a E d i c i o n ( i , j −1) + 1 ;
b o r r a r = D i s t a n c i a E d i c i o n ( i −1 , j ) + 1 ;
return
Math . min ( e m p a r e j a , Math . min ( i n s e r t a r , b o r r a r ) ) ;
}
}

16.7.6.1. Solución no recursiva al problema distancia de edición

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;
/∗
∗ S o l u c i o n no r e c u r s i v a a l p r o b l e m a
352 Capı́tulo 16 Programación Dinámica
∗ D i s t a n c i a de E d i c i o n
∗ Autor Jorge Teran
∗/

public class DistanciaNoRecursivo {


s t a t i c i n t [ ] [ ] m;
// s t a t i c S t r i n g s = new S t r i n g ( ” g a t o s ” ) ;
// s t a t i c S t r i n g t = new S t r i n g ( ” p a t o ” ) ;
s t a t i c S t r i n g t = new S t r i n g ( ” a u s p i c i o ” ) ;
s t a t i c S t r i n g s = new S t r i n g ( ” a p r e c i o ” ) ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t f ( ” Mejor C o s t o = %d \n ” ,
DistanciaEdicion (s , t ) ) ;
}

s t a t i c int DistanciaEdicion ( String s , String t ) {


i n t e m p a r e j a =0 , i n s e r t a r =0 , b o r r a r = 0 ;
i n t i = 0 , j = 0 , k = 0; /∗ contadores ∗/
m = new i n t [ t . l e n g t h ( ) + 1 ] [ s . l e n g t h ( ) + 1 ] ;
/ / m a t r i z de PD
f o r ( i = 0 ; i <= s . l e n g t h ( ) ; i ++)
m[ 0 ] [ i ] = i ;
f o r ( i = 0 ; i <= t . l e n g t h ( ) ; i ++)
m[ i ] [ 0 ] = i ;
f o r ( i = 1 ; i < s . l e n g t h ( ) ; i ++)
f o r ( j = 1 ; j < t . l e n g t h ( ) ; j ++) {
i f ( s . c h a r A t ( i ) == t . c h a r A t ( j ) )
e m p a r e j a =m[ i − 1 ] [ j − 1 ] + 0 ;
else
e m p a r e j a = m[ i − 1 ] [ j − 1 ] + 1 ;
i n s e r t a r = m[ i ] [ j − 1 ] + 1 ;
b o r r a r = m[ i − 1 ] [ j ] + 1 ;
m[ i ] [ j ] = Math . min ( e m p a r e j a ,
Math . min ( i n s e r t a r , b o r r a r ) ) ;
}
ImprimirMatriz ( s , t ) ;
r e t u r n (m[ s . l e n g t h ( ) − 1 ] [ t . l e n g t h ( ) − 1 ] ) ;
}

s t a t i c void ImprimirMatriz ( S t r i n g t , S t r i n g s ) {
char [ ] s1 = s . toCharArray ( ) ;
char [ ] t1 = t . toCharArray ( ) ;
System . o u t . p r i n t ( ” ” ) ;
f o r ( i n t i = 0 ; i < s 1 . l e n g t h ; i ++) {
16.7 Distancia de edición 353

System . o u t . p r i n t ( ” ”+ s 1 [ i ] ) ;
}
System . o u t . p r i n t l n ( ) ;
f o r ( i n t i = 0 ; i < t . l e n g t h ( ) ; i ++) {
System . o u t . p r i n t ( t 1 [ i ] + ” ” ) ;
f o r ( i n t j = 0 ; j < s . l e n g t h ( ) ; j ++) {
System . o u t . p r i n t (m[ i ] [ j ] + ” ” ) ;
}
System . o u t . p r i n t l n ( ) ;
}
}
}

16.7.6.2. Reconstruir la solución del problema distancia de edición

import java . u t i l . Arrays ;


import java . u t i l . Scanner ;
/∗
∗ S o l u c i o n a l p r o b l e m a D i s t a n c i a de e d i c i o n
∗ Autor Jorge Teran

∗ Creamos una c l a s e P a r p a r a g u a r d a r
∗ e l p r e d e c e s o r , c o s t o y metodo p a r a
∗ i m p r i m i r l a m a t r i z de PD
∗/

public class EditDistanceBack1 {


s t a t i c f i n a l i n t EMPAREJA = 0 ;
s t a t i c f i n a l i n t INSERTAR = 1 ;
s t a t i c f i n a l i n t BORRAR = 2 ;
s t a t i c S t r i n g t = new S t r i n g ( ” a u s p i c i o ” ) ;
s t a t i c S t r i n g s = new S t r i n g ( ” a p r e c i o ” ) ;

// s t a t i c S t r i n g s = new S t r i n g ( ” p a t o s ” ) ;
// s t a t i c S t r i n g t = new S t r i n g ( ” g a t o ” ) ;
s t a t i c p a r [ ] [ ] m = new
par [ t . length ()+ 1][ s . length ( ) + 1 ] ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
System . o u t . p r i n t f ( ” Mejor c o s t o = %d \n ” ,
ProgramacionDinamica ( s , t ) ) ;
r e c o n s t r u i r C a m i n o ( s . l e n g t h () −1 , t . l e n g t h ( ) −1);
}
354 Capı́tulo 16 Programación Dinámica
s t a t i c i n t ProgramacionDinamica ( S t r i n g s , S t r i n g t ) {
i n t i = 0 , j = 0 , k = 0; /∗ contadores ∗/
i n t o p t [ ] = new i n t [ 3 ] ; / ∗ c o s t o de l a s o p c i o n e s ∗ /

f o r ( i n t l = 0 ; l <= t . l e n g t h ( ) ; l ++)
f o r ( i n t l 2 = 0 ; l 2 <= s . l e n g t h ( ) ; l 2 ++)
m[ l ] [ l 2 ] = new p a r ( ) ;

f o r ( i = 0 ; i <= s . l e n g t h ( ) ; i ++) {
m[ 0 ] [ i ] . c o s t o = i ;
i f ( i > 0)
m[ 0 ] [ i ] . p a d r e = INSERTAR ;
else
m[ 0 ] [ i ] . p a d r e = −1;
}
f o r ( i = 0 ; i <= t . l e n g t h ( ) ; i ++) {
m[ i ] [ 0 ] . c o s t o = i ;
i f ( i > 0)
m[ i ] [ 0 ] . p a d r e = BORRAR;
else
m[ i ] [ 0 ] . p a d r e = −1;
}

f o r ( i = 1 ; i < s . l e n g t h ( ) ; i ++)
f o r ( j = 1 ; j < t . l e n g t h ( ) ; j ++) {
i f ( s . c h a r A t ( i ) == t . c h a r A t ( j ) )
o p t [EMPAREJA] =m[ i −1][ j − 1 ] . c o s t o + 0 ;
else
o p t [EMPAREJA] = m[ i −1][ j − 1 ] . c o s t o + 1 ;
o p t [ INSERTAR ] = m[ i ] [ j − 1 ] . c o s t o + 1 ;
o p t [BORRAR] = m[ i −1][ j ] . c o s t o + 1 ;
m[ i ] [ j ] . c o s t o = o p t [EMPAREJA ] ;
f o r ( k = INSERTAR ; k <= BORRAR; k ++)
i f ( o p t [ k ] <= m[ i ] [ j ] . c o s t o ) {
m[ i ] [ j ] . c o s t o = o p t [ k ] ;
m[ i ] [ j ] . p a d r e = k ;
}
}
printMatrix (s , t );
r e t u r n (m[ s . l e n g t h ( ) − 1 ] [ t . l e n g t h ( ) − 1 ] . c o s t o ) ;
}

s t a t i c i n t empareja ( char c , char d ) {


i f ( c == d )
16.7 Distancia de edición 355

return 0;
return 1;
}

s t a t i c void reconstruirCamino ( i n t i , i n t j ) {
i f ( j ==−1)
return ;
i f ( i ==−1)
return ;

i f (m[ i ] [ j ] . p a d r e == −1) / / f i n c u a n d o e s m( 0 , 0 )
return ;

i f (m[ i ] [ j ] . p a d r e == EMPAREJA) {
r e c o n s t r u i r C a m i n o ( i −1 , j − 1 ) ;
i f ( s . c h a r A t ( i ) == t . c h a r A t ( j ) )
System . o u t . p r i n t f ( ” S ” ) ;
else
System . o u t . p r i n t f ( ” R ” ) ;
return ;
}
i f (m[ i ] [ j ] . p a d r e == INSERTAR ) {
reconstruirCamino ( i , j − 1);
System . o u t . p r i n t f ( ” I ” ) ;
return ;
}
i f (m[ i ] [ j ] . p a d r e == BORRAR) {
r e c o n s t r u i r C a m i n o ( i −1 , j ) ;
System . o u t . p r i n t f ( ” B ” ) ;
return ;
}
}

s t a t i c void p r i n t M a t r i x ( S t r i n g t , S t r i n g s ) {
char [ ] s1 = s . toCharArray ( ) ;
char [ ] t1 = t . toCharArray ( ) ;
System . o u t . p r i n t ( ” ”);
f o r ( i n t i = 0 ; i < s 1 . l e n g t h ; i ++) {
System . o u t . p r i n t ( s 1 [ i ] + ” ” ) ;
}
System . o u t . p r i n t l n ( ) ;
f o r ( i n t i = 0 ; i < t . l e n g t h ( ) ; i ++) {
System . o u t . p r i n t ( t 1 [ i ] + ” ” ) ;
f o r ( i n t j = 0 ; j < s . l e n g t h ( ) ; j ++) {
356 Capı́tulo 16 Programación Dinámica
System . o u t . p r i n t (m[ i ] [ j ] . p a d r e + ” ” ) ;
}
System . o u t . p r i n t l n ( ) ; }
}

s t a t i c c l a s s par {
i n t costo , padre ;
}
}

16.8. La subsecuencia común más grande


16.8.1. Enunciado
El problema de la subsecuencia común mas grande (LCS por sus siglas en inglés: longest common subse-
quence) trata de hallar una subsecuencia más larga que es común a un conjunto de secuencias. Es una medida
de similitud y surge naturalmente en distintas aplicaciones prácticas, como búsqueda de patrones en molécu-
las de ADN, comparación de archivos, etc., [Wikipedia 2016b]. Este problema es un ejemplo tı́pico en el
campo de la bio informática donde generalmente hay que comparar cadenas de moléculas para saber cuan
similares son [Thomas H. Cormen and Rivest 1990].
El planteamiento del problema es como sigue:

Dadas dos secuencias A[1...M ] y B[1...N ] diremos que C[1...P ] es una subsecuencia de B si la
primera puede obtenerse a partir de la segunda borrando 0 o más letras conservando la posición
relativa de las letras restantes.

Por ejemplo: Dados A = ”abcde”B = ”bcdeadg” hallar la subsecuencia más larga entre A y B se escribe
como LCS(A,B). Subsecuencias comunes de A y B tenemos de varias longitudes. De longitud 2 tenemos
ad, de longitud tres se tiene bcd y bce.
Para resolver este problema una posible estrategia es partir del problema distancia de edición, se cambian
los costos asociados, solo se coloca un valor máximo en el costo de remplazo, dejando los otros costos sin
cambios.
En lo que sigue desarrollaremos una solución que es igual de eficiente para mostrar como se plantea la misma
desde el punto de la programación dinámica.
En este problema no desarrollaremos el grafo dirigido que se forma dado que es exactamente igual al ejercicio
de programación dinámica.

16.8.2. Solución por fuerza bruta


Supongamos que A = bcdeadg todas las posibles secuencias son: b, bc, c, bcd......bcdeadg Cuántas subse-
cuencias existen? Podemos ver que existen 2n subconjuntos que podemos formar. Podemos hacer lo mismo
con B = bcd, luego hallamos las secuencias iguales y escogemos la más larga. Cuánto vale la complejidad?
Tenemos 2n posibles conjuntos que comparar esto nos dice que la complejidad será del orden de O(n) = 2n
Es una alternativa buena porque produce la solución. ¿Será rápido? obviamente no pensemos por ejemplo en
16.8 La subsecuencia común más grande 357
una cadena de 30 de caracteres longitud, esto requerirá comparar 1073741824 subconjuntos, que como se
dará cuenta es mucho tiempo de proceso.

16.8.3. Solución utilizando programación dinámica

La solución de programación dinámica indica que, si podemos expresar como hallar el mejor Si recursiva-
mente tendremos una solución
Como tenemos 2 secuencias requeriremos una tabla de dos dimensiones, Para almacenar los resultados
previos. Después escribimos un procedimiento no recursivo que llene las entradas (i,j) de la tabla asumiendo
que otras entradas ya se llenaron. El tiempo de proceso será igual al tamaño de la tabla multiplicado por el
tiempo de llenar cada entrada.
El problema inicial lo podemos dividir en sub problemas con los siguientes criterios:

S = S1 ∪ S2
S1 son todas las secuencias que comienzan con A[1]
S2 el resto, comienzan con cualquier otra letra

¿Como se busca S1 ?

R es el elemento más largo de S y es R[1....p]


R[1] = A[1]
R[1] debe aparecer en B
B[k] = R[1] = A[1]
R[2..p] es una subsecuencia de R[2...m] y B[k + 1...n]

Sea LCS(i, j) la subsecuencia común más larga de A[1...i] y B[1...j] Entonces los casos base son
LCS(0, j) = 0 y LCS(i, 0) = 0. Luego, LCS(i, j) = 1 + LCS(i + 1, j + 1) si A[i] = B[j], y el
caso general LCS(i, j) = max(LCS(i + 1, j), LCS(i, j + 1))
Caracterizamos la ecuación de recurrencia


 0 si i = 0

 0 si j = 0
LCS(i, j) =
 1 + LCS(i + 1, j + 1)
 si i, j > 0 y Ai = Bj

max(LCS(i + 1, j), LCS(i, j + 1)) si (i, j) > 0 y Ai 6= Bj

Definimos nuestra matriz de programación dinámica LCS(1...m, 1...n) y comencemos de atrás para adelan-
te, y recorremos todos los valores como se muestra en el algoritmo 45.
El resultado estará en la primera posición de la matriz LCS[0][0]. ¿Cual es el tiempo de proceso? Como ve
se tienen dos ciclos anidados que nos muestran que el tiempo de proceso es proporcional a O(n2 ).

16.8.3.1. Reconstruyendo la solución


No solamente es necesario hallar el valor óptimo sino que es necesario reconstruir el camino, esto es decir
cual es la subsecuencia que tiene la longitud máxima.
358 Capı́tulo 16 Programación Dinámica

Algoritmo 45: Algoritmo para hallar la similitud de dos cadenas

for ([i = M − 1, 0])


for ([j = N − 1, 0])
if (a(i) == b(j))
lcs[i][j] = lcs[i + 1][j + 1] + 1 ;
else
lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]) ;

Para reconstruir la solución, una opción es seguir las estrategias mostradas en el problema distancia de
edición. Esto es crear una clase Par o definir un segundo arreglo donde se guarden los datos utilizados
para hallar la solución.
En este problema es posible reconstruir la cadena más larga sin utilizar espacio adicional. Para esto recorre-
mos la matriz partiendo del valor óptimo y de acuerdo a la definición cuando igualan los valores de las dos
cadenas imprimimos el valor. Para saber si debemos ir comparando los caracteres de la primera o segunda
cadena usamos los resultados en la matriz que indica cual es el próximo valor a considerar. El algoritmo 46
muestra esta implementación.

Algoritmo 46: Algoritmo para hallar la similitud de dos cadenas

i = 0, j = 0 ;
while (i < M and j < N )
if (a(i) == b(j))
print(a.charAt(i)) ;
i++ ;
j++ ;
else
if (lcs[i + 1][j] >= lcs[i][j + 1])
i++ ;
else
j++ ;

Por ejemplo las cadenas A = abcdef y B = acf x producen la siguiente tabla:

a c f x
a 3 2 1 0
b 2 2 1 0
c 2 2 1 0
d 1 1 1 0
e 1 1 1 0
f 1 1 1 0
16.8 La subsecuencia común más grande 359
El valor buscado se encuentra en la posición (0, 0) y es 3. Partiendo de los valores de las cadenas buscamos
la primera ocurrencia donde tienen un caracter en común, es a y esta en la posición cero. Se imprime.
Luego hay que comparar los valores lcs[i + 1][j] >= lcs[i][j + 1] y vemos que son iguales lo que hará
que se incremente i, o j dando la próxima posición de la cadena a. Este proceso finalmente en tiempo lineal
imprimirá la secuencia buscada.
360 Capı́tulo 16 Programación Dinámica

16.8.4. Programas mencionados en el texto


16.8.4.1. Solución al problema de la subsecuencia más larga

import java . u t i l . Arrays ;


/∗
∗ Solucion r e c u r s i v a a l prob .
∗ S u b s e c u e n c i a Comun mas L a r g a
∗ Autor Jorge Teran
∗/
p u b l i c c l a s s LCS {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
String a = ” abcdef ”;
String b = ” acfx ”;
int M = a . length ( ) ;
int N = b . length ( ) ;
i n t [ ] [ ] l c s = new i n t [M + 1 ] [N + 1 ] ;
/ / Calcular todas las secuencias
/ / via programacion dinamica
f o r ( i n t i = M − 1 ; i >= 0 ; i −−) {
f o r ( i n t j = N − 1 ; j >= 0 ; j −−) {
i f ( a . c h a r A t ( i ) == b . c h a r A t ( j ) )
l c s [ i ] [ j ] = l c s [ i + 1] [ j + 1] + 1;
else
l c s [ i ] [ j ] = Math . max ( l c s [ i + 1 ] [ j ] ,
lcs [ i ][ j + 1]);
}
}
for ( int [] is : lcs ) {
System . o u t . p r i n t l n ( A r r a y s . t o S t r i n g ( i s ) ) ;
}
System . o u t . p r i n t l n ( ” LCS e s : ” + l c s [ 0 ] [ 0 ] ) ;
/ / r e c u p e r a r e l LCS y i m p r i m i r
i n t i = 0 , j = 0;
w h i l e ( i < M && j < N) {
i f ( a . c h a r A t ( i ) == b . c h a r A t ( j ) ) {
System . o u t . p r i n t ( a . c h a r A t ( i ) ) ;
i ++;
j ++;
} e l s e i f ( l c s [ i + 1 ] [ j ] >= l c s [ i ] [ j + 1 ] )
i ++;
else
j ++;
}
16.9 Problema de la suma de subconjuntos 361

System . o u t . p r i n t l n ( ) ;
}
}

16.9. Problema de la suma de subconjuntos


16.9.1. Enunciado
Dado un conjunto de números enteros. ¿Existe algún subconjunto cuya suma sea exactamente S?
Para entender el problema construyamos un ejemplo:
Sea X = {5, 4, 2, 1} y la suma que buscamos S = 10.
Una primera idea de solución es la de hallar todos los subconjuntos de X y hallar la suma de estos y verificar
que alguno sume S. En nuestro ejemplo los subconjuntos son:

Sub Conjunto Suma


1 {} 0
2 {5} 5
3 {4} 4
4 {1} 1
5 {2} 2
6 {5,4} 9
7 {5,1} 6
8 {5,2} 7
9 {4,1} 5
10 {4,2} 6
11 {1,2} 3
12 {5,4,1} 10
13 {5,4,2} 11
14 {5,1,2} 8
15 {4,1,2} 7
16 {1,2,4} 7

Todos los subconjuntos de un conjunto de n elementos es 2n , en el ejemplo 24 = 16. El tiempo de proceso


(complejidad) será proporcional a 2n que claramente es un problema exponencial. Con una cantidad de
elementos grande el tiempo de proceso es inaceptable.

16.9.2. Grafo que representa la solución


Para entender este problema vamos a construir un grafo en base de los elementos del conjunto X =
{7, 4, 6, 3} y la suma que buscamos S = 10. Como se ve existen dos soluciones {4, 6} y {3, 7}.
Primero ordenamos los elementos de X = {3, 4, 6, 7} y se parte desde 0 hasta llegar la suma que buscamos.
Colocamos en la raı́z del grafo el cero, y luego iremos colocando a la izquierda los elementos que se incluyen
en la solución y en la derecha los que no se incluyen. En los arcos anotamos los elementos incluidos y en los
nodos las sumas. Después de considerar el primer elemento, el 3, obtenemos el grafo siguiente:
362 Capı́tulo 16 Programación Dinámica

considera el 3 no considera 3

3 0

Continuando con el siguiente elemento el 4: si se incluye lo registramos a la izquierda, entonces en el nodo


3 registremos los efectos de incluir o no el elemento, obteniendo:

considera el 3 no considera 3

3 0

considera 4 no considera 4

7 3

Ahora cuando tratamos de incluir el elemento 6, se ve que se excede la suma buscada que es 10, motivo por
el cual pusimos una x en este nodo. Si no se incluye se mantiene el valor del nodo padre, el resultado es el
siguiente:

3 3

3 0

4 4

7 3

6 6

X 7

Al no considerar el 4, es posible tomar en cuenta el 6 y el 7. Se ve que al escoger el 6 llegamos a sumar 9 y


con el 7 se suma 10 obteniendo un resultado, el grafo es:
16.9 Problema de la suma de subconjuntos 363

3 3

3 0

4 4

7 3

6 6 6 6

X 7 9 3

7 7

X 10

Finalmente continuamos el proceso y halla todos los subconjuntos que suman 10. EL grafo final es:

3 3

3 0

4 4 4 4

7 3 4 0

6 6 6 6 6 6 6 6

X 7 9 3 10 4 6 0

7 7 7 7 7 7

X X 10 X X 7

Una segunda alternativa de construir el grafo es comenzar del valor buscado, en el ejemplo es el 10, y luego
ir restando los valores que forman parte del subconjunto escogido. En este caso los nodos cuyo valor son los
que suman el valor buscado. El grafo siguiente muestra este caso.
364 Capı́tulo 16 Programación Dinámica

10

3 3

7 10

4 4 4 4

3 7 6 10

6 6 6 6 6 6 6 6

X 3 1 7 0 6 4 10

7 7 7 7 7 7

X X 0 X X 3

16.9.3. Ecuación de recurrencia


Del desarrollo visto cuando se incluye un elemento en el subconjunto hay que restar este valor de la suma
buscada, subSetsum(n − 1, suma − set[n − 1]. En caso de no incluir el elemento no se resta de la suma
buscada, subsetSum(n − 1, suma), como se vió en en el grafo solo ocurre uno u otro.
Ninguna recursión está completa sin los casos base, que quedan especificados de las condiciones iniciales,
si la suma es 0 es posible construir esta suma al no escoger ningún elemento y si la suma es mayor a 0 y no
quedan elementos para procesar no existe una solución.



 f alse si suma > 0
&&n == 0




subsuma(n, suma) = true si suma == 0 (16.9.1)




 subsuma(n − 1, suma)k
subsuma(n − 1, suma − set[n − 1]) otros casos

Para mejorar el tiempo de proceso cuando se encuentra un elemento cuyo valor es mayor a la suma que se
busca, no hay que incluir este elemento y se mantiene la suma.
El algoritmo 47 implementa la ecuación de recurrencia:

16.9.4. Solución iterativa utilizando Programación Dinámica


Para desarrollar la solución iterativa seguiremos la estrategia común que es la de construir una matriz e
inicializar con los valores que representan el caso base, que son lo que terminan la recursión. En el problema
que estamos resolviendo tenemos los elementos de un conjunto que colocaremos en las filas. Para la suma
que se quiere hallar utilizaremos las columnas. El ejemplo X = {7, 4, 6, 3} y la suma que buscamos S = 10,
produce el cuadro 16.1.
Suma cero es posible hallar al no escoger elementos por esto la primera columna se ha inicializado en 0.
16.9 Problema de la suma de subconjuntos 365

Algoritmo 47: Implementación del algoritmo subsetSum


boolean subsetSum (n, suma)
if (suma = 0)
return true ;
if (n = 0&&suma! = 0)
return false ;
if (set[n − 1] > suma)
return subsetSum(n-1, suma) ;
return subsetSum(n-1, suma) k subsetSum(n-1, suma-set[n-1]) ;

0 1 2 3 4 5 6 7 8 9 10
3 T F F T F T T T T T T
4 T
6 T
7 T
Cuadro 16.1 Subset Sum valores iniciales de la solución iterativa

Con relación a la primera fila podemos ver que una suma con valor 3 puede hallarse escogiendo el 3. Esto
hace que este valor sea verdadero y por el mismo motivo en el resto de las columnas el valor inicial en falso.
Para llenar el resto de la tabla es necesario tomar en consideración la ecuación recursiva que indica
subsuma(n − 1, suma)k
subsuma(n − 1, suma − set[n]) que significa que si el valor de la fila anterior y la misma columna es
verdadero se debe tomar este valor y en otro caso verificar si la columna después de retroceder la cantidad
de columnas que indica el valor del subconjunto es verdadero, que representa que este valor se puede formar
con este valor. El cuadro 16.1 resultante es:

0 1 2 3 4 5 6 7 8 9 10
3 T F F T F F F F F F F
4 T F F T T F F T F F F
6 T F F T T F T T F T T
7 T F F T T F T T F T T
Cuadro 16.2 Subset Sum valores finales de la solución iterativa

El algoritmo 48 muestra esta solución:


366 Capı́tulo 16 Programación Dinámica

Algoritmo 48: Implementación del algoritmo iterativo de subsetSum

for (i=[1,n))
for (j=[1,suma])
matPd[i][j]=matPd[i-1][j] ;
if (matP d[i][j] = f alse&&j >= set[i])
matPd[i][j]= matPd[i][j]——matPd[i-1][j-set[i]] ;

16.10. Reconstruyendo la Solución


Comenzando de la última posición de la tabla 16.3 vemos que el valor verdadero viene de la fila anterior,
esto indica que un elemento de la solución es 6. En la fila anterior retrocedemos 7 columnas. Esto nos hace
llegar a la columna de valor 4, y se ve que viene de la columna anterior, por lo que esta en la solución. Se
sube una columna y recorremos 4 columnas a la izquierda para obtener cero. Una solución es 6, 4.
El cuadro 16.3 muestra el estado final y el proceso de reconstrucción de una solución.

0 1 2 3 4 5 6 7 8 9 10
3 T F F T F F F F F F F
4 T F F T ←T F F T F F F
6 T F F T ↑T F T T F T ←T
7 T F F T T F T T F T ↑T
Cuadro 16.3 Reconstruyendo la solución
16.10 Reconstruyendo la Solución 367

16.10.1. Programas mencionados en el texto


16.10.1.1. Solución recursiva al problema subset sum

public c l a s s PruebaSubSet {
/ / Solucion r e c u r s i v a a l problema subsetsum

s t a t i c i n t s e t [ ] = { 0 ,3 , 4 , 6 , 7 };

p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
i n t suma = 5 ;
int n = set . length ;
i f ( s u b s e t S u m ( n , suma ) == t r u e )
System . o u t . p r i n t l n ( ” E x i s t e un ”+
” s u b c o n j u n t o con suma ” + suma ) ;
else
System . o u t . p r i n t l n ( ” No E x i s t e un ”+
” s u b c o n j u n t o con suma ” + suma ) ;
s u b I t e r a t i v o ( n , suma ) ;
}

s t a t i c b o o l e a n s u b s e t S u m ( i n t n , i n t suma ) {
/ / casos base
i f ( suma == 0 )
return true ;
i f ( n == 0 && suma ! = 0 )
return false ;
/ / c u a n d o s e e x c e d e l a suma b u s c a d a
/ / s e d e j a de b u s c a r
i f ( s e t [ n − 1 ] > suma )
r e t u r n s u b s e t S u m ( n − 1 , suma ) ;
/ / D e c i d i r s i i n c l u i r o no e l e l e m e n t o
r e t u r n s u b s e t S u m ( n − 1 , suma ) | |
s u b s e t S u m ( n − 1 , suma − s e t [ n − 1 ] ) ;
}

16.10.1.2. Solución iterativa al problema subset sum

public c l a s s PruebaSubSet {
/ / Solucion i t e r a t i v a a l problema subsetsum
368 Capı́tulo 16 Programación Dinámica

s t a t i c i n t set [] = { 3 , 4 , 6 , 7 };

p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
i n t suma = 5 ;
int n = set . length ;
i f ( s u b I t e r a t i v o ( n , suma ) == t r u e )
System . o u t . p r i n t l n ( ” E x i s t e un ” +
” s u b c o n j u n t o con suma ” + suma ) ;
else
System . o u t . p r i n t l n ( ” No E x i s t e un ” +
” s u b c o n j u n t o con suma ” + suma ) ;

s t a t i c b o o l e a n s u b I t e r a t i v o ( i n t n , i n t suma ) {
b o o l e a n matPd [ ] [ ] = new b o o l e a n [ n + 1 ] [ suma + 1 ] ;
f o r ( i n t i = 0 ; i < n ; i ++)
matPd [ i ] [ 0 ] = t r u e ;
f o r ( i n t i = 1 ; i <= suma ; i ++)
i f ( i == s e t [ 0 ] )
matPd [ 0 ] [ i ] = t r u e ;
else
matPd [ 0 ] [ i ] = f a l s e ;
f o r ( i n t i = 1 ; i < n ; i ++){
f o r ( i n t j = 1 ; j <= suma ; j ++) {
matPd [ i ] [ j ] = matPd [ i −1][ j ] ;
i f ( matPd [ i ] [ j ]== f a l s e && j >= s e t [ i ] )
matPd [ i ] [ j ] = matPd [ i ] [ j ] | |
matPd [ i −1][ j −s e t [ i ] ] ;
}
}
r e t u r n matPd [ n −1][ suma ] ;
}
}
16.11 Ejercicios Propuestos 369

16.11. Ejercicios Propuestos


16.11.1. Super Suma
Problema 1308, disponible en https://jv.umsa.bo/

La función SuperSuma se define como:


SuperSuma(0, n) = n, para todo n positivo
SuperSuma(k, n) = SuperSuma(k − 1, 1) + SuperSuma(k − 1, 2) + ... + SuperSuma(k − 1, n), para
todo entero positivo k, n.
Dados k, n, devuelva el valor para SuperSuma(k, n).

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba esta en una lónea que tiene los número
(1 ≤ k, n ≤ 20), separados por un espacio.

Salida
Escriba en una lı́nea el resultado de SuperSuma(k, n).

Ejemplos
1 3
Respuesta: 6
Cuando k = 1, SuperSuma es igual a la suma de los n = 3 números: 1 + 2 + 3 = 6.
2 3
Respuesta: 10
SuperSuma(2, 3) = SuperSuma(1, 1) + SuperSuma(1, 2) + SuperSuma(1, 3) = 1 + 3 + 6 = 10.

Ejemplos de entrada Ejemplos de salida


1 3 6
2 3 10
4 10 2002
10 10 167960
370 Capı́tulo 16 Programación Dinámica

16.11.2. Recaudando Fondos


Problema 1556, disponible en https://jv.umsa.bo/.

En un pequeño pueblo todas las casas se han construido en cı́rculo alrededor de una plaza. Le han asignado
la tarea de recaudar donativos para la restauración.
Cada residente del pueblo, ubicado al contorno de la plaza, está deseoso de donar una cierta cantidad de
dinero. Sin embargo nadie está de acuerdo en contribuir a un fondo en el cual sus vecinos que se encuentra
en la puerta contigua, hayan contribuido. Los vecinos que viven en la puerta contigua siempre se listan
consecutivamente. La última casa también es vecina de la primera.
Halle el máximo dinero que puede ser recaudado.
Por ejemplo, dada la secuencia de donativos 10, 3, 2, 5, 7, 8, el valor máximo se obtiene con 10 + 2 + 7. Es
mejor 10 + 5 + 8, sin embargo el 8 y 10 corresponden a vecinos. El valor máximo que se puede recaudar es
19.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea contiene un número (2 ≤ N ≤ 40)
que representa el número de personas. Luego siguen (0 ≤ di ≤ 1000) valores separados por espacio
representando el valor que cada vecino puede donar. Los casos de prueba terminan cuando no hay más
datos.

Salida
Por cada caso de prueba escriba en una lı́nea la máxima donación que puede obtener.

Ejemplos de entrada
6
10 3 2 5 7 8
2
11 15
7
7 7 7 7 7 7 7
10
1 2 3 4 5 1 2 3 4 5
40
94 40 49 65 21 21 106 80 92 81 679
4 61 6 237 12 72 74 29 95 265 35
47 1 61 397 52 72 37 51 1 81 45
435 7 36 57 86 81 72

Ejemplos de salida
19
15
16.11 Ejercicios Propuestos 371
21
16
2926
372 Capı́tulo 16 Programación Dinámica

16.11.3. La Subsecuencia ascendente más larga


Problema número 1554, disponible en https://jv.umsa.bo//

La secuencia ascendente más larga se define como la secuencia más larga que podemos hallar quitando
algunos elementos. Para solucionar este problema también utilizaremos programación dinámica. Por ejemplo
sea la secuencia: (10, 22, 9, 33, 21, 50, 41, 60, 80). La secuencia ascendente más larga que podemos encontrar
es de longitud 6 y es (10, 22, 33, 50, 60, 80). Si vemos la secuencia (6, 3, 5, 2, 7, 8, 1) tenemos las siguientes
secuencias:

Subsecuencia no acendente: (5, 2, 1)


Subsecuencia ascendente: (3, 5, 9)
Subsecuencia ascendente: (2, 7, 8)
La subsecuencia ascendente más larga: (3, 5, 7, 8)

Entrada
La entrada consiste de múltiples casos de prueba. Cada caso de prueba viene en dos lı́neas. La primera lı́nea
contiene el número N ≤ 10000 de elementos de la secuencia. La siguiente lı́nea tiene N números enteros
que son los elementos de la secuencia. La entrada termina cuando no hay más datos.

salida
Por cada caso de entrada escriba el tamaño de la subsecuencia ascendente más larga que se puede formar.

Ejemplos de entrada Ejemplos de salida


9 6
10 22 9 33 21 50 41 60 80 4
7
6 3 5 2 7 8 1
16.11 Ejercicios Propuestos 373

16.11.4. Secuencias que suben y bajan


Problema número 1555, disponible en: https : //jv.umsa.bo/

Una secuencia de números se denomina que sube y baja si la diferencia de números sucesivos alterna entre
números negativos y positivos. La primera diferencia si existe puede ser positiva o negativa.
Una secuencia con dos o menos números es la secuencia trivial.
Por ejemplo la secuencia (1, 7, 4, 9, 2, 5) tiene la propiedad sube y baja porque las diferencias (6, −3, 5, −7, 3)
son alternadamente positivas y negativas. En contraste la secuencia (1, 4, 7, 2, 5) no es una secuencia sube
y baja porque la primera diferencia es positiva, La secuencia (1, 7, 4, 5, 5) tampoco es una secuencia sube y
baja porque la última diferencia es cero.
Dada una secuencia de enteros, devuelva la longitud máxima de la subsecuencia sube y baja que se puede
formar. Una subsecuencia se obtiene borrando algunos elementos (tal vez cero) de la secuencia original,
dejando los elementos restantes en su orden original.

Entrada
La entrada consiste de varios casos de prueba. La primera lı́nea de cada caso de prueba tiene el número
(0 ≤ N ≤ 50 de elementos de la secuencia. La segunda lı́nea de cada caso de prueba contiene los
(0 ≤ Ni ≤ 1000) números de la secuencia separados por un espacio. La entrada termina cuando no hay
más datos.

Salida
Para cada caso de prueba imprima en una lı́nea la máxima subsecuencia que sube y baja que se pueda formar.

Ejemplos de entrada
6
1 7 4 9 2 5
10
1 17 5 10 13 15 10 5 16 8
1
44
9
1 2 3 4 5 6 7 8 9
19
70 55 13 2 99 2 80 80 80 80 100
19 7 5 5 5 1000 32 32
50
374 40 854 203 203 156 362 279 812
955 600 947 978 46 100 953 670 862
568 188 67 669 810 704 52 861 49 640
370 908 477 245 413 109 659 401 483
308 609 120 249 22 176 279 23 22 617
462 459 244
374 Capı́tulo 16 Programación Dinámica

Ejemplos de salida
6
7
1
2
8
36
16.11 Ejercicios Propuestos 375

16.11.5. Dulces

Problema número 1552, disponible en https://jv.umsa.bo/

Carlitos es un niño adicto a los dulces. Hasta se subscribió a la revista especializada en dulces que lo escogió
para participar en el concurso internacional de escoger dulces.
En este concurso existe un número aleatorio de cajas que contienen dulces y esta dispuestas en M filas con
N columnas, ası́ que se tienen un total de M × N cajas. Cada caja tiene un número indicando cuantos dulces
contiene.
El concursante puede elegir una caja cualquiera y obtener todos los dulces que contiene. Pero siempre hay
una trampa, cuando escoge una caja todas las cajas de la filas inmediatamente encima e inmediatamente abajo
de la caja escogida, se vacı́an, ası́ como la caja de la izquierda y la caja de la derecha de la caja escogida.
El concursante continúa hasta que no hay más dulces disponibles.
La figura siguiente ilustra esto, paso a paso. Cada celda representa una caja y el número de dulces que
contiene. En cada paso la caja escogida está en un cı́rculo y las cajas sombreadas son la que se se vacı́an.
Después de ocho pasos el juego se termina y Carlitos escogió 10 + 9 + 8 + 3 + 7 + 6 + 10 + 1 = 54 dulces.

Para números pequeños de M y N , Carlitos puede muy fácilmente hallar el máximo número de dulces que
puede escoger, sin embargo cuando los números son altos esta completamente perdido. ¿Puede ayudar a
Carlitos a maximizar el número de duluces que puede alzar?

Entrada

La entrada consiste de varios casos de prueba. La primera lı́nea de cada caso de prueba contiene los enteros
positivos M y N (1 ≤ M × N ≤ 105 ) separados por un espacio, indicando el número de filas y columnas
respectivamente. Las siguientes M lı́neas contienen N enteros separados por un espacio, representando el
número inicial de dulces en la caja correspondiente. Inicialmente cada caja tendrá al menos 1 y como máximo
103 dulces.
El final de la entrada se representa por una lı́nea que contiene dos ceros separados por un espacio.
376 Capı́tulo 16 Programación Dinámica
Salida
Para cada caso de prueba en la entrada, su programa debe imprimir en una sola lı́nea la cantidad máxima de
dulces que Carlitos puede alzar.

Ejemplos de entrada Ejemplos de salida


5 5 54
1 8 2 1 9 40
1 7 3 5 2 17
1 2 10 3 10
8 4 7 9 1
7 1 3 1 6
4 4
10 1 1 10
1 1 1 1
1 1 1 1
10 1 1 10
2 4
9 10 2 7
5 1 1 5
0 0
16.11 Ejercicios Propuestos 377

16.11.6. Palı́ndrome
Problema número 1553 disponible en https://jv.umsa.bo

Un palı́ndrome es una cadena que se lee igual de izquierda a derecha como lo hace desde la derecha. Por
ejemplo, I, GAG y MADAM son palı́ndromes, pero ADAM no lo es. En este sentido, consideramos también
la cadena vacı́a como un palı́ndrome.
De cualquier cadena no palindrómica, siempre se puede quitar algunas letras, y obtener una subsecuencia
palindrómica. Por ejemplo, dada la cadena de ADAM, se elimina la letra M y obtener un palı́ndrome ADA.
Escriba un programa para determinar el palı́ndrome de mayor longitud que se puede obtener a partir de una
cadena.

Entrada
La primera lı́nea de entrada contiene un número entero (T ≤ 60). Cada una de las siguientes T lı́neas es una
cadena, cuya longitud es siempre menor que 1000.
Para el 90 % de los casos de prueba, cadena de longitud menor o igual que 255.

Salida
Para cada cadena de entrada, el programa debe imprimir la longitud del mayor palı́ndrome se puede obtener
mediante la eliminación de cero o más caracteres de la misma.

Ejemplos de entrada Ejemplos de salida


3 3
ADAM 5
MADAM 13
supercalifragilisticespialidoso
378 Capı́tulo 16 Programación Dinámica

16.11.7. Computadora de cadenas

Problema número 164 disponible en https://uva.onlinejudge.org

Extel acaba de sacar su nueva computadora, una computadora de procesamiento de cadenas llamada X9091.
Se espera que tenga algún valor en criptografı́a y campos relacionados. (Se rumorea que los taiwaneses están
trabajando en un clon que corregirá ensayos de la etapa 1, pero ignoraremos tales errores).
Este equipo aceptará cadenas de entrada y producirá cadenas de salida de ellas, dependiendo del programa
cargado en ese momento. El chip es lo último en tecnologı́a RISC por los que, sólo tiene tres instrucciones
de transformación

borrar un caracter en una posición particular


insertar un caracter en una posición particular
cambiar un caracter en una posición particular por un caracter diferente.

Los programas para esta máquina están escritos en una forma de código máquina donde el código para la
instrucción (D, I o C) está en el formato ZXdd, X es un caracter y dd representa un número de dos dı́gitos.
Un programa se termina con una instrucción de parada especial que consiste en la letra E.
Tenga en cuenta que cada instrucción funciona en la cadena en memoria en el momento en que se ejecuta
la instrucción. Para ver cómo funciona todo esto, considere el siguiente ejemplo. Se desea transformar la
cadena abcde a la cadena bcgf e. Esto puede lograrse mediante una serie de comandos de cambio, pero no es
mı́nimo. El siguiente programa es mejor:

abcde
Da01 bcde %vea que la letra $a$ es necesaria porque es verificado por el hardware
Cg03 bcge
If04 bcgfe
E bcgfe %termina el programa

Escriba un programa que lea dos cadenas, (la cadena inicial y la cadena de destino) y produzca el programa
X9091 minimo necesario para transformar la cadena de entrada a la cadena de salida. Cualquier solución que
cumpla este criterio será aceptada.

Entrada
La entrada consiste de múltiples lı́neas cada una con dos cadenas separadas exactamente por un espacio. La
entrada termina en una lı́nea que contiene un solo caracter el #.

Salida
La salida consiste en una serie de lı́neas, una por cada lı́nea de entrada. Cada lı́nea consiste de un programa
en el lenguaje X9091.
16.11 Ejercicios Propuestos 379

Ejemplos de entrada Ejemplos de salida


abcde bcgfe Da01Cg03If04E
#
380 Capı́tulo 16 Programación Dinámica

16.11.8. Distancia de Edición y Proceso de Transformación


Problema número 526 disponible en https://uva.onlinejudge.org

La distancia de edición es un entero no negativo que mide la distancia entre dos cadenas. La definición es:
una lista de transformación de una lista de cadenas, donde cada cadena, excepto la última, puede cambiarse a
la cadena después de agregar un carácter, borrar un carácter o reemplazar un carácter. La longitud de una lista
de transformación es el recuento de cadenas menos 1 (que es el recuento de operaciones para transformar
estas dos cadenas). La distancia entre dos cadenas es la longitud de una lista de transformación de una cadena
a la otra con la longitud mı́nima. Usted debe escribir un programa para calcular la distancia entre dos cadenas
y dar la correspondiente lista de transformación.

Entrada
La entrada consiste de dos cadenas, cada una ocupa una lı́nea, la longitud de una cadena no es mayor a 80
caracteres. La entrada termina cuando no hay más datos.

Salida
Para cada par de cadenas, debe dar un número entero para indicar la longitud entre ellas en la primera lı́nea,
y en las siguientes lı́neas de la secuencia de comandos para transformar la cadena 1 a la cadena 2. Cada
comando se imprime en una lı́nea, con el siguiente formato:

Insert pos , valor


Delete pos
Replace pos , valor

donde pos es la posición en la cadena y debe estar entre 1 y la longitud de la cadena. Valor, es el caracter.
Existen muchas soluciones, sin embargo se requiere una sola de ellas.

Ejemplos de entrada Ejemplos de salida


abcac 3
bcd 1 Delete 1
aaa 2 Replace 3,d
aabaaaa 3 Delete 4

4
1 Insert 1,a
2 Insert 2,a
3 Insert 3,b
4 Insert 7,a
16.11 Ejercicios Propuestos 381

16.11.9. Secuencia de Búsqueda en una Base de Datos de biologı́a molecular

Problema número 1192 disponible en https://uva.onlinejudge.org

Los biólogos moleculares frecuentemente comparan las bio-secuencias para ver si existen similitudes con
la esperanza de que Lo que es cierto de una secuencia es también cierto en su análogo. En este problema,
nos centramos en las secuencias de ácido nucleico Que se componen de cuatro sı́mbolos ✭✭A✮✮, ✭✭C✮✮, ✭✭G✮✮ o
✭✭T✮✮. Generalmente, tales comparaciones implican alinear secciones De las dos secuencias de una manera
que expone las similitudes entre ellas. Dada una secuencia de consulta y un conjunto de almacenadas en una
base de datos, se le pedirá que escriba un programa que busca en la base de datos y halle la secuencia que
tiene la puntuación de similitud más grande con la secuencia de consulta.
La puntuación de similitud entre la secuencia de consulta y una secuencia de base de datos es la suma de
las puntuaciones de alineación De los pares alineados de sı́mbolos de una alineación de las dos secuencias.
Dos sı́mbolos idénticos que son alineados reciben una puntuación de +5 mientras que un par de sı́mbolos
no coincidentes tiene una puntuación de -4. Una brecha es introducida en una alineación si un sı́mbolo en
una secuencia no está alineado con sı́mbolos en el otro. La penalización por una brecha es una puntuación
de -7. Por ejemplo, dada una secuencia de consulta m = GAAGGCAy una base de datos Secuencia n =
GCAGAGCA, la siguiente es la alineación entre ellas (los pares alineados de sı́mbolos se escriben uno por
encima del otro) tiene una puntuación de similitud de 5 + (- 4) + 5 + 5 + (- 7) + 5 + 5 + 5 = 21.

Sequence m: G A A G - G C A
Sequence n: G C A G A G C A

Vea que la brecha en la alineación presentada, la hemos representada por −. El algoritmo de programación
dinámica provee una estrategia matemáticamente rigurosa, para resolver este problema.

Entrada
La entrada consiste de una secuencia de búsqueda en las primeras dos lı́neas y una lista de secuencia, cada
una en dos lı́neas, la primera es el nombre de la secuencia y la segunda la secuencia. Existe una lı́nea en
blanco entre dos secuencias adyacentes.

Salida
La salida indica la secuencia que tiene el mayor puntaje de similitud con la secuencia de búsqueda. El
resultado debe imprimirse en el siguiente formato:

The query sequence is:


(la secuencia de busqueda)
The most similar sequences are:
(dtos de la secuencia 1)
The similarity score is: (grado de similitud)
(datos de la secuencia 2)
The similarity score is: (grado de similitud)
382 Capı́tulo 16 Programación Dinámica

Ejemplos de entrada Ejemplos de salida


>query The query sequence is:
ACGGG ACGGG
>seq1 The most similar sequences are:
ACGGT ACGGGG
>seq2 The similarity score is: 18
ACGGGG AACGGG
>seq3 The similarity score is: 18
TCCGGTT
>seq4
TCGGG
>seq5
AACGGG
A
A.1.
Juez Virtual de la Carrera de Informáti-
ca
Introducción
El juez virtual de la carrera de informática es un programa cuya finalidad es la de verificar programas
desarrollados en Java y C,C++, que sean correctos. Esta verificación se realiza en base de datos de prueba.
El documento está destinado a la presonas que quieren aprender a programar, para que puedan hacer uso
efectivo del juez.

A.2. Comenzar
Para empezar debe dirigirse a la p[agina del juez cuya dirección (figura:A.1) es: www.jv.umsa.bo.
En la página se visualizan pestañas: Principal, problemas, estado, ranklist, concursos, FAQs y en una
posición izquierda superior derecha Entrar Registro.

A.3. Registrarse
En la parte superior derecha se cuenta con un enlace para realizar el registro (figura:A.1):
En la página de registro (figura: A.2 debe llenar todos los datos requeridos. Debe tener cuidado de proporcio-
nar datos que recuerde, el nombre de usuario será con el que será identificado y la contraseña la clave para su
ingreso. Es importante que proporcione un correo activo y no olvidar el captcha, para finalizar oprima crear
y listo puede ingresar al juez y resolver problemas.

A.4. Ingresar con mi cuenta de usuario


Dirigirse la página principal y oprimir el enlace de entrar (figura: A.4)

Ingrese su usuario con el que se registro en el juez.


Ingresar su contraseña.
Presionar Submit para ingresar.

Una vez que haya ingresado al juez notara que su usuario aparecerá en la esquina superior derecha

A.5. Envı́o de problemas


Muy independiente de los concursos se tienen la opción de resolver todo el conjunto de problemas disponibles
en el juez. Para lo cual se debe dirigir a la parte de problemas:
Para poder enviar un problema (figura A.3) se debe ingresar al enunciado del problema por ejemplo
resolvamos el problema 1000 A+B.
Cada problema tiene varios conceptos que describimos

383
384 Apéndice A Juez Virtual de la Carrera de Informática

Figura A.1 Juez Virtual

Descripción.- Describe el enunciado del problema.


Entrada.- Describe la forma en la que están los datos de entrada.
Salida.- Describe como se espera que presente la solución del problema.
Ejemplo de entrada.- Este es un ejemplo de como son sus datos de entrada.
Ejemplo de salida.- Esta es la solución en el formato esperado para los datos de entrada.

En el problema 1000 que resolveremos nos muestra la información de la imagen A.4


Si leemos el enunciado nos indica que debemos leer dos números A, B y luego imprimir el resultado. El
programa debe leer los datos del teclado y mostrar el resultado en la pantalla. No se deben incluir mensajes
tales como ingrese A, la respuesta es, etc. La respuesta debe tener un formato idéntico al presentado en el
ejemplo.
Para enviar una solución (figura: A.5) primero se debe escoger el lenguaje de programación, que se encuentra
al centro de la pantalla. En el caso de Java la clase debe tener el nombre de Main y el método principal debe
llamarse main. Hay que recalcar que el programa debe escribirse en un solo archivo.
En segundo lugar copiamos el programa en recuadro y se debe oprimir el enlace de ENVIAR.
El programa que resuelve el problema 1000 es el siguiente:

1 import java . u t i l . ∗ ;
2
3 p u b l i c c l a s s Main {
4 p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
5 S c a n n e r c i n = new S c a n n e r ( System . i n ) ;
6 int a , b ;
7 while ( cin . hasNext ( ) ) {
8 a = cin . nextInt () ; b = cin . nextInt () ;
9 System . o u t . p r i n t l n ( a + b ) ;
A.5 Envı́o de problemas 385

Figura A.2 Registro de usuario nuevo

10 }
11 }
12 }

La clase Scanner es la que nos permite leer los datos del teclado. El método hasNext() permite saber si existen
más datos en el archivo de entrada. Esto asociado a la instrucción while nos permite procesar todos los datos
de la entrada.
Una vez copiado el código y presionada la tecla ENVIAR, se evaluará la solución, las posibles respuestas que
nos puede dar el juez son:

Pending.- Indica que aún está pendiente de revisión. Puede ser que el juez tenga muchos envios.
386 Apéndice A Juez Virtual de la Carrera de Informática

Figura A.3 Envı́o de problemas

Pending Rejudge.- Rejudge, significa que se ha decidió volver a evaluar los envı́os de un problema
especifico. Esto ocurre cuando los autores de los problemas descubren errores en los datos de prueba y
por lo tanto es necesario, volver a revisar todos los envı́os. Esto puede hacer que programas aceptados,
luego se consideren fallidos y también problemas no aceptados ahora se consideren correctos.
Compiling.- Indica que el programa está siendo compilado.
Running & Rejudging. Running indica que el programa está siendo procesado y rejudging que está
volviendo a juzgar.
Accepted.- Esto es lo que esperamos. La solución que enviamos es correcta y ha sido aceptada.
Presentation Error.- Este error se presenta cuando hay espacios adicionales en la respuesta. Esto puede
ser uno o más espacios al final de la lı́nea. Una lı́nea en blanco que falta o sobra en una respuesta, etc.
Significa que eliminando los espacios probablemente se obtenga el resultado esperado.
A.5 Envı́o de problemas 387

Figura A.4 Enunciado del ejercicio de prueba

Figura A.5 Envió de un programa

Wrong Answer. Indica que los resultados generados por el programa son diferentes a los registrados en
el juez.
Time Limit Exceeded.- Se da cuando el tiempo de proceso es mayor al esperado. Esto ocurre en los
casos que existe una solución más eficiente que la desarrollada. Tambien si su programa está en un ciclo
y no termina.
Memory Limit Exceeded.- Los programas generalmente deben resolverse utilizando 64kb o 128kb de
memoria ram si exceden esto se produce el error.
Output Limit Exceeded.- Cuando el archivo de salida es mayor que el permitido se obtiene el error.
388 Apéndice A Juez Virtual de la Carrera de Informática

Figura A.6 Estado de un problema enviado

Compile Error.- Significa error de compilación. En java puede indicar que su clase principal no es Main.

Para revisar el estado (figur A.6 de su envı́o acceda al menú donde indica ESTADO puede revisar su envı́o
ingresando su usuario y el número del problema.

A.6. Competencias
Periódicamente, ya sea en clases o como tarea se crean competencias . Estas competencias son listas de
problemas que se espera que deben resolver en un tiempo especifico. Esta opción tiene varias pestañas:

Standings.- Esta opción nuestra el detalle de problemas enviados, resueltos, y el número de intentos para
resolver un problema.
Statistics.- Muestra los problemas indicando como fueron resueltos, cuales fueron resuelto, y otras
estadı́sticas.
Enviar una solución.- Para esto solo ingrese el nombre del problema y tendrá todas las opciones para
enviar problemas tal como se explicó anteriormente.

Para establecer el orden de los participantes se toma la cantidad de problemas resueltos y el tiempo que
demoró en resolver los problemas. En clases solo nos interesa el número de problemas resueltos y no ası́ el
número de intentos.
B Errores comunes en tiempo de ejecu-
ción
Durante la ejecución de un programa pueden producirse diversos errores. Se muestran los más comunes.
ArrayIndexOutOfBoundsException
Se produce cuando intentamos acceder a un vector (array) o cadena (String) con un index inválido (menor
que 0 o mayor que su longitud .). Por ejemplo si definimos un vector de tamaño n el recorrido del mismo
debe ser desde 0 hasta un n − 1. Eso completa los n valores del tamaño del mismo. El recorrido correcto es:

for (int i= 0; i<n; i++){


.....
}

NumberFormatException
Se obtiene cuando un método convierte una cadena a un número y esta cadena no puede ser convertido.
Cuando usamos Integer.parseInt y el argumento no es un número entero. Lo mismo puede ocurrir al leer
datos del flujo de entrada. El siguiente comando proporcionara éste error, dado que, el argumento contiene
elementos que no son números.

Integer.parseInt("a123")

StackOverflowError
Ocurre tı́picamente cuando un método recursivo excede la capacidad de memoria para almacenar los valores
intermedios.
NullPointerException
Ocurre cuando intentamos acceder a un objeto con una variable de referencia cuyo valor actual es null.

NoSuchElementException
Ocurre cuando tratamos de leer más allá del final de un archivo. Cuando leemos datos del teclado no ocurre
porque siempre podemos seguir ingresando datos.

389
Bibliografı́a
A.V. Aho and J.D. Ullman. Foundations of Computer Science: C Edition. Principles of computer science
series. W. H. Freeman, 1994. ISBN 9780716782841. URL https://books.google.com.bo/books?id=
q7-HQgAACAAJ.
Jon Bentley. Programming Pearls (2Nd Ed.). ACM Press/Addison-Wesley Publishing Co., New York, NY,
USA, 2000. ISBN 0-201-65788-0.
David M. Bressoud. Factorization and Primality Testing. Springer - Verlag, 1988.
Gary Cornell Cay S. Horstman. Core Java 2 J2SE 5.0 Volume 1. Sun Microsystems Press, 2005.
csgeek. Backtracking @ONLINE, May 2016. URL http://www.csegeek.com/csegeek/view/tutorials/
algorithms/backtrack/backtrack part2.php.
S. Dasgupta, C. H. Papadimitriou, and U. V. Vazirani. Algorithms. McGraw-Hill Education, 2006.
Robert Fisher. Fibonacci Applications and Strategies for Traders. Wiley, 2010.
Peter Giblin. Primes and Programming. Cambridge University Press, 1993.
Paul Bratley Gilles Brassard. Fundamentals of Algoritms. Printice Hall, 1996.
Steven Halim and Felix Halim. Competitive Programming. Lulu, 2013.
Ron Knott. Fibonacci Numbers and the Golden Section. 2010. URL http://www.maths.surrey.ac.uk/
hosted-sites/R.Knott/Fibonacci/.
Mark Korenblit and Vadim E. Levit. Fibonacci graphs and their expressions. CoRR, abs/1305.2647, 2013.
URL http://arxiv.org/abs/1305.2647.
Steven S. Skiena Miguel A. Revilla. Programming Challenges. Springer, 2003.
Jorge Teran Pommier. Fundamentos de Programación. Johon Wiley, 1993.
Oren Patashnik Ronald L. Graham, Donald E. Knuth. Concrete Mathematics. Addison Wessley, 1994.
Michael Soltys. An Introduction to the Analysis of Algorithms. World Scientific Publishing, 2012.
Charles E. Leiserson Thomas H. Cormen and Ronald L. Rivest. Introduction to Algorithms. Massachusetts
Institute of Technology, 1990.
Davide Wells. Prime Numbers - The Most Mysterious Figures in Math. John Wiley, 2005.
Wikipedia. Levenshtein distance — wikipedia, the free encyclopedia, 2016a. URL https://en.wikipedia.org/
w/index.php?title=Levenshtein distance&oldid=757041009. [Online; accessed 28-December-2016].
Wikipedia. Problema de subsecuencia común mas larga — wikipedia, la enciclopedia libre,
2016b. URL https://es.wikipedia.org/w/index.php?title=Problema de Subsecuencia Com%C3%BAn
mas Larga&oldid=94760716. [Internet; descargado 10-enero-2017].
Wikipedia. Programacion dinamica — wikipedia, la enciclopedia libre, 2016c. URL https://es.wikipedia.org/
w/index.php?title=Programaci%C3%B3n din%C3%A1mica&oldid=95719761. [Internet; descargado
27-diciembre-2016].

391
Índice de figuras

1.1 Estructura de un programa Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3


1.2 Especificar el área de trabajo en Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Pantalla de bienvenida de Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Entorno de trabajo de Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.5 Opciones para crear un proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.6 Opciones para crear una clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.7 Plantilla de programa inicial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.8 Programa para mostrar un texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.9 Salida del programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.1 Direccionamiento de las variables en la memoria . . . . . . . . . . . . . . . . . . . . . . . 19

4.1 Flujo de ejecución de un if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52


4.2 Flujo de ejecución de un if else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3 Diagrama de la instrucción for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.4 Diagrama de la instrucción while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.5 Diagrama de la instrucción do while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.6 Como anidar Ciclos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.7 Oscilación de la puerta vaivén . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

7.1 Definición de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145


7.2 Definición de un vector de cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.3 Juego de bolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

9.1 Estructura de un programa Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

12.1 Representación de una secuencia de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . 246


12.2 Precondición de una secuencia de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.3 Postcondición de una secuencia de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
12.4 Invariante de clasificación por inserción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
12.5 Invariante de clasificación por selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
12.6 Invariante de clasificación rápida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251

13.1 Arbol de la recursion para el Fibonacci 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . 273

16.1 Grafo de Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326

392
ÍNDICE DE FIGURAS 393
16.2 Grafo que representa los vértices después de utilizar una de las denominaciones. . . . . . . 329
16.3 Grafo que representa los vértices después de utilizar cada una de las denominaciones. . . . . 329
16.4 Grafo que representa los vértices después de utilizar cada una de las denominaciones. . . . . 330
16.5 Grafo que representa el corte de un tronco de tamaño 4 . . . . . . . . . . . . . . . . . . . . 335

A.1 Juez Virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382


A.2 Registro de usuario nuevo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
A.3 Envı́o de problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
A.4 Enunciado del ejercicio de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
A.5 Envió de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
A.6 Estado de un problema enviado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Índice de algoritmos
1 Modelo para construir problemas recursivos . . . . . . . . . . . . . . . . . . . . . . . . . . 268
2 Cálculo del factorial recursivamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
3 Completando el caso recursivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
4 Prueba de programa para calcular factorial del algoritmo . . . . . . . . . . . . . . . . . . . . 269
5 Cálculo del factorial iterativamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
6 Estructura básica para comenzar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
7 Estructura básica con flujo de lectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
8 Estructura con caso base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
9 Programa recursivo para imprimir una cadena en orden inverso . . . . . . . . . . . . . . . . 271
10 Cálculo recursivo del máximo común divisor . . . . . . . . . . . . . . . . . . . . . . . . . . 272
11 Números de Fibonacci solución recursiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
12 Recordando los valores calculados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
13 Recordando los valores calculados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
14 Solución iterativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
15 Cálculo de factores binomiales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
16 Calculo de factores binomiales iterativamente . . . . . . . . . . . . . . . . . . . . . . . . . . 277
17 Solución recursiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
18 Generar secuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
19 Generar secuencias iterativamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
20 Generar las permutaciones de una secuencia . . . . . . . . . . . . . . . . . . . . . . . . . . 280
21 Generar todas las permutaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
22 Generar todos los subconjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
23 Generar todos los subconjuntos iterativamente . . . . . . . . . . . . . . . . . . . . . . . . . 282
24 Hallar los subconjuntos que suman un valor . . . . . . . . . . . . . . . . . . . . . . . . . . 282

25 Verificar si es una celda válida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300


26 Buscar la solución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
27 Verificar si es una posible solucion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

395
396 ÍNDICE DE ALGORITMOS
28 Llamada a la función de retroceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
29 Verificar si es una posible solucion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303

30 Hallar el subconjunto solución trivial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309


31 Solución cuadrática . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
32 Solución cuadrática con vector acumulado . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
33 Solución logaritmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
34 Solución lineal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
35 Hallar la matriz acumulada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314

36 Hallar el número mı́nimo de monedas, usando un arreglo unidimensional . . . . . . . . . . . 331


37 Hallar el número mı́nimo de monedas, usando un arreglo bidimensional . . . . . . . . . . . . 333
38 Implementación directa de la ecuacion de recurrencia de 16.6.1 . . . . . . . . . . . . . . . . 337
39 Implementación recordando soluciones anteriores de la ecuacion de recurrencia de 16.6.1 . . 338
40 Solución bottom up a la ecuación recurrencia 16.5 . . . . . . . . . . . . . . . . . . . . . . . 339
41 Recostruyendo la solución de la ecuación recurrencia 16.5 . . . . . . . . . . . . . . . . . . . 340
42 Algoritmo que implemente de la ecuacion de recurrencia de 16.7.1 . . . . . . . . . . . . . . . 345
43 Algoritmo que implemente de la ecuacion de recurrencia de 16.7.1 iterativamente . . . . . . . 346
44 Algoritmo para reconstruir la secuencia de cambios en la distancia de edición . . . . . . . . . 349
45 Algoritmo para hallar la similitud de dos cadenas . . . . . . . . . . . . . . . . . . . . . . . . 356
46 Algoritmo para hallar la similitud de dos cadenas . . . . . . . . . . . . . . . . . . . . . . . . 357
47 Implementación del algoritmo subsetSum . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
48 Implementación del algoritmo iterativo de subsetSum . . . . . . . . . . . . . . . . . . . . . 364
Índice alfabético
8 reinas, 301 Eficiencia, 240
Backtracking, 299
Accepted, 384
Agrupamiento de instrucciones, 51 Caching, 274
Algoritmo, 15 Cadenas
Algoritmo de Kadane, 309 Convertir tipo datos, 95
And, 55 Definición, 91
Anidar, 64 Lectura del teclado, 94
Aplicación de manejo de bits, 32 Métodos, 92
aplicaciones de la criba, 205 Recorrido, 91
Aritmética básica, 119 Cambiar el tipo de datos, 35
Aritmética modular, 127, 128 Caracteres ascii, 21
aplicaciones, 128 Caracteres especiales, 23
Propiedades, 128 Carmichael, 208
ArrayList Cast, 35
Métodos, 171 Ciclo
Arreglos Anidar, 63
Cadenas, 146 For, 58
Definición, 145 While, 61
Ejemplos, 147 While do, 62
Ejercicios, 152 Clase Math, 34
Métodos, 152 Clases, 190
Matrices, 165 Clasificación, 239, 242
Sintaxis, 145 Arrays, 243
Split, 146 Burbuja, 247
Unidimensionales, 145 Eficiencia, 248
Valor inicial, 147 CompareTo, 244
Arreglos Dinámicos, 169 Inserción, 248
Arreglos Multidimensionales, 165 Invariante, 248
Cuadrado mágico, 166 Java, 243
Definición, 165 Lineal, 253
Dimensión variable, 168 Eficiencia, 254
Ejercicios, 166 Pivote, 251
Atkin, 203 Postcondición, 247
Precondición, 247
Búsqueda, 239 Rápida, 251
Binaria, 240 Representación, 246
Eficiencia, 239 Selección, 250
Secuencial, 239, 240 Eficiencia, 250

397
398 ÍNDICE ALFABÉTICO
Invariante, 250 Ejemplo de salida, 382
Sort, 243 Entrada, 382
Coeficientes binomiales, 275 Envı́o de problemas, 381
Solución iterativa, 276 Eratostenes, 201
solución recursiva, 276 Errores
Competencias, 386 Lógica, 16
Compilador, 1 Sintaxis, 16
Compile Error, 386 Errores comunes, 387
Constantes, 19 Errores de desborde, 18
Constructor, 191 Errores de redondeo, 18, 38
Construir y compilar un programa Estructura de directorios de Eclipse, 9
Eclipse, 4 Estructura de pila, 267
Editor de textos, 1 Estructura de un programa Java, 2, 187
Contar factores primos, 205 Estructuras condicionales, 52
Corte de troncos Estructuras de control, 51
Ecuación de recurrencia, 337 Euclides, 131
Enunciado, 335 Exponenciación rápida, 129
Grafo, 335
Factores primos, 206
Reconstruir la solución, 339
Factorial, 268
Solución bottom-up, 338
Ecuación de recurrencia, 268
Solución de programación dinámica, 337
Factorización, 206
Solución top-down, 338
Fermat, 208
Criba
Fibonacci, 227, 272
Atkin, 203
Árbol de recursión, 272
Eratostenes, 201
Ecuación de recurrencia, 272
lineal, 204
grafo, 326
Descripción, 382 Número aúreo, 228
Desplazamiento de bits, 29 Programas, 227
Despliegue de números con formato, 23 Propiedades, 231
Dirección web, 381 Solución iterativa, 274
Distancia de edición Triángulo de Pascal, 230
Ecuación de recurrencia, 345 For each, 147
Enunciado, 344 Formato de salida, 23
Grafo, 347 Funciones, 188
Reconstruir acciones realizadas, 348 Ejemplo, 189
Solución con programación dinámica, 346
Geométrica, 125
Transformaciones, 344
grafo
Divisibilidad, 127
definición, 326
Divisor común, 127
grafo dirigido
hallar todos los caminos, 327
Eclipse, 3
Eficiencia, 239, 253 Herramientas de desarrollo, 3, 187
Ejemplo de entrada, 382
Ejemplo de Expresiones aritméticas, 34 IDE, 3
ÍNDICE ALFABÉTICO 399
If, 52 Generación, 201
If else, 53 Nombre del programa, 382
If else if, 54 Nombres de Variables, 17
Ineficiencia, 248 Not, 56
Ingresar, 381
Instalación de Eclipse, 4 Open Java, 1
Interpretación de los datos, 22 Operador and, 30
Invariante, 239 Operador or, 31
Operador xor, 31
Lectura Operadores aritméticos, 33
Lotes, 64 Operadores condicionales, 52
Lectura del teclado, 36 Operadores de asignación, 35
Lenguaje, 1 Operadores para números binarios, 29
Logaritmos, 123 Or, 56
Definición, 123 Oracle, 1
Propiedades, 123 orden topológico
grafo dirigido, 326
Máximo común divisor, 130, 131, 272
Output Limit Exceeded, 385
binario, 132
Propiedades, 130
Pagar con monedas
Método de retroceso, 299
Complejidad, 331
Métodos, 187
Enunciado, 328
Ejemplo, 192
Solución basada en arreglo bidimensional, 332
Mı́nimo común múltiplo, 132
Solución basada en arreglo unidimensional,
Manejo de excepciones, 96
330
Math, 34
Solución general, 329
Media, 147
Permutaciones, 280
Memory Limit Exceeded, 385
Post condición, 239
Miller - Rabin, 208
Potencia, 34
Moda, 149
Precondición, 239
Número aúreo, 228 Presentation Error, 384
Números Probable primo, 211
Binarios, 123 Procedimientos, 188, 189
Cambio de base, 122 Programa
Componer, 121 Criba Eratostenes, 202
Descomponer, 121 Divisiones Sucesivas, 199
Fibonacci, 127 Ejemplo de Rsa, 212
Hexadecimales, 123 Excluye múltiplos conocidos, 200
Octales, 123 Factorizar, 206
Perfectos, 126 Miller - Rabin, 208
Primos, 127 Primo de 512 bits, 211
Triangulares, 126 Programación dinámica
Números grandes, 210 Definición, 325
Números primos, 199, 202 programación dinámica
Definicion, 199 grafo dirigido, 326
400 ÍNDICE ALFABÉTICO
grafos, 326 Solución trivial, 355
optimalidad, 325 subsetsum
Propiedades Operadores condicionales, 56 ecuación de recurrencia, 362
Prueba de primalidad, 207 enunciado, 359
Pseudo lenguaje, 16 recosntruyendo la solución, 364
representación con un grafo, 359
Raı́z cuadrada, 34 solución trivial, 359
Recursividad, 267 solucion iterativa, 362
Pila de recursión, 270 Sumatoria
Registro, 381 propiedades, 125
RSA, 211 Switch, 57

Salida, 382 Teorı́a de números, 119


Salida por pantalla, 22 Teorema fundamental de la numeración, 120
Scanner, 36 Test de primalidad, 199
Métodos, 37 Time Limit Exceeded, 385
Secuencias, 279 Tipos de datos, 17
Importantes, 125 Clases, 20
Serie Numéricos, 17, 119
Concepto, 124 Triángulo de Pascal, 230
Aritmética, 124
Variables, 19
Geométrica, 124
Variables globales, 189
Importantes, 125
Variables locles, 189
Srmónica, 125
Varianza, 148
Sistema de numeración, 119
Vector
Solución
Métodos, 170
Casi primos, 216
Primos redondos, 213 Wrong Answer, 385
Ssubsecuencia común más grande
Enunciado, 354
standings, 386
Statistics, 386
Sub vector máximo
Divide y vencerás, 310
Enunciado, 309
Problema en dos dimensiones, 312
Solución cuadrática, 310
solución Lineal, 311
Solución logarı́tmica, 310
Solución trivial, 309
Subconjuntos, 280
Subsecuencia común más grande
Ecuación de recurrencia, 356
Reconstruir la solución, 356
Solución de programación dinámica, 355

También podría gustarte