JAVA3elatestCapitulo1 en Es
JAVA3elatestCapitulo1 en Es
JAVA3elatestCapitulo1 en Es
com
¿Cuántas ciudades con más de 250.000 habitantes se encuentran a 500 millas de Dallas, Texas?
¿Cuántas personas en mi empresa ganan más de 100.000 dólares al año? ¿Podemos conectar a
todos nuestros clientes telefónicos con menos de 1,000 millas de cable? Para responder a
preguntas como éstas no basta con tener la información necesaria. Debemos organizar esa
información de una manera que nos permita encontrar las respuestas a tiempo para satisfacer
nuestras necesidades.
Representar información es fundamental para la informática. El objetivo principal de
la mayoría de los programas informáticos no es realizar cálculos, sino almacenar y
recuperar información, normalmente lo más rápido posible. Por esta razón, el estudio de
las estructuras de datos y los algoritmos que las manipulan está en el corazón de la
informática. Y de eso se trata este libro: ayudarle a comprender cómo estructurar la
información para respaldar un procesamiento eficiente.
Este libro tiene tres objetivos principales. El primero es presentar las estructuras de datos comúnmente
utilizadas. Estos forman el “kit de herramientas” de estructura de datos básica de un programador. Para muchos
problemas, alguna estructura de datos del kit de herramientas proporciona una buena solución.
3
4 Cap. 1 Estructuras de datos y algoritmos
Idealmente, el programa resultante es fiel a ambos objetivos. Podríamos decir que un programa
así es “elegante”. Si bien los algoritmos y ejemplos de código de programa presentados aquí intentan
ser elegantes en este sentido, no es el propósito de este libro tratar explícitamente cuestiones
relacionadas con el objetivo (1). Estas son principalmente preocupaciones de la disciplina de Ingeniería
de Software. Más bien, este libro trata principalmente de cuestiones relacionadas con el objetivo (2).
Se podría pensar que con computadoras cada vez más potentes, la eficiencia de los programas es
cada vez menos importante. Después de todo, la velocidad del procesador y el tamaño de la memoria
siguen mejorando. ¿No se resolverá cualquier problema de eficiencia que podamos tener hoy con el
hardware del mañana?
A medida que desarrollamos computadoras más potentes, nuestra historia hasta ahora siempre
ha sido la de utilizar esa potencia informática adicional para abordar problemas más complejos, ya sea
en forma de interfaces de usuario más sofisticadas, problemas de mayor tamaño o problemas nuevos
que antes se consideraban computacionalmente inviables. Los problemas más complejos exigen más
cálculos, lo que hace aún mayor la necesidad de programas eficientes. Peor aún, a medida que las
tareas se vuelven más complejas, se vuelven menos parecidas a nuestra experiencia cotidiana. Los
científicos informáticos de hoy deben estar capacitados para tener una comprensión profunda de los
principios detrás del diseño eficiente de programas, porque sus experiencias de vida ordinarias a
menudo no se aplican al diseñar programas informáticos.
En el sentido más general, una estructura de datos es cualquier representación de datos y sus
operaciones asociadas. Incluso un número entero o de punto flotante almacenado en la computadora puede
verse como una estructura de datos simple. Más comúnmente, la gente usa el término "estructura de datos"
para referirse a una organización o estructuración de una colección de elementos de datos. Una lista
ordenada de números enteros almacenados en una matriz es un ejemplo de tal estructuración.
Segundo. 1.1 Una filosofía de las estructuras de datos 5
Dado suficiente espacio para almacenar una colección de elementos de datos, siempre es posible buscar
elementos específicos dentro de la colección, imprimir o procesar de otro modo los elementos de datos en
cualquier orden deseado, o modificar el valor de cualquier elemento de datos en particular. Por tanto, es
posible realizar todas las operaciones necesarias en cualquier estructura de datos. Sin embargo, utilizar la
estructura de datos adecuada puede marcar la diferencia entre un programa que se ejecuta en unos pocos
segundos y uno que requiere muchos días.
Se dice que una solución eseficientesi resuelve el problema dentro del plazo requerido
limitaciones de recursos.Ejemplos de limitaciones de recursos incluyen el espacio total
disponible para almacenar los datos (posiblemente dividido en limitaciones separadas de
memoria principal y espacio en disco) y el tiempo permitido para realizar cada subtarea. A veces
se dice que una solución es eficiente si requiere menos recursos que las alternativas conocidas,
independientemente de si cumple con algún requisito particular. Elcostode una solución es la
cantidad de recursos que consume la solución. Muy a menudo, el costo se mide en términos de
un recurso clave, como el tiempo, con el supuesto implícito de que la solución cumple con las
otras limitaciones de recursos.
No hace falta decir que la gente escribe programas para resolver problemas. Sin embargo, es
fundamental tener en cuenta esta perogrullada al seleccionar una estructura de datos para resolver
un problema en particular. Sólo analizando primero el problema para determinar los objetivos de
desempeño que deben alcanzarse puede haber alguna esperanza de seleccionar la estructura de
datos adecuada para el trabajo. Los diseñadores de programas deficientes ignoran este paso del
análisis y aplican una estructura de datos con la que están familiarizados pero que es inapropiada
para el problema. El resultado suele ser un programa lento. Por el contrario, no tiene sentido adoptar
una representación compleja para "mejorar" un programa que puede cumplir sus objetivos de
desempeño cuando se implementa utilizando un diseño más simple.
Al seleccionar una estructura de datos para resolver un problema, debes seguir estos
pasos.
1.Analice su problema para determinar las operaciones básicas que deben ser compatibles.
Ejemplos de operaciones básicas incluyen insertar un elemento de datos en la estructura de
datos, eliminar un elemento de datos de la estructura de datos y encontrar un elemento de
datos específico.
2.Cuantificar las limitaciones de recursos para cada operación.
3.Seleccione la estructura de datos que mejor cumpla con estos requisitos.
Este enfoque de tres pasos para seleccionar una estructura de datos pone en práctica una visión
centrada en los datos del proceso de diseño. La primera preocupación es por los datos y las
operaciones que se realizarán con ellos, la siguiente preocupación es la representación de esos datos
y la última preocupación es la implementación de esa representación.
Las limitaciones de recursos en ciertas operaciones clave, como la búsqueda, la inserción y
eliminación de registros de datos, normalmente impulsan el proceso de selección de la
estructura de datos. Muchas cuestiones relacionadas con la importancia relativa de estas
operaciones se abordan mediante las siguientes tres preguntas, que usted debe hacerse
siempre que deba elegir una estructura de datos:
6 Cap. 1 Estructuras de datos y algoritmos
• ¿Se insertan todos los elementos de datos en la estructura de datos al principio o las
inserciones se intercalan con otras operaciones? Las aplicaciones estáticas (donde los
datos se cargan al principio y nunca cambian) normalmente requieren sólo estructuras
de datos más simples para lograr una implementación eficiente que las aplicaciones
dinámicas.
• ¿Se pueden eliminar elementos de datos? Si es así, esto probablemente complicará la
implementación.
• ¿Se procesan todos los elementos de datos en algún orden bien definido o se permite la búsqueda de
elementos de datos específicos? La búsqueda de “acceso aleatorio” generalmente requiere
estructuras de datos más complejas.
Ejemplo 1.1Un banco debe admitir muchos tipos de transacciones con sus
clientes, pero examinaremos un modelo simple en el que los clientes desean abrir
cuentas, cerrar cuentas y agregar dinero o retirar dinero de las cuentas. Podemos
considerar este problema en dos niveles distintos: (1) los requisitos para la
infraestructura física y el proceso de flujo de trabajo que el banco utiliza en sus
interacciones con sus clientes, y (2) los requisitos para el sistema de base de datos
que administra las cuentas.
El cliente típico abre y cierra cuentas con mucha menos frecuencia de la que
accede a ellas. Los clientes están dispuestos a esperar muchos minutos mientras se
crean o eliminan cuentas, pero normalmente no están dispuestos a esperar más que
un breve tiempo para transacciones de cuentas individuales, como un depósito o un
retiro. Estas observaciones pueden considerarse como especificaciones informales
para las limitaciones de tiempo del problema.
Es una práctica común que los bancos proporcionen dos niveles de servicio.
Cajeros humanos o cajeros automáticos (ATM) respaldan el acceso de los clientes
Segundo. 1.1 Una filosofía de las estructuras de datos 7
Ejemplo 1.2Una empresa está desarrollando un sistema de base de datos que contiene
información sobre ciudades y pueblos de Estados Unidos. Hay miles de ciudades y pueblos, y
el programa de base de datos debería permitir a los usuarios encontrar información sobre
un lugar en particular por su nombre (otro ejemplo de consulta de coincidencia exacta). Los
usuarios también deberían poder encontrar todos los lugares que coincidan con un valor
particular o rango de valores para atributos como la ubicación o el tamaño de la población.
Esto se conoce como unconsulta de rango.
Un sistema de base de datos razonable debe responder consultas con la suficiente rapidez para
satisfacer la paciencia de un usuario típico. Para una consulta de coincidencia exacta, unos segundos son
satisfactorios. Si la base de datos está destinada a admitir consultas de rango que pueden devolver
Es posible que se permita que la operación tarde más tiempo, tal vez del orden de un minuto. Para
cumplir con este requisito, será necesario respaldar operaciones que procesen consultas de rango
de manera eficiente procesando todas las ciudades del rango como un lote, en lugar de como una
serie de operaciones en ciudades individuales.
Por ejemplo, la Sección 12.2 describe la estructura de datos utilizada para implementar una matriz
dispersa, una gran matriz bidimensional que almacena sólo relativamente pocos valores distintos de
cero. Esta implementación es bastante diferente de la representación física de una matriz como
ubicaciones de memoria contiguas.
Untipo de datos abstractos (ADT) es la realización de un tipo de datos como
componente de software. La interfaz del ADT se define en términos de un tipo y un
conjunto de operaciones sobre ese tipo. El comportamiento de cada operación está
determinado por sus entradas y salidas. Un ADT no especificacómose implementa el tipo
de datos. Estos detalles de implementación están ocultos para el usuario del ADT y
protegidos del acceso externo, un concepto conocido comoencapsulación.
Aestructura de datoses la implementación de un ADT. En un lenguaje orientado a
objetos como Java, un ADT y su implementación juntos forman unclase. Cada operación
asociada con el ADT es implementada por unfunción miembroo método.Las variables que
definen el espacio requerido por un elemento de datos se denominanmiembros de datos.
Unobjetoes una instancia de una clase, es decir, algo que se crea y ocupa almacenamiento
durante la ejecución de un programa de computadora.
El término "estructura de datos" a menudo se refiere a los datos almacenados en la memoria principal de una
Ejemplo 1.3El concepto matemático de número entero, junto con las operaciones que
manipulan números enteros, forman un tipo de datos. la javaEn tEl tipo de variable es
una representación física del entero abstracto. ElEn ttipo de variable, junto con las
operaciones que actúan sobre unaEn tvariable, forma un TDA. Desafortunadamente, el
En tLa implementación no es completamente fiel al entero abstracto, ya que existen
limitaciones en el rango de valores.En tvariable puede almacenar. Si estas limitaciones
resultan inaceptables, entonces se debe idear alguna otra representación para el
“entero” ADT y se debe utilizar una nueva implementación para las operaciones
asociadas.
Ejemplo 1.4Un ADT para una lista de números enteros podría especificar las siguientes
operaciones:
Una aplicación que utiliza algún ADT podría utilizar funciones miembro particulares de
ese ADT más que una segunda aplicación, o las dos aplicaciones podrían tener diferentes
requisitos de tiempo para las distintas operaciones. Estas diferencias en los requisitos de
las aplicaciones son la razón por la que un determinado ADT puede ser compatible con
más de una implementación.
Ejemplo 1.6Al operar un automóvil, las actividades principales son dirigir, acelerar y frenar.
En casi todos los turismos, se conduce girando el volante, se acelera pisando el pedal del
acelerador y se frena pisando el pedal del freno. Este diseño para automóviles puede verse
como un ADT con operaciones de “dirección”, “aceleración” y “freno”. Dos automóviles
podrían implementar estas operaciones de maneras radicalmente diferentes, digamos con
diferentes tipos de motor, o con tracción delantera versus tracción trasera. Sin embargo, la
mayoría de los conductores pueden operar muchos automóviles diferentes porque el ADT
presenta un método de operación uniforme que no requiere que el conductor comprenda
los detalles de ningún motor o diseño de transmisión en particular. Estas diferencias se
ocultan deliberadamente.
Los tipos de datos tienen tanto unlógicoy unfísicoforma. La definición del tipo de
datos en términos de un ADT es su forma lógica. La implementación del tipo de datos
como estructura de datos es su forma física. La Figura 1.1 ilustra esta relación entre las
formas lógicas y físicas de los tipos de datos. Cuando implementas un ADT, estás tratando
con la forma física del tipo de datos asociado. Cuando utiliza un ADT en otra parte de su
programa, le preocupa la forma lógica del tipo de datos asociado. Algunas secciones de
este libro se centran en implementaciones físicas para un
12 Cap. 1 Estructuras de datos y algoritmos
Tipo de datos
TMD:
Tipo Elementos de datos:
Forma lógica
Operaciones
Estructura de datos:
Elementos de datos:
Espacio de almacenamiento
Forma física
Subrutinas
estructura de datos dada. Otras secciones utilizan el ADT lógico para la estructura de datos en el
contexto de una tarea de nivel superior.
Ejemplo 1.9Un entorno Java particular podría proporcionar una biblioteca que
incluya una clase de lista. La forma lógica de la lista está definida por las
funciones públicas, sus entradas y salidas que definen la clase. Esto podría ser
todo lo que sabes sobre la implementación de la clase de lista, y esto debería ser
todo lo que necesitas saber. Dentro de la clase, es posible una variedad de
implementaciones físicas para listas. Varios se describen en la Sección 4.1.
que las compensaciones son posibles. Por lo tanto, un patrón de diseño determinado podría tener variaciones en su
aplicación para adaptarse a las diversas compensaciones inherentes a una situación determinada.
El resto de esta sección presenta algunos patrones de diseño simples que se utilizan más
adelante en el libro.
El patrón de diseño Flyweight está destinado a resolver el siguiente problema. Tienes una
aplicación con muchos objetos. Algunos de estos objetos son idénticos en la información que
contienen y en el papel que desempeñan. Pero hay que llegar a ellos desde varios lugares, y
conceptualmente son realmente objetos distintos. Debido a que hay tanta duplicación de la
misma información, nos gustaría aprovechar la oportunidad de reducir el costo de la memoria
compartiendo ese espacio. Un ejemplo proviene de la representación del diseño de un
documento. La letra "C" podría razonablemente representarse mediante un objeto que describa
los trazos y el cuadro delimitador de ese carácter. Sin embargo, no queremos crear un objeto
"C" separado en todas partes del documento donde aparece una "C". La solución es asignar una
única copia de la representación compartida para los objetos "C". Luego, cada lugar del
documento que necesite una “C” en una fuente, tamaño y tipo de letra determinados hará
referencia a esta única copia. Los diversos casos de referencias a una forma específica de "C" se
denominan pesos mosca.
Podríamos describir el diseño del texto en una página utilizando una estructura de árbol. La
raíz del árbol representa la página completa. La página tiene varios nodos secundarios, uno
para cada columna. Los nodos de columna tienen nodos secundarios para cada fila. Y las filas
tienen nodos secundarios para cada personaje. Estas representaciones de personajes son los
pesos mosca. El peso mosca incluye la referencia a la información de forma compartida y puede
contener información adicional específica de esa instancia. Por ejemplo, cada instancia de "C"
contendrá una referencia a la información compartida sobre trazos y formas, y también podría
contener la ubicación exacta de esa instancia del carácter en la página.
1.3.2 Visitante
Dado un árbol de objetos para describir el diseño de una página, es posible que deseemos
realizar alguna actividad en cada nodo del árbol. La sección 5.2 analiza el recorrido del árbol,
que es el proceso de visitar cada nodo del árbol en un orden definido. Un ejemplo sencillo para
nuestra aplicación de composición de texto podría ser contar el número de nodos en el árbol.
14 Cap. 1 Estructuras de datos y algoritmos
que representa la página. En otro momento, es posible que deseemos imprimir una lista de todos los
nodos para fines de depuración.
Podríamos escribir una función transversal separada para cada actividad que pretendemos
realizar en el árbol. Un mejor enfoque sería escribir una función transversal genérica y pasar la
actividad que se realizará en cada nodo. Esta organización constituye el patrón de diseño de
visitantes. El patrón de diseño de visitantes se utiliza en las Secciones 5.2 (recorrido de árboles)
y 11.3 (recorrido de gráficos).
1.3.3 Compuesto
Hay dos enfoques fundamentales para abordar la relación entre una colección de acciones y
una jerarquía de tipos de objetos. Consideremos primero el enfoque procesal típico. Digamos
que tenemos una clase base para entidades de diseño de página, con una jerarquía de
subclases para definir subtipos específicos (página, columnas, filas, figuras, caracteres, etc.). Y
digamos que hay acciones que se deben realizar en una colección de dichos objetos (como
representar los objetos en la pantalla). El enfoque de diseño procedimental es que cada acción
se implemente como un método que toma como parámetro un puntero al tipo de clase base.
Cada acción de este método atravesará la colección de objetos, visitando cada objeto por turno.
Cada método de acción contiene algo así como una declaración de cambio que define los
detalles de la acción para cada subclase de la colección (por ejemplo, página, columna, fila,
carácter). Podemos reducir un poco el código usando el patrón de diseño de visitante para que
solo necesitemos escribir el recorrido una vez y luego escribir una subrutina de visitante para
cada acción que pueda aplicarse a la colección de objetos. Pero cada subrutina de visitante debe
contener lógica para tratar con cada una de las posibles subclases.
En nuestra aplicación de composición de páginas, sólo hay unas pocas actividades que nos
gustaría realizar en la representación de la página. Podríamos representar los objetos con todo
detalle. O podríamos querer una representación de “borrador” que imprima solo los cuadros
delimitadores de los objetos. Si se nos ocurre una nueva actividad para aplicar a la colección de
objetos, no necesitamos cambiar nada del código que implementa las actividades existentes. Pero
agregar nuevas actividades no será frecuente para esta aplicación. Por el contrario, podría haber
muchos tipos de objetos y con frecuencia podríamos agregar nuevos tipos de objetos a nuestra
implementación. Desafortunadamente, agregar un nuevo tipo de objeto requiere que modifiquemos
cada actividad, y las subrutinas que implementan las actividades obtienen declaraciones de cambio
bastante largas para distinguir el comportamiento de las muchas subclases.
Un diseño alternativo es hacer que cada subclase de objeto en la jerarquía encarne la acción de
cada una de las diversas actividades que podrían realizarse. Cada subclase tendrá código para realizar
cada actividad (como la representación completa o la representación del cuadro delimitador). Luego, si
deseamos aplicar la actividad a la colección, simplemente llamamos al primer objeto de la colección y
especificamos la acción (como una llamada a un método en ese objeto). En el caso de nuestro diseño
de página y su colección jerárquica de objetos, aquellos objetos que contienen otros objetos (como
una fila de objetos que contiene letras)
Segundo. 1.3 Patrones de diseño 15
llamará al método apropiado para cada niño. Si queremos agregar una nueva actividad con esta
organización, tenemos que cambiar el código para cada subclase. Pero esto es relativamente raro en
nuestra aplicación de composición de texto. Por el contrario, agregar un nuevo objeto a la jerarquía de
subclases (lo cual para esta aplicación es mucho más probable que agregar una nueva función de
representación) es fácil. Agregar una nueva subclase no requiere cambiar ninguna de las subclases
existentes. Simplemente requiere que definamos el comportamiento de cada actividad que se puede
realizar en la nueva subclase.
1.3.4 Estrategia
Nuestro ejemplo final de patrón de diseño nos permite encapsular y hacer intercambiable un
conjunto de acciones alternativas que podrían realizarse como parte de una actividad más
amplia. Continuando nuevamente con nuestro ejemplo de composición de texto, cada
dispositivo de salida al que deseemos renderizar requerirá su propia función para realizar la
renderización real. Es decir, los objetos se dividirán en píxeles o trazos constituyentes, pero la
mecánica real de renderizar un píxel o un trazo dependerá del dispositivo de salida. No
queremos incorporar esta funcionalidad de representación en las subclases de objetos. En lugar
de eso, queremos pasar a la subrutina que realiza la acción de renderizado un método o clase
que realice los detalles de renderizado apropiados para ese dispositivo de salida. Es decir,
deseamos entregarle al objeto la "estrategia" adecuada para realizar los detalles de la tarea de
renderizado. Por lo tanto, este enfoque se denomina patrón de diseño de estrategia.
El patrón de diseño Estrategia se puede utilizar para crear funciones de clasificación
generalizadas. La función de clasificación se puede llamar con un parámetro adicional. Este parámetro
es una clase que comprende cómo extraer y comparar los valores clave de los registros que se van a
ordenar. De esta manera, la función de clasificación no necesita conocer ningún detalle sobre cómo se
implementa su tipo de registro.
Uno de los mayores desafíos para comprender los patrones de diseño es que a veces
uno sólo se diferencia sutilmente de otro. Por ejemplo, es posible que se sienta
confundido acerca de la diferencia entre el patrón compuesto y el patrón de visitante. La
distinción es que el patrón de diseño compuesto trata sobre si se debe dar el control del
proceso transversal a los nodos del árbol o al árbol mismo. Ambos enfoques pueden hacer
uso del patrón de diseño de visitantes para evitar reescribir la función transversal muchas
veces, al encapsular la actividad realizada en cada nodo.
¿Pero no hace lo mismo el patrón de diseño de estrategias? La diferencia entre el patrón de
visitante y el patrón de estrategia es más sutil. Aquí la diferencia es principalmente de intención
y enfoque. Tanto en el patrón de diseño de estrategia como en el patrón de diseño de visitante,
se pasa una actividad como parámetro. El patrón de diseño de estrategia se centra en
encapsular una actividad que es parte de un proceso más amplio, de modo que
dieciséis Cap. 1 Estructuras de datos y algoritmos
que se pueden sustituir diferentes formas de realizar esa actividad. El patrón de diseño de
visitantes se centra en encapsular una actividad que se realizará en todos los miembros de una
colección para que se puedan sustituir actividades completamente diferentes dentro de un
método genérico que acceda a todos los miembros de la colección.
entrada de un proceso físico más allá del control del usuario). La relación entre programas
y funciones se explora con más detalle en la Sección 17.3.
Proporciona sugerencias para muchas implementaciones de estructuras de datos y algoritmos que están
disponibles en la Web.
La afirmación de que todos los lenguajes de programación modernos pueden implementar los
mismos algoritmos (dicho con mayor precisión, cualquier función que sea computable por un lenguaje
de programación es computable por cualquier lenguaje de programación con ciertas capacidades
estándar) es un resultado clave de la teoría de la computabilidad. Para una introducción sencilla a este
campo, consulte James L. Hein,Estructuras discretas, lógica y computabilidad[Hola09].
Gran parte de la informática se dedica a la resolución de problemas. De hecho, esto es lo
que atrae a mucha gente al campo.Cómo resolverlode George Pólya [Pól57] se considera el
trabajo clásico sobre cómo mejorar la capacidad de resolución de problemas. Si quieres ser un
mejor estudiante (y también un mejor solucionador de problemas en general), consulta
Estrategias para la resolución creativa de problemaspor Folger y LeBlanc [FL95],Resolución
eficaz de problemaspor Marvin Levine [Lev94], yResolución de problemas y comprensiónpor
Arthur Whimbey y Jack Lochhead [WL99], yAprendizaje basado en rompecabezas por Zbigniew y
Matthew Michaelewicz [MM08].
VerEl origen de la conciencia en la ruptura de la mente bicameralde Julian Jaynes [Jay90]
para una buena discusión sobre cómo los humanos usan el concepto de metáfora para manejar
la complejidad. Más directamente relacionado con la educación y la programación en
informática, consulte “Cogito, Ergo Sum! Cognitive Processes of Students Dealing with Data
Structures” de Dan Aharoni [Aha00] para una discusión sobre cómo pasar del pensamiento en
contexto de programación a un pensamiento libre de programación de nivel superior (y más
orientado al diseño).
En un nivel más pragmático, la mayoría de la gente estudia estructuras de datos para
escribir mejores programas. Si espera que su programa funcione correcta y eficientemente,
primero debe ser comprensible para usted y sus compañeros de trabajo. Kernighan y PikeLa
práctica de la programación[KP99] analiza una serie de cuestiones prácticas relacionadas con la
programación, incluido un buen estilo de codificación y documentación. Para obtener una
introducción excelente (¡y entretenida!) a las dificultades que implica escribir programas
grandes, lea el clásicoEl mítico mes del hombre: ensayos sobre ingeniería de software por
Frederick P. Brooks [Bro95].
Si desea ser un programador Java exitoso, necesita buenos manuales de referencia a mano.
David FlanaganJava en pocas palabras[Fla05] proporciona una buena referencia para quienes
están familiarizados con los conceptos básicos del idioma.
Después de adquirir competencia en la mecánica de la redacción de programas, el
siguiente paso es dominar el diseño de programas. El buen diseño es difícil de aprender en
cualquier disciplina, y el buen diseño para software orientado a objetos es una de las artes más
difíciles. El diseñador novato puede impulsar el proceso de aprendizaje estudiando patrones de
diseño bien conocidos y utilizados. La referencia clásica sobre patrones de diseño esPatrones de
diseño: elementos de software orientado a objetos reutilizablespor Gamma, Helm, Johnson y
Vlissides [GHJV95] (comúnmente conocido como el libro de la “banda de los cuatro”).
Desafortunadamente, este es un libro extremadamente difícil de entender,
20 Cap. 1 Estructuras de datos y algoritmos
en parte porque los conceptos son inherentemente difíciles. Hay varios sitios web
disponibles que analizan patrones de diseño y que proporcionan guías de estudio para el
Patrones de diseñolibro. Otros dos libros que analizan el diseño de software orientado a
objetos sonDiseño y construcción de software orientado a objetos conC++por Dennis
Kafura [Kaf98], yHeurística de diseño orientada a objetospor Arthur J. Riel [Rie96].
1.6 Ejercicios
Los ejercicios de este capítulo son diferentes de los del resto del libro. La mayoría de
estos ejercicios se responden en los siguientes capítulos. Sin embargo, deberías no
Busque las respuestas en otras partes del libro. Estos ejercicios pretenden hacerle
pensar en algunos de los temas que se tratarán más adelante. Respóndelas lo mejor
que puedas con tus conocimientos actuales.
1.1Piense en un programa que haya utilizado y que sea inaceptablemente lento. Identifique las
operaciones específicas que hacen que el programa sea lento. Identifique otras operaciones básicas
que el programa realiza con la suficiente rapidez.
1.2La mayoría de los lenguajes de programación tienen un tipo de datos entero incorporado.
Normalmente, esta representación tiene un tamaño fijo, lo que impone un límite al tamaño que se
puede almacenar un valor en una variable entera. Describa una representación de números enteros
que no tenga restricción de tamaño (aparte de los límites de la memoria principal disponible de la
computadora) y, por lo tanto, no tenga límite práctico sobre el tamaño de un número entero que se
puede almacenar. Muestre brevemente cómo se puede utilizar su representación para implementar
las operaciones de suma, multiplicación y exponenciación.
1.3Defina un ADT para cadenas de caracteres. Su ADT debe consistir en funciones típicas
que se pueden realizar en cadenas, con cada función definida en términos de su
entrada y salida. Luego defina dos representaciones físicas diferentes para
cadenas.
1.4Defina un ADT para una lista de números enteros. Primero, decida qué funcionalidad
debe proporcionar su ADT. El ejemplo 1.4 debería darle algunas ideas. Luego,
especifique su ADT en Java en forma de declaración de clase abstracta, mostrando
las funciones, sus parámetros y sus tipos de retorno.
1.5Describa brevemente cómo se representan típicamente las variables enteras en una
computadora. (Busque aritmética en complemento a uno y en complemento a dos en un libro
de texto de introducción a la informática si no está familiarizado con ellas.) ¿Por qué esta
representación de números enteros califica como una estructura de datos como se define en
la Sección 1.2?
1.6Defina un ADT para una matriz bidimensional de números enteros. Especifique con precisión las
operaciones básicas que se pueden realizar en dichas matrices. A continuación, imagine una
aplicación que almacena una matriz con 1000 filas y 1000 columnas, donde menos
Segundo. 1.6 Ejercicios 21
de 10.000 de los valores de la matriz son distintos de cero. Describa dos implementaciones
diferentes para dichos arreglos que serían más eficientes en cuanto a espacio que una
implementación de arreglo bidimensional estándar que requiere un millón de posiciones.
1.7Imagine que le han asignado la tarea de implementar un programa de clasificación. El objetivo es hacer
que este programa sea de uso general, en el sentido de que no desea definir de antemano qué
registros o tipos de claves se utilizan. Describa formas de generalizar un algoritmo de clasificación
simple (como la clasificación por inserción o cualquier otra clasificación con la que esté familiarizado)
para respaldar esta generalización.
1.8Imagine que le han asignado la tarea de implementar una búsqueda secuencial simple en
una matriz. El problema es que quieres que la búsqueda sea lo más general posible. Esto
significa que debe admitir tipos de claves y registros arbitrarios. Describa formas de
generalizar la función de búsqueda para respaldar este objetivo. Considere la posibilidad
de que la función se utilice varias veces en el mismo programa, en diferentes tipos de
registros. Considere la posibilidad de que la función deba usarse en diferentes claves
(posiblemente con el mismo o diferentes tipos) del mismo registro. Por ejemplo, se
puede buscar un registro de datos de un estudiante por código postal, por nombre, por
salario o por GPA.
1.9¿Todos los problemas tienen un algoritmo?
1.10¿Cada algoritmo tiene un programa Java?
1.11Considere el diseño de un programa de revisión ortográfica destinado a ejecutarse en una
computadora personal. El corrector ortográfico debería poder manejar rápidamente un
documento de menos de veinte páginas. Supongamos que el corrector ortográfico viene
con un diccionario de unas 20.000 palabras. ¿Qué operaciones primitivas deben
implementarse en el diccionario y cuál es una restricción de tiempo razonable para cada
operación?
1.12Imagine que lo han contratado para diseñar un servicio de base de datos que contiene
información sobre ciudades y pueblos de Estados Unidos, como se describe en el
ejemplo 1.2. Sugiera dos posibles implementaciones para la base de datos.
1.13Imagine que se le proporciona una serie de registros ordenados con respecto a algún
campo clave contenido en cada registro. Proporcione dos algoritmos diferentes
para buscar en la matriz para encontrar el registro con un valor clave específico.
¿Cuál consideras “mejor” y por qué?
1.14¿Cómo harías para comparar dos algoritmos propuestos para ordenar una serie de
números enteros? En particular,
(a)¿Cuáles serían las medidas de costo apropiadas para usar como base para comparar
los dos algoritmos de clasificación?
(b)¿Qué pruebas o análisis realizaría para determinar cómo se desempeñan los
dos algoritmos bajo estas medidas de costos?
1.15Un problema común para los compiladores y editores de texto es determinar si los paréntesis (u
otros corchetes) en una cadena están equilibrados y anidados correctamente.
22 Cap. 1 Estructuras de datos y algoritmos