Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Conceptos y Ejercicios de Programacion E
Conceptos y Ejercicios de Programacion E
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.
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:
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
viii
ÍNDICE GENERAL ix
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
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
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
Bibliography 387
ÍNDICE GENERAL xvii
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.
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().
System.out.println();
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
Para el desarrollo elegimos la herramienta Eclipse. Las razones para esto son las siguientes:
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
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
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
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");
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
Entrada
No hay entrada, el programa no lee nada.
Salida
Escriba una lÃnea con el texto solicitado
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
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.
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:
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.
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.
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
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.
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:
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:
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:
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’;
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;
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);
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");
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);
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.
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
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.
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
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.
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.
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
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
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:
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
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 |
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
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 )).
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
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;
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);
double d = 55.34;
float f = (float)d;
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);
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
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.
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
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
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.
3.8. Ejercicios
3.8 Ejercicios 39
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.
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.
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.
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.
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.
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.
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.
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
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.
Entrada
La entrada consiste de una letra en una lı́nea.
Salida
La salida consiste de una letra de acuerdo al enunciado.
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.
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.
{
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
falso
if(condición)
Verdadero
Bloque de instrucciones
if (condición) {
bloque de instrucciones
}
if (condición)
instrucción;
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:
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.
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)
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 ” ) ;
}
}
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:
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.
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;
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.
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:
}
}
}
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.
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
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:
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:
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
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
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 ) ;
}
}
Equivale a
expresión 1;
while(expresión 2) {
bloque de instrucciones;
expresión 3;
}
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
Ciclo 1 Ciclo 1
Ciclo 2 Ciclo 2
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 ( ) ;
}
}
}
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:
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:
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.
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:
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:
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:
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
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.
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
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.
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
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
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.
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.
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
1 1 1 1
Hn = + + ...... +
1 2 3 n
Entrada
La entrada consiste en un número ”n”.
Salida
Imprima el n-simo número armónico, con 4 decimales de precisión.
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.
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.
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.
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.
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
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.
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.
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”.
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
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.
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.
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.
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";
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));
Para ejemplificar el uso de los métodos descritos consideremos las siguientes definiciones:
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
la casa
de la escuela
es
El siguiente código
hará que:
cad1 = "la";
cad2 = "casa";
cad3 = "de";
5.5 Convertir de cadena a Integer 95
Si leemos por lı́neas:
hará que:
Ahora si
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.
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.
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:
}
}
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.
}
}
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
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
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
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.
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
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.
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.
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.
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.
Entrada
Una cadena de longitud no mayor a 100000.
Salida
”HanSolo” si está hablando Han Solo y ”Chewbacca” si está hablando Chewbacca.
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.”.
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
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
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
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.
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.
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.
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
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
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.
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.
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.
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
k=2
X
10i di = 1x102 + 2x101 + 3x100
i=0
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
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 +” ” ) ;
}
}
}
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:
Volvemos a factorizar b
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.
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
sn = u1 + u2 + u3 ........ + un .
o simplemente
n
X
sn = ui .
i=1
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:
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
Una serie geométrica es una secuencia de términos donde los términos tiene la forma
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
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
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
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
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
4
X
2i − 1 = 1 + 3 + 5 + 7 = 16 = 42
i=1
6.5 Divisibilidad 127
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
Propiedades
(8 + 7) mód 3 = 15 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:
si simplificamos el 6 tenemos
2 mód 3 = 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
a b p
2 7 1
4 3 2
16 1 8
256 0 128
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
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:
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.
mcd(a, b)mcm(a, b) = ab
6.11. Ejercicios
134 Capı́tulo 6 Aritmética Básica
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.
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 ! =.
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
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.
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
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.
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.
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.
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.
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.
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.
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
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
Cuando los valores de un vector se encuentra en una cadena se utiliza el método split de la clase String. Por
ejemplo:
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
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:
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.
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:
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:
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:
// 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:
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
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
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
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.
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.
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
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.
/\
/ \
/
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
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.
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.
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,
Este arreglo tiene 2 filas por 3 columnas. Para definir valores constantes la sintaxis es como sigue:
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: 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:
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 ( ) ;
}
}
}
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
0 1 6
3 5 7
4 0 2
Y termina en:
8 1 6
3 5 7
4 9 2
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 ] ;
// 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.
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:
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:
{
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 ) ;
/ / 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 ( ) ) ;
}
}
[]
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.
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
}
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;
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:
----------
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:
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:
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.
2 3 -9 6
3 4 4 -5
5 5 6 3
-1 -1 -1 10
4 4 -5
5 6 3
-1 -1 10
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
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.
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
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
1 2 3 4 5
6 10
11 15
16 20
21 22 23 24 25
7 8 9
12 14
17 18 19
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
1 2 3
4 5 6
7 8 9
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
7 8 9
4 5 6
1 2 3
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
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:
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:
En este caso recibirá un vector de tipo cadena con tres valores args[0]=los,args[1]=parámetros,args[2]=deseados.
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
}
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:
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.
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.
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:
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:
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.
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 ) ;
}
}
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 ;
}
}
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.
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:
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.
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 ;
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 ;
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
}
}
/∗∗
∗ 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 ) ;
}
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
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 ) ;
}
}
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
}
}
.
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.
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
∗∗/
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 ] ;
}
}
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:
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.
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 ) ;
}
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 ;
}
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 ) ;
}
}
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:
2. Calcular n = pq
n = p.multiply(q);
d = e.modInverse(m);
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 ) ;
}
}
/∗∗
∗ 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.
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:
Ejemplos:
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
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
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.
Entrada
No hay datos de entrada
Salida
Su programa de contar cuanto números primos redondos existen entre 1 y 1010
714 = 2 × 3 × 7 × 17
715 = 5 × 11 × 13
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.
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.
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
x=y;
11.2 Programando la secuencia 229
y=x+y;
!
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
a = a · a + b · by
b = b · a + a · b + b · b.
x=a·x+b·y
y = b · x + a · y + b · y.
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
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:
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
2, 1, 3, 4, 7, 11, . . .
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
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.
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.
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, . . .
0, 1, 1, 2, 3, 5, 8, 3, 1, 4, 5, 9, 4, 3, 7, 0, 7, . . .
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.
1, 2, 3, 5, 8, 13, 21, . . . ,
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.
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.
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
1. Inicialización
2. Preservación de propiedades
3. Terminación
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.
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:
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:
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.
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;
}
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:
Además es necesario especificar que la clase persona incluye el método Comparable y se especifica ası́:
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 ;
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 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;
}
}
Una vez ordenados todos los elementos, la figura 12.3 que representa a la postcondición es la siguiente:
void Burbuja ( i n t [ ] x ) {
248 Capı́tulo 12 Algoritmos de búsqueda y clasificació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.
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
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
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.
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
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;
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
27 2 11 81 81 87 80 7
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:
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]
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:
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
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.
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.
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.
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.
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
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
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.
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.
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
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.
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:
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
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
long r=1;
for (i=(1,n])
r*=i;
return r;
Primero los parámetros, en este caso pondremos el flujo del cual vamos a leer los datos.
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
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
(
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.
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
Fib(n) ;
if (n = 0)
return 0 ;
if (n = 1)
return 1;
return (Fib(n - 1) + Fib(n - 2));
3 4
2 1 3 2
1 0 2 1 1 0
1 0
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
Fib(n) ;
if (f [n] = 0 && n > 0)
f[n] = Fib(n - 1) + Fib(n - 2) ;
return f[n]);
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
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.
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)!
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.
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] ;
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.
Una vez que se tiene calculada la matriz x para hallar n tomados de k es suficiente tomar el valor x[n][k].
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.
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.
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
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
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] ;
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.
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.
Escribir un programa para hallar todos los subconjuntos. Por ejemplo tomemos el conjunto a = {1, 2, 3}.
Todos los subconjunto que podemos construir son:
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.
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:
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]) ;
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.
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}
(
0 si n = 0
f (n) =
max(p(n − 1) + f (n − 1), 1 + p(n)) otros casos
p = {70, 55, 13, 2, 99, 2, 80, 80, 80, 80, 100, 19, 7, 5, 5, 5}
(
0 si n = 0
f (n) =
max(p(i) + f (n − 1), 1 + p(i)) para 1 ≤ i ≤ n
(
0 si n = 0
f (n) =
max(p(i) + f (n − 1)) para 1 ≤ i ≤ n
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:
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
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);
}
}
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
}
}
/∗
∗ 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
∗/
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 ) ) ;
}
}
/∗
∗ 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 ];
}
}
/∗
∗ 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 ];
}
}
/∗
∗ 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 ) ;
}
}
/∗
∗ 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
/∗
∗ 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 ];
}
}
/∗
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 ] ;
}
}
/∗
∗ Autor Jorge Teran
∗/
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 ;
}
}
/∗
∗ Autor Jorge Teran
∗/
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 ( ” ” ) ;
}
/∗
∗ 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 ( ” ” ) ;
}
}
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 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 ) ;
}
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
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
∗/
/∗
∗ Autor Jorge Teran
∗/
/∗
∗ 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
}
}
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
0 0 1 0
1 0 0 0
0 1 0 1
1 0 0 0
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.
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
1 2 3 4 5 6 7 8
1
2
3
4 տ ↑ ր
5 ← ♣ →
6 ւ ↓ ց
7
8
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.
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.
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
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
1 2 3 4 5 6 7 8
1 ♣
2 ♣
3 ♣
4 ♣
5 ♣
6 ♣
7 ♣
8 ♣
304 Capı́tulo 14 Backtracking
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 ( ) ;
}
}
}
/∗
∗ 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 ;
}
/ / 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):
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.
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
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:
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 ).
a b
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:
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.
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.
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) ;
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.
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)
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];
//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] ;
imprimir(maximo) ;
15.7 Ejercicios 315
15.7. Ejercicios
15.7.1. La mejor empresa
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.
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.
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.
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]
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.
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
/∗
∗ 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 ;
}
}
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:
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.
1 2 3 4 5
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:
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 ;
}
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}
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.
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.
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
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.
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:
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
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.
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
/∗
∗ 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
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 ) ) ;
}
}
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
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:
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
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(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
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.
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
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.
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.
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
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.
/∗
∗ 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;
}
}
/ / 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 ] ;
}
}
/∗
∗ 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 ] ;
}
}
/∗
∗ 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
∗/
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
En el desarrollo se utiliza el ejemplo de cambiar aprecio − − > auspicio donde la cadena t = aprecio y la
cadena s = auspicio.
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.
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.
Ahora solo hay que recorrer la matriz de la programación dinámica hallando los costos intermedios, para
obtener el algoritmo 43.
¿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.
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.
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.
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
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 ( ) ;
}
}
}
∗ 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
∗/
// 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 ) ;
}
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 ;
}
}
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.
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 ?
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 ).
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.
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++ ;
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
System . o u t . p r i n t l n ( ) ;
}
}
considera el 3 no considera 3
3 0
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
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
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:
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
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]] ;
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
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 ] ) ;
}
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
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.
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
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:
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.
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
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.
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.
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
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
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:
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.
4
1 Insert 1,a
2 Insert 2,a
3 Insert 3,b
4 Insert 7,a
16.11 Ejercicios Propuestos 381
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:
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.
Una vez que haya ingresado al juez notara que su usuario aparecerá en la esquina superior derecha
383
384 Apéndice A Juez Virtual de la Carrera de Informática
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
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
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
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
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:
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
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
395
396 ÍNDICE DE ALGORITMOS
28 Llamada a la función de retroceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
29 Verificar si es una posible solucion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
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