5
§
#2
3
3
S
3
2
5
rsTHOMSON
———-
Estructuras de datos y algoritmos con Java, 28. edicién
Presidente de Thomson Learning
oeroamérica:
avie Arollano Gutiérrez
Director editorial Iberoamérica:
Jose Tomas Pérez Bonia
Gerente editorial y de produccién:
Ula Moreno Olvera
COPYRIGHT © 2007 por international
‘Thomson Eatores, SA. de CV, una
> (desplazamiento a la derecha), >>> (desplazamiento a
la derecha rellenando con ceros), y ~ (complemento a). Las abreviaturas &=, |=,
<<=, >=, y >>>= también son posibles. Excepto por el operador >>>, el resto de los
operadores también se incluyen en C/C++. El operador >> desplaza hacia la derecha un
ntimero especifico de bits del extremo derecho (menos significativos) y desplaza hacia
adentro el mismo nimero de ceros (0) para los ntimeros positivos y de unos (1) para los
niimeros negativos. Por ejemplo, el valor de m después de las asignaciones
int n=
int m= n >> 1;Seccién 1.1 Java rudimentario M5
5 -2 debido a que ~4 en n es una representacién del complemento a dos como la
secuencia de 32 bits 11 . . . 1100, la cual después de desplazar un bit a la derecha da el
patrn 11... 1110, en m, el cual es una representacién del complemento a dos de ~2.
Para hacer que los 0 también se desplacen hacia adentro para los niimeros negativos,
debe utilizarse el operador >>> ,
int n= —4;
int m= n >>> 1;
en cuyo caso, el patrén 11, .. 1100 en n se transforma en el patron 01... 1110 en m,
que ¢s el nimero 2147483646 (uno menos que el valor maximo para un entero).
1.1.3 Instrucciones de decision
‘Una instruccién de decision es una instruccion if
if (condiciony
hacer algo;
hacer algo mds;)
cn la cual la palabra 4¢ va seguida por una condicion encerrada entre paréntesis, luego
por el cuerpo de la clausula if, que es un bloque de instrucciones, y después por una
cldusula else opcional, la cual es es la palabra e1se seguida por un bloque de instruc-
ciones. Una condicién debe regresar un valor booleano (en C/C++, puede regresar cual-
4quier valor). Una condicién se forma con los operadores relacionales <, <=, ==,!=, >=, >
que toman dos argumentos y regresan un valor booleano, y con operadores logicas que
toman uno (!) 0 dos (&&, ) argumentos booleanos y regresan un valor boolean.
Una alternativa a una instruccién if-else es el operador condicional de la forma
condicién ? hacer-algo-si-verdadero + hacer-algo-si-falso;
El operador condicional regresa un valor, mientras que una instruccién 4£ no lo hace,
asi el anterior puede utilizarse, por ejemplo, en asignaciones tales como
neice 0? 101 20;
‘Otra instruccién de decisién es una instruccién switch, la cual es una abreviatara
para instrucciones i anidadas. Su forma es la siguiente:
switch (expresinentera) {
ease valorl: bloquel; break;
case valorN: bloqueN; break;
default: bloque predeterminado;
+
La expresion de prueba que va después de switch debe ser una expresién entera de
modo que cualquier expresion del tipo byte, char, short ¢ int pueda utilizarse. El
valor de la expresin se compara con los valores que siguen a la palabra case. Si se en~
cuentra una coincidencia, el bloque de instrucciones que va después de este valor se
¢jecuta, y al encontrar break, se sale de la instruccin switch. Observe que si falta laCapitulo 1 Programaci6n orientada a objetos utilizando Java
palabra break, entonces la ejecucién contimia para el bloque de la siguiente cléusula
case. Después de ejecutar la instruccién
switch (i) {
case 5: x + 10;
case 6 : x = 20;
case 7: x *= 2; break;
default : x = 30;
y
el valor de x ¢5 10 si i ¢s igual a 5, 40 si 4 es igual a 6, se duplica si { es igual a7 y es 30
para cualquier otro valor de i.
1.1.4 Ciclos (loops)
El primer ciclo disponible en Java es el ciclo while:
while (condicién)
hhacer algo;
La condicién debe ser una expresién booleana.
El segundo ciclo es un ciclo do-whil
do
hacer algo;
while (condicién);
El ciclo continia hasta que la con
El tercer ciclo es el ciclo £
tor (inicializacién; condicién; incremento)
hacer algo;
La parte de inicializacién también puede declarar variables y estas variables existen slo
durante la ejecucién del ciclo.
Es posible salir de un ciclo antes de que todas las instrucciones en su cuerpo se eje-
cuten con una instruccién break sin etiqueta. Ya hemos visto una instruccién break
utilizada en la instrucci6n switeh. En el caso de los ciclos anidados, cuando se encuen-
tra una instruccién break, se sale del ciclo actual de modo que el ciclo exterior pueda
continuarse, Una instruccién continue sin etiqueta provoca que el ciclo omita el resto
del cuerpo y comience con la iteracién siguiente.
1.1.5 Manejo de excepcién
Si se detecta un error durante la ejecucién de un programa Java, Java formula una
excepcién, después de la cual el programa finaliza y se despliega un mensaje de error
informando al usuario cual excepcién se formulé (es decir, que tipo de error ocurrié y
cen qué parte del programa). No obstante, si llega a ocurrir un error el usuario puede
mangjarlo en el programa, cuando menos puede hacer que el programa ignore el error
de modo que contintie la ejecucién del programa. Pero si se despliega una excepcién, seSeccién 1.1 Javarudimentario @ 7
puede tomar una medida especial y entonces el programa puede continuar. Es posible
descubrir un error al utilizar un mecanismo try-catch.
‘Un formato general de la instruccién try-catch es
try {
hacer algo;
} catch (tipo-de-excepcién-nombre-de-excepcion) {
hacer algo;
?
El nimero de cldusulas catch no esté limitado a uno. Puede haber tantas como se re-
La clausula catch imprime un mensaje, pero no realiza ninguna operacién de repara-
ci6n en el arreglo a, aunque podria hacerlo. En este ejemplo, la clausula catch también
incluye la instruccién throw, aun cuando esto no es muy comiin. De esta manera, la
excepcién no sélo se descubre y maneja en £2( ), sino que ademés una llamada de £2 ()
se ve forzada a manejarla, como en el método £3( ):
public void £3() {
try {
£205
) catch (ArrayIndexOutofBoundsException e) {
systen.out.print1n("Exception caught in £3()");
,8 Mi Capitulo 1 Programacién orientada a objetos utilizando Java
Sila Hamada de £2( no maneja la excepcién, el programa se colapsa, aun cuando la ex-
cepcién se descubrié en £2). La misma suerte sucede a un programa si una llamada de
£1() no descubre la excepcién:
public void £4() {
int{] a= (1,2,3,4,5};
for (int i= 0; i < a.length; itt)
system.out.print(fl(a,i) + * ");
,
Observe que el comportamiento del programa en todos estos casos es el mismo (es
decir, el manejo de una excepcién, el paso de la misma a otro método o el colapso del
programa) sila cléusula throws no se incluye en el encabezado de £1() de modo que
£1() podria ser simplemente:
public int f1(int(] a, int ny ¢
return n * a(nt2];
}
La cléusula throws es por lo tanto una seftal muy importante para el usuario de que un
problema posible puede ocurrir cuando se lama a un método en particular.
No todos los tipos de excepciones pueden ignorarse ya que la excepcién formulada
por £1() ¢s ignorada por £4(). La mayor parte del tiempo, las excepciones deben
manejarse en el programa, de lo contrario, el programa no se compila. Este, por ejem-
plo, es el caso del ZOEXception arrojado por los métodos 1/0; por consiguiente, estos
métodos generalmente se llaman dentro de las cliusulas try-catch.
PROGRAMACION ORIENTADA A OBJETOS EN JAVA
1.2.1 Encapsulamiento
La programacién orientada a objetos (OOP: Object-oriented programming) gira en
torno al concepto de un objeto. Los objetos, sin embargo, se crean utilizando una defini-
ci6n de clase. Una clase es una plantilla a partir de la cual se crean los objetos. Una clase es
una pieza de software que incluye una especificacién de datos y funciones que operan en
estos datos y posiblemente en los datos que pertenecen a otras instancias de clases. Las
funciones definidas en una clase se llaman métodos y la variables utilizadas en una clase
se llaman variables de alcance de clases (para distinguirlas de las variables locales para el
método 0 los bloques), los campos de datos, o simplemente campos. Esta combinacién de
los datos y las operaciones relacionadas se llama encapsulamiento de datos. Un objeto es
tuna instancia de una clase, una entidad creada utilizando una definicién de clase,
En contraposicién a las funciones en lenguajes que no estén orientados a objetos
(QOL: non-object-oriented languajes), los objetos hacen la conexién entre los datos y
Jos métodos mucho mas estrecha y mas significativa. En los lenguajes no orientados a
objetos, las declaraciones de datos y las definiciones de funciones pueden intercalarse a
Jo largo de todo el programa, y solo la documentacién del programa indica que hay una
conexién entre ellas. En los OOL, una conexidn se establece justo al principio; de hecho,
el programa se basa en esta conexi6n. Un objeto engloba datos y operaciones relaciona-
dos y debido a que puede haber muchos objetos utilizados en el mismo programa, los
objetos se comunican al intercambiar mensajes, revelindose entre si de esta manera
tantos, o tan pocos, detalles sobre su estructura interna como se requiera para unaSeccion 1.2 Programacién orientada a objetos en Java M9
comunicacién adecuada. La estructuracién de los programas en términos de los objetos
1nos permite lograr varios objetivos.
Primero, este acoplamiento de datos y operaciones puede usarse mucho mejor en
la modelacién de un fragmento del mundo, lo cual enfatiza especialmente la ingenieria
de software. Como es l6gico, la OOP tiene sus origenes en la simulacién; esto es, en la
modelacién de sucesos de la realidad. El primer OOL fue Simula; se desarrollé a
finales de la década de 1960 en Noruega.
Segundo, los objetos permiten encontrar errores de una manera mds facil debido a
gue las operaciones se localizan dentro de los confines de sus objetos. Aun cuando
pueden ocurrir efectos colaterales, son mis ficiles de trazar.
‘Tercero, los objetos nos permiten ocultar ciertos detalles de sus operaciones a otros
objetos, de modo que estas operaciones tal vez no se vean afectadas de manera adversa
por otros objetos. Esto se conoce como el principio de ocultacién de la informacién, En
Jenguajes que no son orientados a objetos, este principio puede encontrarse hasta cierto
punto en el caso de las variables locales, 0 como en Pascal, en las funciones y proce-
dimientos locales, que slo pueden utilizarse y accederse por medio de la funcién que los
define. Esto es, no obstante, una ocultacion muy leve o nula ocultaci6n. Algunas veces tal
vez lleguemos a necesitar del uso (de nuevo, al igual que en Pascal) de una funcién f2
definida en fl fuera de fl, pero no podemos hacerlo. En ocasiones se puede necesitar tener
acceso a algunos datos locales para fl sin conocer la estructura exacta de esos datos, pero
en los lenguajes de programacidn no orientados a objetos, es imposible hacerlo. Por con-
siguiente se requiere cierta modificacién y esto se logra en los OL.
Un objeto en OL ¢s como un reloj. Como usuarios estamos interesados en lo que
muestran las manecillas, pero no en el funcionamiento interno del reloj. Estamos cons-
cientes de que existen engranajes y resortes dentro del reloj en su interior, pero por lo
general sabemos muy poco sobre por qué todas estas partes tienen una configuracién en
particular. Para evitar datios voluntarios o involuntarios, no se tiene acceso a este meca-
nismo. Debido a ello, no debemos tener acceso a este mecanismo de modo que no lo
dafemos, sin querer 0 a propésito. Por lo tanto, este mecanismo nos esté oculto, no te-
nemos acceso inmediato a él, y por consiguiente el reloj esta protegido y funciona mejor
que cuando el mecanismo esta abierto para que todos lo vean.
Por consiguiente, un objeto es como una caja negra cuyo comportamiento esté muy
bien definido; y utilizamos el objeto debido a que sabemos lo que hace, no porque tenga-
‘mos una idea de cémo lo hace. Esta opacidad de los objetos es muy itil para mantenerlos
independientes unos de otros. Silos canales de comunicacién entre los objetos estén bien
definidos entonces los cambios hechos dentro de un objeto pueden afectar a otros objetos
s6lo en la medida que estos cambios afecten a los canales de comunicacién. Al conocer el
tipo de informacién enviado y recibido por un objeto, éste puede reemplazarse de una
manera més facil por otro objeto mas adecuado en una situacién en particular: un ob-
jeto nuevo puede realizar la misma tarea de manera distinta pero mis répidamente o en
cierto entorno de hardware. Por consiguiente, un objeto revela sélo lo que el usuario
necesita utilizar. Tiene una parte publica a la que puede acceder cualquier usuario cuan-
do uno de ellos envia un mensaje que coincide con cualquier nombre de método revelado
por el objeto. En esta parte pablica, el objeto muestra al usuario botones que pueden pre-
sionarse para invocar las operaciones del objeto. El usuario sélo conoce los nombres de
estas operaciones y el comportamiento esperado.
El ocultamiento de informacién tiende a desdibujar Ja linea divisoria entre los datos
y las operaciones. En los lenguajes tipo Pascal, la distincién entre los datos y las fun-
ciones/procedimientos es clara y rigida. Estin definidos de una manera distinta en sus
funciones y son muy diferentes. Los OL ponen los datos y los métodos juntos, y para el
usuario del objeto esta distincién es mucho menos perceptible. Hasta cierto punto, ésta es
tuna incorporacién de las funciones de los lenguajes funcionales. LISP, uno de los
primeros lenguajes de programacién, permite al usuario tratar a los datos y a las fun-
iones en el mismo nivel, debido a que la estructura de ambos es la misma,10 M Capitulo 1 Programacidn orientada a objetos utilizando Java
Hemos hecho una distincién entre objetos particulares y tipos de objetos o clases.
Escribimos métodos para utilizarlos con distintas variables y, por analogia, no quere-
‘mos vernos obligados a escribir tantas declaraciones de objetos como objetos requeri-
dos por el programa, Ciertos objetos son del mismo tipo y nos gustaria utilizar s6lo una
referencia a una especificacin de objetos en general. Para las variables individuales,
queremos una distincién entre la declaracién de tipo y la declaracién de variable. En el
«caso de los objetos, se hace una distincién entre una declaracién de clase y su instan-
public void method2(int i, String s) {
dataPield2 =
System.out.printin(i + * received from * + s);
>
private String dataFieldi;
private int dataField2;
private double dataField3;
public static void main (String args{]) {
€ object = new C(*object1*,100,2000),
object? = new C("object2"), object3 = new C();
object1.method2 (123);
object 1.methodl();
object2.method2 (123, "object2");Seccién 1.2 Programacién osientada a objetos en Java MIT
El programa contiene una declaracién de la clase . Dentro del método main( ), los
objetos de la clase tipo c se generan al declararlos e instanciarlos:
© object1 = new C("object1",100,2000),
object2 = new C("object2"), object3 = new C();
Es muy importante ver que las declaraciones de objetos no crean objetos, asi que
las dos lineas
© objecti;
object methodl ();
dan como resultado un error de compilacién. La variable de objeto object debe
asignarse a un objeto, lo cual puede realizarse directamente en la declaracién al ini-
public void h(String ) {
Systen.out.println(*Método h() en DerivediLeveli llamado desde “ + 8);
?
void kiString 8) ¢
System.out-printin(*Método k() en DerivediLevell llamado desde * + 8);
,
>
class Derived2ievell extends BaseClass {
public void £(string 5) ¢
System.out.printin ("Método £() en Derived2Levell llamade desde * + 5);
“ h("Derived2Levell"); // h() tiene acceso privado en basePackag:“
uw
uw
"
uw
u
7
SecciOn 1.2 Programacién orientada a objetos en Java M19
k(*Derived2uevell*); // k() no es piblico en basePackage.BaseClass;
// no se puede tener acceso a 61 desde el paquete exterior.
»
protected void g(String s) {
Systen.out.printLn( "Método g() en Derived2Levell llamado desde " + 8);
Derivedtevel2 extends Deriveditevell (
public void #(String 6) {
Systen.out.printin( “Método £() en Derivedtevel2 Llanado desde * + 8);
9(*Derivedtevei2")
h("Derivedtevel2*);
k("Derivedtevel2");
super. £(*Derivedtevel2");
‘Testinheritance {
void run() {
BaseClass be = new BaseClass();
Derivediteveli dill = new Deriveditevell();
Derivedatevell d211 = new Derived2Levell();
DerivedLevel2 di2 = new DerivedLevel2();
be. £(*main(1)")3
be.g(*main(2)"); // g{) ha protegido el acceso en basePackage.BaseClass.
be-h("main(3)")}; // h{) tiene acceso privado en basePackage.BaseClai
be-k(*main(4)*); // k() n0 @& piblico en basePackage.BaseClas
// 20 puede accederse desde el paquete exterior.
A2Ld£¢omain(s)*)s
d111.g("main(6)"); // 9() tiene acceso protegide en basePacka:
A2L1-h(*main(7)*) 5
111 k¢mmain(s)");
d211.£("main(9)*
d221.g(*main(10)*);
d211.b("main(11)*); // h() tiene acceso privado en basePackage-BaseClass.
d12.£("main(12)*);
d12.g("main(13)"); // g{) tiene acceso protegide en basePacka:
d12.h(*main(14)*) 5
Basecl
Baseci
?
public static void main(String args{}) ¢
(new Testinheritance()).2un();20 WM Capitulo 1 Programacion orientada a objetos utilizando Java
La ejecucién de este cédigo genera la salida siguiente:
Método £() en Liamado desde main(1)
Método h() en. llamado desde BaseClass
Método £() en DerivediLevell llamado desde main(5)
Método g() en BaseClass llamado desde Deriveditevell
Método h() en DerivediLevell llamado desde main(7)
Método k() en DerivediLevell llamado desde main(8)
Método £() en Derived2Level! llamado desde main(9)
Método g() en Derived2teveli llamado desde main(10)
Método £() en Derivedtevel2 llamado desde main(12)
Método g() en BaseClass llamado desde Derivedtevel2
Método h() en DerivediLevell llamado desde DerivedLevel2
Método k() en DerivediLeveli llamado desde DerivedLevel2
Método £() en DerivedtLeveli llamado desde Derivedtevel2
Método g() en BaseClass llamado desde DerivediLevell
Método h() en DerivediLevell llamado desde main(14)
La clase Baseclass se llama clase base o superclase, y otras clases se llaman subclases
co clases derivadas debido a que se derivan de la superclase en la cual pueden usar los cam-
pos de datos y los métodos especificados en BaseClass como protected, public, 0
cuando las subclases estén en el mismo paquete que la clase base, no tienen modificador
de acceso, Heredan todos estos campos y métodos de su clase base de modo que no tienen
que repetir las mismas definiciones. Sin embargo, una clase derivada puede anular la
definicién de un método que no es final al introducir su propia definicién, De esta
manera, tanto la clase base como la clase derivada tienen alguna medida de control sobre
‘sus métodos.
La clase base puede decidir cudles métodos y campos de datos pueden revelarse a las
clases derivadas, de modo que el principio de ocultacién de informacidn se sostiene no slo
con respecto al usuario de la clase, sino tambien a las clases derivadas, Ademés, la clase de-
rivada puede decidir cuales métodas y campos de datos publicos y protegidos conservar y
utiliza, y cudles modifica Por ejemplo, tanto DerivediLevel y Derived2ievel re-
definen al método £() al dar sus propias versiones de £ (). Sin embargo, el acceso al mé-
todo con el mismo nombre en la clase padre atin es posible al preceder el nombre del
método con la palabra clave super, como se muestra en la llamada de super .£() desde
£() en DerivedLevel2.
Una clase derivada puede afiadir nuevos métodos y campos propios. Este tipo de
clase puede volverse una clase base para otras clases que pueden derivarse de la misma
manera que la jerarquia de herencia puede ampliarse deliberadamente. Por ejemplo, la
clase DerivediLevelt se deriva de BaseClass, pero al mismo tiempo, s la clase base
para DerivedLevel2.
Los métodos o campos protegidos de la clase base estin accesibles para las clases
derivadas. También estan accesibles para las clases no derivadas si estas clases se encuen-
tran dentro del mismo paquete que la clase que define los métodos y campos protegi-
dos. Por esta razén, DerivediLeveli puede llamar al método protegido a() de
BaseCiass, pero una llamada a este método desde run() en TestInheritance se
interpreta como ilegal.
No obstante, run() puede lamar al método 9(), declarado como protegido en
Derived2Levell, debido a que tanto Derived2Levell como TestInheritance
en las que run( ) se define, estin en el mismo paquete.Seccién 1.2 Programacién orientada a objetos en Java M21
Los métodos y los campos de datos sin modificador de acceso pueden accederse
por cualquier clase del mismo paquete. Por ejemplo, el método k( ) en BaseClass no
puede accederse incluso en una clase derivada, Derived2beveli, debido a que la
clase derivada esté en un paquete distinto, Pero el método k() en DerivediLevel1
puede accederse aun cuando esté en una clase no derivada, como Test Inheritance
en run{ ), ya que tanto DerivedlLevel1 como Test Inheritance se encuentran
en el mismo paquete.
A diferencia de C++, el cual soporta herencia multiple, la herencia en Java debe limi-
tarse a una clase solamente, de modo que no ¢s posible declarar una clase nueva con la
declaracién
class Derived2tevel2 extends Derivediteveli, Derived2Levell { ... }
Adems, una clase declarada £inal no puede ampliarse (las clases de envoltura son
ejemplos de las clases final).
1.2.4 Polimorfismo
El polimorfismo se refiere a la capacidad de adquirir muchas formas. En el contexto de
1a OOP, esto significa que el mismo método denota muchos métodas que son miembros
de diferentes abjetos, Esto se logra por medio del conocido enlace dindmico, cuando el
tipo de un método que se va a ejecutar puede retardarse hasta el tiempo de ejecucion.
Esto se distingue del enlace estatico, cuando el tipo de respuesta se determina en el tiempo
de compilacién, como en el caso de IntOb ject y Doubleob ject presentados en la sec-
ci6n 1.2.1, Estos dos objetos se declaran como objetos cuyos campos de almacenamiento
guardan datos del tipo object y no enteros 0 dobles. La conversion se realiza dinémica-
‘mente, pero fuera del objeto mismo. Para el enlace dindmico, considere las declaraciones
siguientes:
cla:
AG
public void process() {
System.out.printin( "Dentro de A");
>
)
class Exta extends A {
public void process() {
System.out.printin(*Dentro de ExtA");
>
)
entonces el ebdigo
A object = new A();
object..process();
object = new ExtA();
object .process();
da como resultado la salida
Dentro de A
Dentro de Exta22 = W Capitulo 1 Programacién orientada a objetos utilizando Java
Esto se debe al enlace dindmico: el sistema verifica dindmicamente el tipo de objeto al
cual se esté refiriendo actualmente la variable y elige el método apropiado para este tipo.
Por lo tanto, aun cuando la variable object se declara para ser del tipo A, se asigna en
la segunda asignacién de un objeto del tipo BxtA y ejecuta el método process), el
ccual se define en la clase ExtA, en vez del método con el mismo nombre definido en la
clase a,
Esto también es vilido para las interfaces. Por ejemplo, si las declaraciones
interface B {
void process();
i
class Imp1B1 implements B {
public void process() {
system.out.println(*Dentro de Imp1B1*);
}
»
class ImplB2 implements B {
public void process() {
System.out.printin(*Dentro de Imp1B2");
y
y
vvan seguidas por las instrucciones
B object = new ImplB1();
object .process();
object = new ImplB2();
object .process();
entonces la salida es
Dentro de Imp1B1
Dentro de Imp1B2
a pesar del hecho de que el objeto es del tipo 8. El sistema reconoce que, para la primera
llamada de process (), object se refiere a un objeto del tipo tmp131, yen la segunda
llamada, se refiere a un objeto del tipo Imp1B2.
El polimorfismo es por tanto una herramienta poderosa en la OOP. Es suficiente
para enviar un mensaje estindar a muchos objetos distintos sin especificar como se
seguiré el mensaje. No hay necesidad de saber qué tipo de abjetos son. El receptor es
responsable de interpretar el mensaje y seguirlo. El emisor no tiene que modificar el
mensaje dependiendo de! tipo de receptor. No hay necesidad de instrucciones switch
0 if-else. Ademés, se pueden afiadir unidades nuevas a un programa complejo sin
la necesidad de volver a compilar al programa entero,
El enlace dindmico permite delegar la definicién de Genclass. Suponga que la
definicin de esta clase también incluye un método para encontrar una posicion de una
pieza de informacién en particular. Sila informacidn no se encuentra, se regresa a1. La
definicién ahora esSeccidn 1.2 Programacién orientada a objetos en Java M23
cla
GenClass {
Object(] storage = new Object {50};
int find(Object el) ¢
for (int i= 0; i < 50; it+)
if (storage[i) != null 6 storage{i}.equals(el))
return i;
return -1;
,
void store(object el) {
El método £ind() devuelve el resultado correcto si se usan las envolturas de los tipos
bisicos —character(), Integer ( ), et.— pero, zqué ocurre si queremos almace-
nar objetos no estindar en una instancia de GenClass? Considere fa declaracion
class SomeInfo {
SomeInfo (int n) (
this.n =n;
>
private int n;
,
Ahora el problema es qué pasa si para una declaracién
GenClass object = new GenClass();
ejecutamos:
object..store(new SomeInfo(17));
System.out.print1n(object.find(new SomeInfo(17)));
Al final, 1 se imprime para indicar una biisqueda sin éxito. EI resultado es generado por
el método equas( ). El sistema esté utilizando un método integrado para el tipo Ob-
Sect que regresa true_si las referencias de las variables comparadas son las mismas, no
el contenido de los objetos a los cuales se hace referencia, Para superar esta limitacién, el
método equals() debe redefinirse al invalidar la definicién estindar con una nueva
definicién. Por lo tanto, la definicién de Some nfo esté incompleta y debe ampliarse a
class Someinfo {
Someinfo (int n) (
this.n = n;
»
public boolean equais(Object si) {
return n == ((SomeInfo) si).n;
,
private int n;24 WM Capitulo 1 Programacidn orientada a objetos utilizando Java
Con esta nueva definicion, £ind( ) regresa la posicién del objeto que contiene el
niimero 17. La raz6n de que esto funcione adecuadamente es que todas las clases son
extensiones de la clase Object. El sistema realiza esta extensién de manera expresa de
‘manera que la declaraciOn de SomeIn£o es en realidad
cu
SomeInfo extends Object {
Elcalificador extends object esté implicito en la definici6n original y no tiene
que ser explicito, De esta manera, el método estindar equals) se sobreescribe por re-
definicién de este método en la clase Sometnfo y por el poder del enlace dinmico.
Cuando se ejecuta la llamada object: .find(new Sometn£o( 17) ), el sistema utiliza el
método equals ( )definido en la clase Sometn£o debido a que una instancia de esta clase
un argumento en el método call. Por lo tanto, dentro de £ind( ), la variable local se
‘vuelve una referencia a un objeto del tipo Sometn£o, aun cuando esta definido como un
parémetro del tipo Object. El problema es, sin embargo, que un ajuste répido como éste
s6lo puede realizarse para los métodos integrados para Ob ject, en particular, equals ()
y toString( ). Sélo ligeramente mas complicado ¢s el caso cuando queremos definir una
clase genérica que permita las comparaciones, Un ejemplo més realista de polimorfismo
se proporciona en un caso de estudio.
ENTRADA Y SALIDA
El paquete java. io proporciona varias clases para leer y escribir datos. Para utilizar las
clases, el paquete debe incluirse de manera explicita con la instruccién
io.
import 3)
En esta seccidn, introducimos sucintamente las clases para la lectura desde un dis-
positivo estindar (teclado), la escritura a un dispositivo estindar (monitor) y el proce-
samiento de E/S hacia y desde los archivos. También existen varias otras clases que son
particularmente importantes para interactuar con la red, como flujos con bifer,filtra-
dos y con cauce (piped).
Para imprimir cualquier cosa en la pantalla, son suficientes dos instrucciones:
System.out.print (mensaje) ;
system.out .print1n( mensaje) ;
Las dos instrucciones difieren en que la segunda versién produce el caricter de fin de
linea después de imprimir el mensaje. EI mensaje impreso por la instruccién es una ca-
dena. La cadena puede componerse de cadenas literales, cadenas de variables y cadenas
generadas por el método tostring( ) para un objeto en particular; todos los compo-
nentes de la instruccién print estin concatenados con el operador +. Por ejemplo, al
haber declarado la clase C:
class ¢ ¢
int $= 10;
char a= ‘At;
public String toString() ¢
return "("# it" Hae yh;
,Seccién 1.3 Entrada y salida M25
yun objeto
€ obj = new c();
una instruccién de impresién
System.out.printin("El objeto: * + obj);
produce
El objeto: (10 A)
Observe que si tostring() no estuviera definido en ¢, la salida seria
Bl objeto: cé1ce789
debido a que la clase C es una extensién predeterminada de la clase Object cuyo método
toString() imprime la direccién de un objeto en particular. Por consiguiente, casi
siempre es critico que toString( ) se redefina en una clase de usuario para tener una
salida mis significativa que una direccién.
La entrada de lectura es marcadamente mas engorrosa, Los datos se leen desde la en-
trada estindar con el flujo de entrada System. in. En ese punto, puede utilizarse el
metodo read( jel cual regresa un entero, Para leer una linea, el usuario debe definir un
método nuevo, por ejemplo,
public String readLine() ¢
int ch;
String s = **;
while (true) (
ery ¢
ch = System.in.read();
if (ch == -1 || (char)ch == ‘\n') // fin de archivo o fin de linea;
break;
else if ((char)ch != '\r') // ignora el retorno de carro;
8 = 8 + (char)ch;
} cateh( IOException @) ¢
y
,
return s;
Dado que read( ) esti definido como un método que lanza un TOException, la
‘excepcidn tiene que manejarse con Ja cliusula try-catch.
Observe que ch debe ser un entero de modo que detecte el final del archivo (que es
Ctrl-z introducido desde el teclado de la PC). El marcador del final de archivo es
niimero =I, y los caracteres en realidad son enteros sin signo. Si ch se declaré como un
cardcter, entonces la instruccién de asignacién tendria que ser
ch = (char) System.in.read();
con lo que el marcador de fin de linea -1 se almacenaria como el ntimero 65535 en ch,
por medio de lo cual la condicién ch == —1 se evaluaria como falsa y el sistema espe-
raria otra entrada.26 MW Capitulo 1 Programacién orientada a odjetos utilizando Java
Por fortuna, la tarea puede lograrse por un procedimiento diferente al declarar
primero un flujo de entrada con las declaraciones:
InputStreamReader cin = new InputStreamReader (System. in);
BufferedReader br = new BufferedReader (cin);
‘© con una declaracién:
BufferedReader br = new BufferedReader(new InputStreamReader (System. in));
y luego un método integrado readt.ine( ) puede llamarse para asignar un valor a una
cadena 8:
try {
8 = br-readbine(};
} catch(IOException e) ¢
}
Para leer un niimero, una entrada debe leerse como una cadena y luego convertirse a
tun ntimero, Por ejemplo, sila entrada estd en la cadena s, entonces la conversién puede
realizarse con
try (
i = Integer.parsernt(s-trim());
} catch (NumberFormatException e) {
System.out.println("No un nimero");
>
La entrada puede simplificarse después de que se importa javax. swing .Jop-
tionPane:
String s = JoptionPane.showInputDialog(*Introduzca un némero*);
i = Integer.parsetnt(s.trim());
Para realizar la entrada y la salida en un archivo, debe tomarse una decisién
respecto a qué tipo de datos se van a procesar de modo que el tipo apropiado del archivo
pueda elegirse.
Un archivo binario se procesa como una secuencia de bytes, un archivo de texto como
tuna secuencia de caracteres. Por lo general, los archivos de texto son portables; los archivos
binarios no lo son debido a que los datos en el archivo tienen la misma representacion que
Jos datos almacenados en la memoria principal, y esta representacién varia de un sistema a
‘tro. No obstante, los archivos binarios de Java son independientes de la plataforma.
El paquete java. io incluye muchos flujos para realizar la E/S que estin organiza
dos en una jerarquia, Las clases InputStream y OutputStream se utilizan para proce-
sar los archivos binarios, y las clases Reader y Weiter son para archivos de texto.
1.3.1. Lectura y escritura de bytes
Considere un método que lea un byte ala ver desde un archivo de entrada y a la vez lo
escriba en un archivo de salida y en la pantalla:
void readBytesl(String fInName, String fOutName) throws IOException (
Fileznputstream £In = new FileInputStream(fInNane
Fileoutputstrean f0ut = new Pileoutputstream(fouttame)
int i;
while ((i = fIn.read()) I= -1) (Seccién 1.3 Entraday salida M27
System.out.print((char)i + " "); // muestra caracteres;
// System.out.print(i + * "); // muestra valores ASCII;
fOut.write(i);
,
fIn.close();
fOut .close(
Una variable entera se utiliza para leer la entrada, y el final del archivo se indica por
medio de -1. Para hacer la Jectura y escritura mas eficientes, la entrada y la salida se al-
macenan temporalmente en un biifer:
void readBytes2(String €InName, String fOutName) throws IOException {
BufferedinputStream fIn = new Bufferedinputstream(
new FileInputStream(fInName));
Bufferedoutputstream fOut = new BufferedOutputstream(
new FiledutputStream(foutName) );
int i;
while ((i = fIn.read()) != -1) {
System.out.print(i +" ");
fOut.weite(i);
>
fIn-close();
fOut.close();
1.3.2 Lectura de lineas
Para leer una linea a la vez, se utiliza el método readLine( ) desde BufferedReader,
como en el ejemplo:
void readLines(String fInName, String fOutName) throws IOException (
BufferedReader fIn = new BufferedReador(
new FileReader(fInName));
PrintWriter fOut = new PrintWriter(new FileWriter(foutName));
String s;
while ((s = fIn.readbine()) != null) {
System.out .print1n(s);
fout-printin(s);
>
£In.close();
fout.close();
El final del archivo se detecta una vez que se realiza Ja lectura de una cadena nula
(después de la lectura de una linea vacia, la cadena s no es nula, pero su longitud es 0).28 W Capitulo 1 Programacidn orientada a objetos utilizando Java
1.3.3 Lectura de simbolos: palabras y nimeros
Un Streamrokenizer extrac desde un archivo de texto varios tips de simbolos (to-
zens) incluyendo identificadores (secuencias de letras y digitos que comienzan con una
letra o un byte del intervalo de \uOOA0' a '\uOOFF') y ntimeros. También puede extraer
cadenas entrecomilladas y varios estilos de comentarios cuando el sefializador se ins-
tala correctamente. El método nextToken() omite los caracteres de espacio que
separan a los simbolos y actualiza las variables de instancia del sefalizador: sval del
tipo String, que contiene el simbolo actual cuando ésta es una palabra; nval
del tipo double, que contiene el simbolo actual cuando es un ntimero, y ttype del tipo
int, que contiene el tipo de simbolo actual. Existen cuatro tipos de simbolos: P7_EOF
(inal de archivo), 77_£01, (final de linea), '?_WORD y TT_NUMBER. A continuacién se
muestra un ejemplo:
void readTokens (String fInName) throws IOException {
Streamfokenizer fIn = new Streamfokenizer(
new BufferedReader (
new FileReader(£InName)));
fIn.nextToken( );
String 8;
while (fIn.ttype != Streamfokenizer.TT EOF) {
if (£In.ttype == Streamfokenizer.TT_WORD)
8 = “word”;
else if (£In.ttype == Streamfokenizer.TT_NUMBER)
s = “number”;
else 5 = "other";
System.out.println(s + ":\t" + £In);
£In.nextToken( );
‘Cuando un archivo de texto se compone de palabras o miimeros separados por es-
pacios en blanco, entonces tal vez sea més facil extraer cada palabra o nimero al leer
una linea ala ver y luego aplicar un seftalizador de cadena, como en
void readTokens2(String fInName) throws IOException {
BufferedReader fIn = new BufferedReader(
new FileReader(fInName));
String
while ((s = £In.readLine()) != null) (
java.util.stringTokenizer line = new java.util.StringTokenizer(s);
while (1ine.hasMoreTokens(})
System. out. printin(Line.nextToken());
»
£In.close();
in de un sefializador de flujo puede encontrarse en laSeccidn 1.3 Entradaysalida M28
1.3.4 Lectura y escritura de tipos de datos primitivos
La clase DataInputStream proporciona métodos para la lectura de tipos de datos
Primitivos en formato binario. Los métodos incluyen readBoolean(), readByte(),
readShort(), readChar(), readInt(), readLong() yreadUTF() (para leer
cadenas en formato de texto Unicode),
void writePrimitives(String foutName) throws IOException {
DataOutputStream fout = new Datadutputstream(
new FileOutputStream(fOutName) );
fOut .writeBoolean(5<6);
fout.writechar(*A');
fOut.writeDouble(1.2);
fOut.writeFloat (3.4f);
fout.writeshort (56);
fOut.writernt (78);
fOut .writeLong(90);
fout.writesyte('*");
fOut.writeUTF ("abe");
fOut.close();
>
void readPrimitives(string £InName) throws IOException {
DatainputStream fIn = new DataInputstream(
new FileInputStream(fInName));
System.out.printin(fIn.readBoolean() +" * +
fIn.readchar() + * "+
fIn.readDouble() +" " +
fIn.readFloat() +" " +
fIn.readshort() +" * +
fIn.readint() +" * +
fIn.readtong() + * * +
£In.readByte() + * * +
£In.readUTF()):
fIn.close();
El caso de estudio al final del capitulo se basa en la E/S para tipos de datos primitivos.
1.3.5 Lectura y escritura de objetos
Los objetos también pueden guardarse en un archivo si se yuelven persistentes. Un ob-
jeto se vuelve persistente si su tipo de clase se establece para implementar la interface
Serializable,comoen
class C implements Serializable {
int i;
char c) (WM Capitulo | Programacién orientada a odjetos utilizando Java
is jich= cy
y
public String toString() (
return "(7+ it" + ch ty;
y
>
‘Una declaracién como ésta ¢s posible si todas las variables de instancia también son
Serializable, que es el caso de la clase C, ya que en Java todos los tipos de datos bisi-
os, arreglos y muchas clases son Serializable. He aqui un ejemplo de escritura y
lectura de un objeto de clase tipo ¢:
void writeobjects(String foutName) throws IOException {
C el = new C(20,'A'), C2 = new C(20,'B');
Objectoutputstream fout = new Objectoutputstream(
new FileOutputStream(foutName));
fout.writedbject (cl);
fout .writeObject (c2);
fout .close();
>
void readObjects(String fInName) throws IOException (
€ el = new C(30,'C"), 2 = cl;
ObjectInputStream fIn = new ObjectInputstream(
new FileInputStream(fInName) );
try ¢
et = (C)£tn.readobject();
2 = (C)£In-readobject ();
} catch(ClassNotFoundexception e) ¢
y
System.out.printin(el + * " + €2);
1.3.6 Archivos de acceso aleatorio
Los archivos estudiados hasta ahora se procesan en forma secuencial: leemos (escribi-
mos) un elemento a la vez y continuamos hacia el final del archivo. Para poder leer y es-
cribir en el mismo archivo en cualquier posicién dentro del archivo, debe utilizarse un
archivo de acceso aleatorio. Un archiva se crea con el constructor
RandomAccessFile(nombre, modo) ;
El constructor abre un archivo con el nombre especificado ya sea para solo lectura
© para lectura y escritura. El modo se especifica por medio de la letra w 0 por las letras,
rw; por ejemplo,
RandomAccessFile = raf new RandomAccessFile("myFile", *rw'
Podemos movernos a cualquier parte del archivo, pero para saber que estamos den-
tro del archivo podemos utilizar el método length() que regresa el tamano del
archivo medido en bytes. El método getPilePointer() regresa la posicién actual en.
elarchivo. El método seek (pos) mueve el apuntador del archivo a ta posicién especi-
ficada por un entero posSeccién 1.4 Javay apuntadores M31
La lectura se realiza por el método read( ), el cual regresa un byte como un
entero; por read (b), que llena por completo un arreglo de byte b; por read(»,off,
Len), que Hlena las celdas Len del arreglo del byte b que comienza desde la celda of £5
Y por readLine( ), que lee una linea de entrada. Otros métodos de lectura regresan un.
valor especificado segin su nombre: readBoolean(), readByte(), reaShort(),
readChar(), readint(), readLong() y readUTF( ). Todos estos métodos de
Jectura tienen métodos de escritura correspondientes, por ejemplo, write(c),donde ¢
esunint, write(b) ywrite(b,off, len), ademis del método writeBytes(s)
para escribir una cadena s como una secuencia de bytes.
Después de que termina el procesamiento, el archivo debe cerrarse con el método
close():
raf.close():
Ejemplos de la aplicacién de archivos de acceso aleatorio pueden encontrarse en el caso
de estudio de la secci6n 1.7.
EE Java v aruntapores
Eneesta seccién, se analiza un problema de implementacién de objetos de Java.
‘Aun cuando Java no utiliza apuntadores explicitos y no permite al programador
usarlos, el acceso a los objetos se implementa en términos de apuntadores. Un objeto
‘ocupa un poco de espacio de memoria a partir de una cierta localidad de memoria. Un
apuntador a este objeto es una variable que contiene la direccién del objeto y esta direc
entonces la instrucci6n de impresi6n
systen.out.print(p.equals(q));Seccién 1.4 Javay apuntadores M33
produce true. (Esto puede lograrse de una manera mucho més elegante en C++ al so-
brecargar el operador de igualdad ==; es decir, al definir un método que permite la apli-
cacién de este operador a las instancias de la clase Node.)
La comprensién de que las variables de objeto son en realidad referencias a los ob-
jetos, ayuda a explicar la necesidad de cautela con el uso del operador de asignacién. La
intencién de las declaraciones
Node nodel = new Node(*Roger",20), node2 = node1;
¢s crearel objeto node1, asignar valores a los dos campos en node}, y luego crear el ob-
jeto node? e inicializar sus campos a los mismos valores que en node1. Estos objetos
van a ser entidades independientes de modo que la asignacién de valores a uno de ellos
no debe afectar a los valores del otro. Sin embargo, después de las asignaciones
node2.name = "Wendy";
node2.age = 30;
la instruccién de impresién
System.out.print1n(nodel.name+" "+nodel.age+" "+node2.name+" “+ node2.age!
genera la salida
Wendy 30 Wendy 30
Tanto las edades como los nombres en los dos objetos son los mismos. ;Qué ocu-
rri6? Debido a que node1 y node? son apuntadores, las declaraciones de node y
node? provocan la situacién ilustrada en la figura 1.2a. Después de las asignaciones a
Jos dos campos de node, la situacién es como aquella de la figura 1.2b. Para evitar
que esto ocurra, tenemos que crear una copia nueva del objeto referenciado y hacer que
FIGURA 1.2 —_Ejemplificacién de la necesidad de utilizar el método clone( )
node! node!
Relelele Welala
veo *f o ] matt x]
@ o
1 node!
alalgia[el gaia
20
node?
Rlolslel+ I34M Capitulo 1 Programacién orientada a objetos utilizande Java
node? se vuelva una referencia a esta copia, Esto puede hacerse al definir el método
clone( ) marcado en la interface Cloneable. Una nueva definicién de Node es ahora:
class Node implements Cloneable (
String name;
int age;
Node(String n, int a) {
name = n; age = a;
>
Wode() {
this(*",0);
»
public Object clone() {
return new Node(name,age)
,
public boolean equals(Node a) {
return name.equals(n.name) && age
d
»
Con esta definicién, las deciaraciones deben ser
Node nodel = new Node("Roger",20), node2 = (Node) nodel.clone();
Jo cual da como resultado la situacién mostrada en la figura 1.2c, de modo que las dos asig-
naciones
node2.name = "Wendy";
node2.age = 30;
afectan sélo al segundo objeto (figura 1.24).
Los apuntadores de Java estin ocultos al programador. No hay tipo apuntador en
Java. La falta de un tipo de apuntador explicito esta motivada por el deseo de eliminar un
comportamiento dastino de los programas, Primero, no es posible en Java tener una refe-
rencia no nula o a un objeto no existente. Si una variable de referencia no es nula, siem-
pre apunta a un objeto debido a que el programador no puede destruir un objeto
referenciado por una variable, Un objeto puede destruirse en Pascal utilizando la funcién.
dispose() y en C++ por medio de delete. La raz6n para utilizar dispose() 0
delete es la necesidad de regresar al espacio del administrador de memoria ocupado
por un objeto innecesario, Justo después de la ejecucion de dispose() 0 delete, las
variables de apuntador alojan las direcciones de los objetos que ya se han regresado al ad~
ministrador de memoria. Si estas variables no se establecen como nulas o como la direc
cin de un objeto accesible desde el programa, surge el conocido problema de referencia a
tun objeto borrado que puede conducir a un colapso del programa. En Java, el problema
de referencia a un objeto borrado no se presenta. Si una variable de referencia p cambia
su referencia desde un objeto a otro, y el objeto no esti referenciado por otra variable a,
entonces el espacio ocupado por el objeto se reclama automiticamente por el sistema
operative a través de la recoleccién de basura (véase el capitulo 12). No existe un equi
lente en Java de dispose() o delete. Los objetos innecesarios simplemente se aban-
donan y el recolector de basura los incluye en el grupo de celdas de memoria libres en
forma automitica durante la eecucién del programa de usuario,Seccion 1.5 Vectores en java.util M@ 38
tra raz6n para no tener apuntadores explicitos en Java es un peligro constante de
tener una referencia a una localidad de memoria que no tiene nada que ver con la logica
del programa. Esto seria posible a través del apuntador aritmético que no esté permi-
tido en Java, donde las asignaciones tales como
(p + q)-ch = "bY;
(Hp) en = 67
son ilegales.
Resulta interesante que aun cuando los apuntadores explicitos estén ausentes en
Java, éste se base mis en apuntadores que C/C++. Una declaracién de objeto siempre es
tuna declaracién de referencia a un objeto; por lo tanto, una declaracién de objeto
Node p;
debe ir seguida por la inicializacién de la variable p ya sea al utilizar de manera explicita
tun constructor, como en
p= new Node);
‘© porla asignaciGn de un valor desde una variable que ya se ha inicializado, como en
pea
Debido a que un arreglo también es un objeto, la declaracion
int a(10];
¢s ilegal; esta declaraci6n se considera un intento por definir una variable cuyo nombre es
{10}. Una declaracién tiene que ir seguida por la inicializacién, que con frecuencia
se combina con la declaracién como en
int[] a = new int(10};
De esta manera, Java no permite variables que nombren a los objetos directa-
mente. Por lo tanto, la notacién de puntos utilizada para tener acceso alos campos del
objeto, como en p.name, en realidad es una referencia indirecta al campo nane debido
a que p noes el nombre del objeto con el campo name, sino una referencia a (direccién
de) este objeto. Por fortuna, el programador no tiene que preocuparse por esta distin-
ci6n debido a que todo es cuestion de una implementacion del lenguaje. Pero como se
mencioné, una comprensién de estos detalles de implementacion ayuda a explicar los
resultados de algunas operaciones, como se ilustr6 antes con el operador
Hs vecrores en java.util
Una clase util en el paquete java.util ¢s la clase Vector aun cuando en la actualidad
se considera una clase heredada. Un vector es una estructura de datos con un bloque
de memoria contiguo, al igual que un arreglo. Debido a que las localidades de memoria
son contiguas, pueden accederse aleatoriamente de modo que el tiempo de acceso de
cualquier elemento del vector sea constante. El almacenamiento se maneja en forma au-
tomdtica de modo que en un intento por insertar un elemento en un vector completo,
un bloque de memoria més grande se asigna para el vector, los elementos del vector se
copian al bloque nuevo y el bloque viejo se libera. El vector es por lo tanto un arreglo
flexible; es decir, un arreglo cuyo tamafo puede cambiar dinmicamente,36 «M Capitulol ramacién orientada a objetos utilizando Java
La jerarquia de clases del paquete java.util esa siguiente:
Object = AbstractCollection = AbstractList = Vector
La figura 1.3 lista en orden alfabético los métodos de la clase Vector. Algunos de estos
métodos se heredan de AbstractList; otros son de AbstractCollection. La
figura 1.3 lista la mayoria de los métodos de la clase. S6lo los métodos iterator() y
List Iterator), heredados de la clase AbstractList, y los métodos finalize(),
getClass(), notify(), notifyAll() y wait), heredados de la clase Object,
no estan incluidos,
FIGURA 1.; Lista alfabética de las funciones miembro de la clase java.util.Vector.
Método Operacion
void add(object ob) {insert el objeto ob al final del vector
void add(int pos, Object ob) —_insertaelabjeto ob en a posicién pos después de desplazar una
‘posicidn los elementos en las posiciones que estin despus de pos; arroja
ArrayIndexOutOfBoundsException si pos esti fuera del intervalo
boolean addAll (Collection c) _afade todos los elementos de la coleccibn e al final del vector; tegresa true
sic mo estévacio;arroja Array IndexOutOfBoundsException si
[POs esté fuera del intervalo yNul1PointerException sices nulo
boolean addall(int pos, atade todos los elementos dela coleccin ¢ en la posicién pos del vector
Collection ¢) después de desplaza ls objetos que van después de a posicidn pos; arroja
ArrayIndexOutOfBoundsException si pos ests fuera del inter-
valoy Wall PointerException sic.esnulo
void addBlement (Object ob) inserta el objeto ob al final del vector; lo mismo que add. (ob)
int capacity() regresa el niimero de objetos que pueden almacenarse en el vector
void clear()
public boolean equals(Object pr) {
return SSN.equals(((Personal)pr).SSN);
,
public void writeToFile(RandomAccessFile out) throws IOException {
writeString(SSN,out);
writeString(name, out);
writestring(city, out);
out.writernt (year
out .writeLong( salary);
}
public void writeLegibly() {Seccién 1.7 Caso de estudio: archivo de acceso
FIGURA 1.5 (continuacién)
System.out-print("SSN = " + SSN +", name = * + name.trim()
city =" + city.trim() +", year = * + year
salary = " + salary:
,
public void readPromFile(RandomAccessFile in) throws IOException {
SSN = readString(9, in);
name = readString(nameLen, in);
city = readstring(cityLen, in);
year = in.readInt();
ry = in.readLong();
>
Public void readKey() throws IOException {
System.out.print("Enter SSN: *);
SSN = readLine();
}
public void readFromConsole() throws I0Exception {
System.out.print("Enter SSN: ");
SSN = readtine();
System.out.print("Name: *);
name = readLine();
for (int i= name.length(); i < nameLen; i++)
name += ' ';
System.out.print("City: *
city = readLine();
for (int i = city-length(); i < cityLen; i++)
city + *
System.out.print("Birthyear: ");
year = Integer.valueOf(readLine().trim()).intvalue();
System.out.print("salary: ");
salary = Long.valueof(readLine().trim()).longvalue();
>
public void copy(Dbobject{] d) ¢
4[0] = new Personal(SSN,name,city, year, salary);
,
Ve steteseeaeseenes Student. J
sto.
import jav
public class Student extends Personal {
public int size() ¢
‘continda46 WM Capitulo 1 Programacion orientada a objetos utilizando Java
FIGURA 1.5 (continuacién)
return super.size() + majorLen*2;
?
protected String majo
protected final int majorLen = 10;
Student() ¢
super()+
)
Student (String ssn, String n, String c, int y, long s, String m) {
super(ssn,n,c,y,s
major = m;
>
public void writeToFile(RandomAccessFile out) throws IOException {
super.writeToFile(out);
writestring(major,out);
)
public void readFromPile(RandomAccessFile in) throws IOException {
super.readFromFile(in);
major = readString(majorLen, in);
,
public void readFromConsole() throws IOException {
super. readFromConsole();
System.out.print(*Introduzca una asignatura principal: “);
major = readLine();
for (int i = major.length(); 4 < nameLen; i++)
major += * ';
?
public void writebegibly() (
super.writeLegibly();
System.out.print(", major = “ + major.trim());
»
public void copy(DbObject{] d) {
[0] = new Student (SSN,name,city, year
ary,major);
,
»
[[ttetesaeseseetesesaeesaes Database.
a tensaesaeanssaneaseenens
import java.io.*;
public class Database (
private RandomAccessFile database;
private String fName = new String();7Seccién 1.7 Caso de estudio: archivo de acceso aleatorio M47
FIGURA 1.5 (continuacién)
private Tomethods io = new Tomethods();
Database() throws IOException {
System.out.print("File name: *);
fame = io.readbine();
ss
private void add(Dbobject d) throws IOException {
database = new RandomAccessFile(fName, "rw");
database. seek(database.length());
d.writeToFile(database) ;
database.close(
,
Private void modify(Dbobject d) throws ToException {
DbObject{] tmp = new DbObject[1};
.copy (tmp) +
database = new RandomAccessFile(fName,"rw");
while (database.getFilePointer() < database.length()) {
tmp[0].readFromFile(database);
if (tmp[0}.equals(d)) {
tmp[0].readFromConsole();
database. seek (database. getFilePointer()-d.size());
tmp[0}.writeToFile(database);
database.close();
return;
,
)
database.close();
System.out.print1n("El registro que se va a modificar no esta en
la base de datos");
,
private boolean find(DbObject d) throws IOException {
DbObject{} tmp = new Dbobject (1);
copy (tmp) ;
database = new RandomAccessFile(fName, "r*
while (database.getFilePointer() < databa
‘tmp[0].readFronFile(database);
if (tmp[0}.equals(d)) {
database.close();
return true:
slength()) {
,
}
database.close();
continda48 WM Capitulo 1 Programacién orientada a objetos utilizando Java
FIGURA 1.5 (continuacién)
return false;
>
private void printDb(Dbobject d) throws IOException (
database = new RandomAccessFile(fName,"r");
while (database.getFilePointer() < database.length()) {
4. readFromFile(databas
d.writeLegibly();
System.out.printin();
)
database.close();
f
public void run(pbobject rec) throws IOException (
String option;
System.out.printin("1. Afadir 2. Buscar 3. Modificar un registro;4.
salir");
System.out.print("Introduzca una opcién: ");
option = io.readLine();
while (true) {
if (option.charat(0 Say
rec.readFromconsole();
add(rec);
>
else if (option.charAt(0) == '2') {
rec.readKey();
System.out.print("El registro es ");
if (find(rec) == false)
System.out.print("no ");
System.out.printin("en la base de datos");
t
else if (option.charAt(0) == '3') (
rec.readKey();
modify(rec);
)
else if (option.charAt(0) != '4')
System. out.print1n("Opcién errénea");
else return;
printDb(rec);
Systen.out.print(*Introduzca una opcién: *);
option = io.readLine();Seccidn 1.7 Caso de estudio: archivo de acceso aleatorio Mm 49
FIGURA 1.5
[]asenesnennsenens:
(continuacién)
* UseDatabase. java
io.*;
UseDatabase {
static public void main(string| ]) throws I0Exception {
” (new Database())-run(new Personal());
(new Database(}).run(new Student ());
El método €ind( ) utiliza hasta cierto punto el hecho de que el archivo es aleatorio
al inspeccionarlo registro por registro, no byte por byte. Para estar seguros, los registros
se construyen a partir de bytes y todos los bytes que pertenecen a un registro en particu-
lar que se vaa leer, pero solo los bytes requeridos por el operador de igualdad estén par-
ticipando en la comparacién.
El método modi fy () actualiza la informacién almacenada en un registro en par-
ticular. El registro primero se recupera desde el archivo, también utilizando la biisqueda
secuencial, y la informacién nueva es lefda por el usuario que utiliza el método
readFromFile() definido para una clase en particular. Para almacenar en el archivo
el registro tmp 0} actualizado, modi fy () obliga a la base de datos de apuntadores de
archivo a regresar al principio del registro tmp 0} que se acaba de leer; de lo contrario,
el registro que va después de tmp 0} en el archivo se sobreescribiria. La posicién inicial
de tmp puede determinarse de inmediato debido a que cada registro ocupa el mismo
niimero de bytes; por consiguiente, es suficiente regresar al mimero de bytes ocupados
por un registro. Esto se logra al llamar a database. seek (database. getFile-
Pointer ()-d.size()),donde size() debe definirse para la clase en particular.
La clase Database genérica incluye dos métodos més. El método add( ) coloca un
registro al final del archivo. El método printDb( ) imprime el contenido del archivo.
Para ver la clase Database en accién, tenemos que definir una clase que especifique
el formato de un registro en un archivo de acceso aleatorio. Como ejemplo, definimos la
clase Personal con cinco campos, SSN, name, city, year y salary. Los primeros tres
campos son cadenas, pero s6lo SSX tiene siempre el mismo tamafio. Para tener un poco
més de flexibilidad con las otras dos cadenas, hay otras dos constantes definidas, name~
Leny cityLen.
El almacenamiento de datos desde un objeto requiere particular atencidn, ésta es la
tarea del método writeToFile(). El campo SSN ¢s el més simple de manejar. Un
niimero de seguridad social siempre incluye nueve digitos; por lo tanto, puede utilizarse
el operador de salida <<. No obstante, las longitudes de los nombres y ciudades varian
de registro a registro, y aun asi las secciones de un registro en el archivo de datos dise-
fiado para estos dos campos siempre deben tener la misma longitud. Para garantizar
esto, el método readFromConsole () afiade a las cadenas espacios en blanco de relleno.
‘Otro problema es planteado por los campos numéricos, year y salary, en particu-
lar el ultimo campo. Si salary se escribe en el archivo con el método printLong( ),
entonces el salario 50 000 se escribe como una cadena de 5 bytes de longitud *50000*, y5D WM Capitulo 1 Programacién orientada a abjetos utilizando Java
1 salario 100 000 como una cadena de 6 bytes de longitud *100000’, lo cual viola la
condicién de que cada registro en el archivo de acceso aleatorio debe tener la misma
longitud. Para evitar el problema, los nsimeros se almacenan en forma binaria. Por
ejemplo, 50 000 esté representado en el campo salary como una cadena de 32 bits,
‘600000000000000011100001 101010000. Ahora podemos tratar a esta secuencia de bits no
‘como si representara un ntimero grande, sino una cadena de cuatro caracteres, 00000000,
00000000, 11000011, 01010000; es decir, los caracteres cuyos cédigos ASCII son, en
sistema decimal, los ntimeros 0,0, 195 y 80. De esta forma, sin importar el valor de salary,
el valor siempre se almacena en 4 bytes. Esto se logra en Java con el método writeLong( ).
Este método de almacenar registros en un archivo de datos plantea un problema de
legibilidad, en particular en el caso de mimeros. Por ejemplo, 50 000 se almacena como 4
bytes: dos caracteres nulos, un carécter especial y una letra maydiscula P. Para una persona
que lee, es mas que evidente que estos caracteres representan 50 000, Por lo tanto, se nece-
sita una rutina especial para imprimir los registros en forma legible. Esto se logra utilizando
el método writeLegibly (), el cual explica por qué este programa utiliza dos métodos
para leer registros y dos para escribir registros: uno es para mantener los datos en un
archivo de acceso aleatorio, el oteo es para leery escribir datos en forma legible.
Para probar la lexibilidad de la clase Database, se define otra clase de usuario, la
clase Student. Esta clase también se utiliza para mostrar un ejemplo mas de la herencia.
La clase Student utiliza los mismos campos de datos que la clase Personal al
estar definida como una clase derivada de Personal més un campo adicional, un
campo de cadena ma jor. B! procesamiento de la entrada y la salida en los objetos de la
clase tipo Student es muy parecida a aquella de Ja clase Personal, pero el campo
adicional debe ser representado, Esto se hace redefiniendo los métodos a partir de la
clase base y al mismo tiempo reutilizindolos. Considere el método writeToPile()
para escribir registros de estudiantes en un archivo de datos con un formato de longitud
fija:
public void writeToFile(RandomAccessFile out) throws IOException{
super .writeToPile(out);
writestring(major, out) ;
El método utiliza el writeToPile( ) de la clase base para inicializar los cinco campos,
S8N,name, city, year y salary, ¢ inicializa el campo ma jor. Observe que una varia-
ble especial super se debe utilizar para indicar claramente que el weiteToFile() que
se esti definiendo para la clase Student llama al writeToFile( ) que ya esté definido
en la clase base Personal. Sin embargo, la clase Student hereda sin la modificacién al
método readkey() y el método equals(), debido a que la misma clave se utiliza
tanto en Jos objetos Personal como en Student para identificar en forma tinica
cualquier registro, en concreto,a SSW.Seccién 1.8 Ejercicios M51
HB) csercicios
1. ;Cuil debe ser el tipo de constructores definidos en clases?
2, Suponga que la clase e1assa incluye una variable k privada, una variable m sin modi-
ficador, una variable privada n protegida, una variable p protegida y una variable pablica
q-Ademiés, la clase cLassB se deriva de la clase classA, la clase class no se deriva de
elassa, y las tres clases estin en el mismo paquete. Adems, la clase classD se deriva
de classA, la clase class? no sederiva de class, y class esti en un paquete dis-
tinto a aquel en que estén las clases cLassD y classE. ;Cual de las cinco variables
definidas en la clase cLassa puede utilizarse por cualquiera de las otras cuatro clases?
3 Qué pasa sila declaracién dec:
class ¢ ¢
void processi(char ch) {
System.out.printin(" slenC* + ch);
entro de proc
»
void process2(char ch) {
System.out.printin(*Dentro de process? en C * + ch);
>
void process3(char ch) {
System.out.print1n( "Dentro de process} en C * + ch);
process2(ch);
>
vva seguida de la siguiente declaracién de su extensién:
class ExtC extends C {
void processi(int n) ¢
system.out.printin( "Dentro de process} en Extc * + n);
d
void process2(char ch) {
system.out-printin(*Dentro de process? en ExtC * + ch);
5
void process4(int n) ¢
System.out.print1n( "Dentro de process4 en Extc * + n);
»
»
Cuales métodos se invocan sila declaracién de los tres objetos
ExtC object1 = new ExtC();
€ object2 new Extc(), object3 = new Extc();52M Capitulo 1 Programacién orientada a objetos utilizanda Java
vva seguida por estas instrucciones; indique cualquier problema que estas instruc-
ciones puedan causar:
object] .proct
object 1.proces:
object2.proct
object2.procr
object3.proces:
object3.proces:
object3.proces:
1(1000);
4(2000);
1 (3000);
4( 4000);
1(1PY);
20" DF
BCR DT
4. Para la declaracién 11:
interface T1 {
int T1£1Q;
void T1f2(int i
,
identifique los errores.
a. enladeclaracién de la interfaz 12+
interface I2 extends I1 {
double 12£1();
void 12f2(int 1);
int T1£10);
double 12#1() { return 10; }
private int Acl£4();
private int n = 10;
y
b, ena decalaracién della clase C11:
el
CT implements 11 {
int 11£1() { weed
void T1f2(int i) {- +--+}
int Cr1€3() (++ + + +}
>
c. yenladeclaracién del objeto e6:
11 c6 = new 110);
5. Identifique los errores de:
a. abstract class ACI {
int acIf1() (. + + +}
void AcIf2(int i) {-- +}
int ac1£3()7Seccién 1.9 Tareas de programacién M53
b. interface C6 extends CaCl {. . - }
donde caci es una clase,
© class CACIAC2 extends ACI, AC2{... }
donde Aci y Acz2 son dos clases abstractas.
d. acl ¢7 = new acl();
donde Aci ¢s una clase abstracta.
6. Qué pasa sila clase Somezn£o en ver de la definicién de equals ( ) de la seccién
1.2.4 utiliza la definicién siguiente de este método:
Public boolean equals(SomeInfo si) {
return n == si.nj
>
TAREAS DE PROGRAMACION
1. Escriba una clase Fract ion que defina la suma, resta, multiplicacién y division de
fracciones. Luego escriba un método para reducir los factores y métodos para intro-
ducir ¢ imprimir fracciones.
2. Escriba una clase Quaternion que defina las cuatro operaciones bisicas de cuater-
niones y las dos operaciones de E/S. Los cuaterniones, segtin los definié en 1843,
William Hamilton y publicé en sus Lectures on Quaternions en 1853, son una exten-
si6n de los niimeros complejos. Los cuaterniones son cuddruplos de los némeros
reales, (aod) = a + bi + 6 + dk, donde 1 = (1,0,0,0),1= (0,1,0,0),j= (0,0,1,0) y k=
(0,0,0,1), y las ecuaciones siguientes sostienen que:
Wk jk=iki=j,jf=-k kj= ii
(a+ bit oj +dk) + (pt git rj+sk)
=(atp)+ (b+ git (ct nj+(dt9k
(a+ bi+ 6 +dk)-(p + qi r+ 5k)
= (ap —bq~ cr ds) + (ag + bp + es~ dri
+ (ar+ ep + dq—bs)j + (as + dp + br~ eq)k.
Utilice estas ecuaciones en la implementacién de una clase de cuaterniones.
3. Escriba un programa para reconstruir un texto a partir de una concordancia de
palabras. Este fue un problema real de reconstruccién de algunos textos inéditos
de los Rollos del Mar Muerto utilizando concordancias, Por ejemplo, en seguida se
presenta el poema de William Wordsworth, Nature and the Poet (La naturaleza y el
poeta), y una concordancia de palabras que corresponden al poema.
So pure the sky, so quiet was the air! (Tan claro el cielo, tan quieto el viento)
So like, so very like, was day to day! (Tan parecido, todos los dias)
‘Whene'er [look'd, thy image still was there; (Siempre que veia, tu imagen seguia alli)
It trembled, but it never pass'd away. (Se estremecia, sin desaparecer)54 M_ Capitulo 1 Programaci6n orientada a objetos utilizando Java
5
La concordancia de 33 palabras ¢s la siguiente:
1:1 so quiet was the *air! tan quieto el *viento!
1:4 but it never pass'd "away. sin "desaparecer
1:4 [t trembled, “but it never ‘se estremecia *sin
1:2 was *day to day! todos “los dias!
1:2.was day to “day! todos “los dias!
3 thy “image still was there; ‘tu “imagen seguia ahis
1:2 so very like, “was day tan parecido, *todos los dias
1:3 thy image stil "was there; ‘tuimagen seguia *ahi
1:3 *Whene'er I look’d, “siempre que veia,
Enesta concordancia, cada palabra se muestra en un contexto de hasta cinco palabras,
ya la palabra referida en cada linea le antecede un asterisco. Para concordancias mas
‘grandes, dos niimeros deben incluirse, uno que corresponda al poema y uno al
iimero de linea donde se encuentran las palabras. Por ejempio, suponiendo que 1 es
el niimero de Nature and the Poet, inea1:4 but it never pass'd “away” significa que la
palabra “away” se encuentra en este poema en la linea 4. Observe que las marcas de
puntuacién se incluyen en el contexto.
Escriba un programa que cargue una concordancia desde un archivo y cree un vector
en el que cada celda se asocie con una linea de la concordancia. Luego, utilizando una
buisqueda binaria, reconstruya cl texto.
Modifique el programa del caso de estudio manteniendo un orden durante la inser-
cién de los registros nuevos en el archivo de datos. Esto requiere que se defina ef
método compareTo() en Personal y en Student para utilizarlos en un método
add() modificado en Database. E método encuentra una posicién apropiada para
un registro d, mueve todos los registros del archivo para hacer espacio para d y es-
cribe d en el archivo. Con la nueva organizacién del archivo de datos, find() y
modi fy () también pueden modificarse. Por ejemplo, £ind( ) detiene la busqueda
secuencial cuando encuentra un registro mayor que el registro buscado (o llega al
final del archivo). Una estrategia mds eficaz puede utilizar la busqueda binaria, estu-
dada en la seccién 2.7.
Escriba un programa que mantenga un orden en el archivo de datos en forma indi-
recta. Utilice un vector de apuntadores de posicién de archivo (obtenidos a través de
getFilepointer()) y mantenga el vector en el orden de clasificacién sin cambiar
el orden de los registros dentro del archivo.
Modifique el programa del caso de estudio para eliminar los registros del archivo de
datos. Defina el método iswu11 ( ) en las clases Personal y Student para determ
nar que un registro es nulo, Defina también el método writeNul1ToF ile( ) en las
dos clases para sobreescribir un registro que se va a eliminar por un registro nulo. Un
registro nulo puede definirse como si tuviera un cardcter no numérico (una lipida)
en la primera posicién del campo SSN. Luego defina el método remove( ) en Data~
base (muy parecido a modi fy () ), que localiza la posiciOn de un registro que se va a
eliminar y lo sobreescribe con el registro nulo, Después de que se termina una sesi6n,
un método Database del destructor purge ( ) debe invocarse con copias de registros
no nulos a un archivo de datos nuevo, elimina el archivo de datos viejo y renombra ef
archivo de datos nuevo con el nombre del archivo de datos viejo,Bidliogratia M55
BB sisuiograria
Programacién orientada a objetos
Cardelli, Luca y Wegner, Peter,”On Understanding Types, Data Abstraction, and Polymorphism’,
Computing Surveys 17 (1983),471-522.
Ege, Raimund K., Programmting in an Object-Oriented Environment, San Diego: Academic
Press, 1992,
Khoshafian, Setrag y Razmik, Abnous, Object Orientation: Concepts, Languages, Databases, User
Interfaces, Nueva York: Wiley, 1995.
Meyer, Bertrand, Object-Oriented Software Construction, Upper Saddie River, Nueva Jersey:
Prentice Hall, 1997.
Java
Dunn, Douglas, Java Rules, Reading, Massachusetts: Addison-Wesley, 2002.
Gittleman, Art, Computing with Java, El Granada, California: Scott/Jones, 2002.
Gosling, James, Joy Bll y Bracha, Silad, The Java Language Specification, Boston: Addison-Wesley,
2000.
Naughton, Patrick y Schildt, Herbert, Java 2: The Complete Reference, Berkeley, California: Os-
borne McGraw-Hill, 1999,
‘Weber, Joe (ed.), Using java 2 Platform, Indianspolis: Quebec, 1999,
‘Weiss, Mark A, Data Structures and Problem Solving Using Java, Reading, Massachusetts:
Addison-Wesley, 2001.Analisis de la
complejidad
COMPLEJIDAD COMPUTACIONAL Y ASINTOTICA
Con frecuencia el mismo problema puede resolverse con algoritmos que difieren en efi-
iencia. Las diferencias entre los algoritmos pueden ser irrelevantes para el proce-
samiento de un pequefio nimero de elementos de datos, pero estas diferencias crecen
con la cantidad de informacién. Para comparar la eficiencia de los algoritmos, Juris
Hartmanis y Richard E. Stearns desarrollaron una medida del grado de dificultad de un
algoritmo llamada complejidad computacional.
La complejidad computacional indica el esfuerzo que se requiere aplicar a un algo-
ritmo 0 cudn costoso resulta. Este costo puede medirse en una variedad de formas, y el
contexto particular determina su significado. Este libro se ocupa de los dos criterios de efi-
ciencia: el tiempo y el espacio. El factor del tiempo por lo general es mas importante que
el factor del espacio, asi que es comiin que las consideraciones de eficiencia se centren en la
cantidad de tiempo transcurrido cuando se procesan datos. Sin embargo, el algoritmo
mis ineficiente que corre en una computadora Cray puede ejecutarse mucho mis répido
que el algoritmo mas eficaz.que corre en una PC, asi que el tiempo de ejecucién depende
siempre del sistema. Por ejemplo, para comparar 100 algoritmos, todos ellos tendrian
{que ejecutarse en la misma maquina. Ademés, los resultados de las pruebas en tiempo de
ejecucién dependen del lenguaje en que se escribe un algoritmo dado, incluso si las prue-
bas se realizan en la misma méquina. Si los programas se compilan, se ejecutan de una
manera mucho més répida que cuando se interpretan. Un programa escrito en C 0 Ada
puede ser 20 veces mds rapido que el mismo programa codificado en BASIC 0 LISP.
Para evaluar la eficiencia de un algoritmo, no deben utilizarse unidades en tiempo
real como los microsegundos y los nanosegundos. En su lugar deben usarse las unidades
logicas que expresan una relacién entre el tamafio n de un archivo un arreglo y la canti-
dad de tiempo t requerida para procesar los datos. Si existe una relacién lineal entre el
tamafo n y el tiempo tes decir, f, = en,— entonces un incremento de datos por un factor
de 5 da como resultado un aumento en el tiempo de ejecucién por el mismo factor; sin, =
5n, entonces t, = 5t,. De modo parecido, si t, = log,n, entonces al duplicar n aumenta t
s6lo una unidad de tiempo. Por lo tanto, sit, = log,(2n), entonces t, =f, + 1.
Una funcién que expresa la relacién entre ry t por lo general es mucho més com-
pleja, y el célculo de una funcién como ésta es importante solamente en lo que atafie a
los grandes cuerpos de datos; cualquier término que no cambie de manera sustancial la
‘magnitud de la funcién debe eliminarse de la funcidn. La funcién resultante s6lo da unaSeccién 2.2 Notacién de ordend M57
medida aproximada de la eficiencia de la funcién original. Sin embargo, esta aproxi-
macién esté lo suficientemente cerca de la original, en especial para una funcién que
procesa cantidades grandes de datos. Esta medida de la eficiencia se lama complejidad
asintética, y se usa cuando no se toman en cuenta ciertos términos de una funcién para
expresar la eficiencia de un algoritmo o cuando se calcula una funcién que ¢s dificil 0
imposible y tinicamente se pueden encontrar aproximaciones. Para ilustrar el primer
caso, considere el ejemplo siguiente:
fn) =n? + 100n + log, + 1.000 (24)
Para los pequefios valores de n, el tltimo término, 1 000, es el mayor. Cuando n es igual
a 10, el segundo término (100n) y el ultimo (1 000) estén en igualdad de condiciones
con respecto a los otros términos, haciendo la misma contribucién al valor de la fun-
cin. Cuando m alcanza el valor de 100, el primero y el segundo términos hacen la
misma contribucién al resultado. Pero cuando n se vuelve mayor que 100, la contribu-
cién del segundo término se vuelve menos significativa. Por consiguiente, para los
valores grandes de n, debido al crecimiento cuadritico del primer término (n°), el valor
de la funcién f depende del valor del primer término, como se demuestra en la figura
2.1. Otros términos pueden ignorarse para n mayores.
FIGURA 2.1 Indice de crecimiento de todos los términos de la funcién
fin) = 1 + 100 + log,n+ 1.000.
* ) ° 1000 log, 1000
Valor Valor. % Valor == -% Valor %
1 1101 1 O1 100 91 0 00
10 2101 100° 4:76 1000 476 ©1005
100 21002 10000476 10000 476 2 «0.001 1000 4.76
1000 1101003 1000000 90.8 100000 91 3 0.0003 1000 009
10.000 101001004 100.000.0009. 1000000 099 4 00 1000 0.001
100.000 10010001005 10000.000000. 99.9 10000000 0.099 5 00 1000 0,00
NOTACION DE ORDEN O
La notacién de uso més comiin para especificar la complejidad asintética —es decir,
para estimar el indice de crecimiento de funciones— es la notacién de orden O intro-
ducida en 1894 por Paul Bachmann. Dadas dos funciones valuadas como positivas fy g,
considere la definicion siguiente:
Definicién 1: f(n) es O(g(7)) si existen los nimeros positivos cy N tales que flrt) $
¢g(n) para todas las nN.
Esta definicién se lee: fes de orden O de g si existe un niimero positivo ¢ tal que fno es
mayor que ¢g para n suficientemente grandes; es decir, para todas las n mayores que58 MW Capitulo 2 Analisis de la complejidad
algdn niimero N. La relaciOn entre fy g puede expresarse al establecer ya sea que g(n) es
un limite superior del valor de f(n) 0 que, ala larga, ferece cuando mucho a la misma
velocidad que g.
El problema con esta definicin es que, primero, establece que deben existirciertas ¢
y N, pero no da ninguna pista de cémo calcular dichas constantes. Segundo, no pone
ninguna restriccién a estos valores y proporciona poca orientacién en situaciones donde
hay muchos candidatos. De hecho, por lo general existe una infinidad de pares de cy N
‘que pueden darse para el mismo par de funciones fy g. Por ejemplo, para
fin) = 20 + 3n+ 1 = O(n") (22)
donde g(n) = n?, los valores candidatos para cy N se muestran en la figura 2.2.
FIGURA 2.2 _Diferentes valores de cy N para la funcién f(n) = 2n? + 3n+1
acuerdo con la definicién de orden O.
baci tesa sia eas
N i 2 H 4 5 : > =
Obtenemos estos valores al resolver la desigualdad:
20 +3n+1Sen*
ode manera equivalente
3,4
2+ + 75Se
para diferentes n. La primera desigualdad da como resultado la sustitucién de la fun-
cién cuadratica de la ecuacién 2.2 para f(n) en la definicion de la notacién de orden O y
de n? para g(n). Dado que se trata de una desigualdad con dos pares de constantes ¢ y N
desconocidas y distintas para la misma funcién g(= n*) puede determinarse. Para elegir
las constantes ¢ y N mayores, debe determinarse para cul N un cierto término en f se
vuelve mayor y permanece como el mayor. En la ecuacién 2.2, los tinicos candidatos
para el término mayor son 2n* y 31; estos términos pueden compararse usando la de-
sigualdad 2n? > 3n que se cumple para n > 1. Por lo tanto, N = 2 y c2 32, como se indica
ena figura 2.2.
Cua es el significado prictico de los pares de constantes que se acaban de listar?
‘Todos ellos estan relacionados con la misma funcién g(n) = 1 y con la misma f(n). Para
uuna ¢ fija, puede identificarse un mimero infinito de pares de cy N. El punto es que fy g
crecen al mismo ritmo. La definicién establece, no obstante, que g casi siempre es mayor
© igual que fsi esté multiplicado por una constante c. "Casi siempre" significa para todas
las m que no son menores que una constante N. La esencia del problema es que el valor de
cdepende de cual N se elige, y viceversa. Por ejemplo, si se elige 1 como el valor de N—es
decir, si g se multiplica por ¢ de modo que cg(n) no sea menor que f inmediatamente—
entonces c debe ser igual o mayor que 6. Si cg(7) es mayor 0 igual que f(1) a partir den =
2, entonces es suficiente que c sea igual a 3.75. La constante c debe ser cuando menos 3Seccién 2.3 Propiedades de la notacién de ordend Mm 59
si qg(m) no es menor que f(n) a partir de n = 3. La figura 2.3 muestra las grificas de las
funciones fy g. La funcién g se traz6 con diferentes coeficientes c. Ademis, N siempre es
tun punto donde las funciones cg(n) y fs intersecan.
FIGURA 2.3 Comparacidn de las funciones para distintos valores de c y N tomados de la figura 2.2.
La imprecisidn inherente a la notacién de orden O va incluso mis lejos, debido a que
pueden existir muchisimas funciones gms para una funcién fdada. Por ejemplo, la fde la
ecuacién 2.2 esel orden O no sélo de m, sino también dem n',...,nt,.... para cualquier
22, Para evitar este conflicto se elige la funcién g més pequefia, n* en este caso.
La aproximacién de la funcién f puede refinarse utilizando la notacién de orden ©
para la parte de la ecuacién que contiene informacién no relevante. Por ejemplo, en la
ecuacién 2.1, la contribucién del tercer y ultimo términos al valor de la funcién puede
omitirse (véase la ecuacién 2.3).
fin) =n? + 100n + O(log.) (23)
De modo parecido, la funcién fen la ecuacién 2.2 puede aproximarse como
J(n) = 20? + O(n) (24)
PROPIEDADES DE LA NOTACION DE ORDEN O
La notacién de orden O tiene algunas propiedades ttiles que pueden utilizarse cuando
se estima la eficacia de los algoritmos.
Hecho 1. (transitividad) Si f(n) es O(g(r)) y g(m) es O(h(n)), entonces f(n) O(h(n)).
(Esto puede expresarse de otra manera como O(O(g(1t))) es O(g(n)).)60 WM Capitulo 2 Analisis de la complejidad
Prueba: De acuerdo con la definici6n, f(n) es O(g(n)) si existen ntimeros positivos ¢, y
N, tales que f(n) < ¢,¢(n) para todas las n 2-N,,y g(1) es O(h(n)) siexisten ntimeros
positivos c, y N, tales que g(1) S c,)t(n) para todas las n 2 N,,. Por consiguiente, ¢,¢(1) $
¢,¢:h(n) para n 2 N donde N es el mayor entre N, y N;-Sitomamos ¢= ¢,¢, entonces
f(n) S$ ch(n) para n2N, lo cual significa que fes O(h(n).
Hecho 2. Si f(n) es O(bi(n)) y g(n) es O(h(7)), entonces f(n) + g(n) es OCh(n)).
Prueba: Después de establecer que ces igual ac, + cy, f(n) + s(n) Sch(n).
Hecho 3. La funcién ant es Ont).
Prueba: Para que la desigualdad an < cnt se cumpla, es necesario que c2 a.
Hecho 4. La funcién n* es O(n'*’) para cualquier j positiva.
1
Prueba: La afirmacién sostiene que si c= N
A partir de estos hechos se deduce que todo polinomio es del orden O de n elevado
ala mayor potencia o
fin) =a +a nh +++ ayn-ta, es On)
‘También es evidente que en el caso de los polinomios, fin) es O(n") para cualquier j
positiva.
Una de las funciones mas importantes en la evaluaci6n de la eficiencia de los algo-
ritmos es la funcidn logaritmica. De hecho, si puede decirse que la complejidad de un
algoritmo es de orden de funcién logaritmica, puede considerarse que el algoritmo es
muy bueno. Existe un nimero infinito de funciones que pueden considerarse mejores
que la funcién logaritmica, entre las que solo unas cuantas, como O(lg Ig n) 0 O(1),
son relevantes en la prictica. Anteriormente mostramos un hecho importante sobre las
funciones logaritmicas, expongimoslo sin prueba:
Hecho $. Si f(n) = cg(), entonces f(n) es O(g(n)).
Hecho 6. La funcién log, 1 es Otlog, n) para cualesquier nuimeros positivos a y b# 1.
Esta correspondencia se mantiene entre las funciones logaritmicas. El hecho 6 establece
que a pesar de sus bases, las funciones logaritmicas son de orden O entre si; es decir,
todas estas funciones tienen el mismo indice de crecimiento.
Prueba: Sea log, n= xy log, n = y, tenemos, por la definicién de logaritmo, que a = 1
yW=n
Al tomar In de los dos lados obtenemos
xlna=Inn y yinb=Inn
Porlo tanto
xlna=ylnb,
In alog, n=In blog, n,
Jog, n= Hb og, n= clog,Seccién 2.4 NotacionesQy@ mM 61
loccual demuestra que log, ny log, n son miltiplos uno de otro. Por el hecho 5, log, m es
O(log, n).
‘Como la base del logaritmo es irrelevante en el contexto de la notacién de orden O,
siempre se utiliza s6lo una base y el hecho 6 puede escribirse como
Hecho7. Ellog, n es O(Ig n) para cualquier ntimero positivo a% 1, donde Ig n = log, n.
Notaciones Q y @
La notacién de orden O se refiere alos limites superiores de las funciones. Hay una defi-
nici6n simétrica para un limite inferior en la definicién de orden Q:
Definicién 2: La funcién f(n) es Q(g(1)) siexisten mimeros positivos cy N tales que
f(t) 2 eg(n) para todas las nN.
Esta definicién se lee: fes de orden Q (omega) de ¢ si existe un muimero positive ctal que
{Fes cuando menos igual a ¢g para casi todas las n, En otras palabras, eg(n) es un limite
inferior en el tamafio de f(n), 0, a la larga, fcrece cuando menos al ritmo de g.
La tinica diferencia entre esta notacién y la definicién de orden O es la direccién de
desigualdad; una definicién puede convertirse en la otra al reemplazar el signo “2” con
“<7 Existe una interconexién entre estas dos notaciones expresada por la equivalencia
f(n) es Q¢g(n)) si y sdlo si gm) es O(f(n))
La notacién Q padece el mismo problema de profusién que la notacién de orden,
©: Existe un niimero ilimitado de opciones para las constantes cy N. Para la ecuacion
2.2, estamos buscando una c para la cual 2n? + 3n + 1 2 cn’, lo cual es verdadero para
cualquier n2 0, sic 2, donde 2 es el limite para cen la figura 2.2. Ademés, si fes Q de g
y hg, entonces fes Q de h; es decir, i para f podemos encontrar una g tal que f sea Q
de g, entonces podemos encontrar un ntimero infinito. Por ejemplo, la funcién 2.2 es de
orden Q de 1 pero también de 1, n,n", n'4,...,y también de Ig n, Ig Ig m,.-.5¥
de muchas otras funciones. Para propésitos pricticos, dnicamente las 2 mas cercanas
resultan interesantes (por ejemplo, los limites inferiores mayores). Esta restriccién se
hace de manera implicita cada vez que elegimos una Q de una funcion f.
Existe un ntimero infinito de limites inferiores posibles para la funcién f; es decir,
hay un conjunto infinito de ¢ tales que f(n) es £2(g(n)) asi como un numero infinito de
imites superiores posibles de f. Esto puede ser un tanto inquietante, ast que restringi-
‘mos nuestra atencidn a los limites superiores mas pequefios y a los limites inferiores
més grandes. Observe que hay un lugar comin para las notaciones de orden O y de
orden &2 indicadas por las igualdades en las definiciones de estas notaciones: el orden O
se define en términos de “S” y el orden @ en términos de “2”; “=” se incluye en las dos
desigualdades. Esto sugiere una forma de restringir los conjuntos de limites inferiores y
superiores posibles. Esta restriccién puede lograrse por medio de la siguiente definicion
de la notacién @ (teta):
Definicion 3: f(n) es (¢(n)) si existen nimeros positives ¢,¢, y N tales que ¢,g(n) S
J (n) Scxg(n) para todas las n2 N.62M Capitulo 2 Andlisis de la complejidad
Esta definicin se lee: ftiene un orden de magnitud g, festé en el orden de g, 0 las dos
funciones a la larga crecen a la misma velocidad. Vemos que f(n) es @(g(1)) si f(n) es
Otg4n)) yfln) es gn).
La dnica funcion que acabamos de listar que es tanto de orden © como de orden
de la funcién 2.2 es n°, Sin embargo, no es la tinica opci6n, y todavia existe un nimero
infinito de opciones ya que las funciones 2n?, 3n?, 4nz, ... también son @ de la funcién
2.2. Pero es muy obvio que se elegird la ms simple, 1,
Cuando aplique cualquiera de estas notaciones (orden O, 2. y @), no olvide que son
aproximaciones que ocultan algunos detalles que en muchos casos pueden considerarse
importantes.
PROBLEMAS POSIBLES
‘Todas las notaciones sirven para el propésito de comparar la eficiencia de varios algorit-
mos disefados para resolver el mismo problema. No obstante, si sélo se utiliza la de
orden O para representar la eficiencia de los algoritmos, entonces algunos de ellos
pueden rechazarse de manera prematura, El problema es que en la definicién de la
notacion de orden O, f se considera O(g(n)) si la desigualdad f(n) < cg(n) se cumple a
Ja larga para todos los ntimeros naturales con unas cuantas excepciones. El nimero de n
‘que Viola esta desigualdad siempre es finito. Basta con cumplir la condicion de la defini-
ci6n, Como indica la figura 2.2, este niimero de excepciones puede reducirse al elegir una
¢ lo suficientemente grande. Sin embargo, esto puede tener muy poca trascendencia
prictica sila constante c en f(n) $ eg(n) es demasiado grande, digamos 10*, aun cuando
la funcién gen si misma parece ser prometedora.
Considere que existen dos algoritmos para resolver cierto problema y suponga que
el niimero de operaciones requerido por estos algoritmos es 10% y 10n?. La primera
funcién es O(n) y la segunda es O(n). Utilizando solamente la informacién del orden
, el segundo algoritmo se rechaza porque el n~imero de pasos aumenta demasiado
ripido. Es cierto pero, de nuevo, ala larga, debido a que para n $107, que es 10 millones,
el segundo algoritmo realiza menos operaciones que el primero. Aun cuando 10 mi-
ones no ¢s un ntimero insblito de elementos a procesar por un algoritmo, en muchos
casos el ntimero es mucho menor, por lo que es preferible el segundo algoritmo,
Por estas razones, puede resultar atractivo utilizar una notacién més que incluye
constantes muy grandes por razones précticas. Udi Manber propone una notacién de
doble © (00) para denotar estas funciones: fes OO(¢(n)) si es Of¢(n)) y la constante €
es demasiado grande para tener una relevancia préctica. Por lo tanto, 10° es OO(n). Sin
embargo, la definicién “demasiado larga” depende de la aplicacién particular.
EJEMPLOS DE COMPLEJIDADES
Los algoritmos pueden clasificarse segiin las complejidades de tiempo y espacio, y a este
respecto pueden distinguirse varias clases de algoritmos, como lo ilustra la figura 2.4. Su
crecimiento también se muestra en la figura 2.5. Por ejemplo, un algoritmo se llama
constante si su tiempo de ejecucién sigue siendo el mismo para cualquier mimero de
elementos; se lama cuadratico si su tiempo de ejecucidn es O(n"). Para cada una de estas,Seccién 2.6 Ejemplos de complejidades Ml 63
FIGURA 2.4 Clases de algoritmos y sus tiempos de ejecucién en una computadora que realiza un
millon de operaciones por segundo (1 s = 108 ys = 10% ms).
Clase —-—Complejidad~—_Nimero de operaciones y tiempo de ejecucién (1 instr/ys)
a 0 w w
constante —O(1) 1 1s 1 Ips 1 Ips
logaritmica Ollgn) 3.32, 3s 664 7s 997 10 ps
lineal O(n) 10 10 ps 10 100 ys 10° Ims
O(nign) —Olngn) 33.233 ps 664 664s 9970 10ms
cuadritica O(n?) 10 100 ps Jo! 10ms 10" Is
cubica ln?) 10 ims ws 1 167 min
‘exponencial O(2") 1024 10ms 10" 3.17710 anos_10%"
a 0 w we
constante O(1) 1 Ips 1 Ips 1 1s
logaritmica O(lgn) 13.313 ps 166 7s 1993 20s
lineal On) 108 10ms 1 Os 10° Is
O(nlgn) — Olnign) 133 10" 133 ms 166 *10* 16s 199.34 10° 205
cuadritica O(n?) 10" L7 min 10 16.7 min 10" 11.6 dias
cbica ——Ofn?) 10% 1.6dias_—— 1031.7 aftos 10" 31 709 anos
‘exponencial 0(2*) 19" 19 19”
FIGURA 2.5 Funciones tipicas aplicadas en las estimaciones de orden O.64 M Capitulo 2 Andlisis de la complejidad
return -
77 Ya celda ocupada por 1a lave;
// falla: 1a llave no est en el arreglo;
Si key esti en medio del arreglo, el ciclo se ejecuta s6lo una vez. ;Cudntas veces se
ejecuta el ciclo si key no esti en el arreglo? El algoritmo primero considera el arreglo
entero de tamafio 1, luego una de sus mitades de tamaito después una de las mitades
de esta mitad, de tamano —, y asi sucesivamente hasta que el arreglo mide 1. Por lo
tanto, tenemos la secuencia n, %, +» ¥ queremos saber el valor de m. Pero el til-
timo término de esta secuencia es igual a 1, a partir de lo cual tenemos m = Ig n. Ast
que el hecho de que k no esté eft el arreglo puede determinarse después de Ig m itera-
iones del ciclo.
EL MEJOR CASO, EL PEOR CASO Y EL CASO PROMEDIO
Los iiltimos dos ejemplos de la seccién anterior indican la necesidad de distinguir
cuando menos tres casos para los que debe determinarse la eficiencia de los algoritmos.
El peor caso es cuando un algoritmo requiere un ntimero maximo de pasos, y el mejor
caso es cuando se requiere el menor miimero de pasos. El caso promedio cae dentro de
estos dos extremos. En los casos simples, la complejidad promedio se establece al con-
siderar entradas posibles a un algoritmo, determinando el niimero de pasos realizados
por el algoritmo para cada entrada, sumando el mimero de pasos para todas las
entradas y dividiendo entre el niimero de entradas. Esta definicién, no obstante, supone
{que la probabilidad de la ocurrencia de cada entrada es la misma, lo cual no siempre es
1 caso, Para considerar la probabilidad de manera explicita, la complejidad promedio
se define como el nimero de pasos ejecutados cuando cada entrada se pondera por
medio de la probabilidad de la ocurrencia de esta entrada, 0
Coon = &,PCinput,)steps(input,)
Esta es la definiciGn del valor esperado, que asume la determinacién de todas las posi-
bilidades y que la distribucién de la probabilidad es conocida, lo que simplemente de-
termina una probabilidad de ocurrencia de cada entrada, p(input). La funcién deSeccion 2.8 E! mejor case, el peor casoy el caso promedio @ 67
probabilidad p satisface dos condiciones: nunca es negativa, p(input,) 2 0y todas as pro-
babilidades suman hasta 1, ¥,p(input,) = 1.
Como ejemplo, considere realizar una bdsqueda en secuencia en un arreglo sin or-
denar para encontrar un mimero. El mejor caso es cuando el nfimero se encuentra en la
primera celda. E1 peor es cuando el niimero esta en la iltima celda 0 no esta en el arreglo.
Cuando esto ocurre, todas las celdas se marcan para determinar este hecho. ;Y el caso
promedio? Podemos suponer que existe una oportunidad igual para el ntimero que se
encontrar en cualquier celda del arreglo; es deci, a distribucion dela probabilidad es
uniforme. En este caso, hay una probabilidad igual a 4 de que el mimero esté en la
primera celda, una probabilidad igual a + de que esté eit la segunda celda, ...,y final-
mente, una probabiidad igual a de que été en la htima, la ceda n sto significa que la
probabidad de encontrar el nimero después de un intento es igual a la probabilidad
de encontrarlo en dos intentos es igual a ,...,yla probabilidad de encontrarlo en n in-
tentos es igual a! Por consiguiente, odefnos promeiar todos estos niimeros de intentos
posibles con el niimero de posibilidades y concluir que en promedio se requieren
L+2t-4n
”
pasos para encontrar un niimero. Pero si las probabilidades difieren, entonces el caso
promedio arroja un resultado diferente. Por ejemplo, sila probabilidad de encontrar un
numero en la primera cela es igual a4 la probabiidad de encontraro en In segunda
celda es igual a +, yla probabilidad de localizarlo en cualquiera de las celdas restantes es
Ja misma y es igual a
n(n+1)-6 | nt3
+ ena = tg
pasos para encontrar un mimero, lo cual ¢s aproximadamente cuatro veces mejor que el
=! encontrado antes para la distribucién uniforme, Observe que las probabilidades de te-
ner acceso a una celda en particular no tienen ningiin impacto en el peor caso ni en el mejor
de los casos.
La complejidad de los tres casos fue relativamente facil de determinar para la biis-
queda secuencial, pero por lo general no es tan sencilla. En particular, la complejidad del
caso promedio puede plantear problemas computacionales dificiles. Sielcilculo es muy
complejo, se utilizan las aproximaciones y ahi es donde resultan mas atiles las notacio-
nes del orden 0, Qy @.
Como ejemplo, considere el caso promedio de la bisqueda binaria, Suponga que el ta
mafio del arreglo es una potencia de 2 y que un niimero que se va a buscar tiene una opor-
tunidad igual de estar en cualquiera de las celdas del arreglo. La busqueda binaria puede
localizarlo ya sea después de un intento en la mitad del arreglo; después de dos intentos en
la mitad de la primera mitad del arreglo, o de dos intentos en la segunda mitad del mismo;
después de tres intentos en la mitad del primer cuarto del arreglo,.. después de tres inten-
tos en la mitad del cuarto del arreglo; después de cuatro intentos en la mitad del primer88 WM Capitulo 2 Andlisis de la complejidad
octavo del arreglo, ode cuatro intentos en la mitad de la octava parte de un octavo del arre-
glo,o.... después de intentar con lg nen la primera celda, o después de probar con Ig nen
Ja tercera celda....o, finalmente, después de intentar con Ig n en la iltima celda. Es decir, el
niimero de todos los intentos posibles es igual a
}
1.142-244-348. seoatign= S2G +0
2 io
Jo cual debe dividirse entre + para determinar la complejidad del caso promedio. ;A
qué es igual esta suma? Sabemos que esté entre I (el resultado del mejor caso) y Ig
(el peor caso) segiin se determiné en la seccién anterior. Pero seth mis cerca del mejor
caso —digamos, lg lg n— 0 del peor caso —por ejemplo, *, 0 lg 4? La suma no se
presta a si misma para una simple conversién hacia una forma cerrada; por lo tanto,
debe usarse su estimacién. Nuestra conjetura és que la suma no es menor que la suma de
potencias de 2 en el intervalo especificado multiplicada por una mitad de lg m, ¢s deci
-Srosnate So =
La raz6n para esta opci6n es que s; ¢s una serie de potencias multiplicada por un factor
constante, y por lo tanto, puede presentarse en forma cerrada muy ficilmente,en concreto,
et
aol SY tery 4 22h | _ Weng _y
2a? zy 2
Jo cual es (11 Ig n). Debido a que s, es el limite inferior para la suma s, bajo escrutinio
—es decir, s, €5 Q(s,)— entonces * también es el limite inferior de la complejidad bus-
cada del caso promedio *t —es decir, #1 = Q().Como * es Q(lg n), entonces debe ser
42, Como Ign es una evaluacién de la complejidad del peot caso, la complejidad del caso
Promedio es igual a O(lg n).
‘Atin queda un problema por resolver: js, 2 5,? Para determinar esto, conjeturamos
que la suma de cada par de términos posicionados en forma simétrica con respecto al
centro de la suma s, no es menor que la suma de los términos correspondientes de s,
es decir,
29-1428" Ign 220 BR yak! Co
2-242 (lgn= 122! BE gg et
Ign,
G+ 42 ign —j)22 ‘et geen) A
donde j = 8" — 1, La tiltima desigualdad, la cual representa a todas las demas desigual-
dades, se transforma aSeccion 2.9 Complejidad amortizada M69
en ( i -i}pa(t2--)
yluegoa
den
get yh
(2.5)
‘Todas estas transformaciones estin permitidas debido a que los términos que se
movieron de un lado de la desigualdad conjeturada al otro, son no negativos y por
Jo tanto no cambian la direccion de la desigualdad. jLa desigualdad es verdadera?
Debido a que j = le ~ 1,2#"-'-2/22,y el lado derecho de la desigualdad (2.5) siempre
es menor que 1, la desigualdad conjeturada es verdadera.
Esto concluye nuestra investigacion sobre el caso promedio para una biisqueda binaria.
El algoritmo es relativamente sencillo, pero el proceso de encontrar la complejidad para el
caso promedio es bastante engorroso, incluso para distribuciones uniformes de la probabili-
dad, Para algoritmos més complejos, estos cilculos implican un reto mucho mayor.
COMPLEJIDAD AMORTIZADA
En muchas situaciones, las estructuras de datos estan sujetas @ una secuencia de opera-
ciones en vez de estarlo a una sola. En esta secuencia, una operacién posiblemente realiza
ciertas modificaciones que tienen un impacto en el tiempo de ejecucion de la siguiente
operacién dentro de la secuencia, Una manera de evaluar el peor caso es afadir las
cficiencias para cada operacién. Pero esto puede resultar en un limite excesivamente
grande ¢ irreal en el tiempo de ejecucién actual. Para ser més realistas, se utiliza el andli-
sis amortizado con el fin de encontrar la complejidad promedio de una secuencia
de operaciones en el peor caso. Al analizar las secuencias de operaciones en vez de
‘operaciones aisladas, el anslisis amortizado toma en cuenta la interdependencia entre
las operaciones y sus resultados. Por ejemplo, si un arreglo se ordena y s6lo se afiaden
unos cuantos elementos nuevos, entonces la reordenacién de este arreglo debe ser
mucho mas répida que su ordenacién la primera ver debido a que, después de las
nuevas adiciones, el arreglo esta casi ordenado. Por lo tanto, debe ser mas répido poner
todos los elementos en perfecto orden que en un arreglo completamente desorganizado.
Sin tomar esta correlacién en cuenta, el tiempo de ejecucién de las dos operaciones de
‘ordenacién puede considerarse como el doble de la eficiencia del peor caso. El anslisis
amortizado, por otra parte, decide que la segunda ordenacién casi no se aplica en la
situacién del peor caso, asi que la complejidad combinada de las dos operaciones de
“ordenacién es mucho menor que el doble de la complejidad del peor caso. En consecuen-
, (cost(op,)+ potential( ds) potential(ds, ,))
i
= > (cost(op,)+ potential(ds,) — potential(ds,))Seccién 2.10 Completitud NP mM 73
En la mayoria de los casos, la funcién potencial es cero inicialmente y siempre ¢s no
negativa de modo que el tiempo amortizado es un limite superior del tiempo real. Esta
forma de costo amortizado se utiliza mas adelante en el libro.
El costo amortizado de incluir nuevos elementos en un vector ahora puede expre-
satse en términos de la funcién potencial definida como
0 si size, = capacity, (el véctor est lleno)
potential( vector,)=
2size, capacity, en caso contrario
Para ver que la funcién trabaja como se pretende, considere tres casos. El primer caso es
cuando una insercién barata va después de una insercién barata (el vector no se extiende
justo antes de la insercién actual y no se extiende como consecuencia de a insercién actual) y
amCost( push) 3
1 + 2size,, + 2—capacity, , — 2size, , + capaci
‘como la capacidad no cambia, size, = size, , + 1,y el costo actual es igual a 1. Para una in-
sercién cara después de una insercién barata,
amCost{push,()) =
ize, ,+2+0—2size, , + capacity,
debido a que size, , + 1 = capacity, , y el costo actual es igual a size, + 1 = size, + 2,10
cual ¢s el costo de copiar los elementos del vector y aftadir el nuevo elemento. Para una
insercién barata que va después de una insercién cara,
=3
‘amCost(push,()) = 1+ 2size,— capacity, —
dado que 2(size,~ 1) = capacity, y el costo actual es igual a 1. Observe el cuarto caso, la
insercién cara que va después de la insercién cara, ocurre slo dos veces, cuando la capaci-
dad cambia de cero a uno y de uno a cero. En ambos casos, el costo amortizado es igual a 3.
CompLeTiTup NP
Un algoritmo determinista es una secuencia de pasos definida (determinada) de manera
exclusiva para una entrada en particular; es decir, dada una entrada y un paso durante la
ejecucién del algoritmo, s6lo hay una manera de determinar el siguiente paso que el al-
goritmo puede dar, Un algoritmo no determinista es un algoritmo que puede utilizar
tuna operacién especial que realiza un célculo cuando debe tomarse una decision. Con-
sidere la versi6n no determinista de la biisqueda binaria.
Si tratamos de localizar el mimeo k en un arreglo de nuimeros sin ordenar, en-
tonces el algoritmo primero accede al elemento medio m del arreglo. Si m = k, entonces
el algoritmo regresa a la posicién de m; de lo contrario, el algoritmo hace un célculo
respecto a qué camino debe seguir: ala izquierda de mo a su derecha. Una decisién pa-
recida se toma en cada etapa: si el ntimero k no se localiza, continia la biisqueda en una
de las dos mitades del subarreglo que se estd examinando actualmente. Es facil ver que
un célculo como éste nos puede llevar por un mal camino con mucha facilidad, asi
que necesitamos dotar a la maquina con la capacidad de hacer célculos correctos. Sin
embargo, una implementacién de este algoritmo no determinista tendria que probar, en
el peor de los casos, todas las posibilidades. Una manera de hacerlo es al exigir que la
decision en cada iteracién sea en realidad ésta: si m # k, entonces va tanto a la derecha
como a la izquierda de m, De esta manera, se crea un érbol que representa las decisiones
tomadas por el algoritmo (Johnson & Papadimitriou, pig. 53). El algoritmo resuelve ela
You have either reached 2 page thts unevalale fer vowing or reached your ievina tit for his
book.Seccién 2.10 Completitud NP Ml 75
El proceso de reduccién utiliza un problema NP-completo para mostrar que otro
problema también es NP-completo. Sin embargo, debe haber cuando menos un pro-
blema NP-completo probado directamente por otros medios distintos a la reduccién
para hacer posible el proceso de reduccién. Un problema que Stephen Cook demostré
que entraba en esa categoria es el problema dela satisfacibilidad.,
El problema de la satisfacibilidad se refiere a expresiones booleanas en forma nor-
mal conjuntiva (CNF: conjunctive normal form). Una expresién esté en CNF si es una
conjuncién de alternativas donde cada alternativa implica variables booleanas y sus ne-
gaciones, y cada variable es ya sea verdadera o falsa, Por ejemplo,
@vyvaAwyrvayvaAGwyoy)
esti en CNF. Una expresion booleana es satisfacible si existe un argumento de valores ver-
daderos y falsos que interpreta toda la expresion como verdadera. Por ejemplo, nuestra ex-
presién es satisfacible para x= falso, y= falso y z= verdadero. El problema de satifacibilidad
consiste en determinar si una expresién booleana es satisfacible (no deben darse asigna-
ciones de valor). El problema es NP, debido a que las asignaciones pueden caleularse y des-
pués ala expresién se le pueden aplicar pruebas de satisfacibilidad en tiempo polinomial.
‘Cook demuestra que el problema de satisfacibilidad es NP-completo al utilizar un con-
cepto teGrico de la maquina de Turing que puede realizar decisiones no deterministas (hacer
‘buenos cficulos). Las operaciones de esa maquina luego se describen en términos de las
expresiones booleanas, y se demuestra que la expresién es satisfacible si y s6lo sila maquina
de Turing termina para una entrada en particular (para la prueba, véase el apéndice B).
Para ilustrar el proceso de reduccién, considere el problema de satisfacibilidad de tres
variables, el cual se presenta cuando cada alternativa en una expresién booleana en CNF
incluye s6lo tres variables distintas. Afirmamos que el problema es NP-completo. El
problema es NP, debido a que una asignacidn calculada de valores verdaderos para las
variables en una expresién booleana puede verificarse en tiempo polinomial. Mostramos
que el problema de satisfacibilidad de tres variables es NP-completo al reducirlo a un
problema de satisfacibilidad. El proceso de reduccién implica mostrar que una alterna-
tiva con cualquier ntimero de variables booleanas puede convertirse en una conjuncién
de alternativas, cada alternativa con tres variables booleanas tinicamente. Esto se hace al
introducir nuevas variables. Considere una alternativa
A=(,VP2V VP
para k2 4 donde p, € {x, —x,}. Con las nuevas variables y,,. .
A= PLVPV IDA PSV “1 VIIAPV “9 IIA
(2 V 4 V Yes) A Par V PV Yea)
Sila alternativa A ¢s satisfacible, entonces cuando menos un término p, es verdadero, ast
‘que los valores de las y, pueden elegirse de modo que A’ sea verdadera: sip, es verdadera,
‘entonces establecemos y, como verdaderas y las y;,,,..., j.5 restantes como
falsas. A la inversa, si A’ es satisfacible, entonces cuando menos una p, debe ser ver-
dadera, ya que si todas las p, son falsas, entonces la expresion
A’= (false \ false \/ y,) (false > y, \V ¥,) /\ (false \V my, \V¥5) A+
J\ (false \V false \/ > y, ,)
tiene el mismo valor de verdad que la expresion
MACAVIDAGY VIDA. ACK)
la cual no puede ser verdadera para ninguna eleccién de valores para y,, por lo tanto no
es satisfacible.
¥,stransformamos A enHW Capitulo 2 Analisis de 1a complejidad
EJERCICIOS
Explique el significado de las expresiones siguientes:
a. fines O(1).
b. fimes@cn).
& fln)esn™®,
Suponiendo que fn) es O(g,(n)) y fla) €s Olg,(n)), demuestre la instrucciones si-
guientes:
a. filn) + flr) es Olmaxtg,(n).g,(m))).
b. Siun ndmero k puede determinarse como aquel para todas las 1 > k, g,(r) Sg,(m),
entonces O(¢,(1)) + O(g,(n)) ¢s O(g,(1))..
filed)" fn) es O(g, (1) * g,(n)) (regla del producto).
4. Oleg(n)) es Ofg(n)).
€. cesO(l),
._Demuestre las instrucciones siguientes:
©"... es Or?) y de manera més general, D%,_, # es O(n").
ant ig n es On) pero an'/ig n no es O(n").
all + nignes O(n").
|. 2" es O(n!) yn! noes O(2"),
2m es O(2").
2 no es O(2").
g 2¥ Hes O(n").
meen ee
Haga las mismas suposiciones que en el ejercicio 2 y, por medio de contraejemplos,
refute las instrucciones siguientes:
a filn) fn) es Og (n) -g,(m)).
be filmfiln) es Olg,(m/g,(m))-
Encuentre las funcionesf,y f, tales que tanto f,(n) como f,(n) sean O(g(n), pero fi(n)
noes O(f,).
iEscierto que
a sif(n) es O(g(n)),entonces 2%” es (26)?
b. fl) + g(n) es @(min(fin) g(n)))?
ce Bes O(2")?
El algoritmo presentado en este capitulo para hallar la longitud del subarreglo més
grande con los niimeros en orden creciente es ineficiente, debido a que no hay necesidad
de continuar buscando otro arreglo sila longitud ya encontrada es mayor que la longitud
del subarreglo que se va a analizar. Por lo tanto, si todo el arreglo ya esti en orden,
podemos suspender la biisqueda de inmediato, convirtiendo el peor caso en el mejor
aso, El cambio necesario radica en el ciclo externo, el cual ahora tiene una prueba més:Seccion 2.11 Ejercicios M77
for (i= 0, length = 1; i < nel 6& length < nesi; i++)
4Cusl es el peor caso ahora? La ineficiencia del peor caso sigue siendo O(r)?
8 Encuentrela complejidad de la funcién utilizada para encontrar el k-ésimo entero més
pequefio en un arreglo de enteros sin ordenar
int selectkth(int a(], int k, int n) {
int i, J, mini, tmp;
for (i= 0; i < ky itt) {
mini =
for (j = itl; 3 < ny j++)
if (a{j}
9 Determine la complejidad de las siguientes implementaciones de algoritmos para
sumar, multiplicar y transponer matrices nx m:
for (i = 0; i a
y | \
becera final
A
eli
(a)
(b)
al
cabecera final
. Sees
Los cuatro pasos se ejecutan por el método addToHead ( ) (figura 3.2). El método
ejecuta los primeros tres pasos de manera indirecta al llamar al constructor Node
(e1, head). El iltimo paso se ejecuta directamente en el método al asignar la direccién
(referencia) del nodo creado recientemente a head.1
3
Seccién 3.1 Listas ligadas simples Ml 87
EI método addtotiead( ) seftala un caso especial, en concreto, la insercién de un
nodo nuevo en una lista ligada vacia. En una lista ligada vacia, tanto head como tail son
nulos; por lo tanto, los dos se vuelven referencias al tinico nodo de la lista nueva. Cuando
se hace una insercién en una lista no vacia,s6lo se necesita actualizar a head.
El proceso de aftadir un nodo nuevo al final dela lista consta de cinco pasos.
Se crea un nodo vacio (figura 3.5a).
El campo info del nodo se inicializa en un entero el (figura 3.5b).
Debido a que el nodo se incluye al final de la lista, el campo next se establece como
nulo (figura 3.5¢).
El nodo ahora se incluye en la lista al convertir el campo next del iltimo nodo de la
lista en una referencia al nodo recién creado (figura 3.54),
El nodo nuevo va después de todos los nodos de a lista, pero este hecho debe reflejarse
en el valor de tai, que ahora se vuelve la referencia al nodo nuevo (figura 3.5e).
FIGURA 3.5
Insercién de un nodo nuevo al final de una lista ligada simple.
cabecera final
Sy |
@ 1
cabecera final
Os ones | 3 | PA a 10.
‘cabecera. final
SCs 4S AG 10
© ; 7
cabecera
@
cabgoera
© “t34
Todos estos pasos se ejecutan en la clusula del método addtorail() (figura
3.2). La cliusula eise de este método se ejecuta s6lo si la lista ligada esta vacia. Si este
caso no se incluyera, el programa se colapsaria porque en la cldusula i¢ hacemos una
asignacién al campo next del nodo referido por tail. En el caso de una lista ligada
vacfa, es una referencia a un campo inexistente de un nodo inexistente, lo cual conduce
a incrementar Nul 1PointerException.88 M Capitulo 3 Listas ligadas
El proceso de insertar un nodo nuevo al principio de una lista es muy parecido al
proceso de insertar un nodo al final de la lista. Esto se debe a que la implementacién de
IntSLList utiliza dos campos de referencia: head y tail. Por esta raz6n, tanto
.d() como addToTail() pueden ejecutarse en el tiempo constante O(1);
es decir, sin importar cual sea el ntimero de nodos en la lista, el ntimero de operaciones
realizadas por estos dos métodos no excede un nimero constante ¢ Observe que
debido a que la referencia head nos permite tener acceso a una lista ligada, la referencia
‘tail no es indispensable; su tinica funcién es tener acceso inmediato al tiltimo nodo
de la lista. Con este acceso, puede afiadirse un nodo nuevo fécilmente al final de la lista.
Pero como lo ilustra una implementacién de lista ligada de stList en la figura 3.9
posteriormente en este capitulo, la referencia tail no tiene que utilizarse. En este
caso, afiadir un nodo nuevo al final de la lista es més complicado debido a que primero
tenemos que llegar al diltimo nodo con el fin de aftadirle un nodo nuevo. Esto implica
explorar la lista y requiere O(n) pasos para completarse; es decir, es linealmente
proporcional ala longitud de la lista. El proceso de explorar las listas se ilustra cuando
se explica la eliminacién del iltimo nodo.
3.1.2. Eliminacién
Una operacién de eliminacién consiste en eliminar un nodo al principio de la lista y
regresar el valor almacenado en él. Esta operacién se implementa mediante el método
deleteFromiead( ). En esta operacién la informacién del primer nodo se almacena
temporalmente en una variable local e1, y luego head se reinicializa de modo que el
segundo nodo se vuelve el primer nodo. De esta manera, el primer nodo anterior se
abandona para ser procesado posteriormente por el colector de basura (figura 3.6). Ob-
serve que el primer nodo anterior atin accede a la lista ligada, pero el nodo en si mismo es
inaccesible. Por lo tanto, se considera inexistente. Debido a que el nodo head es accesible
de inmediato, deleteFromHead( ) toma el tiempo constante O(1) para realizar su tarea.
FIGURA 3.6
Eliminacion de un nodo que esta al principio de una lista ligada
ccabecera final
‘SS
(@)
(b)
A diferencia de lo anterior, ahora hay dos casos especiales a considerar. Un caso es
cuando intentamos eliminar un nodo desde una lista ligada vacia. Si se hace un
intento como éste, el programa se colapsa debido a Nul 1PointerExcept ion, lo cual
no queremos que ocurra. El invocador también debe saber que se hizo un
intento de realizar cierta accién. Después de todo, si el invocador espera que la llamadaa
You have either reached 2 page thts unevalale fer vowing or reached your ievina tit for his
book.90M Capitulo 3 Listas ligadas
Si se afiade una instruccién if, entonces la cliusula else también debe aftadirse; de lo
contrario, el programa no compila debido a la “instruccién de regreso faltante”, Pero
ahora, si se regresa 0, el invocador no sabe si el 0 devuelto es la sefal de fala o si es una
literal 0 recuperada de la lista. Para evitar cualquier confusién, el invocador debe usar
una instruccién i £ para probar sila lista esté vacia antes de llamar adeleteProntiead ).
De esta manera, la prueba dentro del método se vuelve redundante.
El segundo caso especial en deleteF romHead ) es cuando la lista tiene s6lo un
nodo a eliminar. En este caso, la lista se vuelve vacia, lo que requiere establecer head y
tail como nulos,
La segunda operacién de eliminacién consiste en eliminar un nodo del final de la
lista, y se implementa como un método deletePromtai(). El problema radica en
‘que después de eliminar un nodo, tail debe referirse al nuevo tltimo elemento de la
lista;es decir, tail debe moverse hacia atrés un nodo. Pero el movimiento hacia atras es
imposible debido a que no hay un enlace directo desde el dltimo nodo a su predecesor.
Por consiguiente, este predecesor debe encontrarse al buscar desde el principio de la
lista y detenerse justo antes de tai. Esto se logra con una variable temporal tmp que
explora la lista dentro del ciclo for. La variable tmp se inicializa como la cabecera de la
lista, y luego en cada iteracién del ciclo avanza al nodo siguiente. Si la lista es como
FIGURA 3.7
Eliminacién de un nodo desde el final de una lista ligada simple.
© 4 4a
final
3
\
ae i (es =
© SS
cabecera ‘mp fna
o E16 Hl
4 EYE
cabecera
eo Ts)Seccién 3.1 Listas ligadas simples MM 91
aquella de la figura 3.7a, entonces tmp primero se refiere al nodo de cabecera que
contiene al niimero 6; después de ejecutar la asignacién tmp = tmp.next, tmp se
tefiere al segundo nodo (figura 3.7). Después de la segunda iteracién y ejecutar la
misma asignaci6n, tmp se refiere al tercer nodo (figura 3,7¢). Como este nodo también
std al lado del tltimo nodo, se sale del ciclo, después de lo cual tail se vuelve la refe-
rencia al siguiente dltimo nodo (figura 3.74), y luego el campo next: al lado de este
nodo se establece como nulo (figura 3.7e). Después de la ultima asignacién, el que fue el
Ultimo nodo ahora se quita de la lista y queda inaccesible desde la misma. A su debido
tiempo, este nodo es reclamado por el colector de basura,
Observe que en el ciclo for, se utiliz6 una variable temporal para examinar la lista,
Sil ciclo se simplificara a
for ( ; head.next != tail; head = head.next);
entonces la lista se examinaria sélo una vez, y el acceso al principio de la lista se pierde
debido a que head se actualizé de manera permanente al nodo que esta al lado del
iiltimo nodo, mismo que esta a punto de convertirse en el tiltimo nodo. Es de funda-
‘mental importancia que, en casos como éste, se utilice una variable temporal de modo
que el acceso al principio de la lista se mantenga intacto.
Al liminar el ultimo nodo, los dos casos especiales son los mismos que en
deleteFromHead ). Sila lista esta vacia, entonces nada se puede eliminar; lo que debe
hacerse en este caso se decide en el programa del usuario precisamente como en el caso de
deleteFrontiead( ). Fl segundo caso es cuando una lista de un solo nodo se vuelve
vacia después de eliminar este tinico nodo, lo que también requiere establecer head y
tail como nulos.
La parte que més consume tiempo de deleteFromrail() es la biisqueda del
nodo al lado del ultimo nodo realizada por el ciclo for. Es evidente que el ciclo realiza
n~ 1 iteraciones en una lista de nodos, lo que es la raz6n principal de que este método
requiera un tiempo de O(n) para eliminar el ultimo nodo.
Las dos operaciones de eliminacién analizadas eliminan un nodo de cabecera o del
final (es decir, siempre de la misma posicién) y regresan el entero que por casualidad
esté en el nodo que se esta eliminando. Un método diferente se utiliza cuando queremos
climinar un nodo que guarda un entero en particular sin importar cual es la posicién de
este nodo dentro de la lista. Puede estar justo al principio, al final o en cualquier parte de la
misma, En suma, un nodo primero debe localizarse y luego quitarse de la lista al ligar el
predecesor de este nodo directamente con su sucesor. Como no sabemos dénde esti
el nodo, el proceso de encontrar y eliminar un nodo con cierto entero es mucho mas
complejo que las operaciones de eliminacién estudiadas hasta ahora. El método
delete ( ) (figura 3.2) es una implementacién de este proceso.
Un nodo se elimina desde el interior de una lista al ligar su predecesor con su suce-
sor. Pero como la lista tiene enlaces sélo hacia adelante, no es posible legar al predecesor
de un nodo desde el nodo. Una manera de realizar la tarea es encontrar el nodo que s¢ va
aeliminar, al examinar primero la lista y luego examinarla de nuevo para encontrar a su.
predecesor. Otra forma se presenta en delete( ), como se muestra en la figura 3.8.
‘Suponga que queremos eliminar un nodo que contiene al riimero 8. El método utiliza
dos variables de referencia, pred y tmp, las cuales se inicializan en el ciclo for de modo
que apuntan al primero y segundo nodos de la lista, respectivamente (figura 3.8a). Como
el nodo tmp tiene el mimero 5, se ejecuta la primera iteracién en la que tanto pred
como tmp se mueven a Jos nodos siguientes (figura 3.8b). Dado que la condicion del
ciclo for ahora es verdad (tmp sefiala al nodo con 8), termina el ciclo y se ejecuta una82 WM Capitulo 3 Listas ligadas
FIGURA 3.8
L
Eliminacién de un nodo desde una lista ligada simple.
final
“ e . 5 3
. re
cabecera_pred ___tmp final
o Vee eS
a fine 7B SCs 7]
asignaci6n pred.next = tmp.next (figura 3.8c). Esta asignacién realmente excluye al
nodo con 8 dela lista. Aunque el nodo atin esté accesible desde la variable tmp, no seré
accesible después de salir del método delete() debido a que tmp es local para el
método. Por consiguiente, deja de existir después de salir de este método. Como antes,
el nodo con el niimero 8 ahora inaccesible ser procesado por el colector de basura.
El parrafo anterior contemplé solamente un caso. En seguida se presentan los casos
restantes:
Un intento por eliminar un nodo desde una lista vacia, en cuyo caso el método termina
de inmediato.
Eliminar el ‘inico nodo desde una lista ligada de un solo nodo: tanto head como tail
se establecen como nulos.
Eliminar el primer nodo de a lista con al menos dos nodos, lo cual requiere actualizar
head.
Eliminar el timo nodo de la lista con al menos dos nodos; lo cual conduce ala actuali-
zacion de tail.
Un intento de eliminar un nodo con un nimero que no esta en la lista: no hace nada.
Es obvio que el mejor caso para delete( ) es cuando se va a eliminar el nodo de
cabecera, lo que requiere un tiempo O(1) para realizarse. El peor caso es cuando el
‘ultimo nodo debe eliminarse, lo que reduce delete() a deleteFromTail() y a su
rendimiento de O(n). ;Cudl es caso promedio? Depende de cudntas iteraciones ejecuta
el ciclo for. Suponiendo que cualquier nodo de la lista tiene una oportunidad igual de
eliminarse, el ciclo no realiza ninguna iteracién si es el primer nodo, realiza una itera-
ci6n sies el segundo nodo,....,y finalmente n — 1 iteraciones si es el tltimo nodo, Para
una secuencia larga de eliminaciones, una eliminacién requiere en promedioa
You have either reached 2 page thts unevalale fer vowing or reached your ievina tit for his
book.$4 M Capitulo 3 Listas ligadas
FIGURA 3.9 —_implementacion de una lista ligada simple genérica.
Tha ses SLLNode.java **#*H
public class SLLNode {
public Object info;
public SLLNode next;
public SLINode() {
next = null;
}
public SLLNode(Object el) {
info = el; next = null;
»
public SLLNode(Object el, SLLNode ptr) {
info = el; next = ptr;
seseeeeseees SLList.java stteeeeeesesensereseeeesess
de lista ligada simple genérica sélo con cabecera
public class SLList {
protected SLLNode head = null;
public SLList() {
>
public boolean isEmpty() {
return head == null;
¥
public Object first() {
return head. info;
,
public void printAll(java.io.PrintStream out) (
for (SLLNode tmp = head; tmp != null; tmp = tmp.next)
out.print (tmp. info);
>
Public void add(Object el) {
head = new SLLNode(el,head);
>
Public Object find(object el) {
SLLNode tmp = head;
for ( ; tmp != null £6 Jel.equals(tmp.info); tmp = tmp.next);
if (tmp == null)
return null;
else return tmp. info;
Contindaa
You have either reached 2 page thts unevalale fer vowing or reached your ievina tit for his
book.96 WM Capitulo 3 Listas ligadas
FIGURA 3.11 implementacién de una lista ligada doble.
[tesenesnesesesanseses
IntDLLNode. java
public class IntDLLNode {
public int info;
public IntDLLNode next, prev;
public IntDLLNode(int el) ¢
this(el,nul1,nul1);
,
public IntDLLNode(int el, IntDLLNode n, IntDLLNode p) {
info = el; next = n; prev = p;
,
[saeestenaeeaeennessenees IntDLbist.java ttttteeseseereseeeeeseeeees/
public class IntDLbist (
private IntDLLNode head, tail;
public IntDLList() {
head = tail = null;
}
public boolean isEmpty() {
return head == null;
>
public void addToTail(int el) {
if (1isEmpty()) ¢
tail = new IntDLINode(el,null,tail);
tail.prev.next = tail;
>
else head = tail = new IntDLLNode(el);
>
public int removeFromrail() {
int el = tail.info;
if (head == tail) // si s6lo hay un nodo en la lista;
head = tail = null;
else { 7/ si hay més de un nodo en 1a lista;
tail = tail.prev;
tail.next = null;Seccién 3.2 Listas ligadas dobles M97
Los métodos para procesar las istas ligadas dobles son ligeramente mas complica-
dos que sus homlogos debido a que existe un campo de referencia mas por mantener.
Sélo se estudian dos métodos: un método para insertar un nodo al final de una lista
ligada doble, y un método para climinar un nodo desde el final (figura 3.11).
Para afiadir un nodo a la lista, el nodo tiene que crearse, sus campos se inicializan
de manera apropiada, y luego el nodo necesita incorporarse ala lista. La insercién de un
nodo al final de una lista ligada doble se ilustra en la figura 3.12. El proceso se realiza en
seis pasos:
1. Unnodo nuevo se crea (figura 3.12a), y luego sus tres campos se inicializan:
2. elcampo info en el niimero e1 que se esté insertando (figura 3.12),
3. elcamponext como nulo (figura 3.12c),
FIGURA 3.12 — Adicién de un nodo nuevo al final de una lista ligada doble.
‘cabecera
(a)
(©)
@
©)
0